Merge pull request #38 from DmitrySamoylov/feature-attribute-option
Add support for Option<struct> in 'attribute' fields
This commit is contained in:
		
						commit
						df674965f8
					
				| @ -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<std::string::String>, | ||||
|     #[yaserde(attribute)] | ||||
|     attr_option_enum: Option<other_mod::AttrEnum>, | ||||
|   } | ||||
| 
 | ||||
|   convert_and_validate!( | ||||
|     r#"<Struct />"#, | ||||
|     Struct, | ||||
|     Struct { | ||||
|       attr_option_string: None, | ||||
|       attr_option_enum: None | ||||
|     } | ||||
|   ); | ||||
| 
 | ||||
|   convert_and_validate!( | ||||
|     r#"<Struct attr_option_string="some value" attr_option_enum="variant 2" />"#, | ||||
|     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)] | ||||
|  | ||||
| @ -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<std::string::String>, | ||||
|     #[yaserde(attribute)] | ||||
|     attr_option_enum: Option<other_mod::AttrEnum>, | ||||
|   } | ||||
| 
 | ||||
|   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#" | ||||
|     <?xml version="1.0" encoding="utf-8"?> | ||||
|     <Struct /> | ||||
|     "#
 | ||||
|   ); | ||||
| 
 | ||||
|   convert_and_validate!( | ||||
|     Struct { | ||||
|       attr_option_string: Some("some value".to_string()), | ||||
|       attr_option_enum: Some(other_mod::AttrEnum::Variant2), | ||||
|     }, | ||||
|     r#" | ||||
|     <?xml version="1.0" encoding="utf-8"?> | ||||
|     <Struct attr_option_string="some value" attr_option_enum="variant 2" /> | ||||
|     "#
 | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn ser_rename() { | ||||
|   #[derive(YaSerialize, PartialEq, Debug)] | ||||
|  | ||||
| @ -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<Self::Value, String> { | ||||
|                 let content = "<".to_string() + #struct_id + ">" + v + "</" + #struct_id + ">"; | ||||
|                 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<Self::Value, String> { | ||||
|               let content = "<".to_string() + #struct_id + ">" + v + "</" + #struct_id + ">"; | ||||
|               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<syn::Ident>) -> Option<syn::Ident> { | ||||
|     .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, | ||||
|   ) | ||||
| } | ||||
|  | ||||
| @ -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<FieldType> { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| fn get_sub_type(t: &syn::PathSegment) -> Option<syn::PathSegment> { | ||||
| fn get_sub_type(t: &syn::PathSegment) -> Option<syn::Path> { | ||||
|   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()); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @ -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 { .. } => { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user