Add support for serialize_with attribute

This commit is contained in:
Dirkjan Ochtman 2022-11-23 13:08:06 -08:00
parent 61df3a7835
commit f6e22b3e31
3 changed files with 68 additions and 12 deletions

View File

@ -45,12 +45,6 @@ impl<'input> ContainerMeta<'input> {
for (item, span) in meta_items(&input.attrs) {
match item {
MetaItem::Attribute => {
return Err(syn::Error::new(
span,
"attribute key invalid in container xml attribute",
))
}
MetaItem::Ns(namespace) => ns = namespace,
MetaItem::Rename(lit) => rename = Some(lit),
MetaItem::RenameAll(lit) => {
@ -63,6 +57,12 @@ impl<'input> ContainerMeta<'input> {
None => mode = Some(new),
Some(_) => return Err(syn::Error::new(span, "cannot have two enum modes")),
},
_ => {
return Err(syn::Error::new(
span,
"invalid field in container xml attribute",
))
}
}
}
@ -111,6 +111,7 @@ struct FieldMeta {
attribute: bool,
ns: NamespaceMeta,
tag: TokenStream,
serialize_with: Option<Literal>,
}
impl FieldMeta {
@ -129,6 +130,7 @@ impl FieldMeta {
MetaItem::Attribute => meta.attribute = true,
MetaItem::Ns(ns) => meta.ns = ns,
MetaItem::Rename(lit) => meta.tag = quote!(#lit),
MetaItem::SerializeWith(lit) => meta.serialize_with = Some(lit),
MetaItem::RenameAll(_) => {
return Err(syn::Error::new(
span,
@ -514,6 +516,8 @@ fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
} else if id == "wrapped" {
items.push((MetaItem::Mode(Mode::Wrapped), span));
MetaState::Comma
} else if id == "serialize_with" {
MetaState::SerializeWith
} else {
panic!("unexpected key in xml attribute");
}
@ -541,6 +545,13 @@ fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
items.push((MetaItem::RenameAll(lit), span));
MetaState::Comma
}
(MetaState::SerializeWith, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
MetaState::SerializeWithValue
}
(MetaState::SerializeWithValue, TokenTree::Literal(lit)) => {
items.push((MetaItem::SerializeWith(lit), span));
MetaState::Comma
}
(state, tree) => {
panic!(
"invalid state transition while parsing xml attribute ({}, {tree})",
@ -562,6 +573,8 @@ enum MetaState {
RenameValue,
RenameAll,
RenameAllValue,
SerializeWith,
SerializeWithValue,
}
impl MetaState {
@ -574,6 +587,8 @@ impl MetaState {
MetaState::RenameValue => "RenameValue",
MetaState::RenameAll => "RenameAll",
MetaState::RenameAllValue => "RenameAllValue",
MetaState::SerializeWith => "SerializeWith",
MetaState::SerializeWithValue => "SerializeWithValue",
}
}
}
@ -635,6 +650,7 @@ enum MetaItem {
Rename(Literal),
Mode(Mode),
RenameAll(Literal),
SerializeWith(Literal),
}
enum Namespace {

View File

@ -166,9 +166,11 @@ fn serialize_struct(
match data.fields {
syn::Fields::Named(ref fields) => {
fields.named.iter().for_each(|field| {
process_named_field(field, &mut body, &mut attributes, &meta);
});
for field in &fields.named {
if let Err(err) = process_named_field(field, &mut body, &mut attributes, &meta) {
return err.to_compile_error();
}
}
}
syn::Fields::Unnamed(_) => todo!(),
syn::Fields::Unit => {}
@ -238,16 +240,29 @@ fn process_named_field(
body: &mut TokenStream,
attributes: &mut TokenStream,
meta: &ContainerMeta,
) {
) -> Result<(), syn::Error> {
let field_name = field.ident.as_ref().unwrap();
let field_meta = match FieldMeta::from_field(field, meta) {
Ok(meta) => meta,
Err(err) => {
body.extend(err.into_compile_error());
return;
return Ok(());
}
};
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 default_ns = match &meta.ns.uri {
Some(ns) => quote!(#ns),
@ -292,7 +307,7 @@ fn process_named_field(
#error
serializer.write_attr(#tag, #ns, &self.#field_name)?;
));
return;
return Ok(());
}
let ns = match field_meta.ns.uri {
@ -315,4 +330,6 @@ fn process_named_field(
}
}
));
Ok(())
}

23
instant-xml/tests/with.rs Normal file
View File

@ -0,0 +1,23 @@
use std::fmt;
use instant_xml::{to_string, Error, Serializer, ToXml};
#[derive(ToXml)]
struct Foo {
#[xml(serialize_with = "serialize_foo")]
foo: u8,
}
fn serialize_foo<W: fmt::Write + ?Sized>(
value: &u8,
serializer: &mut Serializer<'_, W>,
) -> Result<(), Error> {
serializer.write_str(&format_args!("foo: {value}"))
}
#[test]
fn serialize_with() {
let v = Foo { foo: 42 };
let xml = r#"<Foo>foo: 42</Foo>"#;
assert_eq!(xml, to_string(&v).unwrap());
}