Add support for rename annotations
This commit is contained in:
parent
f64634155e
commit
fb7570056d
|
@ -1,5 +1,5 @@
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
use super::{discard_lifetimes, ContainerMeta, FieldMeta, Namespace};
|
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 attributes_names = attributes_tokens.names;
|
||||||
let attr_type_match = attributes_tokens.r#match;
|
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!(
|
quote!(
|
||||||
impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause {
|
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> {
|
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,
|
field_meta: FieldMeta,
|
||||||
container_meta: &ContainerMeta,
|
container_meta: &ContainerMeta,
|
||||||
) {
|
) {
|
||||||
let field_var = field.ident.as_ref().unwrap();
|
let field_name = field.ident.as_ref().unwrap();
|
||||||
let field_var_str = field_var.to_string();
|
let field_tag = match field_meta.rename {
|
||||||
let const_field_var_str = Ident::new(&field_var_str.to_uppercase(), Span::call_site());
|
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();
|
let mut no_lifetime_type = field.ty.clone();
|
||||||
discard_lifetimes(&mut no_lifetime_type);
|
discard_lifetimes(&mut no_lifetime_type);
|
||||||
|
|
||||||
|
@ -182,7 +189,7 @@ fn process_field(
|
||||||
|
|
||||||
tokens.consts.extend(quote!(
|
tokens.consts.extend(quote!(
|
||||||
const #const_field_var_str: Id<'static> = <#no_lifetime_type as FromXml<'_>>::KIND.name(
|
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!(
|
return_val.extend(quote!(
|
||||||
#field_var: match #enum_name {
|
#field_name: match #enum_name {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => <#no_lifetime_type>::missing_value()?,
|
None => <#no_lifetime_type>::missing_value()?,
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,9 +24,10 @@ pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
proc_macro::TokenStream::from(de::from_xml(&ast))
|
proc_macro::TokenStream::from(de::from_xml(&ast))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug, Default)]
|
||||||
struct ContainerMeta {
|
struct ContainerMeta {
|
||||||
ns: NamespaceMeta,
|
ns: NamespaceMeta,
|
||||||
|
rename: Option<Literal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerMeta {
|
impl ContainerMeta {
|
||||||
|
@ -36,16 +37,18 @@ impl ContainerMeta {
|
||||||
match item {
|
match item {
|
||||||
MetaItem::Attribute => panic!("attribute key invalid in container xml attribute"),
|
MetaItem::Attribute => panic!("attribute key invalid in container xml attribute"),
|
||||||
MetaItem::Ns(ns) => meta.ns = ns,
|
MetaItem::Ns(ns) => meta.ns = ns,
|
||||||
|
MetaItem::Rename(lit) => meta.rename = Some(lit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
meta
|
meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug, Default)]
|
||||||
struct FieldMeta {
|
struct FieldMeta {
|
||||||
attribute: bool,
|
attribute: bool,
|
||||||
ns: NamespaceMeta,
|
ns: NamespaceMeta,
|
||||||
|
rename: Option<Literal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FieldMeta {
|
impl FieldMeta {
|
||||||
|
@ -55,13 +58,14 @@ impl FieldMeta {
|
||||||
match item {
|
match item {
|
||||||
MetaItem::Attribute => meta.attribute = true,
|
MetaItem::Attribute => meta.attribute = true,
|
||||||
MetaItem::Ns(ns) => meta.ns = ns,
|
MetaItem::Ns(ns) => meta.ns = ns,
|
||||||
|
MetaItem::Rename(lit) => meta.rename = Some(lit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
meta
|
meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug, Default)]
|
||||||
struct NamespaceMeta {
|
struct NamespaceMeta {
|
||||||
uri: Option<Namespace>,
|
uri: Option<Namespace>,
|
||||||
prefixes: BTreeMap<String, Namespace>,
|
prefixes: BTreeMap<String, Namespace>,
|
||||||
|
@ -346,6 +350,8 @@ fn meta_items(attrs: &[syn::Attribute]) -> Vec<MetaItem> {
|
||||||
MetaState::Comma
|
MetaState::Comma
|
||||||
} else if id == "ns" {
|
} else if id == "ns" {
|
||||||
MetaState::Ns
|
MetaState::Ns
|
||||||
|
} else if id == "rename" {
|
||||||
|
MetaState::Rename
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected key in xml attribute");
|
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)));
|
items.push(MetaItem::Ns(NamespaceMeta::from_tokens(group)));
|
||||||
MetaState::Comma
|
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) => {
|
(state, tree) => {
|
||||||
panic!(
|
panic!(
|
||||||
"invalid state transition while parsing xml attribute ({}, {tree})",
|
"invalid state transition while parsing xml attribute ({}, {tree})",
|
||||||
|
@ -376,6 +389,8 @@ enum MetaState {
|
||||||
Start,
|
Start,
|
||||||
Comma,
|
Comma,
|
||||||
Ns,
|
Ns,
|
||||||
|
Rename,
|
||||||
|
RenameValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaState {
|
impl MetaState {
|
||||||
|
@ -384,6 +399,8 @@ impl MetaState {
|
||||||
MetaState::Start => "Start",
|
MetaState::Start => "Start",
|
||||||
MetaState::Comma => "Comma",
|
MetaState::Comma => "Comma",
|
||||||
MetaState::Ns => "Ns",
|
MetaState::Ns => "Ns",
|
||||||
|
MetaState::Rename => "Rename",
|
||||||
|
MetaState::RenameValue => "RenameValue",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,9 +455,11 @@ impl NsState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum MetaItem {
|
enum MetaItem {
|
||||||
Ns(NamespaceMeta),
|
|
||||||
Attribute,
|
Attribute,
|
||||||
|
Ns(NamespaceMeta),
|
||||||
|
Rename(Literal),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Namespace {
|
enum Namespace {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::{quote, ToTokens};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
use crate::Namespace;
|
use crate::Namespace;
|
||||||
|
@ -42,8 +42,11 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
let ident = &input.ident;
|
let ident = &input.ident;
|
||||||
let root_name = ident.to_string();
|
|
||||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
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!(
|
quote!(
|
||||||
impl #impl_generics ToXml for #ident #ty_generics #where_clause {
|
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>,
|
serializer: &mut instant_xml::Serializer<W>,
|
||||||
) -> Result<(), instant_xml::Error> {
|
) -> Result<(), instant_xml::Error> {
|
||||||
// Start tag
|
// 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);
|
debug_assert_eq!(prefix, None);
|
||||||
|
|
||||||
// Set up element context, this will also emit namespace declarations
|
// 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
|
#body
|
||||||
|
|
||||||
// Close tag
|
// Close tag
|
||||||
serializer.write_close(prefix, #root_name)?;
|
serializer.write_close(prefix, #tag)?;
|
||||||
serializer.pop(old);
|
serializer.pop(old);
|
||||||
|
|
||||||
Ok(())
|
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 {
|
const KIND: ::instant_xml::Kind = ::instant_xml::Kind::Element(::instant_xml::Id {
|
||||||
ns: #default_namespace,
|
ns: #default_namespace,
|
||||||
name: #root_name,
|
name: #tag,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
)
|
)
|
||||||
|
@ -86,9 +89,12 @@ fn process_named_field(
|
||||||
attributes: &mut TokenStream,
|
attributes: &mut TokenStream,
|
||||||
meta: &ContainerMeta,
|
meta: &ContainerMeta,
|
||||||
) {
|
) {
|
||||||
let name = field.ident.as_ref().unwrap().to_string();
|
let field_name = field.ident.as_ref().unwrap();
|
||||||
let field_value = field.ident.as_ref().unwrap();
|
|
||||||
let field_meta = FieldMeta::from_field(field);
|
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;
|
let default_ns = &meta.ns.uri;
|
||||||
if field_meta.attribute {
|
if field_meta.attribute {
|
||||||
|
@ -130,7 +136,7 @@ fn process_named_field(
|
||||||
|
|
||||||
attributes.extend(quote!(
|
attributes.extend(quote!(
|
||||||
#error
|
#error
|
||||||
serializer.write_attr(#name, #ns, &self.#field_value)?;
|
serializer.write_attr(#tag, #ns, &self.#field_name)?;
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -148,13 +154,13 @@ fn process_named_field(
|
||||||
body.extend(quote!(
|
body.extend(quote!(
|
||||||
match <#no_lifetime_type as ToXml>::KIND {
|
match <#no_lifetime_type as ToXml>::KIND {
|
||||||
::instant_xml::Kind::Element(_) => {
|
::instant_xml::Kind::Element(_) => {
|
||||||
self.#field_value.serialize(serializer)?;
|
self.#field_name.serialize(serializer)?;
|
||||||
}
|
}
|
||||||
::instant_xml::Kind::Scalar => {
|
::instant_xml::Kind::Scalar => {
|
||||||
let prefix = serializer.write_start(#name, #ns, true)?;
|
let prefix = serializer.write_start(#tag, #ns, true)?;
|
||||||
serializer.end_start()?;
|
serializer.end_start()?;
|
||||||
self.#field_value.serialize(serializer)?;
|
self.#field_name.serialize(serializer)?;
|
||||||
serializer.write_close(prefix, #name)?;
|
serializer.write_close(prefix, #tag)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
|
@ -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>"
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue