From 894da25f8b2ff9d9214e18a5456f7c697246add5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sat, 26 Nov 2022 12:53:57 -0800 Subject: [PATCH] Add support for unnamed fields --- instant-xml-macros/src/de.rs | 180 +++++++++++++++++++++++++++++----- instant-xml-macros/src/ser.rs | 30 +++++- instant-xml/src/de.rs | 6 +- instant-xml/tests/unnamed.rs | 12 +++ 4 files changed, 199 insertions(+), 29 deletions(-) create mode 100644 instant-xml/tests/unnamed.rs diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index 8e8381c..100aeca 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -13,7 +13,11 @@ pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream { }; match (&input.data, meta.mode) { - (syn::Data::Struct(data), None) => deserialize_struct(input, data, meta), + (syn::Data::Struct(data), None) => match &data.fields { + syn::Fields::Named(fields) => deserialize_struct(input, fields, meta), + syn::Fields::Unnamed(fields) => deserialize_tuple_struct(input, fields, meta), + syn::Fields::Unit => deserialize_unit_struct(input, &meta), + }, (syn::Data::Enum(data), Some(Mode::Scalar)) => deserialize_scalar_enum(input, data, meta), (syn::Data::Enum(data), Some(Mode::Wrapped)) => deserialize_wrapped_enum(input, data, meta), (syn::Data::Struct(_), _) => { @@ -172,7 +176,7 @@ fn deserialize_wrapped_enum( fn deserialize_struct( input: &syn::DeriveInput, - data: &syn::DataStruct, + fields: &syn::FieldsNamed, container_meta: ContainerMeta, ) -> TokenStream { let mut namespaces_map = quote!(let mut namespaces_map = std::collections::HashMap::new();); @@ -190,33 +194,27 @@ fn deserialize_struct( let mut declare_values = TokenStream::new(); let mut return_val = TokenStream::new(); - match &data.fields { - syn::Fields::Named(fields) => { - for (index, field) in fields.named.iter().enumerate() { - let field_meta = match FieldMeta::from_field(field, &container_meta) { - Ok(meta) => meta, - Err(err) => return err.into_compile_error(), - }; + for (index, field) in fields.named.iter().enumerate() { + let field_meta = match FieldMeta::from_field(field, &container_meta) { + Ok(meta) => meta, + Err(err) => return err.into_compile_error(), + }; - let tokens = match field_meta.attribute { - true => &mut attributes_tokens, - false => &mut elements_tokens, - }; + let tokens = match field_meta.attribute { + true => &mut attributes_tokens, + false => &mut elements_tokens, + }; - named_field( - field, - index, - &mut declare_values, - &mut return_val, - tokens, - field_meta, - &container_meta, - ); - } - } - syn::Fields::Unnamed(_) => panic!("unamed"), - syn::Fields::Unit => {} - }; + named_field( + field, + index, + &mut declare_values, + &mut return_val, + tokens, + field_meta, + &container_meta, + ); + } // Elements let elements_enum = elements_tokens.r#enum; @@ -377,6 +375,134 @@ fn named_field( )); } +fn deserialize_tuple_struct( + input: &syn::DeriveInput, + fields: &syn::FieldsUnnamed, + container_meta: ContainerMeta, +) -> TokenStream { + let mut namespaces_map = quote!(let mut namespaces_map = std::collections::HashMap::new();); + for (k, v) in container_meta.ns.prefixes.iter() { + namespaces_map.extend(quote!( + namespaces_map.insert(#k, #v); + )) + } + + // Varying values + let mut declare_values = TokenStream::new(); + let mut return_val = TokenStream::new(); + + for (index, field) in fields.unnamed.iter().enumerate() { + if !field.attrs.is_empty() { + return syn::Error::new( + field.span(), + "attributes not allowed on tuple struct fields", + ) + .to_compile_error(); + } + + unnamed_field(field, index, &mut declare_values, &mut return_val); + } + + let ident = &input.ident; + let name = container_meta.tag(); + let default_namespace = container_meta.default_namespace(); + let generics = container_meta.xml_generics(); + + let (xml_impl_generics, _, _) = generics.split_for_impl(); + let (_, ty_generics, where_clause) = input.generics.split_for_impl(); + + quote!( + impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause { + fn deserialize<'cx>( + deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>, + into: &mut Option, + ) -> Result<(), ::instant_xml::Error> { + use ::instant_xml::de::Node; + use ::instant_xml::{Error, Id}; + + #declare_values + + *into = Some(Self(#return_val)); + Ok(()) + } + + const KIND: ::instant_xml::Kind<'static> = ::instant_xml::Kind::Element(::instant_xml::Id { + ns: #default_namespace, + name: #name, + }); + } + ) +} + +#[allow(clippy::too_many_arguments)] +fn unnamed_field( + field: &syn::Field, + index: usize, + declare_values: &mut TokenStream, + return_val: &mut TokenStream, +) { + let mut no_lifetime_type = field.ty.clone(); + discard_lifetimes(&mut no_lifetime_type); + + let name = Ident::new(&format!("v{index}"), Span::call_site()); + declare_values.extend(quote!( + let node = match deserializer.next() { + Some(result) => result?, + None => return Err(Error::MissingValue(&<#no_lifetime_type as FromXml>::KIND)), + }; + + let #name = match node { + Node::Open(data) => { + let mut value: Option<#no_lifetime_type> = None; + <#no_lifetime_type>::deserialize(deserializer, &mut value)?; + value + } + Node::Text(data) => { + deserializer.push_front(Node::Text(data)); + let mut value: Option<#no_lifetime_type> = None; + <#no_lifetime_type>::deserialize(deserializer, &mut value)?; + value + } + node => return Err(Error::UnexpectedNode(format!("{:?}", node))), + }; + )); + + return_val.extend(quote!( + match #name { + Some(v) => v, + None => <#no_lifetime_type>::missing_value()?, + }, + )); +} + +fn deserialize_unit_struct(input: &syn::DeriveInput, meta: &ContainerMeta) -> TokenStream { + let ident = &input.ident; + let name = meta.tag(); + let default_namespace = meta.default_namespace(); + let generics = meta.xml_generics(); + + let (xml_impl_generics, _, _) = generics.split_for_impl(); + let (_, ty_generics, where_clause) = input.generics.split_for_impl(); + + quote!( + impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause { + fn deserialize<'cx>( + deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>, + into: &mut Option, + ) -> Result<(), ::instant_xml::Error> { + deserializer.ignore()?; + *into = Some(Self); + Ok(()) + } + + const KIND: ::instant_xml::Kind<'static> = ::instant_xml::Kind::Element(::instant_xml::Id { + ns: #default_namespace, + name: #name, + }); + } + ) +} + #[derive(Default)] struct Tokens { r#enum: TokenStream, diff --git a/instant-xml-macros/src/ser.rs b/instant-xml-macros/src/ser.rs index 7429ccf..bb26789 100644 --- a/instant-xml-macros/src/ser.rs +++ b/instant-xml-macros/src/ser.rs @@ -188,7 +188,13 @@ fn serialize_struct( } } } - syn::Fields::Unnamed(_) => todo!(), + syn::Fields::Unnamed(fields) => { + for (index, field) in fields.unnamed.iter().enumerate() { + if let Err(err) = unnamed_field(field, index, &mut body) { + return err.to_compile_error(); + } + } + } syn::Fields::Unit => {} }; @@ -340,3 +346,25 @@ fn named_field( Ok(()) } + +fn unnamed_field( + field: &syn::Field, + index: usize, + body: &mut TokenStream, +) -> Result<(), syn::Error> { + if !field.attrs.is_empty() { + return Err(syn::Error::new( + field.span(), + "unnamed fields cannot have attributes", + )); + } + + let mut no_lifetime_type = field.ty.clone(); + discard_lifetimes(&mut no_lifetime_type); + let index = syn::Index::from(index); + body.extend(quote!( + self.#index.serialize(None, serializer)?; + )); + + Ok(()) +} diff --git a/instant-xml/src/de.rs b/instant-xml/src/de.rs index 3a469df..53d27fd 100644 --- a/instant-xml/src/de.rs +++ b/instant-xml/src/de.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, VecDeque}; use xmlparser::{ElementEnd, Token, Tokenizer}; -use crate::{Kind, Error, Id}; +use crate::{Error, Id, Kind}; pub struct Deserializer<'cx, 'xml> { pub(crate) local: &'xml str, @@ -90,6 +90,10 @@ impl<'cx, 'xml> Deserializer<'cx, 'xml> { } } + pub fn push_front(&mut self, node: Node<'xml>) { + self.context.records.push_front(node); + } + #[inline] pub fn element_id(&self, element: &Element<'xml>) -> Result, Error> { self.context.element_id(element) diff --git a/instant-xml/tests/unnamed.rs b/instant-xml/tests/unnamed.rs new file mode 100644 index 0000000..b93514a --- /dev/null +++ b/instant-xml/tests/unnamed.rs @@ -0,0 +1,12 @@ +use instant_xml::{from_str, to_string, FromXml, ToXml}; + +#[derive(Debug, Eq, FromXml, PartialEq, ToXml)] +struct OneNumber(i32); + +#[test] +fn one_number() { + let v = OneNumber(42); + let xml = r#"42"#; + assert_eq!(xml, to_string(&v).unwrap()); + assert_eq!(v, from_str(xml).unwrap()); +}