From cad7f88c4e1c9791f8d848751f173765116ca53b Mon Sep 17 00:00:00 2001 From: Dmitry Samoylov Date: Wed, 12 Feb 2020 15:45:30 +0700 Subject: [PATCH] Add support for Option in 'attribute' fields --- yaserde/tests/deserializer.rs | 47 +++++++ yaserde/tests/serializer.rs | 60 +++++++++ yaserde_derive/src/de/expand_struct.rs | 160 ++++++++++-------------- yaserde_derive/src/field_type.rs | 12 +- yaserde_derive/src/ser/expand_struct.rs | 48 +++++++ 5 files changed, 226 insertions(+), 101 deletions(-) diff --git a/yaserde/tests/deserializer.rs b/yaserde/tests/deserializer.rs index 3623499..6cfc2a9 100644 --- a/yaserde/tests/deserializer.rs +++ b/yaserde/tests/deserializer.rs @@ -172,6 +172,53 @@ fn de_attributes() { ); } +#[test] +fn de_attributes_complex() { + mod other_mod { + use super::*; + + #[derive(YaDeserialize, PartialEq, Debug)] + pub enum AttrEnum { + #[yaserde(rename = "variant 1")] + Variant1, + #[yaserde(rename = "variant 2")] + Variant2, + } + + impl Default for AttrEnum { + fn default() -> AttrEnum { + AttrEnum::Variant1 + } + } + } + + #[derive(Default, YaDeserialize, PartialEq, Debug)] + pub struct Struct { + #[yaserde(attribute)] + attr_option_string: Option, + #[yaserde(attribute)] + attr_option_enum: Option, + } + + convert_and_validate!( + r#""#, + Struct, + Struct { + attr_option_string: None, + attr_option_enum: None + } + ); + + convert_and_validate!( + r#""#, + Struct, + Struct { + attr_option_string: Some("some value".to_string()), + attr_option_enum: Some(other_mod::AttrEnum::Variant2) + } + ); +} + #[test] fn de_rename() { #[derive(YaDeserialize, PartialEq, Debug)] diff --git a/yaserde/tests/serializer.rs b/yaserde/tests/serializer.rs index eeb9c54..4aea5ed 100644 --- a/yaserde/tests/serializer.rs +++ b/yaserde/tests/serializer.rs @@ -124,6 +124,66 @@ fn se_attributes() { convert_and_validate!(model, content); } +#[test] +fn se_attributes_complex() { + mod other_mod { + use super::*; + + #[derive(YaSerialize, PartialEq, Debug)] + pub enum AttrEnum { + #[yaserde(rename = "variant 1")] + Variant1, + #[yaserde(rename = "variant 2")] + Variant2, + } + + impl Default for AttrEnum { + fn default() -> AttrEnum { + AttrEnum::Variant1 + } + } + } + + #[derive(YaSerialize, PartialEq, Debug)] + pub struct Struct { + #[yaserde(attribute)] + attr_option_string: Option, + #[yaserde(attribute)] + attr_option_enum: Option, + } + + impl Default for Struct { + fn default() -> Struct { + Struct { + attr_option_string: None, + attr_option_enum: None, + } + } + } + + convert_and_validate!( + Struct { + attr_option_string: None, + attr_option_enum: None, + }, + r#" + + + "# + ); + + convert_and_validate!( + Struct { + attr_option_string: Some("some value".to_string()), + attr_option_enum: Some(other_mod::AttrEnum::Variant2), + }, + r#" + + + "# + ); +} + #[test] fn ser_rename() { #[derive(YaSerialize, PartialEq, Debug)] diff --git a/yaserde_derive/src/de/expand_struct.rs b/yaserde_derive/src/de/expand_struct.rs index ea349ef..7886979 100644 --- a/yaserde_derive/src/de/expand_struct.rs +++ b/yaserde_derive/src/de/expand_struct.rs @@ -103,76 +103,51 @@ pub fn parse( let visitor_label = build_visitor_ident(&label_name, field.span(), None); - get_field_type(field).and_then(|f| match f { - FieldType::FieldTypeStruct { struct_name } => { - let struct_id: String = struct_name - .segments - .iter() - .map(|s| s.ident.to_string()) - .collect(); - let struct_ident = build_visitor_ident(&label_name, field.span(), Some(&struct_id)); + let struct_visitor = |struct_name: syn::Path| { + let struct_id: String = struct_name + .segments + .iter() + .map(|s| s.ident.to_string()) + .collect(); - Some(quote! { - #[allow(non_snake_case, non_camel_case_types)] - struct #struct_ident; - impl<'de> Visitor<'de> for #struct_ident { - type Value = #struct_name; + let struct_ident = build_visitor_ident(&label_name, field.span(), Some(&struct_name)); - fn visit_str(self, v: &str) -> Result { - let content = "<".to_string() + #struct_id + ">" + v + ""; - let value : Result<#struct_name, String> = yaserde::de::from_str(&content); - value - } + Some(quote! { + #[allow(non_snake_case, non_camel_case_types)] + struct #struct_ident; + impl<'de> Visitor<'de> for #struct_ident { + type Value = #struct_name; + + fn visit_str(self, v: &str) -> Result { + let content = "<".to_string() + #struct_id + ">" + v + ""; + let value : Result<#struct_name, String> = yaserde::de::from_str(&content); + value } - }) - } - FieldType::FieldTypeOption { data_type } => match *data_type { - FieldType::FieldTypeStruct { ref struct_name } => { - let struct_ident = Ident::new( - &format!("{}", struct_name.into_token_stream()), - field.span(), - ); - Some(quote! { - #[allow(non_snake_case, non_camel_case_types)] - struct #visitor_label; - impl<'de> Visitor<'de> for #visitor_label { - type Value = #struct_ident; - } - }) } - FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None, - simple_type => build_declare_visitor( - &get_simple_type_token(&simple_type), - &get_simple_type_visitor(&simple_type), - &visitor_label, - ), - }, - FieldType::FieldTypeVec { data_type } => match *data_type { - FieldType::FieldTypeStruct { ref struct_name } => { - let struct_ident = Ident::new( - &format!("{}", struct_name.into_token_stream()), - field.span(), - ); - Some(quote! { - #[allow(non_snake_case, non_camel_case_types)] - struct #visitor_label; - impl<'de> Visitor<'de> for #visitor_label { - type Value = #struct_ident; - } - }) - } - FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None, - simple_type => build_declare_visitor( - &get_simple_type_token(&simple_type), - &get_simple_type_visitor(&simple_type), - &visitor_label, - ), - }, - simple_type => build_declare_visitor( + }) + }; + + let simple_type_visitor = |simple_type: FieldType| { + build_declare_visitor( &get_simple_type_token(&simple_type), &get_simple_type_visitor(&simple_type), &visitor_label, - ), + ) + }; + + get_field_type(field).and_then(|f| match f { + FieldType::FieldTypeStruct { struct_name } => struct_visitor(struct_name), + FieldType::FieldTypeOption { data_type } => match *data_type { + FieldType::FieldTypeStruct { struct_name } => struct_visitor(struct_name), + FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None, + simple_type => simple_type_visitor(simple_type), + }, + FieldType::FieldTypeVec { data_type } => match *data_type { + FieldType::FieldTypeStruct { struct_name } => struct_visitor(struct_name), + FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None, + simple_type => simple_type_visitor(simple_type), + }, + simple_type => simple_type_visitor(simple_type), }) }) .filter_map(|x| x) @@ -341,9 +316,14 @@ pub fn parse( } }), FieldType::FieldTypeOption { data_type } => match *data_type { - FieldType::FieldTypeStruct { .. } - | FieldType::FieldTypeOption { .. } - | FieldType::FieldTypeVec { .. } => None, + FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => unimplemented!(), + FieldType::FieldTypeStruct { struct_name } => build_call_visitor_for_attribute( + label, + &label_name, + "e! {= Some(value) }, + "e! {visit_str}, + &build_visitor_ident(&label_name, field.span(), Some(&struct_name)), + ), simple_type => { let visitor = get_simple_type_visitor(&simple_type); @@ -356,28 +336,13 @@ pub fn parse( ) } }, - FieldType::FieldTypeStruct { struct_name } => { - let struct_ident = Ident::new( - &format!( - "__Visitor_{}_{}", - label_name, - struct_name.into_token_stream() - ), - field.span(), - ); - - Some(quote! { - for attr in attributes { - if attr.name.local_name == #label_name { - let visitor = #struct_ident{}; - match visitor.visit_str(&attr.value) { - Ok(value) => {#label = value;} - Err(msg) => {return Err(msg);} - } - } - } - }) - } + FieldType::FieldTypeStruct { struct_name } => build_call_visitor_for_attribute( + label, + &label_name, + "e! {= value }, + "e! {visit_str}, + &build_visitor_ident(&label_name, field.span(), Some(&struct_name)), + ), FieldType::FieldTypeVec { .. } => None, simple_type => { let visitor = get_simple_type_visitor(&simple_type); @@ -646,13 +611,20 @@ fn get_value_label(ident: &Option) -> Option { .map(|ident| syn::Ident::new(&format!("__{}_value", ident.to_string()), ident.span())) } -fn build_visitor_ident(label: &str, span: Span, struct_id: Option<&str>) -> Ident { +fn build_visitor_ident(label: &str, span: Span, struct_name: Option<&syn::Path>) -> Ident { + let struct_id = struct_name.map_or_else( + || "".to_string(), + |struct_name| { + struct_name + .segments + .iter() + .map(|s| s.ident.to_string()) + .collect() + }, + ); + Ident::new( - &format!( - "__Visitor_{}_{}", - label.replace(".", "_"), - struct_id.unwrap_or("") - ), + &format!("__Visitor_{}_{}", label.replace(".", "_"), struct_id), span, ) } diff --git a/yaserde_derive/src/field_type.rs b/yaserde_derive/src/field_type.rs index fb245b8..b9b55c6 100644 --- a/yaserde_derive/src/field_type.rs +++ b/yaserde_derive/src/field_type.rs @@ -37,10 +37,10 @@ impl FieldType { "f32" => Some(FieldType::FieldTypeF32), "f64" => Some(FieldType::FieldTypeF64), "Option" => get_sub_type(t).map(|data_type| FieldType::FieldTypeOption { - data_type: Box::new(FieldType::from_ident(&syn::Path::from(data_type)).unwrap()), + data_type: Box::new(FieldType::from_ident(&data_type).unwrap()), }), "Vec" => get_sub_type(t).map(|data_type| FieldType::FieldTypeVec { - data_type: Box::new(FieldType::from_ident(&syn::Path::from(data_type)).unwrap()), + data_type: Box::new(FieldType::from_ident(&data_type).unwrap()), }), _ => Some(FieldType::FieldTypeStruct { struct_name: path.clone(), @@ -58,14 +58,12 @@ pub fn get_field_type(field: &syn::Field) -> Option { } } -fn get_sub_type(t: &syn::PathSegment) -> Option { +fn get_sub_type(t: &syn::PathSegment) -> Option { if let syn::PathArguments::AngleBracketed(ref args) = t.arguments { if let Some(tt) = args.args.first() { if let syn::GenericArgument::Type(ref argument) = *tt { - if let Path(ref path2) = *argument { - if let Some(ttt) = path2.path.segments.first() { - return Some(ttt.clone()); - } + if let Path(ref path) = *argument { + return Some(path.path.clone()); } } } diff --git a/yaserde_derive/src/ser/expand_struct.rs b/yaserde_derive/src/ser/expand_struct.rs index ed208d4..5ab2f59 100644 --- a/yaserde_derive/src/ser/expand_struct.rs +++ b/yaserde_derive/src/ser/expand_struct.rs @@ -176,6 +176,54 @@ pub fn serialize( }) } } + FieldType::FieldTypeStruct { .. } => { + if let Some(ref d) = field_attrs.default { + let default_function = Ident::new(&d, field.span()); + Some(quote! { + let struct_start_event = if let Some(ref value) = self.#label { + if *value != #default_function() { + struct_start_event.attr(#label_name, &*{ + use std::mem; + match yaserde::ser::to_string_content(value) { + Ok(value) => { + unsafe { + let ret : &'static str = mem::transmute(&value as &str); + mem::forget(value); + ret + } + }, + Err(msg) => return Err("Unable to serialize content".to_owned()), + } + }) + } else { + struct_start_event + } + } else { + struct_start_event + }; + }) + } else { + Some(quote! { + let struct_start_event = if let Some(ref value) = self.#label { + struct_start_event.attr(#label_name, &*{ + use std::mem; + match yaserde::ser::to_string_content(value) { + Ok(value) => { + unsafe { + let ret : &'static str = mem::transmute(&value as &str); + mem::forget(value); + ret + } + }, + Err(msg) => return Err("Unable to serialize content".to_owned()), + } + }) + } else { + struct_start_event + }; + }) + } + } _ => unimplemented!(), }, FieldType::FieldTypeStruct { .. } => {