diff --git a/examples/src/bbigras_namespace.rs b/examples/src/bbigras_namespace.rs index 23d4ad0..e294597 100644 --- a/examples/src/bbigras_namespace.rs +++ b/examples/src/bbigras_namespace.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] // related to issue https://github.com/media-io/yaserde/issues/15 use yaserde::*; diff --git a/examples/src/boscop.rs b/examples/src/boscop.rs index 07f516d..b083344 100644 --- a/examples/src/boscop.rs +++ b/examples/src/boscop.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] // related to issue https://github.com/media-io/yaserde/issues/3 use yaserde::*; diff --git a/examples/src/generic.rs b/examples/src/generic.rs index 5c242e1..f55a53b 100644 --- a/examples/src/generic.rs +++ b/examples/src/generic.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use yaserde::*; #[derive(YaSerialize, YaDeserialize, Debug, Default, Clone, Eq, PartialEq)] diff --git a/examples/src/ln_dom.rs b/examples/src/ln_dom.rs index 78db84e..aaae195 100644 --- a/examples/src/ln_dom.rs +++ b/examples/src/ln_dom.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] // related to issue https://github.com/media-io/yaserde/issues/11 use yaserde::*; diff --git a/examples/src/same_element_different_namespaces.rs b/examples/src/same_element_different_namespaces.rs index 31cb4e1..be3668e 100644 --- a/examples/src/same_element_different_namespaces.rs +++ b/examples/src/same_element_different_namespaces.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] // related to issue https://github.com/media-io/yaserde/issues/186 use yaserde::*; diff --git a/examples/tests/data/svd.xml b/examples/tests/data/svd.xml index 8743645..0d806ae 100644 --- a/examples/tests/data/svd.xml +++ b/examples/tests/data/svd.xml @@ -1,4 +1,4 @@ - + Renesas diff --git a/yaserde/src/de/mod.rs b/yaserde/src/de/mod.rs index b08b138..5840820 100644 --- a/yaserde/src/de/mod.rs +++ b/yaserde/src/de/mod.rs @@ -18,6 +18,7 @@ pub struct Deserializer { depth: usize, reader: EventReader, peeked: Option, + pub inner_struct_label: Option<&'static str>, } impl Deserializer { @@ -26,6 +27,7 @@ impl Deserializer { depth: 0, reader, peeked: None, + inner_struct_label: None, } } diff --git a/yaserde/src/lib.rs b/yaserde/src/lib.rs index 6c86ac9..669e30e 100644 --- a/yaserde/src/lib.rs +++ b/yaserde/src/lib.rs @@ -328,7 +328,7 @@ macro_rules! serialize_and_validate { log::debug!("serialize_and_validate @ {}:{}", file!(), line!()); let data: Result = yaserde::ser::to_string(&$model); - let content = &format!(r#"{}"#, $content); + let content = &format!(r#"{}"#, $content); assert_eq!( data, Ok(content.split("\n").map(|s| s.trim()).collect::()) diff --git a/yaserde/tests/cdata.rs b/yaserde/tests/cdata.rs index a73868d..45b980f 100644 --- a/yaserde/tests/cdata.rs +++ b/yaserde/tests/cdata.rs @@ -18,14 +18,14 @@ fn test_cdata_serialization() { msgdata: "Some unescaped content".to_string(), }; let xml_output = yaserde::ser::to_string(&test_data).expect("Serialization failed"); - let expected_output = r#"Some unescaped content]]>"#; + let expected_output = r#"Some unescaped content]]>"#; assert_eq!(xml_output, expected_output); } #[test] fn test_cdata_deserialization() { init(); - let xml = r#"Some unescaped content]]>"#; + let xml = r#"Some unescaped content]]>"#; let r: TestStruct = yaserde::de::from_str(xml).unwrap(); let expected_output = TestStruct { msgdata: "Some unescaped content".to_string(), diff --git a/yaserde/tests/deserializer.rs b/yaserde/tests/deserializer.rs index 5b0e3af..5aeee70 100644 --- a/yaserde/tests/deserializer.rs +++ b/yaserde/tests/deserializer.rs @@ -493,6 +493,7 @@ fn de_enum() { Black, } + #[allow(dead_code)] #[derive(YaDeserialize, PartialEq, Debug)] pub struct RGBColor { red: String, @@ -583,7 +584,7 @@ fn de_complex_enum() { Dotted(u32), } - let content = r#" + let content = r#" text @@ -598,7 +599,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" text @@ -613,7 +614,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" 56 @@ -628,7 +629,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" @@ -646,7 +647,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" text @@ -661,7 +662,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" @@ -679,7 +680,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" abc @@ -695,7 +696,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" 12 @@ -711,7 +712,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" 1223 @@ -730,7 +731,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" 1223 @@ -749,7 +750,7 @@ fn de_complex_enum() { } ); - let content = r#" + let content = r#" 54 @@ -854,7 +855,7 @@ fn de_subitem_issue_12() { } convert_and_validate!( - r#" + r#" 54 @@ -884,7 +885,7 @@ fn de_subitem_issue_12_with_sub() { } convert_and_validate!( - r#" + r#" 54 @@ -911,7 +912,7 @@ fn de_subitem_issue_12_attributes() { } convert_and_validate!( - r#" + r#" @@ -940,7 +941,7 @@ fn de_subitem_issue_12_attributes_with_sub() { } convert_and_validate!( - r#" + r#" @@ -1101,6 +1102,76 @@ fn de_attribute_sequence() { deserialize_and_validate!(content, model, Outer); } +#[test] +fn de_option_vec_as_attribute() { + init(); + + #[derive(YaDeserialize, PartialEq, Debug)] + #[yaserde(rename = "TestTag")] + pub struct OptionVecAttributeStruct { + #[yaserde(attribute = true)] + field: Option>, + } + + // Test case 1: Some(populated_vec) -> field="1 2 3 4" + let content = r#""#; + let model = OptionVecAttributeStruct { + field: Some(vec![1, 2, 3, 4]), + }; + convert_and_validate!(content, OptionVecAttributeStruct, model); + + // Test case 2: Some(empty_vec) -> field="" + let content = r#""#; + let model = OptionVecAttributeStruct { + field: Some(vec![]), + }; + convert_and_validate!(content, OptionVecAttributeStruct, model); + + // Test case 3: None -> no attribute + let content = r#""#; + let model = OptionVecAttributeStruct { field: None }; + convert_and_validate!(content, OptionVecAttributeStruct, model); +} + +#[test] +fn de_option_vec_enum_as_attribute() { + init(); + + #[derive(YaDeserialize, PartialEq, Debug, Default)] + enum MyEnum { + #[default] + One, + Two, + Three, + } + + #[derive(YaDeserialize, PartialEq, Debug)] + #[yaserde(rename = "TestTag")] + pub struct OptionVecEnumAttributeStruct { + #[yaserde(attribute = true)] + field: Option>, + } + + // Test case 1: Some(vec![MyEnum::One, MyEnum::Two, MyEnum::Three]) -> field="One Two Three" + let content = r#""#; + let model = OptionVecEnumAttributeStruct { + field: Some(vec![MyEnum::One, MyEnum::Two, MyEnum::Three]), + }; + convert_and_validate!(content, OptionVecEnumAttributeStruct, model); + + // Test case 2: Some(empty_vec) -> field="" + let content = r#""#; + let model = OptionVecEnumAttributeStruct { + field: Some(vec![]), + }; + convert_and_validate!(content, OptionVecEnumAttributeStruct, model); + + // Test case 3: None -> no attribute + let content = r#""#; + let model = OptionVecEnumAttributeStruct { field: None }; + convert_and_validate!(content, OptionVecEnumAttributeStruct, model); +} + #[test] fn de_nested_macro_rules() { init(); @@ -1108,6 +1179,7 @@ fn de_nested_macro_rules() { macro_rules! float_attrs { ($type:ty) => { #[derive(PartialEq, Debug, YaDeserialize)] + #[allow(dead_code)] pub struct Outer { #[yaserde(attribute = true)] pub inner: Option<$type>, @@ -1117,3 +1189,133 @@ fn de_nested_macro_rules() { float_attrs!(f32); } + +#[test] +fn de_nested_element_equality() { + #[derive(YaDeserialize, Debug, PartialEq)] + #[yaserde(rename = "EBMLSchema")] + struct Schema { + #[yaserde(rename = "element")] + elements: Vec, + } + + #[derive(YaDeserialize, Debug, PartialEq)] + #[yaserde(rename = "element")] + struct Element { + #[yaserde(rename = "documentation")] + documentation: Vec, + } + + #[derive(YaDeserialize, Debug, PartialEq)] + #[yaserde(rename = "documentation")] + struct Documentation { + #[yaserde(text = true)] + body: String, + } + + let parent = r#" + +Test + +"#; + let parent: Schema = yaserde::de::from_str(parent).unwrap(); + + let child = r#" +Test +"#; + let child: Element = yaserde::de::from_str(child).unwrap(); + + assert_ne!(parent.elements, vec![]); + assert_eq!(parent.elements[0], child); +} + +#[test] +fn de_nested_3_levels() { + #[derive(YaSerialize, YaDeserialize, Debug, PartialEq)] + struct A { + id: String, + #[yaserde(rename = "SAME")] + many: Vec, + } + + #[derive(YaSerialize, YaDeserialize, Debug, PartialEq)] + struct B { + id: Option, + #[yaserde(rename = "SAME")] + many: Vec, + } + + #[derive(YaSerialize, YaDeserialize, Debug, PartialEq)] + struct C { + id: Option, + } + + let content = + r#"a1b1c1c2"#; + let model = A { + id: "a1".to_string(), + many: vec![B { + id: Some("b1".to_string()), + many: vec![ + C { + id: Some("c1".to_string()), + }, + C { + id: Some("c2".to_string()), + }, + ], + }], + }; + deserialize_and_validate!(content, model, A); +} + +#[test] +fn de_nested_issue_192() { + #[derive(Clone, Default, Debug, PartialEq, YaDeserialize)] + #[yaserde( + prefix = "xs", + namespaces = { + "xs" = "http://www.w3.org/2001/XMLSchema", + } + )] + pub struct XSDGroup { + #[yaserde(rename = "ref", attribute = true)] + pub reference: String, + } + + #[derive(Clone, Default, Debug, PartialEq, YaDeserialize)] + #[yaserde( + rename = "sequence", + prefix = "xs", + namespaces = { + "xs" = "http://www.w3.org/2001/XMLSchema", + } + )] + pub struct Sequence { + #[yaserde(rename = "group", prefix = "xs")] + pub groups: Vec, + + #[yaserde(rename = "sequence", prefix = "xs")] + pub sequences: Vec, + } + + let content = r#" + + + + + "#; + let model = Sequence { + groups: vec![ + XSDGroup { + reference: "AR:AR-OBJECT".to_string(), + }, + XSDGroup { + reference: "AR:AUTOSAR".to_string(), + }, + ], + sequences: vec![], + }; + + deserialize_and_validate!(content, model, Sequence); +} diff --git a/yaserde/tests/option.rs b/yaserde/tests/option.rs index fac7804..1cad3e2 100644 --- a/yaserde/tests/option.rs +++ b/yaserde/tests/option.rs @@ -144,7 +144,7 @@ mod tests { #[test] fn deserialize_without_car() { - let person = r#" + let person = r#" brown 25 diff --git a/yaserde/tests/serializer.rs b/yaserde/tests/serializer.rs index b575206..f2770e6 100644 --- a/yaserde/tests/serializer.rs +++ b/yaserde/tests/serializer.rs @@ -415,3 +415,138 @@ fn ser_custom() { let content = "2020110"; serialize_and_validate!(model, content); } + +#[test] +fn ser_vec_as_attribute() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(rename = "TestTag")] + pub struct VecAttributeStruct { + #[yaserde(attribute = true)] + numbers: Vec, + #[yaserde(attribute = true)] + strings: Vec, + #[yaserde(attribute = true)] + bools: Vec, + #[yaserde(attribute = true)] + floats: Vec, + } + + let model = VecAttributeStruct { + numbers: vec![1, 2, 3, 4], + strings: vec!["hello".to_string(), "world".to_string()], + bools: vec![true, false, true], + floats: vec![6.14, 2.71], + }; + + // Expected XML with space-separated attribute values + let content = r#""#; + serialize_and_validate!(model, content); +} + +#[test] +fn ser_vec_as_attribute_nested() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(rename = "TestTag")] + struct VecAttributeStruct { + #[yaserde(attribute = true)] + outer: Vec, + } + + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(rename = "TestTag")] + enum Inner { + One, + Two, + } + + let model = VecAttributeStruct { + outer: vec![Inner::One, Inner::Two], + }; + + // Expected XML with space-separated attribute values + let content = r#""#; + serialize_and_validate!(model, content); +} + +#[test] +fn ser_option_vec_as_attribute() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(rename = "TestTag")] + pub struct OptionVecAttributeStruct { + #[yaserde(attribute = true)] + field: Option>, + } + + // Expected XML with space-separated attribute values + let model = OptionVecAttributeStruct { + field: Some(vec![1, 2, 3, 4]), + }; + let content = r#""#; + serialize_and_validate!(model, content); + + let model = OptionVecAttributeStruct { + field: Some(vec![]), + }; + let content = r#""#; + serialize_and_validate!(model, content); + + // Expected XML with no attributes + let model = OptionVecAttributeStruct { field: None }; + let content = r#""#; + serialize_and_validate!(model, content); +} + +#[test] +fn ser_option_vec_complex() { + #[derive(Default, PartialEq, Debug, YaSerialize)] + pub struct Start { + #[yaserde(attribute = true, rename = "value")] + pub value: String, + } + + #[derive(Default, PartialEq, Debug, YaSerialize)] + #[yaserde(rename = "String")] + pub struct StringStruct { + #[yaserde(rename = "Start")] + pub start: Option>, + } + + // Test serialization with Some(vec) + let model = StringStruct { + start: Some(vec![ + Start { + value: "First string".to_string(), + }, + Start { + value: "Second string".to_string(), + }, + Start { + value: "Third string".to_string(), + }, + ]), + }; + + let content = yaserde::ser::to_string(&model).unwrap(); + assert_eq!( + content, + "" + ); + + // Test serialization with None + let model_none = StringStruct { start: None }; + let content_none = yaserde::ser::to_string(&model_none).unwrap(); + assert_eq!( + content_none, + "" + ); + + // Test serialization with Some(empty_vec) + let model_empty = StringStruct { + start: Some(vec![]), + }; + let content_empty = yaserde::ser::to_string(&model_empty).unwrap(); + assert_eq!( + content_empty, + "" + ); +} diff --git a/yaserde_derive/src/common/field.rs b/yaserde_derive/src/common/field.rs index 06f650c..5ed8715 100644 --- a/yaserde_derive/src/common/field.rs +++ b/yaserde_derive/src/common/field.rs @@ -101,11 +101,11 @@ impl YaSerdeField { .map(|p| format!("{}_", p.to_upper_camel_case())) .unwrap_or_default(); - let attribute = self - .attributes - .attribute - .then_some("Attribute_".to_string()) - .unwrap_or_default(); + let attribute = if self.attributes.attribute { + "Attribute_" + } else { + Default::default() + }; Ident::new( &format!( diff --git a/yaserde_derive/src/de/expand_struct.rs b/yaserde_derive/src/de/expand_struct.rs index fca7486..17dfdd4 100644 --- a/yaserde_derive/src/de/expand_struct.rs +++ b/yaserde_derive/src/de/expand_struct.rs @@ -7,7 +7,6 @@ use syn::{DataStruct, Generics, Ident}; pub fn parse( data_struct: &DataStruct, name: &Ident, - root_namespace: &str, root: &str, root_attributes: &YaSerdeAttribute, generics: &Generics, @@ -25,7 +24,20 @@ pub fn parse( .map(|field| YaSerdeField::new(field.clone())) .filter_map(|field| match field.get_type() { Field::FieldStruct { struct_name } => build_default_value(&field, Some(quote!(#struct_name))), - Field::FieldOption { .. } => build_default_value(&field, None), + Field::FieldOption { data_type } => match *data_type { + Field::FieldVec { + data_type: inner_data_type, + } => match *inner_data_type { + Field::FieldStruct { ref struct_name } => { + build_default_value(&field, Some(quote!(::std::vec::Vec<#struct_name>))) + } + simple_type => { + let type_token: TokenStream = simple_type.into(); + build_default_value(&field, Some(quote!(::std::vec::Vec<#type_token>))) + } + }, + _ => build_default_value(&field, None), + }, Field::FieldVec { data_type } => match *data_type { Field::FieldStruct { ref struct_name } => { build_default_vec_value(&field, Some(quote!(::std::vec::Vec<#struct_name>))) @@ -127,7 +139,12 @@ pub fn parse( Field::FieldStruct { struct_name } => struct_visitor(struct_name), Field::FieldOption { data_type } => match *data_type { Field::FieldStruct { struct_name } => struct_visitor(struct_name), - Field::FieldOption { .. } | Field::FieldVec { .. } => None, + Field::FieldOption { .. } => None, + Field::FieldVec { data_type } => match *data_type { + Field::FieldStruct { struct_name } => struct_visitor(struct_name), + Field::FieldOption { .. } | Field::FieldVec { .. } => None, + simple_type => simple_type_visitor(simple_type), + }, simple_type => simple_type_visitor(simple_type), }, Field::FieldVec { data_type } => match *data_type { @@ -160,6 +177,7 @@ pub fn parse( } if let Ok(::yaserde::__xml::reader::XmlEvent::StartElement { .. }) = reader.peek() { // If substruct's start element found then deserialize substruct + reader.inner_struct_label = Some(#label_name); let value = <#struct_name as ::yaserde::YaDeserialize>::deserialize(reader)?; #value_label #action; // read EndElement @@ -191,9 +209,41 @@ pub fn parse( Field::FieldStruct { struct_name } => { visit_struct(struct_name, quote! { = ::std::option::Option::Some(value) }) } - Field::FieldOption { data_type } => { - visit_sub(data_type, quote! { = ::std::option::Option::Some(value) }) - } + Field::FieldOption { data_type } => match *data_type { + Field::FieldVec { + data_type: inner_data_type, + } => match *inner_data_type { + Field::FieldStruct { struct_name } => { + // Handle Option> + visit_struct( + struct_name, + quote! { + = if #value_label.is_some() { + #value_label.as_mut().unwrap().push(value); + #value_label + } else { + Some(vec![value]) + } + }, + ) + } + simple_type => { + // Handle Option> + visit_simple( + simple_type, + quote! { + = if #value_label.is_some() { + #value_label.as_mut().unwrap().push(value); + #value_label + } else { + Some(vec![value]) + } + }, + ) + } + }, + _ => visit_sub(data_type, quote! { = ::std::option::Option::Some(value) }), + }, Field::FieldVec { data_type } => visit_sub(data_type, quote! { .push(value) }), simple_type => visit_simple(simple_type, quote! { = ::std::option::Option::Some(value) }), } @@ -259,6 +309,23 @@ pub fn parse( }) }; + let visit_option_vec = |visitor: &Ident, visitor_label: &Ident| { + Some(quote! { + for attr in attributes { + if attr.name.local_name == #label_name { + if #label.is_none() { + #label = Some(Vec::new()); + } + for value in attr.value.split_whitespace() { + let visitor = #visitor_label{}; + let value = visitor.#visitor(value)?; + #label.as_mut().unwrap().push(value); + } + } + } + }) + }; + let visit_string = || { Some(quote! { for attr in attributes { @@ -286,7 +353,18 @@ pub fn parse( }; let visit_sub = |sub_type: Box, action: TokenStream| match *sub_type { - Field::FieldOption { .. } | Field::FieldVec { .. } => unimplemented!(), + Field::FieldOption { .. } => unimplemented!(), + Field::FieldVec { data_type } => match data_type.as_ref() { + Field::FieldStruct { struct_name } => visit_option_vec( + &Ident::new("visit_str", field.get_span()), + &field.get_visitor_ident(Some(struct_name)), + ), + Field::FieldOption { .. } | Field::FieldVec { .. } => unimplemented!("Not supported"), + simple_type => visit_option_vec( + &simple_type.get_simple_type_visitor(), + &field.get_visitor_ident(None), + ), + }, Field::FieldStruct { struct_name } => visit_struct(struct_name, action), simple_type => visit_simple(simple_type, action), }; @@ -383,7 +461,6 @@ pub fn parse( build_code_for_unused_xml_events(&call_flatten_visitors) }; - let flatten = root_attributes.flatten; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { @@ -392,6 +469,7 @@ pub fn parse( fn deserialize( reader: &mut ::yaserde::de::Deserializer, ) -> ::std::result::Result { + let root_label= reader.inner_struct_label.take().unwrap_or(#root); let (named_element, struct_namespace) = if let ::yaserde::__xml::reader::XmlEvent::StartElement { name, .. } = reader.peek()?.to_owned() { (name.local_name.to_owned(), name.namespace.clone()) @@ -421,7 +499,7 @@ pub fn parse( match event { ::yaserde::__xml::reader::XmlEvent::StartElement{ref name, ref attributes, ..} => { let namespace = name.namespace.clone().unwrap_or_default(); - if depth == 0 && name.local_name == #root && namespace.as_str() == #root_namespace { + if depth == 0 && name.local_name == root_label { // Consume root element. We must do this first. In the case it shares a name with a child element, we don't // want to prematurely match the child element below. let event = reader.next_event()?; @@ -457,9 +535,8 @@ pub fn parse( depth -= 1; } ::yaserde::__xml::reader::XmlEvent::EndDocument => { - if #flatten { - break; - } + // once we receive this once, we will keep getting it, potentially looping forever + break; } ::yaserde::__xml::reader::XmlEvent::Characters(ref text_content) => { #set_text diff --git a/yaserde_derive/src/de/mod.rs b/yaserde_derive/src/de/mod.rs index 4526209..99d243a 100644 --- a/yaserde_derive/src/de/mod.rs +++ b/yaserde_derive/src/de/mod.rs @@ -15,27 +15,11 @@ pub fn expand_derive_deserialize(ast: &syn::DeriveInput) -> Result expand_struct::parse( - data_struct, - name, - &root_namespace, - &root_name, - &root_attributes, - generics, - ), + syn::Data::Struct(ref data_struct) => { + expand_struct::parse(data_struct, name, &root_name, &root_attributes, generics) + } syn::Data::Enum(ref data_enum) => { expand_enum::parse(data_enum, name, &root_name, &root_attributes, generics) } diff --git a/yaserde_derive/src/ser/element.rs b/yaserde_derive/src/ser/element.rs index 0f2fb0c..24e8cd3 100644 --- a/yaserde_derive/src/ser/element.rs +++ b/yaserde_derive/src/ser/element.rs @@ -2,10 +2,6 @@ use crate::common::YaSerdeField; use proc_macro2::{Ident, TokenStream}; use quote::quote; -pub fn enclose_formatted_characters(label: &Ident, label_name: String) -> TokenStream { - enclose_xml_event(label_name, quote!(format!("{}", &self.#label))) -} - pub fn enclose_formatted_characters_for_value(label: &Ident, label_name: String) -> TokenStream { enclose_xml_event(label_name, quote!(format!("{}", #label))) } diff --git a/yaserde_derive/src/ser/expand_struct.rs b/yaserde_derive/src/ser/expand_struct.rs index 2a4507b..9cf8b51 100644 --- a/yaserde_derive/src/ser/expand_struct.rs +++ b/yaserde_derive/src/ser/expand_struct.rs @@ -75,21 +75,31 @@ pub fn serialize( } }), ), - Field::FieldVec { .. } => { - let item_ident = Ident::new("yaserde_item", field.get_span()); - let inner = enclose_formatted_characters(&item_ident, label_name); - - field.ser_wrap_default_attribute( - None, - quote!({ - if let ::std::option::Option::Some(ref yaserde_list) = self.#label { - for yaserde_item in yaserde_list.iter() { - #inner - } - } - }), - ) - } + Field::FieldVec { data_type } => match *data_type { + Field::FieldString + | Field::FieldBool + | Field::FieldI8 + | Field::FieldU8 + | Field::FieldI16 + | Field::FieldU16 + | Field::FieldI32 + | Field::FieldU32 + | Field::FieldI64 + | Field::FieldU64 + | Field::FieldF32 + | Field::FieldF64 => { + ser_option_vec_attribute(&field, &label, &label_name, quote!(item.to_string())) + } + Field::FieldStruct { .. } => ser_option_vec_attribute( + &field, + &label, + &label_name, + quote!(::yaserde::ser::to_string_content(item).unwrap_or_default()), + ), + _ => { + unimplemented!("Complex data types in Option> attributes not yet supported") + } + }, Field::FieldStruct { .. } => field.ser_wrap_default_attribute( Some(quote! { self.#label @@ -115,10 +125,46 @@ pub fn serialize( struct_start_event.attr(#label_name, &yaserde_inner) }), ), - Field::FieldVec { .. } => { - // TODO - quote!() - } + Field::FieldVec { data_type } => match *data_type { + Field::FieldString + | Field::FieldBool + | Field::FieldI8 + | Field::FieldU8 + | Field::FieldI16 + | Field::FieldU16 + | Field::FieldI32 + | Field::FieldU32 + | Field::FieldI64 + | Field::FieldU64 + | Field::FieldF32 + | Field::FieldF64 => field.ser_wrap_default_attribute( + Some(quote! { + self.#label + .iter() + .map(|item| item.to_string()) + .collect::<::std::vec::Vec<_>>() + .join(" ") + }), + quote!({ + struct_start_event.attr(#label_name, &yaserde_inner) + }), + ), + Field::FieldOption { .. } | Field::FieldVec { .. } => { + unimplemented!("Nested Option or Vec in Vec not supported for attributes") + } + Field::FieldStruct { .. } => field.ser_wrap_default_attribute( + Some(quote! { + self.#label + .iter() + .map(|item| ::yaserde::ser::to_string_content(item)) + .collect::<::std::result::Result<::std::vec::Vec<_>, _>>()? + .join(" ") + }), + quote!({ + struct_start_event.attr(#label_name, &yaserde_inner) + }), + ), + }, } } else { match field.get_type() { @@ -213,18 +259,34 @@ pub fn serialize( }) } Field::FieldVec { .. } => { - let item_ident = Ident::new("yaserde_item", field.get_span()); - let inner = enclose_formatted_characters_for_value(&item_ident, label_name); + // Only use attribute serialization if the field is marked as an attribute + if field.is_attribute() { + let item_ident = Ident::new("yaserde_item", field.get_span()); + let inner = enclose_formatted_characters_for_value(&item_ident, label_name); - Some(quote! { - #conditions { - if let ::std::option::Option::Some(ref yaserde_items) = &self.#label { - for yaserde_item in yaserde_items.iter() { - #inner + Some(quote! { + #conditions { + if let ::std::option::Option::Some(ref yaserde_items) = &self.#label { + for yaserde_item in yaserde_items.iter() { + #inner + } } } - } - }) + }) + } else { + // For non-attribute Option>, use standard serialization + Some(quote! { + #conditions { + if let ::std::option::Option::Some(ref items) = &self.#label { + for item in items.iter() { + writer.set_start_event_name(::std::option::Option::Some(#label_name.to_string())); + writer.set_skip_start_end(false); + ::yaserde::YaSerialize::serialize(item, writer)?; + } + } + } + }) + } } Field::FieldStruct { .. } => Some(if field.is_flatten() { quote! { @@ -364,3 +426,38 @@ pub fn serialize( generics, ) } + +/// Helper function to generate serialization code for Option> attributes +fn ser_option_vec_attribute( + field: &YaSerdeField, + label: &Option, + label_name: &str, + item_serializer: TokenStream, +) -> TokenStream { + let yaserde_inner_expr = quote! { + self.#label + .as_ref() + .map_or_else( + || ::std::string::String::new(), + |yaserde_list| { + yaserde_list + .iter() + .map(|item| #item_serializer) + .collect::<::std::vec::Vec<_>>() + .join(" ") + } + ) + }; + + let attribute_expr = quote!({ + if self.#label.is_some() && !yaserde_inner.is_empty() { + struct_start_event.attr(#label_name, &yaserde_inner) + } else if self.#label.is_some() { + struct_start_event.attr(#label_name, "") + } else { + struct_start_event + } + }); + + field.ser_wrap_default_attribute(Some(yaserde_inner_expr), attribute_expr) +}