Add support for rename annotations

This commit is contained in:
Dirkjan Ochtman 2022-09-07 22:32:24 +02:00
parent f64634155e
commit fb7570056d
4 changed files with 78 additions and 23 deletions

View File

@ -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<Self, ::instant_xml::Error> {
@ -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()?,
},

View File

@ -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<Literal>,
}
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<Literal>,
}
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<Namespace>,
prefixes: BTreeMap<String, Namespace>,
@ -346,6 +350,8 @@ fn meta_items(attrs: &[syn::Attribute]) -> Vec<MetaItem> {
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<MetaItem> {
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 {

View File

@ -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<W>,
) -> 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)?;
}
}
));

View File

@ -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::<Renamed>("<renamed renamed=\"true\"></renamed>"),
Ok(Renamed { flag: true })
);
assert_eq!(
to_string(&Renamed { flag: true }).unwrap(),
"<renamed renamed=\"true\"></renamed>"
);
}