Implement transparent mode for serialization

This commit is contained in:
Dirkjan Ochtman 2023-02-17 12:28:08 +01:00
parent b276ee173f
commit be7902925e
4 changed files with 134 additions and 7 deletions

View File

@ -54,7 +54,7 @@ impl<'input> ContainerMeta<'input> {
} }
MetaItem::Mode(new) => match mode { MetaItem::Mode(new) => match mode {
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 modes")),
}, },
_ => { _ => {
return Err(syn::Error::new( return Err(syn::Error::new(
@ -302,6 +302,7 @@ fn discard_path_lifetimes(
enum Mode { enum Mode {
Forward, Forward,
Scalar, Scalar,
Transparent,
} }
#[cfg(test)] #[cfg(test)]
@ -334,7 +335,7 @@ mod tests {
} }
}) })
.to_string()) .to_string())
.find("compile_error ! { \"missing enum mode\" }") .find("compile_error ! { \"missing mode\" }")
.unwrap(); .unwrap();
} }

View File

@ -298,6 +298,9 @@ pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
} else if id == "direct" { } else if id == "direct" {
items.push((MetaItem::Direct, span)); items.push((MetaItem::Direct, span));
MetaState::Comma MetaState::Comma
} else if id == "transparent" {
items.push((MetaItem::Mode(Mode::Transparent), span));
MetaState::Comma
} else if id == "ns" { } else if id == "ns" {
MetaState::Ns MetaState::Ns
} else if id == "rename" { } else if id == "rename" {

View File

@ -15,13 +15,23 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
match (&input.data, meta.mode) { match (&input.data, meta.mode) {
(syn::Data::Struct(data), None) => serialize_struct(input, data, meta), (syn::Data::Struct(data), None) => serialize_struct(input, data, meta),
(syn::Data::Struct(data), Some(Mode::Transparent)) => {
serialize_inline_struct(input, data, meta)
}
(syn::Data::Enum(data), Some(Mode::Scalar)) => serialize_scalar_enum(input, data, meta), (syn::Data::Enum(data), Some(Mode::Scalar)) => serialize_scalar_enum(input, data, meta),
(syn::Data::Enum(data), Some(Mode::Forward)) => serialize_forward_enum(input, data, meta), (syn::Data::Enum(data), Some(Mode::Forward)) => serialize_forward_enum(input, data, meta),
(syn::Data::Struct(_), _) => { (syn::Data::Struct(_), Some(mode)) => syn::Error::new(
syn::Error::new(input.span(), "enum mode not allowed on struct type").to_compile_error() input.span(),
} format_args!("{mode:?} mode not allowed on struct type"),
(syn::Data::Enum(_), _) => { )
syn::Error::new(input.span(), "missing enum mode").to_compile_error() .to_compile_error(),
(syn::Data::Enum(_), Some(mode)) => syn::Error::new(
input.span(),
format_args!("{mode:?} mode not allowed on enum type"),
)
.to_compile_error(),
(syn::Data::Enum(_), None) => {
syn::Error::new(input.span(), "missing mode").to_compile_error()
} }
_ => todo!(), _ => todo!(),
} }
@ -233,6 +243,82 @@ fn serialize_struct(
) )
} }
fn serialize_inline_struct(
input: &syn::DeriveInput,
data: &syn::DataStruct,
meta: ContainerMeta,
) -> proc_macro2::TokenStream {
if !meta.ns.prefixes.is_empty() {
return syn::Error::new(
input.span(),
"inline structs cannot have namespace declarations",
)
.to_compile_error();
} else if let Some(ns) = meta.ns.uri {
return syn::Error::new(
ns.span(),
"inline structs cannot have namespace declarations",
)
.to_compile_error();
} else if let Some(rename) = meta.rename {
return syn::Error::new(rename.span(), "inline structs cannot be renamed")
.to_compile_error();
}
let mut body = TokenStream::new();
let mut attributes = TokenStream::new();
let mut borrowed = BTreeSet::new();
match &data.fields {
syn::Fields::Named(fields) => {
for field in &fields.named {
if let Err(err) =
named_field(field, &mut body, &mut attributes, &mut borrowed, &meta)
{
return err.to_compile_error();
}
if !attributes.is_empty() {
return syn::Error::new(
input.span(),
"no attributes allowed on inline structs",
)
.to_compile_error();
}
}
}
syn::Fields::Unnamed(fields) => {
for (index, field) in fields.unnamed.iter().enumerate() {
if let Err(err) = unnamed_field(field, index, &mut body, &mut borrowed) {
return err.to_compile_error();
}
}
}
syn::Fields::Unit => body.extend(quote!(serializer.end_empty()?;)),
}
let mut generics = input.generics.clone();
for param in generics.type_params_mut() {
param
.bounds
.push(syn::parse_str("::instant_xml::ToXml").unwrap());
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let ident = &input.ident;
quote!(
impl #impl_generics ToXml for #ident #ty_generics #where_clause {
fn serialize<W: ::core::fmt::Write + ?::core::marker::Sized>(
&self,
field: Option<::instant_xml::Id<'_>>,
serializer: &mut instant_xml::Serializer<W>,
) -> Result<(), instant_xml::Error> {
#body
Ok(())
}
};
)
}
fn named_field( fn named_field(
field: &syn::Field, field: &syn::Field,
body: &mut TokenStream, body: &mut TokenStream,

View File

@ -0,0 +1,37 @@
use instant_xml::{to_string, ToXml};
#[derive(Debug, Eq, PartialEq, ToXml)]
struct Wrapper {
inline: Inline,
}
#[derive(Debug, Eq, PartialEq, ToXml)]
#[xml(transparent)]
struct Inline {
foo: Foo,
bar: Bar,
}
#[derive(Debug, Eq, PartialEq, ToXml)]
struct Foo {
i: u8,
}
#[derive(Debug, Eq, PartialEq, ToXml)]
struct Bar {
s: String,
}
#[test]
fn inline() {
let v = Wrapper {
inline: Inline {
foo: Foo { i: 42 },
bar: Bar {
s: "hello".to_string(),
},
},
};
let xml = r#"<Wrapper><Foo><i>42</i></Foo><Bar><s>hello</s></Bar></Wrapper>"#;
assert_eq!(xml, to_string(&v).unwrap());
}