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

View File

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