From fb7570056dd3c7db62d71115298c08f5b02d1768 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 7 Sep 2022 22:32:24 +0200 Subject: [PATCH] Add support for rename annotations --- instant-xml-macros/src/de.rs | 21 ++++++++++++++------- instant-xml-macros/src/lib.rs | 27 +++++++++++++++++++++++---- instant-xml-macros/src/ser.rs | 30 ++++++++++++++++++------------ instant-xml/tests/rename.rs | 23 +++++++++++++++++++++++ 4 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 instant-xml/tests/rename.rs diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index 88ba950..15d1843 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -1,5 +1,5 @@ use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use super::{discard_lifetimes, ContainerMeta, FieldMeta, Namespace}; @@ -76,7 +76,10 @@ pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream { let attributes_names = attributes_tokens.names; let attr_type_match = attributes_tokens.r#match; - let name = ident.to_string(); + let name = match &container_meta.rename { + Some(name) => quote!(#name), + None => ident.to_string().into_token_stream(), + }; quote!( impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause { fn deserialize<'cx>(deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>) -> Result { @@ -160,9 +163,13 @@ fn process_field( field_meta: FieldMeta, container_meta: &ContainerMeta, ) { - let field_var = field.ident.as_ref().unwrap(); - let field_var_str = field_var.to_string(); - let const_field_var_str = Ident::new(&field_var_str.to_uppercase(), Span::call_site()); + let field_name = field.ident.as_ref().unwrap(); + let field_tag = match field_meta.rename { + Some(name) => quote!(#name), + None => field_name.to_string().into_token_stream(), + }; + + let const_field_var_str = Ident::new(&field_name.to_string().to_uppercase(), Span::call_site()); let mut no_lifetime_type = field.ty.clone(); discard_lifetimes(&mut no_lifetime_type); @@ -182,7 +189,7 @@ fn process_field( tokens.consts.extend(quote!( const #const_field_var_str: Id<'static> = <#no_lifetime_type as FromXml<'_>>::KIND.name( - Id { ns: #ns, name: #field_var_str } + Id { ns: #ns, name: #field_tag } ); )); @@ -225,7 +232,7 @@ fn process_field( } return_val.extend(quote!( - #field_var: match #enum_name { + #field_name: match #enum_name { Some(v) => v, None => <#no_lifetime_type>::missing_value()?, }, diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 90c38f0..3e4969d 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -24,9 +24,10 @@ pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { proc_macro::TokenStream::from(de::from_xml(&ast)) } -#[derive(Default)] +#[derive(Debug, Default)] struct ContainerMeta { ns: NamespaceMeta, + rename: Option, } impl ContainerMeta { @@ -36,16 +37,18 @@ impl ContainerMeta { match item { MetaItem::Attribute => panic!("attribute key invalid in container xml attribute"), MetaItem::Ns(ns) => meta.ns = ns, + MetaItem::Rename(lit) => meta.rename = Some(lit), } } meta } } -#[derive(Default)] +#[derive(Debug, Default)] struct FieldMeta { attribute: bool, ns: NamespaceMeta, + rename: Option, } impl FieldMeta { @@ -55,13 +58,14 @@ impl FieldMeta { match item { MetaItem::Attribute => meta.attribute = true, MetaItem::Ns(ns) => meta.ns = ns, + MetaItem::Rename(lit) => meta.rename = Some(lit), } } meta } } -#[derive(Default)] +#[derive(Debug, Default)] struct NamespaceMeta { uri: Option, prefixes: BTreeMap, @@ -346,6 +350,8 @@ fn meta_items(attrs: &[syn::Attribute]) -> Vec { MetaState::Comma } else if id == "ns" { MetaState::Ns + } else if id == "rename" { + MetaState::Rename } else { panic!("unexpected key in xml attribute"); } @@ -359,6 +365,13 @@ fn meta_items(attrs: &[syn::Attribute]) -> Vec { items.push(MetaItem::Ns(NamespaceMeta::from_tokens(group))); MetaState::Comma } + (MetaState::Rename, TokenTree::Punct(punct)) if punct.as_char() == '=' => { + MetaState::RenameValue + } + (MetaState::RenameValue, TokenTree::Literal(lit)) => { + items.push(MetaItem::Rename(lit)); + MetaState::Comma + } (state, tree) => { panic!( "invalid state transition while parsing xml attribute ({}, {tree})", @@ -376,6 +389,8 @@ enum MetaState { Start, Comma, Ns, + Rename, + RenameValue, } impl MetaState { @@ -384,6 +399,8 @@ impl MetaState { MetaState::Start => "Start", MetaState::Comma => "Comma", MetaState::Ns => "Ns", + MetaState::Rename => "Rename", + MetaState::RenameValue => "RenameValue", } } } @@ -438,9 +455,11 @@ impl NsState { } } +#[derive(Debug)] enum MetaItem { - Ns(NamespaceMeta), Attribute, + Ns(NamespaceMeta), + Rename(Literal), } enum Namespace { diff --git a/instant-xml-macros/src/ser.rs b/instant-xml-macros/src/ser.rs index 12efcc2..709f12a 100644 --- a/instant-xml-macros/src/ser.rs +++ b/instant-xml-macros/src/ser.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; use syn::spanned::Spanned; use crate::Namespace; @@ -42,8 +42,11 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { } let ident = &input.ident; - let root_name = ident.to_string(); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let tag = match &meta.rename { + Some(rename) => quote!(#rename), + None => ident.to_string().into_token_stream(), + }; quote!( impl #impl_generics ToXml for #ident #ty_generics #where_clause { @@ -52,7 +55,7 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { serializer: &mut instant_xml::Serializer, ) -> Result<(), instant_xml::Error> { // Start tag - let prefix = serializer.write_start(#root_name, #default_namespace, false)?; + let prefix = serializer.write_start(#tag, #default_namespace, false)?; debug_assert_eq!(prefix, None); // Set up element context, this will also emit namespace declarations @@ -66,7 +69,7 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { #body // Close tag - serializer.write_close(prefix, #root_name)?; + serializer.write_close(prefix, #tag)?; serializer.pop(old); Ok(()) @@ -74,7 +77,7 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { const KIND: ::instant_xml::Kind = ::instant_xml::Kind::Element(::instant_xml::Id { ns: #default_namespace, - name: #root_name, + name: #tag, }); }; ) @@ -86,9 +89,12 @@ fn process_named_field( attributes: &mut TokenStream, meta: &ContainerMeta, ) { - let name = field.ident.as_ref().unwrap().to_string(); - let field_value = field.ident.as_ref().unwrap(); + let field_name = field.ident.as_ref().unwrap(); let field_meta = FieldMeta::from_field(field); + let tag = match &field_meta.rename { + Some(rename) => quote!(#rename), + None => field_name.to_string().into_token_stream(), + }; let default_ns = &meta.ns.uri; if field_meta.attribute { @@ -130,7 +136,7 @@ fn process_named_field( attributes.extend(quote!( #error - serializer.write_attr(#name, #ns, &self.#field_value)?; + serializer.write_attr(#tag, #ns, &self.#field_name)?; )); return; } @@ -148,13 +154,13 @@ fn process_named_field( body.extend(quote!( match <#no_lifetime_type as ToXml>::KIND { ::instant_xml::Kind::Element(_) => { - self.#field_value.serialize(serializer)?; + self.#field_name.serialize(serializer)?; } ::instant_xml::Kind::Scalar => { - let prefix = serializer.write_start(#name, #ns, true)?; + let prefix = serializer.write_start(#tag, #ns, true)?; serializer.end_start()?; - self.#field_value.serialize(serializer)?; - serializer.write_close(prefix, #name)?; + self.#field_name.serialize(serializer)?; + serializer.write_close(prefix, #tag)?; } } )); diff --git a/instant-xml/tests/rename.rs b/instant-xml/tests/rename.rs new file mode 100644 index 0000000..dfaca96 --- /dev/null +++ b/instant-xml/tests/rename.rs @@ -0,0 +1,23 @@ +use similar_asserts::assert_eq; + +use instant_xml::{from_str, to_string, FromXml, ToXml}; + +#[derive(Debug, Eq, PartialEq, FromXml, ToXml)] +#[xml(rename = "renamed")] +struct Renamed { + #[xml(attribute, rename = "renamed")] + flag: bool, +} + +#[test] +fn renamed() { + assert_eq!( + from_str::(""), + Ok(Renamed { flag: true }) + ); + + assert_eq!( + to_string(&Renamed { flag: true }).unwrap(), + "" + ); +}