Simplify deserializer macro setup
This commit is contained in:
parent
01c896a9b2
commit
674039a791
|
@ -3,6 +3,235 @@ use quote::quote;
|
|||
|
||||
use crate::{ContainerMeta, FieldMeta, Namespace};
|
||||
|
||||
pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream {
|
||||
let ident = &input.ident;
|
||||
let container_meta = ContainerMeta::from_derive(input);
|
||||
let default_namespace = match &container_meta.ns.uri {
|
||||
Some(ns) => quote!(#ns),
|
||||
None => quote!(""),
|
||||
};
|
||||
|
||||
let mut xml_generics = input.generics.clone();
|
||||
let mut xml = syn::LifetimeDef::new(syn::Lifetime::new("'xml", Span::call_site()));
|
||||
xml.bounds
|
||||
.extend(xml_generics.lifetimes().map(|lt| lt.lifetime.clone()));
|
||||
xml_generics.params.push(xml.into());
|
||||
|
||||
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let (xml_impl_generics, _, _) = xml_generics.split_for_impl();
|
||||
|
||||
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 elements_tokens = Tokens::default();
|
||||
let mut attributes_tokens = Tokens::default();
|
||||
|
||||
// Common values
|
||||
let mut declare_values = TokenStream::new();
|
||||
let mut return_val = TokenStream::new();
|
||||
|
||||
match &input.data {
|
||||
syn::Data::Struct(ref data) => {
|
||||
match data.fields {
|
||||
syn::Fields::Named(ref fields) => {
|
||||
fields.named.iter().enumerate().for_each(|(index, field)| {
|
||||
let field_meta = FieldMeta::from_field(field);
|
||||
let tokens = match field_meta.attribute {
|
||||
true => &mut attributes_tokens,
|
||||
false => &mut elements_tokens,
|
||||
};
|
||||
|
||||
process_field(
|
||||
field,
|
||||
index,
|
||||
&mut declare_values,
|
||||
&mut return_val,
|
||||
tokens,
|
||||
field_meta,
|
||||
&container_meta,
|
||||
);
|
||||
});
|
||||
}
|
||||
syn::Fields::Unnamed(_) => panic!("unamed"),
|
||||
syn::Fields::Unit => {}
|
||||
};
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
// Elements
|
||||
let elements_enum = elements_tokens.enum_;
|
||||
let elements_consts = elements_tokens.consts;
|
||||
let elements_names = elements_tokens.names;
|
||||
let elem_type_match = elements_tokens.match_;
|
||||
|
||||
// Attributes
|
||||
let attributes_enum = attributes_tokens.enum_;
|
||||
let attributes_consts = attributes_tokens.consts;
|
||||
let attributes_names = attributes_tokens.names;
|
||||
let attr_type_match = attributes_tokens.match_;
|
||||
|
||||
let name = ident.to_string();
|
||||
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> {
|
||||
use ::instant_xml::de::{Deserializer, Id, Node};
|
||||
use ::instant_xml::Error;
|
||||
use ::core::marker::PhantomData;
|
||||
|
||||
enum __Elements {
|
||||
#elements_enum
|
||||
__Ignore,
|
||||
}
|
||||
|
||||
enum __Attributes {
|
||||
#attributes_enum
|
||||
__Ignore,
|
||||
}
|
||||
|
||||
#declare_values
|
||||
loop {
|
||||
let node = match deserializer.next() {
|
||||
Some(result) => result?,
|
||||
None => break,
|
||||
};
|
||||
|
||||
match node {
|
||||
Node::Attribute(attr) => {
|
||||
let id = deserializer.attribute_id(&attr)?;
|
||||
let field = {
|
||||
#attributes_consts
|
||||
match id {
|
||||
#attributes_names
|
||||
_ => __Attributes::__Ignore
|
||||
}
|
||||
};
|
||||
|
||||
match field {
|
||||
#attr_type_match
|
||||
__Attributes::__Ignore => {}
|
||||
}
|
||||
}
|
||||
Node::Open(data) => {
|
||||
let id = deserializer.element_id(&data)?;
|
||||
let element = {
|
||||
#elements_consts
|
||||
match id {
|
||||
#elements_names
|
||||
_ => __Elements::__Ignore
|
||||
}
|
||||
};
|
||||
|
||||
match element {
|
||||
#elem_type_match
|
||||
__Elements::__Ignore => {
|
||||
let mut nested = deserializer.nested(data);
|
||||
nested.ignore()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::UnexpectedState),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { #return_val })
|
||||
}
|
||||
|
||||
const KIND: ::instant_xml::de::Kind = ::instant_xml::de::Kind::Element(::instant_xml::de::Id {
|
||||
ns: #default_namespace,
|
||||
name: #name,
|
||||
});
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_field(
|
||||
field: &syn::Field,
|
||||
index: usize,
|
||||
declare_values: &mut TokenStream,
|
||||
return_val: &mut TokenStream,
|
||||
tokens: &mut Tokens,
|
||||
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 mut no_lifetime_type = field.ty.clone();
|
||||
discard_lifetimes(&mut no_lifetime_type);
|
||||
|
||||
let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site());
|
||||
tokens.enum_.extend(quote!(#enum_name,));
|
||||
|
||||
let default_ns = match &field_meta.ns.uri {
|
||||
None => &container_meta.ns.uri,
|
||||
_ => &field_meta.ns.uri,
|
||||
};
|
||||
|
||||
let ns = match default_ns {
|
||||
Some(Namespace::Path(path)) => quote!(#path),
|
||||
Some(Namespace::Literal(ns)) => quote!(#ns),
|
||||
None => quote!(""),
|
||||
};
|
||||
|
||||
tokens.consts.extend(quote!(
|
||||
const #const_field_var_str: Id<'static> = <#no_lifetime_type>::KIND.name(
|
||||
Id { ns: #ns, name: #field_var_str }
|
||||
);
|
||||
));
|
||||
|
||||
if !field_meta.attribute {
|
||||
tokens.names.extend(quote!(
|
||||
#const_field_var_str => __Elements::#enum_name,
|
||||
));
|
||||
} else {
|
||||
tokens.names.extend(quote!(
|
||||
#const_field_var_str => __Attributes::#enum_name,
|
||||
));
|
||||
}
|
||||
|
||||
declare_values.extend(quote!(
|
||||
let mut #enum_name: Option<#no_lifetime_type> = None;
|
||||
));
|
||||
|
||||
if !field_meta.attribute {
|
||||
tokens.match_.extend(quote!(
|
||||
__Elements::#enum_name => {
|
||||
if #enum_name.is_some() {
|
||||
return Err(Error::DuplicateValue);
|
||||
}
|
||||
|
||||
let mut nested = deserializer.nested(data);
|
||||
#enum_name = Some(<#no_lifetime_type>::deserialize(&mut nested)?);
|
||||
},
|
||||
));
|
||||
} else {
|
||||
tokens.match_.extend(quote!(
|
||||
__Attributes::#enum_name => {
|
||||
if #enum_name.is_some() {
|
||||
return Err(Error::DuplicateValue);
|
||||
}
|
||||
|
||||
let mut nested = deserializer.for_attr(attr);
|
||||
#enum_name = Some(<#no_lifetime_type>::deserialize(&mut nested)?);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
return_val.extend(quote!(
|
||||
#field_var: match #enum_name {
|
||||
Some(v) => v,
|
||||
None => <#no_lifetime_type>::missing_value()?,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
struct Tokens {
|
||||
enum_: TokenStream,
|
||||
consts: TokenStream,
|
||||
|
@ -21,249 +250,6 @@ impl Default for Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Deserializer {
|
||||
out: TokenStream,
|
||||
}
|
||||
|
||||
impl quote::ToTokens for Deserializer {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.extend(self.out.clone());
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializer {
|
||||
pub fn new(input: &syn::DeriveInput) -> Deserializer {
|
||||
let ident = &input.ident;
|
||||
let container_meta = ContainerMeta::from_derive(input);
|
||||
let default_namespace = match &container_meta.ns.uri {
|
||||
Some(ns) => quote!(#ns),
|
||||
None => quote!(""),
|
||||
};
|
||||
|
||||
let mut xml_generics = input.generics.clone();
|
||||
let mut xml = syn::LifetimeDef::new(syn::Lifetime::new("'xml", Span::call_site()));
|
||||
xml.bounds
|
||||
.extend(xml_generics.lifetimes().map(|lt| lt.lifetime.clone()));
|
||||
xml_generics.params.push(xml.into());
|
||||
|
||||
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let (xml_impl_generics, _, _) = xml_generics.split_for_impl();
|
||||
|
||||
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 elements_tokens = Tokens::default();
|
||||
let mut attributes_tokens = Tokens::default();
|
||||
|
||||
// Common values
|
||||
let mut declare_values = TokenStream::new();
|
||||
let mut return_val = TokenStream::new();
|
||||
|
||||
match &input.data {
|
||||
syn::Data::Struct(ref data) => {
|
||||
match data.fields {
|
||||
syn::Fields::Named(ref fields) => {
|
||||
fields.named.iter().enumerate().for_each(|(index, field)| {
|
||||
let field_meta = FieldMeta::from_field(field);
|
||||
let tokens = match field_meta.attribute {
|
||||
true => &mut attributes_tokens,
|
||||
false => &mut elements_tokens,
|
||||
};
|
||||
|
||||
Self::process_field(
|
||||
field,
|
||||
index,
|
||||
&mut declare_values,
|
||||
&mut return_val,
|
||||
tokens,
|
||||
field_meta,
|
||||
&container_meta,
|
||||
);
|
||||
});
|
||||
}
|
||||
syn::Fields::Unnamed(_) => panic!("unamed"),
|
||||
syn::Fields::Unit => {}
|
||||
};
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
// Elements
|
||||
let elements_enum = elements_tokens.enum_;
|
||||
let elements_consts = elements_tokens.consts;
|
||||
let elements_names = elements_tokens.names;
|
||||
let elem_type_match = elements_tokens.match_;
|
||||
|
||||
// Attributes
|
||||
let attributes_enum = attributes_tokens.enum_;
|
||||
let attributes_consts = attributes_tokens.consts;
|
||||
let attributes_names = attributes_tokens.names;
|
||||
let attr_type_match = attributes_tokens.match_;
|
||||
|
||||
let name = ident.to_string();
|
||||
let out = 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> {
|
||||
use ::instant_xml::de::{Deserializer, Id, Node};
|
||||
use ::instant_xml::Error;
|
||||
use ::core::marker::PhantomData;
|
||||
|
||||
enum __Elements {
|
||||
#elements_enum
|
||||
__Ignore,
|
||||
}
|
||||
|
||||
enum __Attributes {
|
||||
#attributes_enum
|
||||
__Ignore,
|
||||
}
|
||||
|
||||
#declare_values
|
||||
loop {
|
||||
let node = match deserializer.next() {
|
||||
Some(result) => result?,
|
||||
None => break,
|
||||
};
|
||||
|
||||
match node {
|
||||
Node::Attribute(attr) => {
|
||||
let id = deserializer.attribute_id(&attr)?;
|
||||
let field = {
|
||||
#attributes_consts
|
||||
match id {
|
||||
#attributes_names
|
||||
_ => __Attributes::__Ignore
|
||||
}
|
||||
};
|
||||
|
||||
match field {
|
||||
#attr_type_match
|
||||
__Attributes::__Ignore => {}
|
||||
}
|
||||
}
|
||||
Node::Open(data) => {
|
||||
let id = deserializer.element_id(&data)?;
|
||||
let element = {
|
||||
#elements_consts
|
||||
match id {
|
||||
#elements_names
|
||||
_ => __Elements::__Ignore
|
||||
}
|
||||
};
|
||||
|
||||
match element {
|
||||
#elem_type_match
|
||||
__Elements::__Ignore => {
|
||||
let mut nested = deserializer.nested(data);
|
||||
nested.ignore()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::UnexpectedState),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { #return_val })
|
||||
}
|
||||
|
||||
const KIND: ::instant_xml::de::Kind = ::instant_xml::de::Kind::Element(::instant_xml::de::Id {
|
||||
ns: #default_namespace,
|
||||
name: #name,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Deserializer { out }
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_field(
|
||||
field: &syn::Field,
|
||||
index: usize,
|
||||
declare_values: &mut TokenStream,
|
||||
return_val: &mut TokenStream,
|
||||
tokens: &mut Tokens,
|
||||
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 mut no_lifetime_type = field.ty.clone();
|
||||
discard_lifetimes(&mut no_lifetime_type);
|
||||
|
||||
let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site());
|
||||
tokens.enum_.extend(quote!(#enum_name,));
|
||||
|
||||
let default_ns = match &field_meta.ns.uri {
|
||||
None => &container_meta.ns.uri,
|
||||
_ => &field_meta.ns.uri,
|
||||
};
|
||||
|
||||
let ns = match default_ns {
|
||||
Some(Namespace::Path(path)) => quote!(#path),
|
||||
Some(Namespace::Literal(ns)) => quote!(#ns),
|
||||
None => quote!(""),
|
||||
};
|
||||
|
||||
tokens.consts.extend(quote!(
|
||||
const #const_field_var_str: Id<'static> = <#no_lifetime_type>::KIND.name(
|
||||
Id { ns: #ns, name: #field_var_str }
|
||||
);
|
||||
));
|
||||
|
||||
if !field_meta.attribute {
|
||||
tokens.names.extend(quote!(
|
||||
#const_field_var_str => __Elements::#enum_name,
|
||||
));
|
||||
} else {
|
||||
tokens.names.extend(quote!(
|
||||
#const_field_var_str => __Attributes::#enum_name,
|
||||
));
|
||||
}
|
||||
|
||||
declare_values.extend(quote!(
|
||||
let mut #enum_name: Option<#no_lifetime_type> = None;
|
||||
));
|
||||
|
||||
if !field_meta.attribute {
|
||||
tokens.match_.extend(quote!(
|
||||
__Elements::#enum_name => {
|
||||
if #enum_name.is_some() {
|
||||
return Err(Error::DuplicateValue);
|
||||
}
|
||||
|
||||
let mut nested = deserializer.nested(data);
|
||||
#enum_name = Some(<#no_lifetime_type>::deserialize(&mut nested)?);
|
||||
},
|
||||
));
|
||||
} else {
|
||||
tokens.match_.extend(quote!(
|
||||
__Attributes::#enum_name => {
|
||||
if #enum_name.is_some() {
|
||||
return Err(Error::DuplicateValue);
|
||||
}
|
||||
|
||||
let mut nested = deserializer.for_attr(attr);
|
||||
#enum_name = Some(<#no_lifetime_type>::deserialize(&mut nested)?);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
return_val.extend(quote!(
|
||||
#field_var: match #enum_name {
|
||||
Some(v) => v,
|
||||
None => <#no_lifetime_type>::missing_value()?,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn discard_lifetimes(ty: &mut syn::Type) {
|
||||
match ty {
|
||||
syn::Type::Path(ty) => discard_path_lifetimes(ty),
|
||||
|
|
|
@ -6,7 +6,7 @@ mod ser;
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, TokenStream, TokenTree};
|
||||
use quote::{quote, ToTokens};
|
||||
use quote::ToTokens;
|
||||
use syn::parse_macro_input;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token::Colon2;
|
||||
|
@ -20,12 +20,7 @@ pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|||
#[proc_macro_derive(FromXml, attributes(xml))]
|
||||
pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = parse_macro_input!(input as syn::DeriveInput);
|
||||
|
||||
let deserializer = de::Deserializer::new(&ast);
|
||||
|
||||
proc_macro::TokenStream::from(quote!(
|
||||
#deserializer
|
||||
))
|
||||
proc_macro::TokenStream::from(de::from_xml(&ast))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
Loading…
Reference in New Issue