From d277d5137bdaf64c9a4f1e5ec74e66b0ad281b26 Mon Sep 17 00:00:00 2001 From: Dmitry Samoylov Date: Fri, 27 Dec 2019 20:37:56 +0700 Subject: [PATCH] Add deserialization for enums with values (#8) --- yaserde/tests/deserializer.rs | 172 +++++++++ yaserde_derive/src/de/expand_enum.rs | 511 +++++++++++++-------------- 2 files changed, 423 insertions(+), 260 deletions(-) diff --git a/yaserde/tests/deserializer.rs b/yaserde/tests/deserializer.rs index d3f4636..9bb38b2 100644 --- a/yaserde/tests/deserializer.rs +++ b/yaserde/tests/deserializer.rs @@ -351,6 +351,178 @@ fn de_attribute_enum() { ); } +#[test] +fn de_complex_enum() { + #[derive(YaDeserialize, PartialEq, Debug)] + pub struct XmlStruct { + background: Color, + } + + #[derive(YaDeserialize, PartialEq, Debug, Default)] + pub struct OtherStruct { + fi: i32, + se: i32, + } + + #[derive(YaDeserialize, PartialEq, Debug)] + pub enum Color { + White, + Black(String), + Orange(std::string::String), + Red(i32), + Green(OtherStruct), + Yellow(Option), + Brown(Option), + Blue(Vec), + Purple(Vec), + Magenta(Vec), + #[yaserde(rename = "NotSoCyan")] + Cyan(Vec), + } + + impl Default for Color { + fn default() -> Color { + Color::White + } + } + + let content = r#" + + + text + + + "#; + convert_and_validate!( + content, + XmlStruct, + XmlStruct { + background: Color::Black(String::from("text")), + } + ); + + let content = r#" + + + text + + + "#; + convert_and_validate!( + content, + XmlStruct, + XmlStruct { + background: Color::Orange(String::from("text")), + } + ); + + let content = r#" + + + + 12 + 23 + + + + "#; + convert_and_validate!( + content, + XmlStruct, + XmlStruct { + background: Color::Green(OtherStruct { fi: 12, se: 23 }), + } + ); + + let content = r#" + + + + 12 + 23 + + + + "#; + convert_and_validate!( + content, + XmlStruct, + XmlStruct { + background: Color::Brown(Some(OtherStruct { fi: 12, se: 23 })), + } + ); + + let content = r#" + + + abc + def + + + "#; + convert_and_validate!( + content, + XmlStruct, + XmlStruct { + background: Color::Blue(vec![String::from("abc"), String::from("def")]), + } + ); + + let content = r#" + + + 12 + 43 + + + "#; + convert_and_validate!( + content, + XmlStruct, + XmlStruct { + background: Color::Purple(vec![12, 43]), + } + ); + + let content = r#" + + + 1223 + 6398 + + + "#; + convert_and_validate!( + content, + XmlStruct, + XmlStruct { + background: Color::Magenta(vec![ + OtherStruct { fi: 12, se: 23 }, + OtherStruct { fi: 63, se: 98 } + ]), + } + ); + + let content = r#" + + + 1223 + 6398 + + + "#; + convert_and_validate!( + content, + XmlStruct, + XmlStruct { + background: Color::Cyan(vec![ + OtherStruct { fi: 12, se: 23 }, + OtherStruct { fi: 63, se: 98 } + ]) + } + ); +} + #[test] fn de_name_issue_21() { #[derive(YaDeserialize, PartialEq, Debug)] diff --git a/yaserde_derive/src/de/expand_enum.rs b/yaserde_derive/src/de/expand_enum.rs index e983118..5cc66f3 100644 --- a/yaserde_derive/src/de/expand_enum.rs +++ b/yaserde_derive/src/de/expand_enum.rs @@ -1,8 +1,6 @@ use attribute::*; -use de::build_default_value::build_default_value; use field_type::*; use proc_macro2::{Span, TokenStream}; -use quote::TokenStreamExt; use std::collections::BTreeMap; use syn::DataEnum; use syn::Fields; @@ -14,250 +12,18 @@ pub fn parse( root: &str, _namespaces: &BTreeMap, ) -> TokenStream { - let variables: TokenStream = data_enum - .variants - .iter() - .map(|variant| match variant.fields { - Fields::Unit => None, - Fields::Named(ref fields) => { - let enum_fields = fields - .named - .iter() - .map(|field| { - let field_label = &field.ident; - let field_attrs = YaSerdeAttribute::parse(&field.attrs); - - match get_field_type(field) { - Some(FieldType::FieldTypeString) => build_default_value( - field_label, - "e! {String}, - "e! {"".to_string()}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeBool) => build_default_value( - field_label, - "e! {bool}, - "e! {false}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeI8) => { - build_default_value(field_label, "e! {i8}, "e! {0}, &field_attrs.default) - } - Some(FieldType::FieldTypeU8) => { - build_default_value(field_label, "e! {u8}, "e! {0}, &field_attrs.default) - } - Some(FieldType::FieldTypeI16) => build_default_value( - field_label, - "e! {i16}, - "e! {0}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeU16) => build_default_value( - field_label, - "e! {u16}, - "e! {0}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeI32) => build_default_value( - field_label, - "e! {i32}, - "e! {0}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeU32) => build_default_value( - field_label, - "e! {u32}, - "e! {0}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeI64) => build_default_value( - field_label, - "e! {i64}, - "e! {0}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeU64) => build_default_value( - field_label, - "e! {u64}, - "e! {0}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeF32) => build_default_value( - field_label, - "e! {f32}, - "e! {0}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeF64) => build_default_value( - field_label, - "e! {f64}, - "e! {0}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeStruct { struct_name }) => build_default_value( - field_label, - "e! {#struct_name}, - "e! {#struct_name::default()}, - &field_attrs.default, - ), - Some(FieldType::FieldTypeOption { .. }) => { - if let Some(d) = &field_attrs.default { - let default_function = Ident::new(&d, Span::call_site()); - - Some(quote! { - #[allow(unused_mut, non_snake_case, non_camel_case_types)] - let mut #field_label = #default_function(); - }) - } else { - Some(quote! { - #[allow(unused_mut, non_snake_case, non_camel_case_types)] - let mut #field_label = None; - }) - } - } - Some(FieldType::FieldTypeVec { data_type }) => { - let dt = Box::into_raw(data_type); - match unsafe { dt.as_ref() } { - Some(&FieldType::FieldTypeString) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeBool) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeI8) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeU8) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeI16) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeU16) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeI32) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeU32) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeI64) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeU64) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeF32) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeF64) => build_default_value( - field_label, - "e! {Vec}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeStruct { ref struct_name }) => build_default_value( - field_label, - "e! {Vec<#struct_name>}, - "e! {vec![]}, - &field_attrs.default, - ), - Some(&FieldType::FieldTypeOption { .. }) - | Some(&FieldType::FieldTypeVec { .. }) => { - unimplemented!(); - } - None => { - unimplemented!(); - } - } - } - None => None, - } - }) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()) - .fold(TokenStream::new(), |mut sum, val| { - sum.append_all(val); - sum - }); - - Some(enum_fields) - } - Fields::Unnamed(ref _fields) => { - unimplemented!(); - } - }) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()) - .fold(TokenStream::new(), |mut sum, val| { - sum.append_all(val); - sum - }); - let match_to_enum: TokenStream = data_enum .variants .iter() - .map(|variant| { - let field_attrs = YaSerdeAttribute::parse(&variant.attrs); - let renamed_label = match field_attrs.rename { - Some(value) => Ident::new(&value.to_string(), Span::call_site()), - None => variant.ident.clone(), - }; - let label = &variant.ident; - let label_name = renamed_label.to_string(); - - match variant.fields { - Fields::Unit => Some(quote! { - #label_name => { - simple_enum_value = Some(#name::#label); - } - }), - _ => None, - } - }) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()) - .fold(TokenStream::new(), |mut tokens, token| { - tokens.append_all(token); - tokens - }); + .map(|variant| parse_variant(variant, name)) + .filter_map(|f| f) + .collect(); quote! { use xml::reader::XmlEvent; + use yaserde::Visitor; + #[allow(unknown_lints, unused_imports)] + use std::str::FromStr; impl YaDeserialize for #name { #[allow(unused_variables)] @@ -271,41 +37,42 @@ pub fn parse( debug!("Enum: start to parse {:?}", named_element); #[allow(unused_assignments, unused_mut)] - let mut simple_enum_value = None; - - #variables + let mut enum_value = None; loop { match reader.peek()?.to_owned() { - XmlEvent::StartElement{name, attributes, namespace: _namespace} => { - debug!("Enum: {}: {}", named_element, name.local_name.as_str()); - if name.local_name == named_element { - let _next = reader.next_event(); + XmlEvent::StartElement{ref name, ref attributes, ..} => { - if let XmlEvent::Characters(content) = reader.peek()?.to_owned() { - match content.as_str() { - #match_to_enum - _ => {} - } + match name.local_name.as_str() { + #match_to_enum + named_element => { + let _root = reader.next_event(); } } - }, - XmlEvent::EndElement{name} => { - if name.local_name.as_str() == named_element { + + if let XmlEvent::Characters(content) = reader.peek()?.to_owned() { + match content.as_str() { + #match_to_enum + _ => {} + } + } + } + XmlEvent::EndElement{ref name} => { + if name.local_name == named_element { break; } let _root = reader.next_event(); - }, - xml::reader::XmlEvent::Characters(characters_content) => { + } + XmlEvent::Characters(ref text_content) => { let _root = reader.next_event(); - }, + } event => { return Err(format!("unknown event {:?}", event)) - }, + } } } - match simple_enum_value { + match enum_value { Some(value) => Ok(value), None => { Ok(#name::default()) @@ -315,3 +82,227 @@ pub fn parse( } } } + +fn parse_variant(variant: &syn::Variant, name: &Ident) -> Option { + let xml_element_name = YaSerdeAttribute::parse(&variant.attrs) + .rename + .unwrap_or(variant.ident.to_string()); + + let variant_name = { + let label = &variant.ident; + quote! { #name::#label } + }; + + match variant.fields { + Fields::Unit => Some(quote! { + #xml_element_name => { + enum_value = Some(#variant_name); + } + }), + Fields::Unnamed(ref fields) => { + let field_visitors = build_unnamed_field_visitors(fields); + let call_visitors = build_unnamed_visitor_calls(fields, &variant_name); + + if fields.unnamed.len() > 1 { + unimplemented!("enum variant with multiple fields") + } + + Some( + fields + .unnamed + .iter() + .take(1) + .map(|_field| { + quote! { + #xml_element_name => { + #field_visitors + #call_visitors + } + } + }) + .collect(), + ) + } + _ => None, + } +} + +fn build_unnamed_field_visitors(fields: &syn::FieldsUnnamed) -> TokenStream { + fields + .unnamed + .iter() + .enumerate() + .map(|(idx, field)| { + let visitor_label = Ident::new(&format!("__Visitor_{}", idx), Span::call_site()); + + let make_visitor = + |visitor: &TokenStream, field_type: &TokenStream, fn_body: &TokenStream| { + Some(quote! { + #[allow(non_snake_case, non_camel_case_types)] + struct #visitor_label; + impl<'de> Visitor<'de> for #visitor_label { + type Value = #field_type; + + fn #visitor(self, v: &str) -> Result { + #fn_body + } + } + }) + }; + + let simple_type_visitor = |simple_type| { + let (field_type, visitor) = convert_simple_type(simple_type); + + make_visitor( + &visitor, + &field_type, + "e! { Ok(#field_type::from_str(v).unwrap()) }, + ) + }; + + 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(); + + make_visitor( + "e! { visit_str }, + "e! { #struct_name }, + "e! { + let content = "<".to_string() + #struct_id + ">" + v + ""; + let value : Result<#struct_name, String> = yaserde::de::from_str(&content); + value + }, + ) + } + FieldType::FieldTypeOption { data_type } | FieldType::FieldTypeVec { data_type } => { + match *data_type { + FieldType::FieldTypeStruct { .. } => None, + simple_type => simple_type_visitor(simple_type), + } + } + simple_type => simple_type_visitor(simple_type), + }) + }) + .filter_map(|f| f) + .collect() +} + +fn build_unnamed_visitor_calls( + fields: &syn::FieldsUnnamed, + variant_name: &TokenStream, +) -> TokenStream { + fields + .unnamed + .iter() + .enumerate() + .map(|(idx, field)| { + let visitor_label = Ident::new(&format!("__Visitor_{}", idx), Span::call_site()); + + let call_simple_type_visitor = |simple_type, action| { + let (field_type, visitor) = convert_simple_type(simple_type); + + let label_name = format!("field_{}", idx); + + Some(quote! { + let visitor = #visitor_label{}; + + if let XmlEvent::StartElement {name, ..} = reader.peek()?.clone() { + if let Some(namespace) = name.namespace { + match namespace.as_str() { + bad_ns => { + let msg = format!("bad field namespace for {}, found {}", + name.local_name.as_str(), + bad_ns); + return Err(msg); + } + } + } + reader.set_map_value() + } + + let result = reader.read_inner_value::<#field_type, _>(|reader| { + if let XmlEvent::EndElement { .. } = *reader.peek()? { + return visitor.#visitor(""); + } + + if let Ok(XmlEvent::Characters(s)) = reader.next_event() { + visitor.#visitor(&s) + } else { + Err(format!("unable to parse content for {}", #label_name)) + } + }); + + if let Ok(value) = result { + #action + } + }) + }; + + let call_struct_visitor = |struct_name, action| { + Some(quote! { + reader.set_map_value(); + match #struct_name::deserialize(reader) { + Ok(value) => { + #action; + let _root = reader.next_event(); + }, + Err(msg) => { + return Err(msg); + }, + } + }) + }; + + let set_val = quote! { enum_value = Some(#variant_name(value)) }; + let set_opt = quote! { enum_value = Some(#variant_name(Some(value))) }; + let set_vec = quote! { + match enum_value { + Some(ref mut v) => match v { + #variant_name(ref mut v) => v.push(value), + _ => return Err(String::from("Got sequence of different types")) + } + None => { + enum_value = Some(#variant_name(vec![value])); + } + } + }; + + get_field_type(field).and_then(|f| match f { + FieldType::FieldTypeStruct { struct_name } => call_struct_visitor(struct_name, set_val), + FieldType::FieldTypeOption { data_type } => match *data_type { + FieldType::FieldTypeStruct { struct_name } => call_struct_visitor(struct_name, set_opt), + simple_type => call_simple_type_visitor(simple_type, set_opt), + }, + FieldType::FieldTypeVec { data_type } => match *data_type { + FieldType::FieldTypeStruct { struct_name } => call_struct_visitor(struct_name, set_vec), + simple_type => call_simple_type_visitor(simple_type, set_vec), + }, + + simple_type => call_simple_type_visitor(simple_type, set_val), + }) + }) + .filter_map(|f| f) + .collect() +} + +fn convert_simple_type(simple_type: FieldType) -> (TokenStream, TokenStream) { + match simple_type { + FieldType::FieldTypeString => (quote! {String}, quote! {visit_str}), + FieldType::FieldTypeBool => (quote! {bool}, quote! {visit_bool}), + FieldType::FieldTypeU8 => (quote! {u8}, quote! {visit_u8}), + FieldType::FieldTypeI8 => (quote! {i8}, quote! {visit_i8}), + FieldType::FieldTypeU16 => (quote! {u16}, quote! {visit_u16}), + FieldType::FieldTypeI16 => (quote! {i16}, quote! {visit_i16}), + FieldType::FieldTypeU32 => (quote! {u32}, quote! {visit_u32}), + FieldType::FieldTypeI32 => (quote! {i32}, quote! {visit_i32}), + FieldType::FieldTypeU64 => (quote! {u64}, quote! {visit_u64}), + FieldType::FieldTypeI64 => (quote! {i64}, quote! {visit_i64}), + FieldType::FieldTypeF32 => (quote! {f32}, quote! {visit_f32}), + FieldType::FieldTypeF64 => (quote! {f64}, quote! {visit_f64}), + _ => panic!("Not a simple type: {:?}", simple_type), + } +}