Add support for Option<struct> in 'attribute' fields
This commit is contained in:
		
							parent
							
								
									264fa06b8b
								
							
						
					
					
						commit
						cad7f88c4e
					
				| @ -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] | #[test] | ||||||
| fn de_rename() { | fn de_rename() { | ||||||
|   #[derive(YaDeserialize, PartialEq, Debug)] |   #[derive(YaDeserialize, PartialEq, Debug)] | ||||||
|  | |||||||
| @ -124,6 +124,66 @@ fn se_attributes() { | |||||||
|   convert_and_validate!(model, content); |   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] | #[test] | ||||||
| fn ser_rename() { | fn ser_rename() { | ||||||
|   #[derive(YaSerialize, PartialEq, Debug)] |   #[derive(YaSerialize, PartialEq, Debug)] | ||||||
|  | |||||||
| @ -103,76 +103,51 @@ pub fn parse( | |||||||
| 
 | 
 | ||||||
|       let visitor_label = build_visitor_ident(&label_name, field.span(), None); |       let visitor_label = build_visitor_ident(&label_name, field.span(), None); | ||||||
| 
 | 
 | ||||||
|       get_field_type(field).and_then(|f| match f { |       let struct_visitor = |struct_name: syn::Path| { | ||||||
|         FieldType::FieldTypeStruct { struct_name } => { |         let struct_id: String = struct_name | ||||||
|           let struct_id: String = struct_name |           .segments | ||||||
|             .segments |           .iter() | ||||||
|             .iter() |           .map(|s| s.ident.to_string()) | ||||||
|             .map(|s| s.ident.to_string()) |           .collect(); | ||||||
|             .collect(); |  | ||||||
|           let struct_ident = build_visitor_ident(&label_name, field.span(), Some(&struct_id)); |  | ||||||
| 
 | 
 | ||||||
|           Some(quote! { |         let struct_ident = build_visitor_ident(&label_name, field.span(), Some(&struct_name)); | ||||||
|             #[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> { |         Some(quote! { | ||||||
|                 let content = "<".to_string() + #struct_id + ">" + v + "</" + #struct_id + ">"; |           #[allow(non_snake_case, non_camel_case_types)] | ||||||
|                 let value : Result<#struct_name, String> = yaserde::de::from_str(&content); |           struct #struct_ident; | ||||||
|                 value |           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), |       let simple_type_visitor = |simple_type: FieldType| { | ||||||
|             &visitor_label, |         build_declare_visitor( | ||||||
|           ), |  | ||||||
|         }, |  | ||||||
|         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( |  | ||||||
|           &get_simple_type_token(&simple_type), |           &get_simple_type_token(&simple_type), | ||||||
|           &get_simple_type_visitor(&simple_type), |           &get_simple_type_visitor(&simple_type), | ||||||
|           &visitor_label, |           &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) |     .filter_map(|x| x) | ||||||
| @ -341,9 +316,14 @@ pub fn parse( | |||||||
|           } |           } | ||||||
|         }), |         }), | ||||||
|         FieldType::FieldTypeOption { data_type } => match *data_type { |         FieldType::FieldTypeOption { data_type } => match *data_type { | ||||||
|           FieldType::FieldTypeStruct { .. } |           FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => unimplemented!(), | ||||||
|           | FieldType::FieldTypeOption { .. } |           FieldType::FieldTypeStruct { struct_name } => build_call_visitor_for_attribute( | ||||||
|           | FieldType::FieldTypeVec { .. } => None, |             label, | ||||||
|  |             &label_name, | ||||||
|  |             "e! {= Some(value) }, | ||||||
|  |             "e! {visit_str}, | ||||||
|  |             &build_visitor_ident(&label_name, field.span(), Some(&struct_name)), | ||||||
|  |           ), | ||||||
|           simple_type => { |           simple_type => { | ||||||
|             let visitor = get_simple_type_visitor(&simple_type); |             let visitor = get_simple_type_visitor(&simple_type); | ||||||
| 
 | 
 | ||||||
| @ -356,28 +336,13 @@ pub fn parse( | |||||||
|             ) |             ) | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         FieldType::FieldTypeStruct { struct_name } => { |         FieldType::FieldTypeStruct { struct_name } => build_call_visitor_for_attribute( | ||||||
|           let struct_ident = Ident::new( |           label, | ||||||
|             &format!( |           &label_name, | ||||||
|               "__Visitor_{}_{}", |           "e! {= value }, | ||||||
|               label_name, |           "e! {visit_str}, | ||||||
|               struct_name.into_token_stream() |           &build_visitor_ident(&label_name, field.span(), Some(&struct_name)), | ||||||
|             ), |         ), | ||||||
|             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::FieldTypeVec { .. } => None, |         FieldType::FieldTypeVec { .. } => None, | ||||||
|         simple_type => { |         simple_type => { | ||||||
|           let visitor = get_simple_type_visitor(&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())) |     .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( |   Ident::new( | ||||||
|     &format!( |     &format!("__Visitor_{}_{}", label.replace(".", "_"), struct_id), | ||||||
|       "__Visitor_{}_{}", |  | ||||||
|       label.replace(".", "_"), |  | ||||||
|       struct_id.unwrap_or("") |  | ||||||
|     ), |  | ||||||
|     span, |     span, | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  | |||||||
| @ -37,10 +37,10 @@ impl FieldType { | |||||||
|         "f32" => Some(FieldType::FieldTypeF32), |         "f32" => Some(FieldType::FieldTypeF32), | ||||||
|         "f64" => Some(FieldType::FieldTypeF64), |         "f64" => Some(FieldType::FieldTypeF64), | ||||||
|         "Option" => get_sub_type(t).map(|data_type| FieldType::FieldTypeOption { |         "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 { |         "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 { |         _ => Some(FieldType::FieldTypeStruct { | ||||||
|           struct_name: path.clone(), |           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 syn::PathArguments::AngleBracketed(ref args) = t.arguments { | ||||||
|     if let Some(tt) = args.args.first() { |     if let Some(tt) = args.args.first() { | ||||||
|       if let syn::GenericArgument::Type(ref argument) = *tt { |       if let syn::GenericArgument::Type(ref argument) = *tt { | ||||||
|         if let Path(ref path2) = *argument { |         if let Path(ref path) = *argument { | ||||||
|           if let Some(ttt) = path2.path.segments.first() { |           return Some(path.path.clone()); | ||||||
|             return Some(ttt.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!(), |           _ => unimplemented!(), | ||||||
|         }, |         }, | ||||||
|         FieldType::FieldTypeStruct { .. } => { |         FieldType::FieldTypeStruct { .. } => { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user