xmpp-rs/xso-proc/src/enums.rs

1062 lines
34 KiB
Rust

/*!
# Processing of enum declarations
This module contains the main code for implementing the derive macros from
this crate on `enum` items.
It is thus the counterpart to [`crate::structs`].
*/
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::*;
use crate::common::{build_prepare, build_validate, ItemDef};
use crate::compound::Compound;
use crate::error_message::ParentRef;
use crate::meta::{Flag, Name, NamespaceRef, StaticNamespace, XmlCompoundMeta};
use crate::structs::StructInner;
/// Build the necessary objects to rebind all enum fields to collision-free
/// identifiers.
///
/// Takes an iterator over members of the enum's compound and returns, in
/// order:
///
/// - A vector of these identifiers.
/// - A vector with expressions containing the rebound idenifiers.
/// - A function which maps the original identifiers to their rebound
/// versions.
fn build_ident_mapping<I: Iterator<Item = Member>>(
refs: I,
) -> (Vec<Member>, Vec<Expr>, Box<dyn FnMut(Member) -> Expr>) {
let map_ident = |ident: Member| -> Expr {
Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path: Path {
leading_colon: None,
segments: [PathSegment::from(quote::format_ident!("__field_{}", ident))]
.into_iter()
.collect(),
},
})
};
let mut orig_names = Vec::with_capacity(refs.size_hint().1.unwrap_or(0));
let mut mapped_names = Vec::with_capacity(orig_names.capacity());
for field_ref in refs {
orig_names.push(field_ref.clone());
mapped_names.push(map_ident(field_ref));
}
(orig_names, mapped_names, Box::new(map_ident))
}
/// Variant of an enum which switches on a string value extracted from the XML
/// element.
///
/// The caller of the respective methods decides what string the variant
/// matches against and how that match is carried out.
#[derive(Debug)]
struct StrMatchedVariant {
/// The string to match against.
value: LitStr,
/// The identifier of the enum variant.
ident: Ident,
/// The `fallback` flag on the enum variant.
fallback: Flag,
/// The contents of the enum variant.
inner: Compound,
}
impl StrMatchedVariant {
fn prevalidate(meta: &mut XmlCompoundMeta) -> Result<()> {
if let Some(namespace) = meta.namespace.take() {
return Err(Error::new_spanned(
namespace,
"`namespace` not allowed on enum variants.",
));
}
if let Some(attribute) = meta.attribute.take() {
return Err(Error::new_spanned(
attribute,
"`attribute` not allowed on enum variants.",
));
}
if let Flag::Present(transparent) = meta.transparent.take() {
return Err(Error::new(
transparent,
"`transparent` not allowed on enum variants in enums with a fixed namespace.",
));
}
if let Flag::Present(exhaustive) = meta.exhaustive.take() {
return Err(Error::new(
exhaustive,
"`exhaustive` not allowed on enum variants.",
));
}
if let Some(validate) = meta.validate.take() {
return Err(Error::new_spanned(
validate,
"`validate` not allowed on enum variants.",
));
}
if let Some(prepare) = meta.prepare.take() {
return Err(Error::new_spanned(
prepare,
"`validate` not allowed on enum variants.",
));
}
if let Flag::Present(debug) = meta.debug.take() {
return Err(Error::new(debug, "`debug` not allowed on enum variants."));
}
Ok(())
}
/// Parse a [`syn::Variant`] as XML name switched variant.
fn new_xml_name_switched(variant: &Variant) -> Result<Self> {
let mut meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
Self::prevalidate(&mut meta)?;
if let Some(value) = meta.value {
return Err(Error::new_spanned(
value,
"`value` not allowed on enum variants inside enums matching on XML name.",
));
}
let name = if let Some(name) = meta.name {
name.into()
} else {
return Err(Error::new(
meta.span,
"`name` is required on enum variants in enums with a fixed namespace.",
));
};
let fallback = meta.fallback;
Ok(Self {
value: name,
ident: variant.ident.clone(),
fallback,
inner: Compound::from_fields(
meta.on_unknown_child,
meta.on_unknown_attribute,
&variant.fields,
)?,
})
}
/// Parse a [`syn::Variant`] as XML attribute value switched variant.
fn new_xml_attribute_switched(variant: &Variant) -> Result<Self> {
let mut meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
Self::prevalidate(&mut meta)?;
if let Some(name) = meta.name {
return Err(Error::new_spanned(
name,
"`name` not allowed on enum variants inside enums matching on attribute values.",
));
}
let Some(value) = meta.value else {
return Err(Error::new(
meta.span,
"`value` is required on enum variants in enums matching on attribute values.",
));
};
let fallback = meta.fallback;
Ok(Self {
value,
ident: variant.ident.clone(),
fallback,
inner: Compound::from_fields(
meta.on_unknown_child,
meta.on_unknown_attribute,
&variant.fields,
)?,
})
}
}
/// Variant of an enum where each variant has completely independent matching.
#[derive(Debug)]
struct DynamicVariant {
/// The identifier of the enum variant.
ident: Ident,
/// The enum variant's definition.
///
/// The implementation of [`StructInner`] can be reused completely, as the
/// variant has to cover all of its matching logic.
inner: StructInner,
}
impl DynamicVariant {
/// Parse a [`syn::Variant`] as dynamic enum variant.
fn new(variant: &Variant) -> Result<Self> {
let ident = variant.ident.clone();
let mut meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
if let Flag::Present(fallback) = meta.fallback.take() {
return Err(Error::new(
fallback,
"`fallback` cannot be used in dynamic enums.",
));
}
if let Flag::Present(exhaustive) = meta.exhaustive.take() {
return Err(Error::new(
exhaustive,
"`exhaustive` not allowed on enum variants.",
));
}
if let Some(validate) = meta.validate.take() {
return Err(Error::new_spanned(
validate,
"`validate` not allowed on enum variants.",
));
}
if let Some(prepare) = meta.prepare.take() {
return Err(Error::new_spanned(
prepare,
"`validate` not allowed on enum variants.",
));
}
if let Flag::Present(debug) = meta.debug.take() {
return Err(Error::new(debug, "`debug` not allowed on enum variants."));
}
let inner = StructInner::new(meta, &variant.fields)?;
Ok(Self { ident, inner })
}
}
/// An enum which switches on the XML element's name, with a fixed namespace.
#[derive(Debug)]
struct XmlNameSwitched {
/// The exhaustive flag, if set on the enum.
///
/// If this flag is set, the enum considers itself authoritative for its
/// namespace.
///
/// Then, if an XML element matching the namespace but none of the
/// variants is encountered, a hard parse error with an appropriate error
/// message is emitted. That prevents parsing the XML element as other
/// fields of a parent struct (such as #[xml(elements)]`).
///
/// This can be useful in some circumstances.
exhaustive: Flag,
/// The namespace the enum's XML element resides in.
namespace: StaticNamespace,
/// The enum's variants.
variants: Vec<StrMatchedVariant>,
}
impl XmlNameSwitched {
/// Construct a new XML name switched enum from parts.
///
/// - `exhaustive` must be the exhaustive flag (see [`Self::exhaustive`]
/// for details).
///
/// - `namespace` must be the XML namespace of the enum.
///
/// - `input` must be an iterator emitting borrowed [`syn::Variant`]
/// structs to process.
fn new<'x, I: Iterator<Item = &'x Variant>>(
exhaustive: Flag,
namespace: StaticNamespace,
input: I,
) -> Result<Self> {
let mut variants = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
let mut had_fallback = false;
for variant in input {
let variant = StrMatchedVariant::new_xml_name_switched(variant)?;
if let Flag::Present(fallback) = variant.fallback {
if had_fallback {
return Err(syn::Error::new(
fallback,
"only one variant may be a fallback variant",
));
}
had_fallback = true;
}
variants.push(variant);
}
if let Flag::Present(exhaustive) = exhaustive {
if had_fallback {
return Err(syn::Error::new(exhaustive, "exhaustive cannot be sensibly combined with a fallback variant. choose one or the other."));
}
}
Ok(Self {
exhaustive,
namespace,
variants,
})
}
/// Construct an expression which consumes the `minidom::Element` at
/// `residual` and returns a `Result<T, Error>`.
///
/// - `enum_ident` must be the identifier of the enum's type.
/// - `validate` must be a statement which takes a mutable reference to
/// the identifier `result` and `return`'s with an error if it is not
/// acceptable.
/// - `residual` must be the identifier at which the element is found.
fn build_try_from_element(
&self,
enum_ident: &Ident,
validate: Stmt,
residual: &Ident,
) -> Result<TokenStream> {
let xml_namespace = &self.namespace;
let namespace_expr = Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path: xml_namespace.clone(),
});
let mut fallback = quote! {
_ => Err(::xso::error::Error::TypeMismatch("", "", #residual)),
};
let mut iter = quote! {};
for variant in self.variants.iter() {
let ident = &variant.ident;
let xml_name = &variant.value;
let variant_impl = variant.inner.build_try_from_element(
&(Path {
leading_colon: None,
segments: [
PathSegment::from(enum_ident.clone()),
PathSegment::from(ident.clone()),
]
.into_iter()
.collect(),
}
.into()),
&namespace_expr,
residual,
&[],
)?;
let variant_impl = quote! {
let mut result = #variant_impl;
#validate
Ok(result)
};
if variant.fallback.is_set() {
fallback = quote! {
_ => { #variant_impl },
}
}
iter = quote! {
#iter
#xml_name => { #variant_impl },
};
}
if self.exhaustive.is_set() {
// deliberately overriding fallback fields here --- the constructor
// already rejects this combination
fallback = quote! {
_ => Err(::xso::error::Error::ParseError(concat!("This is not a ", stringify!(#enum_ident), " element."))),
};
}
Ok(quote! {
if #residual.ns() == #xml_namespace {
match #residual.name() {
#iter
#fallback
}
} else {
Err(::xso::error::Error::TypeMismatch("", "", #residual))
}
})
}
/// Construct a token stream which contains the arms of a `match`
/// expression dissecting the enum and building `minidom::Element` objects
/// for each variant.
///
/// `ty_ident` must be the identifier of the enum's type.
fn build_into_element(&self, ty_ident: &Ident) -> Result<TokenStream> {
let namespace_expr = Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path: self.namespace.clone(),
});
let builder = Ident::new("builder", Span::call_site());
let mut matchers = quote! {};
for variant in self.variants.iter() {
let xml_name = &variant.value;
let path = Path {
leading_colon: None,
segments: [
PathSegment::from(ty_ident.clone()),
PathSegment::from(variant.ident.clone()),
]
.into_iter()
.collect(),
};
let (orig_names, mapped_names, map_ident) =
build_ident_mapping(variant.inner.iter_members());
let into_element = variant.inner.build_into_element(
&(Path::from(ty_ident.clone()).into()),
&namespace_expr,
&builder,
map_ident,
)?;
matchers = quote! {
#matchers
#path { #( #orig_names: #mapped_names ),* } => {
let #builder = ::xso::exports::minidom::Element::builder(
#xml_name,
#namespace_expr,
);
let #builder = #into_element;
#builder.build()
},
};
}
Ok(matchers)
}
}
/// An enum which switches on the value of an attribute of the XML element.
#[derive(Debug)]
struct XmlAttributeSwitched {
/// The namespace the enum's XML element resides in.
namespace: StaticNamespace,
/// The XML name of the element.
name: Name,
/// The name of the XML attribute to read.
attribute_name: LitStr,
/// Function, if any, to normalize the attribute value with, before
/// matching.
normalize_with: Option<Path>,
/// The enum's variants.
variants: Vec<StrMatchedVariant>,
}
impl XmlAttributeSwitched {
/// Construct a new XML name switched enum from parts.
///
/// - `exhaustive` must be the exhaustive flag. Currently, this must be
/// set on [`XmlAttributeSwitched`] enums.
///
/// - `namespace` must be the XML namespace of the enum.
///
/// - `input` must be an iterator emitting borrowed [`syn::Variant`]
/// structs to process.
fn new<'x, I: Iterator<Item = &'x Variant>>(
exhaustive: Flag,
namespace: StaticNamespace,
name: Name,
attribute_name: LitStr,
normalize_with: Option<Path>,
input: I,
) -> Result<Self> {
let mut variants = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
let mut had_fallback = false;
for variant in input {
let variant = StrMatchedVariant::new_xml_attribute_switched(variant)?;
if let Flag::Present(fallback) = variant.fallback {
if had_fallback {
return Err(syn::Error::new(
fallback,
"only one variant may be a fallback variant",
));
}
had_fallback = true;
}
variants.push(variant);
}
if let Flag::Present(exhaustive) = exhaustive {
if had_fallback {
return Err(syn::Error::new(exhaustive, "exhaustive cannot be sensibly combined with a fallback variant. choose one or the other."));
}
} else {
if !had_fallback {
return Err(syn::Error::new_spanned(
attribute_name,
"enums switching on an attribute must be marked exhaustive or have a fallback variant."
));
}
}
Ok(Self {
namespace,
name,
attribute_name,
normalize_with,
variants,
})
}
/// Construct an expression which consumes the `minidom::Element` at
/// `residual` and returns a `Result<T, Error>`.
///
/// - `enum_ident` must be the identifier of the enum's type.
/// - `validate` must be a statement which takes a mutable reference to
/// the identifier `result` and `return`'s with an error if it is not
/// acceptable.
/// - `residual` must be the identifier at which the element is found.
fn build_try_from_element(
&self,
enum_ident: &Ident,
validate: Stmt,
residual: &Ident,
) -> Result<TokenStream> {
let xml_namespace = &self.namespace;
let xml_name = &self.name;
let attribute_name = self.attribute_name.value();
let namespace_expr = Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path: xml_namespace.clone(),
});
let mut fallback: Option<TokenStream> = None;
let mut iter = quote! {};
for variant in self.variants.iter() {
let ident = &variant.ident;
let xml_name = &variant.value;
let variant_impl = variant.inner.build_try_from_element(
&(Path {
leading_colon: None,
segments: [
PathSegment::from(enum_ident.clone()),
PathSegment::from(ident.clone()),
]
.into_iter()
.collect(),
}
.into()),
&namespace_expr,
residual,
&[attribute_name.as_str()],
)?;
let variant_impl = quote! {
let mut result = #variant_impl;
#validate
Ok(result)
};
if variant.fallback.is_set() {
fallback = Some(quote! {
_ => { #variant_impl },
})
}
iter = quote! {
#iter
#xml_name => { #variant_impl },
};
}
let fallback = fallback.unwrap_or_else(|| quote! {
_ => Err(::xso::error::Error::ParseError(concat!("This is not a ", stringify!(#enum_ident), " element."))),
});
let on_missing = format!(
"Required discriminator attribute '{}' on enum {} element missing.",
attribute_name, enum_ident,
);
let normalize = match self.normalize_with {
Some(ref normalize_with) => quote! {
let attr_value = attr_value.map(|value| #normalize_with(value));
let attr_value = attr_value.as_ref().map(|value| ::std::borrow::Borrow::<str>::borrow(value));
},
None => quote! {},
};
Ok(quote! {
if #residual.is(#xml_name, #xml_namespace) {
let attr_value = #residual.attr(#attribute_name);
#normalize
match attr_value {
Some(v) => match v {
#iter
#fallback
}
None => Err(::xso::error::Error::ParseError(#on_missing)),
}
} else {
Err(::xso::error::Error::TypeMismatch("", "", #residual))
}
})
}
/// Construct a token stream which contains the arms of a `match`
/// expression dissecting the enum and building `minidom::Element` objects
/// for each variant.
///
/// `ty_ident` must be the identifier of the enum's type.
fn build_into_element(&self, ty_ident: &Ident) -> Result<TokenStream> {
let xml_namespace = &self.namespace;
let xml_name = &self.name;
let attribute_name = &self.attribute_name;
let namespace_expr = Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path: xml_namespace.clone(),
});
let builder = Ident::new("builder", Span::call_site());
let mut matchers = quote! {};
for variant in self.variants.iter() {
let attribute_value = &variant.value;
let path = Path {
leading_colon: None,
segments: [
PathSegment::from(ty_ident.clone()),
PathSegment::from(variant.ident.clone()),
]
.into_iter()
.collect(),
};
let (orig_names, mapped_names, map_ident) =
build_ident_mapping(variant.inner.iter_members());
let into_element = variant.inner.build_into_element(
&(Path::from(ty_ident.clone()).into()),
&namespace_expr,
&builder,
map_ident,
)?;
matchers = quote! {
#matchers
#path { #( #orig_names: #mapped_names ),* } => {
let #builder = ::xso::exports::minidom::Element::builder(
#xml_name,
#namespace_expr,
).attr(#attribute_name, #attribute_value);
let #builder = #into_element;
#builder.build()
},
};
}
Ok(matchers)
}
}
/// An enum where each variant has completely independent matching.
#[derive(Debug)]
struct Dynamic {
/// Variants of the enum.
variants: Vec<DynamicVariant>,
}
impl Dynamic {
/// Construct a dynamic enum from an iterable of variants.
fn new<'x, I: Iterator<Item = &'x Variant>>(input: I) -> Result<Self> {
let mut variants = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
for variant in input {
let variant = DynamicVariant::new(variant)?;
variants.push(variant);
}
Ok(Self { variants })
}
/// Construct an expression which consumes the `minidom::Element` at
/// `residual` and returns a `Result<T, Error>`.
///
/// - `enum_ident` must be the identifier of the enum's type.
/// - `validate` must be a statement which takes a mutable reference to
/// the identifier `result` and `return`'s with an error if it is not
/// acceptable.
/// - `residual` must be the identifier at which the element is found.
fn build_try_from_element(
&self,
enum_ident: &Ident,
validate: Stmt,
residual: &Ident,
) -> Result<TokenStream> {
let mut matchers = quote! {};
for variant in self.variants.iter() {
let ident = &variant.ident;
let try_from_impl = variant.inner.build_try_from_element(
&(Path {
leading_colon: None,
segments: [
PathSegment::from(enum_ident.clone()),
PathSegment::from(ident.clone()),
]
.into_iter()
.collect(),
}
.into()),
residual,
)?;
matchers = quote! {
#matchers
let mut #residual = match #try_from_impl {
Ok(mut result) => {
#validate
return Ok(result);
},
Err(#residual) => #residual,
};
}
}
Ok(quote! {
{
#matchers
Err(::xso::error::Error::TypeMismatch("", "", #residual))
}
})
}
/// Construct a token stream which contains the arms of a `match`
/// expression dissecting the enum and building `minidom::Element` objects
/// for each variant.
///
/// `ty_ident` must be the identifier of the enum's type.
fn build_into_element(&self, enum_ident: &Ident) -> Result<TokenStream> {
let mut matchers = quote! {};
for variant in self.variants.iter() {
let ident = &variant.ident;
let path = Path {
leading_colon: None,
segments: [
PathSegment::from(enum_ident.clone()),
PathSegment::from(ident.clone()),
]
.into_iter()
.collect(),
};
let (orig_names, mapped_names, map_ident) =
build_ident_mapping(variant.inner.iter_members());
let into_impl = variant
.inner
.build_into_element(&(path.into()), map_ident)?;
matchers = quote! {
#matchers
#enum_ident::#ident { #( #orig_names: #mapped_names ),* } => { #into_impl },
}
}
Ok(matchers)
}
}
/// Inner part of [`EnumDef`], supporting the different styles of
/// enumerations.
#[derive(Debug)]
enum EnumInner {
/// Enum item where the variants switch on the XML element's name.
XmlNameSwitched(XmlNameSwitched),
/// Enum item where the variants switch on the value of an attribute on
/// the XML element.
XmlAttributeSwitched(XmlAttributeSwitched),
/// Enum item which has no matcher on the enum itself, where each variant
/// may be an entirely different XML element.
Dynamic(Dynamic),
}
impl EnumInner {
/// Construct an expression which consumes the `minidom::Element` at
/// `residual` and returns a `Result<T, Error>`.
///
/// - `enum_ident` must be the identifier of the enum's type.
/// - `validate` must be a statement which takes a mutable reference to
/// the identifier `result` and `return`'s with an error if it is not
/// acceptable.
/// - `residual` must be the identifier at which the element is found.
fn build_try_from_element(
&self,
enum_ident: &Ident,
validate: Stmt,
residual: &Ident,
) -> Result<TokenStream> {
match self {
Self::XmlNameSwitched(inner) => {
inner.build_try_from_element(enum_ident, validate, residual)
}
Self::XmlAttributeSwitched(inner) => {
inner.build_try_from_element(enum_ident, validate, residual)
}
Self::Dynamic(inner) => inner.build_try_from_element(enum_ident, validate, residual),
}
}
/// Construct a token stream which contains the arms of a `match`
/// expression dissecting the enum and building `minidom::Element` objects
/// for each variant.
///
/// `ty_ident` must be the identifier of the enum's type.
fn build_into_element(&self, ty_ident: &Ident) -> Result<TokenStream> {
match self {
Self::XmlNameSwitched(inner) => inner.build_into_element(ty_ident),
Self::XmlAttributeSwitched(inner) => inner.build_into_element(ty_ident),
Self::Dynamic(inner) => inner.build_into_element(ty_ident),
}
}
}
/// Represent an enum.
#[derive(Debug)]
pub(crate) struct EnumDef {
/// The `validate` value, if set on the enum.
///
/// This is called after the enum has been otherwise parsed successfully
/// with the enum value as mutable reference as only argument. It is
/// expected to return `Result<(), Error>`, the `Err(..)` variant of which
/// is forwarded correctly.
validate: Option<Path>,
/// The `prepare` value, if set on the enum.
///
/// This is called before the enum will be converted back into an XML
/// element with the enum value as mutable reference as only argument.
prepare: Option<Path>,
/// The actual contents of the enum.
inner: EnumInner,
/// The `debug` flag, if set on the enum.
#[cfg_attr(not(feature = "debug"), allow(dead_code))]
debug: Flag,
}
impl EnumDef {
/// Construct a new enum from its `#[xml(..)]` attribute and the variants.
fn new<'x, I: Iterator<Item = &'x Variant>>(
meta: Option<XmlCompoundMeta>,
input: I,
) -> Result<Self> {
let meta = match meta {
Some(v) => v,
None => XmlCompoundMeta::empty(Span::call_site()),
};
let prepare = meta.prepare;
let validate = meta.validate;
let debug = meta.debug;
if let Flag::Present(fallback) = meta.fallback {
return Err(syn::Error::new(
fallback,
"`fallback` is not allowed on enums (only on enum variants)",
));
}
if let Flag::Present(transparent) = meta.transparent {
return Err(syn::Error::new(
transparent,
"`transparent` cannot be set set on enums (only on enum variants)",
));
}
if let Some(value) = meta.value {
return Err(syn::Error::new_spanned(
value,
"`value` is not allowed on enums",
));
}
let namespace = match meta.namespace {
None => {
// no namespace -> must be dynamic variant. ensure the other
// stuff isn't there.
if let Some(name) = meta.name {
return Err(syn::Error::new_spanned(
name,
"`name` cannot be set without `namespace` or on dynamic enums",
));
}
if let Flag::Present(exhaustive) = meta.exhaustive {
return Err(syn::Error::new(
exhaustive,
"`transparent` cannot be set set on dynamic enums",
));
}
if let Some(normalize_with) = meta.normalize_with {
return Err(syn::Error::new_spanned(
normalize_with,
"`normalize_with` option is only allowed on attribute value switched enums",
));
};
return Ok(Self {
validate,
prepare,
debug,
inner: EnumInner::Dynamic(Dynamic::new(input)?),
});
}
Some(NamespaceRef::Static(ns)) => ns,
Some(NamespaceRef::Dyn(ns)) => {
return Err(syn::Error::new_spanned(
ns,
"`#[xml(namespace = dyn)]` cannot be used on enums.",
))
}
Some(NamespaceRef::Super(ns)) => {
return Err(syn::Error::new_spanned(
ns,
"`#[xml(namespace = super)]` cannot be used on enums.",
))
}
};
let exhaustive = meta.exhaustive;
let name = match meta.name {
None => {
if let Some(normalize_with) = meta.normalize_with {
return Err(syn::Error::new_spanned(
normalize_with,
"`normalize_with` option is only allowed on attribute value switched enums",
));
};
return Ok(Self {
prepare,
validate,
debug,
inner: EnumInner::XmlNameSwitched(XmlNameSwitched::new(
exhaustive, namespace, input,
)?),
});
}
Some(name) => name,
};
let Some(attribute_name) = meta.attribute else {
return Err(syn::Error::new(
meta.span,
"`attribute` option with the name of the XML attribute to match is required for enums matching on attribute value",
));
};
Ok(Self {
prepare,
validate,
debug,
inner: EnumInner::XmlAttributeSwitched(XmlAttributeSwitched::new(
exhaustive,
namespace,
name.into(),
attribute_name,
meta.normalize_with,
input,
)?),
})
}
}
impl ItemDef for EnumDef {
fn build_try_from_element(
&self,
item_name: &ParentRef,
residual: &Ident,
) -> Result<TokenStream> {
let Some(ty_ident) = item_name.try_as_ident() else {
panic!("EnumDef::build_try_from_element cannot be called with non-ident ParentRef");
};
let validate = build_validate(self.validate.as_ref());
let result = self
.inner
.build_try_from_element(ty_ident, validate, residual)?;
#[cfg(feature = "debug")]
if self.debug.is_set() {
println!("{}", result);
}
Ok(result)
}
fn build_into_element(
&self,
item_name: &ParentRef,
value_ident: &Ident,
) -> Result<TokenStream> {
let Some(ty_ident) = item_name.try_as_ident() else {
panic!("EnumDef::build_try_from_element cannot be called with non-ident ParentRef");
};
let prepare = build_prepare(self.prepare.as_ref(), value_ident);
let matchers = self.inner.build_into_element(ty_ident)?;
let result = quote! {
{
#prepare
match #value_ident {
#matchers
}
}
};
#[cfg(feature = "debug")]
if self.debug.is_set() {
println!("{}", result);
}
Ok(result)
}
fn build_dyn_namespace(&self) -> Result<TokenStream> {
Err(Error::new(
Span::call_site(),
"DynNamespace cannot be derived on enums (yet)",
))
}
}
pub(crate) fn parse_enum(item: &syn::ItemEnum) -> Result<Box<dyn ItemDef>> {
let mut meta = XmlCompoundMeta::try_parse_from_attributes(&item.attrs)?;
let wrapped_with = meta.as_mut().map(|x| (x.wrapped_with.take(), x.span));
let mut def = Box::new(EnumDef::new(meta, item.variants.iter())?) as Box<dyn ItemDef>;
if let Some((Some(wrapped_with), span)) = wrapped_with {
def = crate::wrapped::wrap(&span, wrapped_with, &item.ident, def)?;
}
Ok(def)
}