mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
1062 lines
34 KiB
Rust
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)
|
|
}
|