Add support for direct fields

This commit is contained in:
Dirkjan Ochtman 2022-11-29 13:37:08 +01:00
parent d2605977aa
commit f8b4364acd
5 changed files with 106 additions and 27 deletions

View File

@ -196,9 +196,15 @@ fn deserialize_struct(
// Common values // Common values
let mut declare_values = TokenStream::new(); let mut declare_values = TokenStream::new();
let mut return_val = TokenStream::new(); let mut return_val = TokenStream::new();
let mut direct = TokenStream::new();
let mut borrowed = BTreeSet::new(); let mut borrowed = BTreeSet::new();
for (index, field) in fields.named.iter().enumerate() { for (index, field) in fields.named.iter().enumerate() {
if !direct.is_empty() {
return syn::Error::new(field.span(), "direct field must be the last")
.into_compile_error();
}
let field_meta = match FieldMeta::from_field(field, &container_meta) { let field_meta = match FieldMeta::from_field(field, &container_meta) {
Ok(meta) => meta, Ok(meta) => meta,
Err(err) => return err.into_compile_error(), Err(err) => return err.into_compile_error(),
@ -216,6 +222,7 @@ fn deserialize_struct(
&mut return_val, &mut return_val,
tokens, tokens,
&mut borrowed, &mut borrowed,
&mut direct,
field_meta, field_meta,
&container_meta, &container_meta,
); );
@ -299,6 +306,7 @@ fn deserialize_struct(
} }
} }
} }
#direct
node => return Err(Error::UnexpectedNode(format!("{:?}", node))), node => return Err(Error::UnexpectedNode(format!("{:?}", node))),
} }
} }
@ -323,6 +331,7 @@ fn named_field(
return_val: &mut TokenStream, return_val: &mut TokenStream,
tokens: &mut Tokens, tokens: &mut Tokens,
borrowed: &mut BTreeSet<syn::Lifetime>, borrowed: &mut BTreeSet<syn::Lifetime>,
direct: &mut TokenStream,
mut field_meta: FieldMeta, mut field_meta: FieldMeta,
container_meta: &ContainerMeta, container_meta: &ContainerMeta,
) -> Result<(), syn::Error> { ) -> Result<(), syn::Error> {
@ -353,19 +362,21 @@ fn named_field(
discard_lifetimes(&mut no_lifetime_type, borrowed, field_meta.borrow, true); discard_lifetimes(&mut no_lifetime_type, borrowed, field_meta.borrow, true);
let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site()); let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site());
tokens.r#enum.extend(quote!(#enum_name,)); if !field_meta.direct {
tokens.r#enum.extend(quote!(#enum_name,));
if !tokens.branches.is_empty() { if !tokens.branches.is_empty() {
tokens.branches.extend(quote!(else)); tokens.branches.extend(quote!(else));
}
tokens.branches.extend(quote!(
if <#no_lifetime_type as FromXml>::KIND.matches(id, Id { ns: #ns, name: #field_tag })
));
tokens.branches.extend(match field_meta.attribute {
true => quote!({ __Attributes::#enum_name }),
false => quote!({ __Elements::#enum_name }),
});
} }
tokens.branches.extend(quote!(
if <#no_lifetime_type as FromXml>::KIND.matches(id, Id { ns: #ns, name: #field_tag })
));
tokens.branches.extend(match field_meta.attribute {
true => quote!({ __Attributes::#enum_name }),
false => quote!({ __Elements::#enum_name }),
});
declare_values.extend(quote!( declare_values.extend(quote!(
let mut #enum_name: Option<#no_lifetime_type> = None; let mut #enum_name: Option<#no_lifetime_type> = None;
@ -386,12 +397,26 @@ fn named_field(
if !field_meta.attribute { if !field_meta.attribute {
if let Some(with) = deserialize_with { if let Some(with) = deserialize_with {
if field_meta.direct {
return Err(syn::Error::new(
field.span(),
"direct attribute is not supported deserialization functions",
));
}
tokens.r#match.extend(quote!( tokens.r#match.extend(quote!(
__Elements::#enum_name => { __Elements::#enum_name => {
let mut nested = deserializer.nested(data); let mut nested = deserializer.nested(data);
#with(&mut nested, &mut #enum_name)?; #with(&mut nested, &mut #enum_name)?;
}, },
)); ));
} else if field_meta.direct {
direct.extend(quote!(
Node::Text(text) => {
let mut nested = deserializer.for_node(Node::Text(text));
FromXml::deserialize(&mut nested, &mut #enum_name)?;
}
));
} else { } else {
tokens.r#match.extend(quote!( tokens.r#match.extend(quote!(
__Elements::#enum_name => match <#no_lifetime_type as FromXml>::KIND { __Elements::#enum_name => match <#no_lifetime_type as FromXml>::KIND {
@ -408,6 +433,13 @@ fn named_field(
)); ));
} }
} else { } else {
if field_meta.direct {
return Err(syn::Error::new(
field.span(),
"direct attribute is not supported for attribute fields",
));
}
if let Some(with) = deserialize_with { if let Some(with) = deserialize_with {
tokens.r#match.extend(quote!( tokens.r#match.extend(quote!(
__Attributes::#enum_name => { __Attributes::#enum_name => {

View File

@ -108,6 +108,7 @@ impl<'input> ContainerMeta<'input> {
struct FieldMeta { struct FieldMeta {
attribute: bool, attribute: bool,
borrow: bool, borrow: bool,
direct: bool,
ns: NamespaceMeta, ns: NamespaceMeta,
tag: TokenStream, tag: TokenStream,
serialize_with: Option<Literal>, serialize_with: Option<Literal>,
@ -129,6 +130,7 @@ impl FieldMeta {
match item { match item {
MetaItem::Attribute => meta.attribute = true, MetaItem::Attribute => meta.attribute = true,
MetaItem::Borrow => meta.borrow = true, MetaItem::Borrow => meta.borrow = true,
MetaItem::Direct => meta.direct = true,
MetaItem::Ns(ns) => meta.ns = ns, MetaItem::Ns(ns) => meta.ns = ns,
MetaItem::Rename(lit) => meta.tag = quote!(#lit), MetaItem::Rename(lit) => meta.tag = quote!(#lit),
MetaItem::SerializeWith(lit) => meta.serialize_with = Some(lit), MetaItem::SerializeWith(lit) => meta.serialize_with = Some(lit),

View File

@ -295,6 +295,9 @@ pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
} else if id == "borrow" { } else if id == "borrow" {
items.push((MetaItem::Borrow, span)); items.push((MetaItem::Borrow, span));
MetaState::Comma MetaState::Comma
} else if id == "direct" {
items.push((MetaItem::Direct, span));
MetaState::Comma
} else if id == "ns" { } else if id == "ns" {
MetaState::Ns MetaState::Ns
} else if id == "rename" { } else if id == "rename" {
@ -477,6 +480,7 @@ impl fmt::Debug for Namespace {
pub(crate) enum MetaItem { pub(crate) enum MetaItem {
Attribute, Attribute,
Borrow, Borrow,
Direct,
Ns(NamespaceMeta), Ns(NamespaceMeta),
Rename(Literal), Rename(Literal),
Mode(Mode), Mode(Mode),

View File

@ -277,19 +277,6 @@ fn named_field(
} }
}; };
if let Some(with) = field_meta.serialize_with {
let path = with.to_string();
let path = syn::parse_str::<syn::Path>(path.trim_matches('"')).map_err(|err| {
syn::Error::new(
with.span(),
format!("failed to parse serialize_with as path: {err}"),
)
})?;
body.extend(quote!(#path(&self.#field_name, serializer)?;));
return Ok(());
}
let tag = field_meta.tag; let tag = field_meta.tag;
let default_ns = match &meta.ns.uri { let default_ns = match &meta.ns.uri {
Some(ns) => quote!(#ns), Some(ns) => quote!(#ns),
@ -297,6 +284,13 @@ fn named_field(
}; };
if field_meta.attribute { if field_meta.attribute {
if field_meta.direct {
return Err(syn::Error::new(
field.span(),
"direct attribute is not supported on attributes",
));
}
let (ns, error) = match &field_meta.ns.uri { let (ns, error) = match &field_meta.ns.uri {
Some(Namespace::Path(path)) => match path.get_ident() { Some(Namespace::Path(path)) => match path.get_ident() {
Some(prefix) => match &meta.ns.prefixes.get(&prefix.to_string()) { Some(prefix) => match &meta.ns.prefixes.get(&prefix.to_string()) {
@ -344,9 +338,33 @@ fn named_field(
let mut no_lifetime_type = field.ty.clone(); let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type, borrowed, false, true); discard_lifetimes(&mut no_lifetime_type, borrowed, false, true);
body.extend(quote!( if let Some(with) = field_meta.serialize_with {
self.#field_name.serialize(Some(::instant_xml::Id { ns: #ns, name: #tag }), serializer)?; if field_meta.direct {
)); return Err(syn::Error::new(
field.span(),
"direct serialization is not supported with `serialize_with`",
));
}
let path = with.to_string();
let path = syn::parse_str::<syn::Path>(path.trim_matches('"')).map_err(|err| {
syn::Error::new(
with.span(),
format!("failed to parse serialize_with as path: {err}"),
)
})?;
body.extend(quote!(#path(&self.#field_name, serializer)?;));
return Ok(());
} else if field_meta.direct {
body.extend(quote!(
self.#field_name.serialize(None, serializer)?;
));
} else {
body.extend(quote!(
self.#field_name.serialize(Some(::instant_xml::Id { ns: #ns, name: #tag }), serializer)?;
));
}
Ok(()) Ok(())
} }

View File

@ -0,0 +1,23 @@
use similar_asserts::assert_eq;
use instant_xml::{from_str, to_string, FromXml, ToXml};
#[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
struct Foo {
#[xml(attribute)]
flag: bool,
#[xml(direct)]
inner: String,
}
#[test]
fn direct() {
let v = Foo {
flag: true,
inner: "hello".to_string(),
};
let xml = "<Foo flag=\"true\">hello</Foo>";
assert_eq!(to_string(&v).unwrap(), xml);
assert_eq!(from_str::<Foo>(xml), Ok(v));
}