Implement support for wrapped enum mode
This commit is contained in:
parent
1190c5c345
commit
0be0b6cc45
|
@ -2,7 +2,9 @@ use proc_macro2::{Ident, Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
use super::{discard_lifetimes, ContainerMeta, FieldMeta, Namespace, VariantMeta};
|
use super::{
|
||||||
|
discard_lifetimes, meta_items, ContainerMeta, FieldMeta, Mode, Namespace, VariantMeta,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream {
|
pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream {
|
||||||
let meta = match ContainerMeta::from_derive(input) {
|
let meta = match ContainerMeta::from_derive(input) {
|
||||||
|
@ -10,21 +12,21 @@ pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream {
|
||||||
Err(e) => return e.to_compile_error(),
|
Err(e) => return e.to_compile_error(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match &input.data {
|
match (&input.data, meta.mode) {
|
||||||
syn::Data::Struct(_) if meta.mode.is_some() => {
|
(syn::Data::Struct(data), None) => deserialize_struct(input, data, meta),
|
||||||
|
(syn::Data::Enum(data), Some(Mode::Scalar)) => deserialize_scalar_enum(input, data, meta),
|
||||||
|
(syn::Data::Enum(data), Some(Mode::Wrapped)) => deserialize_wrapped_enum(input, data, meta),
|
||||||
|
(syn::Data::Struct(_), _) => {
|
||||||
syn::Error::new(input.span(), "no enum mode allowed on struct type").to_compile_error()
|
syn::Error::new(input.span(), "no enum mode allowed on struct type").to_compile_error()
|
||||||
}
|
}
|
||||||
syn::Data::Struct(ref data) => deserialize_struct(input, data, meta),
|
(syn::Data::Enum(_), None) => {
|
||||||
syn::Data::Enum(_) if meta.mode.is_none() => {
|
|
||||||
syn::Error::new(input.span(), "missing enum mode").to_compile_error()
|
syn::Error::new(input.span(), "missing enum mode").to_compile_error()
|
||||||
}
|
}
|
||||||
syn::Data::Enum(ref data) => deserialize_enum(input, data, meta),
|
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
fn deserialize_scalar_enum(
|
||||||
fn deserialize_enum(
|
|
||||||
input: &syn::DeriveInput,
|
input: &syn::DeriveInput,
|
||||||
data: &syn::DataEnum,
|
data: &syn::DataEnum,
|
||||||
meta: ContainerMeta,
|
meta: ContainerMeta,
|
||||||
|
@ -36,7 +38,7 @@ fn deserialize_enum(
|
||||||
let v_ident = &variant.ident;
|
let v_ident = &variant.ident;
|
||||||
let meta = match VariantMeta::from_variant(variant, &meta) {
|
let meta = match VariantMeta::from_variant(variant, &meta) {
|
||||||
Ok(meta) => meta,
|
Ok(meta) => meta,
|
||||||
Err(err) => return err.to_compile_error()
|
Err(err) => return err.to_compile_error(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialize_as = meta.serialize_as;
|
let serialize_as = meta.serialize_as;
|
||||||
|
@ -63,6 +65,96 @@ fn deserialize_enum(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_wrapped_enum(
|
||||||
|
input: &syn::DeriveInput,
|
||||||
|
data: &syn::DataEnum,
|
||||||
|
meta: ContainerMeta,
|
||||||
|
) -> TokenStream {
|
||||||
|
if data.variants.is_empty() {
|
||||||
|
return syn::Error::new(input.span(), "empty enum is not supported").to_compile_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
let ident = &input.ident;
|
||||||
|
let mut variants = TokenStream::new();
|
||||||
|
for variant in data.variants.iter() {
|
||||||
|
let field = match &variant.fields {
|
||||||
|
syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
|
||||||
|
fields.unnamed.first().unwrap()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new(
|
||||||
|
input.span(),
|
||||||
|
"wrapped enum variants must have 1 unnamed field",
|
||||||
|
)
|
||||||
|
.to_compile_error()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !meta_items(&variant.attrs).is_empty() {
|
||||||
|
return syn::Error::new(
|
||||||
|
input.span(),
|
||||||
|
"attributes not allowed on wrapped enum variants",
|
||||||
|
)
|
||||||
|
.to_compile_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut no_lifetime_type = field.ty.clone();
|
||||||
|
discard_lifetimes(&mut no_lifetime_type);
|
||||||
|
|
||||||
|
if !variants.is_empty() {
|
||||||
|
variants.extend(quote!(else));
|
||||||
|
}
|
||||||
|
|
||||||
|
let v_ident = &variant.ident;
|
||||||
|
variants.extend(
|
||||||
|
quote!(if ::instant_xml::Kind::Element(id) == <#no_lifetime_type as FromXml>::KIND {
|
||||||
|
let mut nested = deserializer.nested(data);
|
||||||
|
#ident::#v_ident(#no_lifetime_type::deserialize(&mut nested)?)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = meta.tag();
|
||||||
|
let default_namespace = meta.default_namespace();
|
||||||
|
let generics = meta.xml_generics();
|
||||||
|
let (xml_impl_generics, _, _) = generics.split_for_impl();
|
||||||
|
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
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::Node;
|
||||||
|
use ::instant_xml::Error;
|
||||||
|
|
||||||
|
let node = match deserializer.next() {
|
||||||
|
Some(result) => result?,
|
||||||
|
None => return Err(Error::MissingValue),
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = match node {
|
||||||
|
Node::Open(data) => data,
|
||||||
|
_ => return Err(Error::UnexpectedState),
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = deserializer.element_id(&data)?;
|
||||||
|
let value = #variants else {
|
||||||
|
return Err(Error::UnexpectedTag);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(_) = deserializer.next() {
|
||||||
|
return Err(Error::UnexpectedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const KIND: ::instant_xml::Kind<'static> = ::instant_xml::Kind::Element(::instant_xml::Id {
|
||||||
|
ns: #default_namespace,
|
||||||
|
name: #name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn deserialize_struct(
|
fn deserialize_struct(
|
||||||
input: &syn::DeriveInput,
|
input: &syn::DeriveInput,
|
||||||
data: &syn::DataStruct,
|
data: &syn::DataStruct,
|
||||||
|
@ -143,7 +235,7 @@ fn deserialize_struct(
|
||||||
quote!(
|
quote!(
|
||||||
impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause {
|
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> {
|
fn deserialize<'cx>(deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>) -> Result<Self, ::instant_xml::Error> {
|
||||||
use ::instant_xml::de::{Deserializer, Node};
|
use ::instant_xml::de::Node;
|
||||||
use ::instant_xml::{Error, Id};
|
use ::instant_xml::{Error, Id};
|
||||||
use ::core::marker::PhantomData;
|
use ::core::marker::PhantomData;
|
||||||
|
|
||||||
|
|
|
@ -59,8 +59,8 @@ impl<'input> ContainerMeta<'input> {
|
||||||
Err(err) => return Err(syn::Error::new(span, err)),
|
Err(err) => return Err(syn::Error::new(span, err)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
MetaItem::Scalar => match mode {
|
MetaItem::Mode(new) => match mode {
|
||||||
None => mode = Some(Mode::Scalar),
|
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")),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ impl FieldMeta {
|
||||||
"attribute 'rename_all' invalid in field xml attribute",
|
"attribute 'rename_all' invalid in field xml attribute",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
MetaItem::Scalar => {
|
MetaItem::Mode(_) => {
|
||||||
return Err(syn::Error::new(span, "invalid attribute for struct field"));
|
return Err(syn::Error::new(span, "invalid attribute for struct field"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -509,7 +509,10 @@ fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
|
||||||
} else if id == "rename_all" {
|
} else if id == "rename_all" {
|
||||||
MetaState::RenameAll
|
MetaState::RenameAll
|
||||||
} else if id == "scalar" {
|
} else if id == "scalar" {
|
||||||
items.push((MetaItem::Scalar, span));
|
items.push((MetaItem::Mode(Mode::Scalar), span));
|
||||||
|
MetaState::Comma
|
||||||
|
} else if id == "wrapped" {
|
||||||
|
items.push((MetaItem::Mode(Mode::Wrapped), span));
|
||||||
MetaState::Comma
|
MetaState::Comma
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected key in xml attribute");
|
panic!("unexpected key in xml attribute");
|
||||||
|
@ -630,7 +633,7 @@ enum MetaItem {
|
||||||
Attribute,
|
Attribute,
|
||||||
Ns(NamespaceMeta),
|
Ns(NamespaceMeta),
|
||||||
Rename(Literal),
|
Rename(Literal),
|
||||||
Scalar,
|
Mode(Mode),
|
||||||
RenameAll(Literal),
|
RenameAll(Literal),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -697,8 +700,10 @@ fn discard_path_lifetimes(path: &mut syn::TypePath) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
Scalar,
|
Scalar,
|
||||||
|
Wrapped,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -2,9 +2,8 @@ use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
use crate::Namespace;
|
use super::{discard_lifetimes, meta_items, ContainerMeta, FieldMeta, Mode, VariantMeta};
|
||||||
|
use crate::{case::RenameRule, Namespace};
|
||||||
use super::{discard_lifetimes, ContainerMeta, FieldMeta, VariantMeta};
|
|
||||||
|
|
||||||
pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||||
let meta = match ContainerMeta::from_derive(input) {
|
let meta = match ContainerMeta::from_derive(input) {
|
||||||
|
@ -12,21 +11,21 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||||
Err(e) => return e.to_compile_error(),
|
Err(e) => return e.to_compile_error(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match &input.data {
|
match (&input.data, meta.mode) {
|
||||||
syn::Data::Struct(_) if meta.mode.is_some() => {
|
(syn::Data::Struct(data), None) => serialize_struct(input, data, meta),
|
||||||
|
(syn::Data::Enum(data), Some(Mode::Scalar)) => serialize_scalar_enum(input, data, meta),
|
||||||
|
(syn::Data::Enum(data), Some(Mode::Wrapped)) => serialize_wrapped_enum(input, data, meta),
|
||||||
|
(syn::Data::Struct(_), _) => {
|
||||||
syn::Error::new(input.span(), "enum mode not allowed on struct type").to_compile_error()
|
syn::Error::new(input.span(), "enum mode not allowed on struct type").to_compile_error()
|
||||||
}
|
}
|
||||||
syn::Data::Struct(ref data) => serialize_struct(input, data, meta),
|
(syn::Data::Enum(_), _) => {
|
||||||
syn::Data::Enum(_) if meta.mode.is_none() => {
|
syn::Error::new(input.span(), "missing enum mode").to_compile_error()
|
||||||
syn::Error::new(input.span(), "missing enum mode")
|
|
||||||
.to_compile_error()
|
|
||||||
}
|
}
|
||||||
syn::Data::Enum(ref data) => serialize_enum(input, data, meta),
|
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_enum(
|
fn serialize_scalar_enum(
|
||||||
input: &syn::DeriveInput,
|
input: &syn::DeriveInput,
|
||||||
data: &syn::DataEnum,
|
data: &syn::DataEnum,
|
||||||
meta: ContainerMeta,
|
meta: ContainerMeta,
|
||||||
|
@ -35,12 +34,12 @@ fn serialize_enum(
|
||||||
let mut variants = TokenStream::new();
|
let mut variants = TokenStream::new();
|
||||||
|
|
||||||
for variant in data.variants.iter() {
|
for variant in data.variants.iter() {
|
||||||
let v_ident = &variant.ident;
|
|
||||||
let meta = match VariantMeta::from_variant(variant, &meta) {
|
let meta = match VariantMeta::from_variant(variant, &meta) {
|
||||||
Ok(meta) => meta,
|
Ok(meta) => meta,
|
||||||
Err(err) => return err.to_compile_error(),
|
Err(err) => return err.to_compile_error(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let v_ident = &variant.ident;
|
||||||
let serialize_as = meta.serialize_as;
|
let serialize_as = meta.serialize_as;
|
||||||
variants.extend(quote!(#ident::#v_ident => #serialize_as,));
|
variants.extend(quote!(#ident::#v_ident => #serialize_as,));
|
||||||
}
|
}
|
||||||
|
@ -60,6 +59,103 @@ fn serialize_enum(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize_wrapped_enum(
|
||||||
|
input: &syn::DeriveInput,
|
||||||
|
data: &syn::DataEnum,
|
||||||
|
meta: ContainerMeta,
|
||||||
|
) -> TokenStream {
|
||||||
|
if meta.rename_all != RenameRule::None {
|
||||||
|
return syn::Error::new(
|
||||||
|
input.span(),
|
||||||
|
"rename_all is not allowed on wrapped enum type",
|
||||||
|
)
|
||||||
|
.to_compile_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
let ident = &input.ident;
|
||||||
|
let mut variants = TokenStream::new();
|
||||||
|
for variant in data.variants.iter() {
|
||||||
|
match &variant.fields {
|
||||||
|
syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {}
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new(
|
||||||
|
input.span(),
|
||||||
|
"wrapped enum variants must have 1 unnamed field",
|
||||||
|
)
|
||||||
|
.to_compile_error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !meta_items(&variant.attrs).is_empty() {
|
||||||
|
return syn::Error::new(
|
||||||
|
input.span(),
|
||||||
|
"attributes not allowed on wrapped enum variants",
|
||||||
|
)
|
||||||
|
.to_compile_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
let v_ident = &variant.ident;
|
||||||
|
variants.extend(quote!(#ident::#v_ident(inner) => inner.serialize(serializer)?,));
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_namespace = meta.default_namespace();
|
||||||
|
let cx_len = meta.ns.prefixes.len();
|
||||||
|
let mut context = quote!(
|
||||||
|
let mut new = ::instant_xml::ser::Context::<#cx_len>::default();
|
||||||
|
new.default_ns = #default_namespace;
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, (prefix, ns)) in meta.ns.prefixes.iter().enumerate() {
|
||||||
|
context.extend(quote!(
|
||||||
|
new.prefixes[#i] = ::instant_xml::ser::Prefix { ns: #ns, prefix: #prefix };
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 tag = meta.tag();
|
||||||
|
quote!(
|
||||||
|
impl #impl_generics ToXml for #ident #ty_generics #where_clause {
|
||||||
|
fn serialize<W: ::core::fmt::Write + ?::core::marker::Sized>(
|
||||||
|
&self,
|
||||||
|
serializer: &mut instant_xml::Serializer<W>,
|
||||||
|
) -> Result<(), instant_xml::Error> {
|
||||||
|
// Start tag
|
||||||
|
let prefix = serializer.write_start(#tag, #default_namespace, false)?;
|
||||||
|
debug_assert_eq!(prefix, None);
|
||||||
|
|
||||||
|
// Set up element context, this will also emit namespace declarations
|
||||||
|
#context
|
||||||
|
let old = serializer.push(new)?;
|
||||||
|
|
||||||
|
// Finalize start element
|
||||||
|
serializer.end_start()?;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
#variants
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close tag
|
||||||
|
serializer.write_close(prefix, #tag)?;
|
||||||
|
serializer.pop(old);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const KIND: ::instant_xml::Kind<'static> = ::instant_xml::Kind::Element(::instant_xml::Id {
|
||||||
|
ns: #default_namespace,
|
||||||
|
name: #tag,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn serialize_struct(
|
fn serialize_struct(
|
||||||
input: &syn::DeriveInput,
|
input: &syn::DeriveInput,
|
||||||
data: &syn::DataStruct,
|
data: &syn::DataStruct,
|
||||||
|
|
|
@ -110,6 +110,7 @@ pub enum Error {
|
||||||
DuplicateValue,
|
DuplicateValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
pub enum Kind<'a> {
|
pub enum Kind<'a> {
|
||||||
Scalar,
|
Scalar,
|
||||||
Element(Id<'a>),
|
Element(Id<'a>),
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use instant_xml::{from_str, to_string, FromXml, ToXml};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
|
||||||
|
#[xml(wrapped)]
|
||||||
|
enum Foo {
|
||||||
|
Bar(Bar),
|
||||||
|
Baz(Baz),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
|
||||||
|
struct Bar {
|
||||||
|
bar: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
|
||||||
|
struct Baz {
|
||||||
|
baz: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrapped_enum() {
|
||||||
|
let v = Foo::Bar(Bar { bar: 42 });
|
||||||
|
let xml = r#"<Foo><Bar><bar>42</bar></Bar></Foo>"#;
|
||||||
|
assert_eq!(xml, to_string(&v).unwrap());
|
||||||
|
assert_eq!(v, from_str(xml).unwrap());
|
||||||
|
}
|
Loading…
Reference in New Issue