From c9c274e34c600ec210ab6517e4d46744bb5eeed4 Mon Sep 17 00:00:00 2001 From: Lukas Schmierer Date: Sat, 22 Nov 2025 12:37:04 +0100 Subject: [PATCH 1/2] remove unused serde_helpers --- crates/fhir/src/lib.rs | 10 - crates/fhir/src/serde_helpers.rs | 668 ------------------------------- 2 files changed, 678 deletions(-) delete mode 100644 crates/fhir/src/serde_helpers.rs diff --git a/crates/fhir/src/lib.rs b/crates/fhir/src/lib.rs index 22f71cb1..cf0521da 100644 --- a/crates/fhir/src/lib.rs +++ b/crates/fhir/src/lib.rs @@ -41,11 +41,6 @@ //! let precise = PreciseDecimal::from(Decimal::new(12340, 3)); // 12.340 //! ``` -// Allow the crate to reference itself via ::helios_fhir -// This enables the FhirSerde macro to use ::helios_fhir::serde_helpers -// from both inside and outside the crate -extern crate self as helios_fhir; - use chrono::{DateTime as ChronoDateTime, NaiveDate, NaiveTime, Utc}; use helios_fhirpath_support::{EvaluationResult, IntoEvaluationResult, TypeInfoResult}; use rust_decimal::Decimal; @@ -1437,11 +1432,6 @@ pub mod parameters; // Re-export commonly used types from parameters module pub use parameters::{ParameterValueAccessor, VersionIndependentParameters}; -// Make serde_helpers public for use by the FhirSerde derive macro -// This is an internal module and should not be used directly by external code -#[doc(hidden)] -pub mod serde_helpers; - /// Multi-version FHIR resource container supporting version-agnostic operations. /// /// This enum provides a unified interface for working with FHIR resources across diff --git a/crates/fhir/src/serde_helpers.rs b/crates/fhir/src/serde_helpers.rs deleted file mode 100644 index 606b6097..00000000 --- a/crates/fhir/src/serde_helpers.rs +++ /dev/null @@ -1,668 +0,0 @@ -use serde::{ - Serialize, - ser::{self, Serializer}, -}; - -#[derive(Clone, Copy)] -struct ContentFound; - -impl std::fmt::Debug for ContentFound { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("ContentFound") - } -} - -impl std::fmt::Display for ContentFound { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("non-empty content detected") - } -} - -impl std::error::Error for ContentFound {} - -impl ser::Error for ContentFound { - fn custom(_: T) -> Self { - ContentFound - } -} - -struct ContentDetector; - -struct SequenceDetector<'a> { - detector: &'a mut ContentDetector, -} - -impl<'a> ser::SerializeSeq for SequenceDetector<'a> { - type Ok = (); - type Error = ContentFound; - - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - value.serialize(&mut *self.detector) - } - - fn end(self) -> Result<(), Self::Error> { - Err(ContentFound) - } -} - -impl<'a> ser::SerializeTuple for SequenceDetector<'a> { - type Ok = (); - type Error = ContentFound; - - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - value.serialize(&mut *self.detector) - } - - fn end(self) -> Result<(), Self::Error> { - Err(ContentFound) - } -} - -impl<'a> ser::SerializeTupleStruct for SequenceDetector<'a> { - type Ok = (); - type Error = ContentFound; - - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - value.serialize(&mut *self.detector) - } - - fn end(self) -> Result<(), Self::Error> { - Err(ContentFound) - } -} - -impl<'a> ser::SerializeTupleVariant for SequenceDetector<'a> { - type Ok = (); - type Error = ContentFound; - - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - value.serialize(&mut *self.detector) - } - - fn end(self) -> Result<(), Self::Error> { - Err(ContentFound) - } -} - -struct MapDetector<'a> { - _detector: &'a mut ContentDetector, -} - -impl<'a> ser::SerializeMap for MapDetector<'a> { - type Ok = (); - type Error = ContentFound; - - fn serialize_key(&mut self, _: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - Err(ContentFound) - } - - fn serialize_value(&mut self, _: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - Ok(()) - } - - fn serialize_entry(&mut self, _: &K, _: &V) -> Result<(), Self::Error> - where - K: ?Sized + Serialize, - V: ?Sized + Serialize, - { - Err(ContentFound) - } - - fn end(self) -> Result<(), Self::Error> { - Ok(()) - } -} - -struct StructDetector<'a> { - detector: &'a mut ContentDetector, - saw_field: bool, -} - -impl<'a> ser::SerializeStruct for StructDetector<'a> { - type Ok = (); - type Error = ContentFound; - - fn serialize_field(&mut self, _key: &'static str, value: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - match value.serialize(&mut *self.detector) { - Ok(()) => Ok(()), - Err(err) => { - self.saw_field = true; - Err(err) - } - } - } - - fn end(self) -> Result<(), Self::Error> { - if self.saw_field { - Err(ContentFound) - } else { - Ok(()) - } - } -} - -impl<'a> ser::SerializeStructVariant for StructDetector<'a> { - type Ok = (); - type Error = ContentFound; - - fn serialize_field(&mut self, _key: &'static str, value: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - match value.serialize(&mut *self.detector) { - Ok(()) => Ok(()), - Err(err) => { - self.saw_field = true; - Err(err) - } - } - } - - fn end(self) -> Result<(), Self::Error> { - Err(ContentFound) - } -} - -impl<'a> Serializer for &'a mut ContentDetector { - type Ok = (); - type Error = ContentFound; - type SerializeSeq = SequenceDetector<'a>; - type SerializeTuple = SequenceDetector<'a>; - type SerializeTupleStruct = SequenceDetector<'a>; - type SerializeTupleVariant = SequenceDetector<'a>; - type SerializeMap = MapDetector<'a>; - type SerializeStruct = StructDetector<'a>; - type SerializeStructVariant = StructDetector<'a>; - - fn serialize_bool(self, _value: bool) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_i8(self, _value: i8) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_i16(self, _value: i16) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_i32(self, _value: i32) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_i64(self, _value: i64) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_i128(self, _value: i128) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_u8(self, _value: u8) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_u16(self, _value: u16) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_u32(self, _value: u32) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_u64(self, _value: u64) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_u128(self, _value: u128) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_f32(self, _value: f32) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_f64(self, _value: f64) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_char(self, _value: char) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_str(self, _value: &str) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_bytes(self, _value: &[u8]) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_none(self) -> Result<(), Self::Error> { - Ok(()) - } - - fn serialize_some(self, value: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - value.serialize(self) - } - - fn serialize_unit(self) -> Result<(), Self::Error> { - Ok(()) - } - - fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Self::Error> { - Ok(()) - } - - fn serialize_unit_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - ) -> Result<(), Self::Error> { - Err(ContentFound) - } - - fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _value: &T, - ) -> Result<(), Self::Error> - where - T: ?Sized + Serialize, - { - Err(ContentFound) - } - - fn serialize_seq(self, _length: Option) -> Result { - Ok(SequenceDetector { detector: self }) - } - - fn serialize_tuple(self, _length: usize) -> Result { - Ok(SequenceDetector { detector: self }) - } - - fn serialize_tuple_struct( - self, - _name: &'static str, - _length: usize, - ) -> Result { - Ok(SequenceDetector { detector: self }) - } - - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _length: usize, - ) -> Result { - Ok(SequenceDetector { detector: self }) - } - - fn serialize_map(self, _length: Option) -> Result { - Ok(MapDetector { _detector: self }) - } - - fn serialize_struct( - self, - _name: &'static str, - _length: usize, - ) -> Result { - Ok(StructDetector { - detector: self, - saw_field: false, - }) - } - - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _length: usize, - ) -> Result { - Err(ContentFound) - } -} - -/// Internal helper function for the FhirSerde macro. -/// Do not use directly - this is an implementation detail. -#[doc(hidden)] -pub fn has_non_empty_content(value: &T) -> bool -where - T: Serialize, -{ - let mut detector = ContentDetector; - value.serialize(&mut detector).is_err() -} - -#[cfg(test)] -mod tests { - use super::*; - use serde::Serialize; - - #[test] - fn test_primitives_have_content() { - // Boolean - assert!(has_non_empty_content(&true)); - assert!(has_non_empty_content(&false)); - - // Integers - assert!(has_non_empty_content(&42i8)); - assert!(has_non_empty_content(&42i16)); - assert!(has_non_empty_content(&42i32)); - assert!(has_non_empty_content(&42i64)); - assert!(has_non_empty_content(&42i128)); - assert!(has_non_empty_content(&0i32)); - - // Unsigned integers - assert!(has_non_empty_content(&42u8)); - assert!(has_non_empty_content(&42u16)); - assert!(has_non_empty_content(&42u32)); - assert!(has_non_empty_content(&42u64)); - assert!(has_non_empty_content(&42u128)); - - // Floats - assert!(has_non_empty_content(&2.5f32)); - assert!(has_non_empty_content(&2.5f64)); - assert!(has_non_empty_content(&0.0f64)); - - // Char - assert!(has_non_empty_content(&'a')); - - // String - assert!(has_non_empty_content(&"hello")); - assert!(has_non_empty_content(&"")); - assert!(has_non_empty_content(&String::from("world"))); - - // Bytes (as Vec) - let bytes = vec![1u8, 2u8, 3u8]; - assert!(has_non_empty_content(&bytes)); - } - - #[test] - fn test_option_types() { - // None has no content - let none: Option = None; - assert!(!has_non_empty_content(&none)); - - // Some with content - assert!(has_non_empty_content(&Some(42))); - assert!(has_non_empty_content(&Some("text"))); - - // Some wrapping None (nested) - let nested_none: Option> = Some(None); - assert!(!has_non_empty_content(&nested_none)); - } - - #[test] - fn test_unit_types() { - // Unit has no content - assert!(!has_non_empty_content(&())); - } - - #[test] - fn test_sequences() { - // Empty vec is considered to have content (SequenceDetector::end() always returns Err) - let empty: Vec = vec![]; - assert!(has_non_empty_content(&empty)); - - // Non-empty vec has content - assert!(has_non_empty_content(&vec![1, 2, 3])); - - // Array - assert!(has_non_empty_content(&[1, 2, 3])); - } - - #[test] - fn test_tuples() { - // Tuples with content - assert!(has_non_empty_content(&(1, 2))); - assert!(has_non_empty_content(&(1, "text", 2.5))); - - // Empty tuple is unit type - assert!(!has_non_empty_content(&())); - } - - #[test] - fn test_maps() { - use std::collections::HashMap; - - // Empty map has no content - let empty: HashMap = HashMap::new(); - assert!(!has_non_empty_content(&empty)); - - // Non-empty map has content (serialize_key returns error) - let mut map = HashMap::new(); - map.insert("key", 42); - assert!(has_non_empty_content(&map)); - } - - #[derive(Serialize)] - struct EmptyStruct; - - #[derive(Serialize)] - struct StructWithAllNone { - field1: Option, - field2: Option, - } - - #[derive(Serialize)] - struct StructWithSomeContent { - field1: Option, - field2: Option, - } - - #[derive(Serialize)] - struct StructWithContent { - value: i32, - } - - #[derive(Serialize)] - struct NewtypeStruct(i32); - - #[derive(Serialize)] - struct NewtypeStructEmpty(Option); - - #[test] - fn test_structs() { - // Empty struct has no content - assert!(!has_non_empty_content(&EmptyStruct)); - - // Struct with all None fields has no content - let all_none = StructWithAllNone { - field1: None, - field2: None, - }; - assert!(!has_non_empty_content(&all_none)); - - // Struct with at least one Some field has content - let some_content = StructWithSomeContent { - field1: Some(42), - field2: None, - }; - assert!(has_non_empty_content(&some_content)); - - // Struct with regular fields has content - let with_content = StructWithContent { value: 42 }; - assert!(has_non_empty_content(&with_content)); - - // Newtype struct with content - assert!(has_non_empty_content(&NewtypeStruct(42))); - - // Newtype struct with None has no content - assert!(!has_non_empty_content(&NewtypeStructEmpty(None))); - - // Newtype struct with Some has content - assert!(has_non_empty_content(&NewtypeStructEmpty(Some(42)))); - } - - #[derive(Serialize)] - #[allow(clippy::enum_variant_names)] - enum TestEnum { - UnitVariant, - NewtypeVariant(i32), - TupleVariant(i32, String), - StructVariant { field: i32 }, - } - - #[test] - fn test_enum_variants() { - // Unit variant has content - assert!(has_non_empty_content(&TestEnum::UnitVariant)); - - // Newtype variant has content - assert!(has_non_empty_content(&TestEnum::NewtypeVariant(42))); - - // Tuple variant has content - assert!(has_non_empty_content(&TestEnum::TupleVariant( - 42, - "test".to_string() - ))); - - // Struct variant has content - assert!(has_non_empty_content(&TestEnum::StructVariant { - field: 42 - })); - } - - #[derive(Serialize)] - struct NestedStruct { - inner: Option, - } - - #[derive(Serialize)] - struct InnerStruct { - value: Option, - } - - #[test] - fn test_nested_structures() { - // Nested struct with all None - let nested_none = NestedStruct { inner: None }; - assert!(!has_non_empty_content(&nested_none)); - - // Nested struct with Some but inner is None - let nested_some_none = NestedStruct { - inner: Some(InnerStruct { value: None }), - }; - assert!(!has_non_empty_content(&nested_some_none)); - - // Nested struct with actual content - let nested_content = NestedStruct { - inner: Some(InnerStruct { value: Some(42) }), - }; - assert!(has_non_empty_content(&nested_content)); - } - - #[derive(Serialize)] - struct ComplexStruct { - opt_vec: Option>, - opt_string: Option, - nested: Option, - } - - #[test] - fn test_complex_combinations() { - // All None - let all_none = ComplexStruct { - opt_vec: None, - opt_string: None, - nested: None, - }; - assert!(!has_non_empty_content(&all_none)); - - // Empty vec is considered content (sequences always trigger ContentFound in end()) - let empty_vec = ComplexStruct { - opt_vec: Some(vec![]), - opt_string: None, - nested: None, - }; - assert!(has_non_empty_content(&empty_vec)); - - // Non-empty vec - let with_vec = ComplexStruct { - opt_vec: Some(vec![1, 2, 3]), - opt_string: None, - nested: None, - }; - assert!(has_non_empty_content(&with_vec)); - - // With string - let with_string = ComplexStruct { - opt_vec: None, - opt_string: Some("text".to_string()), - nested: None, - }; - assert!(has_non_empty_content(&with_string)); - } - - #[test] - fn test_content_found_error_traits() { - use serde::ser::Error as SerError; - - let err = ContentFound; - - // Test Debug - assert_eq!(format!("{:?}", err), "ContentFound"); - - // Test Display - assert_eq!(format!("{}", err), "non-empty content detected"); - - // Test Error trait (just verify it compiles) - let _: &dyn std::error::Error = &err; - - // Test ser::Error trait - let custom_err = ::custom("test message"); - assert_eq!(format!("{:?}", custom_err), "ContentFound"); - } - - #[derive(Serialize)] - struct TupleStruct(i32, String); - - #[test] - fn test_tuple_struct() { - assert!(has_non_empty_content(&TupleStruct(42, "test".to_string()))); - } - - #[test] - fn test_empty_string_has_content() { - // Even empty strings are considered content - assert!(has_non_empty_content(&"")); - assert!(has_non_empty_content(&String::new())); - } -} From df092f8005c327aef9d39afa05cd377c422432ae Mon Sep 17 00:00:00 2001 From: Lukas Schmierer Date: Sat, 22 Nov 2025 12:54:54 +0100 Subject: [PATCH 2/2] replace __HeliosEmptySerializer by simple is_empty implementations --- crates/fhir-macro/src/lib.rs | 563 +++++++------------------------- crates/fhir/src/lib.rs | 14 + crates/fhir/tests/test_serde.rs | 4 +- 3 files changed, 127 insertions(+), 454 deletions(-) diff --git a/crates/fhir-macro/src/lib.rs b/crates/fhir-macro/src/lib.rs index f39c4a33..cc054399 100644 --- a/crates/fhir-macro/src/lib.rs +++ b/crates/fhir-macro/src/lib.rs @@ -274,6 +274,14 @@ pub fn fhir_serde_derive(input: TokenStream) -> TokenStream { // Pass all generic parts to deserialize generator let deserialize_impl = generate_deserialize_impl(&input.data, &name); + let is_empty_impl = generate_is_empty_impl( + &input.data, + &name, + &impl_generics, + &ty_generics, + where_clause, + ) + .unwrap_or_else(proc_macro2::TokenStream::new); let expanded = quote! { // --- Serialize Implementation --- @@ -299,6 +307,8 @@ pub fn fhir_serde_derive(input: TokenStream) -> TokenStream { #deserialize_impl } } + + #is_empty_impl }; TokenStream::from(expanded) @@ -810,17 +820,6 @@ fn generate_serialize_impl(data: &Data, name: &Ident) -> proc_macro2::TokenStrea // Check if any fields have the flatten attribute - define this at the top level let has_flattened_fields = fields.named.iter().any(is_flattened); - // Check if we need the empty check helper function - let needs_empty_helper = fields.named.iter().any(|field| { - if is_flattened(field) { - return false; - } - let (is_element, is_decimal_element, is_option, is_vec) = - get_element_info(&field.ty); - let is_fhir_element = is_element || is_decimal_element; - !is_option && !is_vec && !is_fhir_element - }); - // Import SerializeMap trait if we have flattened fields let import_serialize_map = if has_flattened_fields { quote! { use serde::ser::SerializeMap; } @@ -1142,8 +1141,7 @@ fn generate_serialize_impl(data: &Data, name: &Ident) -> proc_macro2::TokenStrea if has_flattened_fields { // For SerializeMap quote! { - // Use custom empty check to avoid serde_json::to_value overhead - if !__helios_serde_is_empty::<_, S::Error>(&#field_access)? { + if !#field_access.is_empty() { // Use serialize_entry for SerializeMap state.serialize_entry(&#effective_field_name_str, &#field_access)?; } @@ -1151,8 +1149,7 @@ fn generate_serialize_impl(data: &Data, name: &Ident) -> proc_macro2::TokenStrea } else { // For SerializeStruct quote! { - // Use custom empty check to avoid serde_json::to_value overhead - if !__helios_serde_is_empty::<_, S::Error>(&#field_access)? { + if !#field_access.is_empty() { // Use serialize_field for SerializeStruct state.serialize_field(&#effective_field_name_str, &#field_access)?; } @@ -1163,8 +1160,7 @@ fn generate_serialize_impl(data: &Data, name: &Ident) -> proc_macro2::TokenStrea if has_flattened_fields { // For SerializeMap quote! { - // Use custom empty check to avoid serde_json::to_value overhead - if !__helios_serde_is_empty::<_, S::Error>(&#field_access)? { + if !#field_access.is_empty() { // Use serialize_entry for SerializeMap state.serialize_entry(&#effective_field_name_str, &#field_access)?; } @@ -1172,8 +1168,7 @@ fn generate_serialize_impl(data: &Data, name: &Ident) -> proc_macro2::TokenStrea } else { // For SerializeStruct quote! { - // Use custom empty check to avoid serde_json::to_value overhead - if !__helios_serde_is_empty::<_, S::Error>(&#field_access)? { + if !#field_access.is_empty() { // Use serialize_field for SerializeStruct state.serialize_field(&#effective_field_name_str, &#field_access)?; } @@ -1184,18 +1179,10 @@ fn generate_serialize_impl(data: &Data, name: &Ident) -> proc_macro2::TokenStrea field_counts.push(field_counting_code); field_serializers.push(field_serializing_code); } - // Generate the empty check helper definition if needed - let empty_helper_definition = if needs_empty_helper { - empty_check_helper_definition_tokens() - } else { - proc_macro2::TokenStream::new() - }; - // Use the has_flattened_fields variable defined at the top of the function if has_flattened_fields { // If we have flattened fields, use serialize_map instead of serialize_struct quote! { - #empty_helper_definition let mut count = 0; #(#field_counts)* #import_serialize_map @@ -1206,7 +1193,6 @@ fn generate_serialize_impl(data: &Data, name: &Ident) -> proc_macro2::TokenStrea } else { // If no flattened fields, use serialize_struct as before quote! { - #empty_helper_definition let mut count = 0; #(#field_counts)* #import_serialize_map @@ -1224,443 +1210,116 @@ fn generate_serialize_impl(data: &Data, name: &Ident) -> proc_macro2::TokenStrea } } -/// Generates a helper function that checks if a value would serialize to empty content -/// without actually creating a JSON value. This is more efficient than serde_json::to_value(). -fn empty_check_helper_definition_tokens() -> proc_macro2::TokenStream { - quote! { - #[allow(non_camel_case_types)] - fn __helios_serde_is_empty(value: &T) -> Result - where - T: serde::Serialize, - E: serde::ser::Error, - { - use ::core::marker::PhantomData; +fn generate_is_empty_impl( + data: &Data, + name: &Ident, + impl_generics: &syn::ImplGenerics, + ty_generics: &syn::TypeGenerics, + where_clause: Option<&syn::WhereClause>, +) -> Option { + match data { + Data::Struct(data_struct) => { + let fields = match &data_struct.fields { + Fields::Named(named) => &named.named, + _ => return None, + }; - #[allow(non_camel_case_types)] - struct __HeliosEmptySerializer<'a, E> { - is_empty: &'a mut bool, - _marker: PhantomData, - } + let mut field_checks = Vec::new(); - impl<'a, E> __HeliosEmptySerializer<'a, E> - where - E: serde::ser::Error, - { - fn mark_non_empty(&mut self) { - *self.is_empty = false; - } + for field in fields { + let field_name_ident = field.ident.as_ref().unwrap(); + let (is_element, is_decimal_element, is_option, is_vec) = + get_element_info(&field.ty); + let is_fhir_element = is_element || is_decimal_element; + let field_is_flattened = is_flattened(field); - fn child(&mut self) -> __HeliosEmptySerializer<'_, E> { - __HeliosEmptySerializer { - is_empty: self.is_empty, - _marker: PhantomData, + let field_check = if field_is_flattened { + if is_option { + let tmp = format_ident!("__fhir_flatten_opt_{}", field_name_ident); + quote! { + self.#field_name_ident + .as_ref() + .map_or(true, |#tmp| #tmp.is_empty()) + } + } else if is_vec { + let tmp = format_ident!("__fhir_flatten_vec_{}", field_name_ident); + quote! { + self.#field_name_ident.iter().all(|#tmp| #tmp.is_empty()) + } + } else { + quote! { self.#field_name_ident.is_empty() } } - } - } - - impl<'a, E> serde::ser::Serializer for __HeliosEmptySerializer<'a, E> - where - E: serde::ser::Error, - { - type Ok = (); - type Error = E; - type SerializeSeq = __HeliosEmptySeqSerializer<'a, E>; - type SerializeTuple = __HeliosEmptySeqSerializer<'a, E>; - type SerializeTupleStruct = __HeliosEmptySeqSerializer<'a, E>; - type SerializeTupleVariant = __HeliosEmptySeqSerializer<'a, E>; - type SerializeMap = __HeliosEmptyMapSerializer<'a, E>; - type SerializeStruct = __HeliosEmptyStructSerializer<'a, E>; - type SerializeStructVariant = __HeliosEmptyStructSerializer<'a, E>; - - fn serialize_bool(mut self, _v: bool) -> Result<(), E> { - self.mark_non_empty(); - Ok(()) - } - - fn serialize_i8(self, _v: i8) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_i16(self, _v: i16) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_i32(self, _v: i32) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_i64(self, _v: i64) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_i128(self, _v: i128) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_u8(self, _v: u8) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_u16(self, _v: u16) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_u32(self, _v: u32) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_u64(self, _v: u64) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_u128(self, _v: u128) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_f32(self, _v: f32) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_f64(self, _v: f64) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_char(self, _v: char) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_str(self, _v: &str) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_bytes(self, _v: &[u8]) -> Result<(), E> { - self.serialize_bool(true) - } - - fn serialize_none(self) -> Result<(), E> { - Ok(()) - } - - fn serialize_some(mut self, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - value.serialize(self.child()) - } - - fn serialize_unit(self) -> Result<(), E> { - Ok(()) - } - - fn serialize_unit_struct(self, _name: &'static str) -> Result<(), E> { - Ok(()) - } - - fn serialize_unit_variant( - mut self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - ) -> Result<(), E> { - self.mark_non_empty(); - Ok(()) - } - - fn serialize_newtype_struct(mut self, _name: &'static str, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - value.serialize(self.child()) - } - - fn serialize_newtype_variant( - mut self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - value: &U, - ) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - self.mark_non_empty(); - value.serialize(self.child()) - } - - fn serialize_seq(mut self, _len: Option) -> Result { - self.mark_non_empty(); - Ok(__HeliosEmptySeqSerializer { - is_empty: self.is_empty, - _marker: PhantomData, - }) - } - - fn serialize_tuple(self, len: usize) -> Result { - self.serialize_seq(Some(len)) - } - - fn serialize_tuple_struct( - self, - _name: &'static str, - len: usize, - ) -> Result { - self.serialize_seq(Some(len)) - } - - fn serialize_tuple_variant( - mut self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - self.mark_non_empty(); - Ok(__HeliosEmptySeqSerializer { - is_empty: self.is_empty, - _marker: PhantomData, - }) - } - - fn serialize_map(self, _len: Option) -> Result { - Ok(__HeliosEmptyMapSerializer { - is_empty: self.is_empty, - wrote_entry: false, - _marker: PhantomData, - }) - } - - fn serialize_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Ok(__HeliosEmptyStructSerializer { - is_empty: self.is_empty, - wrote_field: false, - _marker: PhantomData, - }) - } - - fn serialize_struct_variant( - mut self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - self.mark_non_empty(); - Ok(__HeliosEmptyStructSerializer { - is_empty: self.is_empty, - wrote_field: true, - _marker: PhantomData, - }) - } - } - - #[allow(non_camel_case_types)] - struct __HeliosEmptySeqSerializer<'a, E> { - is_empty: &'a mut bool, - _marker: PhantomData, - } - - impl<'a, E> serde::ser::SerializeSeq for __HeliosEmptySeqSerializer<'a, E> - where - E: serde::ser::Error, - { - type Ok = (); - type Error = E; - - fn serialize_element(&mut self, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - value.serialize(__HeliosEmptySerializer { - is_empty: self.is_empty, - _marker: PhantomData, - }) - } - - fn end(self) -> Result<(), E> { - Ok(()) - } - } - - impl<'a, E> serde::ser::SerializeTuple for __HeliosEmptySeqSerializer<'a, E> - where - E: serde::ser::Error, - { - type Ok = (); - type Error = E; - - fn serialize_element(&mut self, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - serde::ser::SerializeSeq::serialize_element(self, value) - } - - fn end(self) -> Result<(), E> { - Ok(()) - } - } - - impl<'a, E> serde::ser::SerializeTupleStruct for __HeliosEmptySeqSerializer<'a, E> - where - E: serde::ser::Error, - { - type Ok = (); - type Error = E; - - fn serialize_field(&mut self, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - serde::ser::SerializeSeq::serialize_element(self, value) - } - - fn end(self) -> Result<(), E> { - Ok(()) - } - } - - impl<'a, E> serde::ser::SerializeTupleVariant for __HeliosEmptySeqSerializer<'a, E> - where - E: serde::ser::Error, - { - type Ok = (); - type Error = E; - - fn serialize_field(&mut self, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - serde::ser::SerializeSeq::serialize_element(self, value) - } - - fn end(self) -> Result<(), E> { - Ok(()) - } - } - - #[allow(non_camel_case_types)] - struct __HeliosEmptyMapSerializer<'a, E> { - is_empty: &'a mut bool, - wrote_entry: bool, - _marker: PhantomData, - } - - impl<'a, E> serde::ser::SerializeMap for __HeliosEmptyMapSerializer<'a, E> - where - E: serde::ser::Error, - { - type Ok = (); - type Error = E; - - fn serialize_key(&mut self, key: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - if !self.wrote_entry { - self.wrote_entry = true; - *self.is_empty = false; + } else if is_option && !is_vec && is_fhir_element { + let tmp = format_ident!("__fhir_element_opt_{}", field_name_ident); + quote! { + self.#field_name_ident + .as_ref() + .map_or(true, |#tmp| { + #tmp.value.is_none() + && #tmp.id.is_none() + && #tmp.extension.is_none() + }) } - key.serialize(__HeliosEmptySerializer { - is_empty: self.is_empty, - _marker: PhantomData, - }) - } - - fn serialize_value(&mut self, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - value.serialize(__HeliosEmptySerializer { - is_empty: self.is_empty, - _marker: PhantomData, - }) - } - - fn serialize_entry(&mut self, key: &K, value: &V) -> Result<(), E> - where - K: ?Sized + serde::Serialize, - V: ?Sized + serde::Serialize, - { - self.serialize_key(key)?; - self.serialize_value(value) - } - - fn end(self) -> Result<(), E> { - Ok(()) - } - } - - #[allow(non_camel_case_types)] - struct __HeliosEmptyStructSerializer<'a, E> { - is_empty: &'a mut bool, - wrote_field: bool, - _marker: PhantomData, - } - - impl<'a, E> __HeliosEmptyStructSerializer<'a, E> { - fn touch(&mut self) { - if !self.wrote_field { - self.wrote_field = true; - *self.is_empty = false; + } else if is_vec && is_fhir_element { + let vec_ident = format_ident!("__fhir_vec_ref_{}", field_name_ident); + let element_ident = format_ident!("__fhir_vec_elem_{}", field_name_ident); + let vec_access = if is_option { + quote! { self.#field_name_ident.as_ref() } + } else { + quote! { Some(&self.#field_name_ident) } + }; + quote! { + #vec_access.map_or(true, |#vec_ident| { + #vec_ident.iter().all(|#element_ident| { + #element_ident.value.is_none() + && #element_ident.id.is_none() + && #element_ident.extension.is_none() + }) + }) } - } - } - - impl<'a, E> serde::ser::SerializeStruct for __HeliosEmptyStructSerializer<'a, E> - where - E: serde::ser::Error, - { - type Ok = (); - type Error = E; - - fn serialize_field(&mut self, _key: &'static str, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - self.touch(); - value.serialize(__HeliosEmptySerializer { - is_empty: self.is_empty, - _marker: PhantomData, - }) - } + } else if !is_vec && is_fhir_element { + quote! { + self.#field_name_ident.value.is_none() + && self.#field_name_ident.id.is_none() + && self.#field_name_ident.extension.is_none() + } + } else if is_option { + quote! { self.#field_name_ident.is_none() } + } else if is_vec { + quote! { self.#field_name_ident.is_empty() } + } else { + quote! { self.#field_name_ident.is_empty() } + }; - fn end(self) -> Result<(), E> { - Ok(()) - } + field_checks.push(field_check); } - impl<'a, E> serde::ser::SerializeStructVariant - for __HeliosEmptyStructSerializer<'a, E> - where - E: serde::ser::Error, - { - type Ok = (); - type Error = E; - - fn serialize_field(&mut self, key: &'static str, value: &U) -> Result<(), E> - where - U: ?Sized + serde::Serialize, - { - serde::ser::SerializeStruct::serialize_field(self, key, value) + let body = if field_checks.is_empty() { + quote! { true } + } else { + quote! { + true #(&& #field_checks)* } + }; - fn end(self) -> Result<(), E> { - Ok(()) + Some(quote! { + impl #impl_generics #name #ty_generics #where_clause { + #[doc(hidden)] + pub fn is_empty(&self) -> bool { + #body + } } - } - - let mut is_empty = true; - let serializer = __HeliosEmptySerializer { - is_empty: &mut is_empty, - _marker: PhantomData, - }; - value.serialize(serializer)?; - Ok(is_empty) + }) } + Data::Enum(_) => Some(quote! { + impl #impl_generics #name #ty_generics #where_clause { + #[doc(hidden)] + pub fn is_empty(&self) -> bool { + false + } + } + }), + Data::Union(_) => None, } } diff --git a/crates/fhir/src/lib.rs b/crates/fhir/src/lib.rs index cf0521da..aa72a6a9 100644 --- a/crates/fhir/src/lib.rs +++ b/crates/fhir/src/lib.rs @@ -1927,6 +1927,14 @@ pub struct Element { pub value: Option, } +impl Element { + /// Returns `true` if no value, id, or extensions are present. + #[inline] + pub fn is_empty(&self) -> bool { + self.value.is_none() && self.id.is_none() && self.extension.is_none() + } +} + // Custom Deserialize for Element // Remove PartialEq/Eq bounds for V and E as they are not needed for deserialization itself impl<'de, V, E> Deserialize<'de> for Element @@ -2414,6 +2422,12 @@ impl DecimalElement { value: Some(precise_value), } } + + /// Returns `true` if the element has no value, id, or extensions. + #[inline] + pub fn is_empty(&self) -> bool { + self.value.is_none() && self.id.is_none() && self.extension.is_none() + } } // Custom Deserialize for DecimalElement using intermediate Value diff --git a/crates/fhir/tests/test_serde.rs b/crates/fhir/tests/test_serde.rs index 413e3987..ad38d915 100644 --- a/crates/fhir/tests/test_serde.rs +++ b/crates/fhir/tests/test_serde.rs @@ -1334,7 +1334,7 @@ struct FlattenTestStruct { #[derive(Debug, PartialEq, FhirSerde, Default)] struct NestedStruct { field1: String, - field2: i32, + field2: Integer, } #[test] @@ -1344,7 +1344,7 @@ fn test_flatten_serialization() { name: "Test".to_string().into(), nested: NestedStruct { field1: "Nested".to_string().into(), - field2: 42, + field2: 42.into(), }, };