mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
429 lines
15 KiB
Rust
429 lines
15 KiB
Rust
/*!
|
|
# Types to represent compounds
|
|
|
|
A [`Compound`] is either an enum variant or a struct. These types are used by
|
|
[`crate::enums`] and [`crate::structs`], as well as for extracted child fields
|
|
in order to build the code to convert to/from XML nodes.
|
|
*/
|
|
use proc_macro2::{Span, TokenStream};
|
|
|
|
use quote::quote;
|
|
use syn::{spanned::Spanned, *};
|
|
|
|
use crate::error_message::ParentRef;
|
|
use crate::field::{FieldDef, FieldParsePart};
|
|
|
|
/// Expand the given identifier to resolve as member of the
|
|
/// `UnknownChildPolicy` enum. If the identifier is absent, invoke
|
|
/// `UnknownChildPolicy::default()` instead.
|
|
fn default_on_unknown_child(p: Option<Ident>) -> Expr {
|
|
match p {
|
|
Some(v) => Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: Path {
|
|
leading_colon: Some(token::PathSep {
|
|
spans: [Span::call_site(), Span::call_site()],
|
|
}),
|
|
segments: [
|
|
PathSegment::from(Ident::new("xso", Span::call_site())),
|
|
PathSegment::from(Ident::new("UnknownChildPolicy", Span::call_site())),
|
|
PathSegment::from(v),
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
},
|
|
}),
|
|
None => syn::parse2(quote! { ::xso::UnknownChildPolicy::default() })
|
|
.expect("failed to construct default unknown child policy"),
|
|
}
|
|
}
|
|
|
|
/// Expand the given identifier to resolve as member of the
|
|
/// `UnknownAttributePolicy` enum. If the identifier is absent, invoke
|
|
/// `UnknownAttributePolicy::default()` instead.
|
|
fn default_on_unknown_attribute(p: Option<Ident>) -> Expr {
|
|
match p {
|
|
Some(v) => Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: Path {
|
|
leading_colon: Some(token::PathSep {
|
|
spans: [Span::call_site(), Span::call_site()],
|
|
}),
|
|
segments: [
|
|
PathSegment::from(Ident::new("xso", Span::call_site())),
|
|
PathSegment::from(Ident::new("UnknownAttributePolicy", Span::call_site())),
|
|
PathSegment::from(v),
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
},
|
|
}),
|
|
None => syn::parse2(quote! { ::xso::UnknownAttributePolicy::default() })
|
|
.expect("failed to construct default unknown attribute policy"),
|
|
}
|
|
}
|
|
|
|
/// An struct or enum variant's contents.
|
|
///
|
|
/// This struct is used to generate the parsing/serialisation loop, but most
|
|
/// notably it is *not* responsible for matching the incoming element; that
|
|
/// is the responsibility of the caller of the corresponding functions.
|
|
#[derive(Debug)]
|
|
pub(crate) struct Compound {
|
|
/// The fields, in declaration order.
|
|
fields: Vec<FieldDef>,
|
|
|
|
/// Information about the field annotated with `#[xml(namespace)]`, if
|
|
/// any.
|
|
namespace_field: Option<(Span, Type, Member)>,
|
|
|
|
/// Member of the `UnknownChildPolicy` enum to use when handling unknown
|
|
/// children.
|
|
on_unknown_child: Expr,
|
|
|
|
/// Member of the `UnknownAttributePolicy` enum to use when handling
|
|
/// unknown attributes.
|
|
on_unknown_attribute: Expr,
|
|
}
|
|
|
|
impl Compound {
|
|
/// Construct a new compound from an iterator of [`FieldDef`] structs.
|
|
pub(crate) fn new<T: Iterator<Item = Result<FieldDef>>>(
|
|
on_unknown_child: Option<Ident>,
|
|
on_unknown_attribute: Option<Ident>,
|
|
input: T,
|
|
) -> Result<Self> {
|
|
let mut fields = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
|
|
let mut text_field: Option<Span> = None;
|
|
let mut namespace_field: Option<(Span, Type, Member)> = None;
|
|
let mut collect_wildcard_field: Option<Span> = None;
|
|
for field in input {
|
|
let field = field?;
|
|
|
|
if let Some(ty) = field.namespace_field_type() {
|
|
if namespace_field.is_some() {
|
|
return Err(Error::new_spanned(
|
|
field.ident,
|
|
"only one #[xml(namespace)] field is allowed",
|
|
));
|
|
}
|
|
namespace_field = Some((field.span, ty.clone(), field.ident.clone()));
|
|
};
|
|
|
|
if field.kind.is_text() {
|
|
if text_field.is_some() {
|
|
return Err(Error::new_spanned(
|
|
field.ident,
|
|
"only one #[xml(text)] field is allowed",
|
|
));
|
|
}
|
|
text_field = Some(field.ident.span());
|
|
}
|
|
|
|
if field.kind.is_child_wildcard() {
|
|
if collect_wildcard_field.is_some() {
|
|
return Err(Error::new_spanned(
|
|
field.ident,
|
|
"only one #[xml(elements)] field without namespace/name selector is allowed",
|
|
));
|
|
}
|
|
collect_wildcard_field = Some(field.ident.span());
|
|
}
|
|
|
|
fields.push(field);
|
|
}
|
|
|
|
Ok(Self {
|
|
fields,
|
|
namespace_field,
|
|
on_unknown_child: default_on_unknown_child(on_unknown_child),
|
|
on_unknown_attribute: default_on_unknown_attribute(on_unknown_attribute),
|
|
})
|
|
}
|
|
|
|
/// Construct a compound from [`syn::Fields`].
|
|
///
|
|
/// This a convenience wrapper around [`Self::new`], converting the
|
|
/// [`syn::Field`] structs to [`FieldDef`].
|
|
pub(crate) fn from_fields(
|
|
on_unknown_child: Option<Ident>,
|
|
on_unknown_attribute: Option<Ident>,
|
|
fields: &Fields,
|
|
) -> Result<Self> {
|
|
Self::new(
|
|
on_unknown_child,
|
|
on_unknown_attribute,
|
|
fields.iter().enumerate().map(|(i, field)| {
|
|
FieldDef::from_field(field, i.try_into().expect("too many fields"))
|
|
}),
|
|
)
|
|
}
|
|
|
|
/// Obtain references to the information about the
|
|
/// `#[xml(namespace)]`-annotated field, if this compound has one.
|
|
pub(crate) fn namespace_field(&self) -> Option<(Span, &Type, &Member)> {
|
|
self.namespace_field.as_ref().map(|(a, b, c)| (*a, b, c))
|
|
}
|
|
|
|
/// Number of fields.
|
|
pub(crate) fn field_count(&self) -> usize {
|
|
self.fields.len()
|
|
}
|
|
|
|
/// Construct a token stream which contains an expression which parses
|
|
/// the contents `minidom::Element` at `residual` into the compound.
|
|
///
|
|
/// - `container_name` is used both for error messages and to construct
|
|
/// the resulting compound. If it directly refers to a path, that path
|
|
/// is used as constructor. Otherwise, the compound is constructed as
|
|
/// tuple.
|
|
///
|
|
/// - `container_namespace_expr` must be an expression which evaluates to
|
|
/// the parsed namespace of the parent element. If
|
|
/// [`Self::namespace_field`] is not `None`, this must be an expression
|
|
/// which can be moved out of and which matches the type, as that
|
|
/// expression will be used to initialize that field.
|
|
///
|
|
/// In all other cases, this expression only needs to be usable in a
|
|
/// `std::cmp::PartialEq<str>` context.
|
|
///
|
|
/// - `residual` must be the identifier at which the element is found.
|
|
/// - `forgive_attributes` must be a (potentially empty) slice of XML
|
|
/// attribute names to ignore during the unknown attribute check. This
|
|
/// can be used to ignore attributes which have been used in element
|
|
/// matching (e.g. enum discriminators).
|
|
pub(crate) fn build_try_from_element(
|
|
&self,
|
|
container_name: &ParentRef,
|
|
container_namespace_expr: &Expr,
|
|
residual: &Ident,
|
|
forgive_attributes: &[&str],
|
|
) -> Result<TokenStream> {
|
|
let readable_name = container_name.to_string();
|
|
let on_unknown_child = &self.on_unknown_child;
|
|
let on_unknown_attribute = &self.on_unknown_attribute;
|
|
|
|
let mut init = quote! {};
|
|
let mut tupinit = quote! {};
|
|
let mut attrcheck = quote! {
|
|
#(
|
|
if key == #forgive_attributes {
|
|
continue;
|
|
}
|
|
)*
|
|
};
|
|
let mut tempinit = quote! {};
|
|
let mut childiter = quote! {};
|
|
let mut childfallback = quote! {
|
|
#on_unknown_child.trigger(concat!("Unknown child in ", #readable_name, "."))?;
|
|
};
|
|
let mut had_fallback: bool = false;
|
|
|
|
for field in self.fields.iter() {
|
|
let ident = field.ident.clone();
|
|
let FieldParsePart {
|
|
tempinit: field_tempinit,
|
|
childiter: field_childiter,
|
|
attrcheck: field_attrcheck,
|
|
value,
|
|
childfallback: field_childfallback,
|
|
} = field.build_try_from_element(container_name, container_namespace_expr)?;
|
|
|
|
attrcheck = quote! { #attrcheck #field_attrcheck };
|
|
tempinit = quote! { #tempinit #field_tempinit };
|
|
childiter = quote! { #childiter #field_childiter };
|
|
|
|
if let Some(field_childfallback) = field_childfallback {
|
|
if had_fallback {
|
|
panic!(
|
|
"internal error: multiple fields attempting to collect all child elements."
|
|
);
|
|
}
|
|
had_fallback = true;
|
|
childfallback = field_childfallback;
|
|
}
|
|
|
|
init = quote! {
|
|
#init
|
|
#ident: #value,
|
|
};
|
|
tupinit = quote! {
|
|
#tupinit
|
|
#value,
|
|
};
|
|
}
|
|
|
|
let construct = match container_name {
|
|
ParentRef::Named(ref path) => quote! {
|
|
#path {
|
|
#init
|
|
}
|
|
},
|
|
ParentRef::Unnamed { .. } | ParentRef::Wrapper { .. } => quote! {
|
|
( #tupinit )
|
|
},
|
|
};
|
|
|
|
Ok(quote! {
|
|
{
|
|
for (key, _) in #residual.attrs() {
|
|
#attrcheck
|
|
#on_unknown_attribute.trigger(concat!("Unknown attribute in ", #readable_name, "."))?;
|
|
}
|
|
|
|
#tempinit
|
|
|
|
for mut residual in #residual.take_contents_as_children() {
|
|
#childiter
|
|
#childfallback
|
|
}
|
|
|
|
#construct
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Construct an expression consuming the `builder` and returning it,
|
|
/// filled with the data from the compound.
|
|
///
|
|
/// - `container_name` is used for error messages.
|
|
///
|
|
/// - `container_namespace_expr` must be an expression which evaluates to
|
|
/// the parsed namespace of the parent element. This needs to implement
|
|
/// `::xso::DynNamespaceEnum` if any fields use
|
|
/// `#[xml(namespace = super)]`.
|
|
///
|
|
/// - `builder` must be an expression which can be moved out from and
|
|
/// which is the `minidom::Builder` into which the element should be
|
|
/// constructed.
|
|
///
|
|
/// - `access_field`: Accessor function for fields within the compound.
|
|
/// That function will be called for each field and the expression is
|
|
/// used exactly once to obtain the data of the field inside the
|
|
/// compound.
|
|
///
|
|
/// This indirection is necessary to be able to handle both structs
|
|
/// (where fields are accessed as struct members) and enumeration
|
|
/// variants (where fields are bound to local names).
|
|
///
|
|
/// Note that the field referenced by [`Self::namespace_field`] is not
|
|
/// accessed by this function.
|
|
pub(crate) fn build_into_element(
|
|
&self,
|
|
container_name: &ParentRef,
|
|
container_namespace_expr: &Expr,
|
|
builder: &Ident,
|
|
mut access_field: impl FnMut(Member) -> Expr,
|
|
) -> Result<TokenStream> {
|
|
let mut build = quote! {};
|
|
for field in self.fields.iter() {
|
|
let field_build = field.build_into_element(
|
|
container_name,
|
|
container_namespace_expr,
|
|
&mut access_field,
|
|
)?;
|
|
build = quote! {
|
|
#build
|
|
builder = #field_build;
|
|
};
|
|
}
|
|
|
|
Ok(quote! {
|
|
{
|
|
let mut builder = #builder;
|
|
#build
|
|
builder
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Return an iterator which returns the [`syn::Member`] structs to access
|
|
/// the compound's fields in declaration order.
|
|
///
|
|
/// For tuple-like compounds that's basically counting up from 0, for
|
|
/// named compounds this emits the field names in declaration order.
|
|
pub(crate) fn iter_members(&self) -> Box<dyn Iterator<Item = Member> + '_> {
|
|
Box::new(self.fields.iter().map(|x| x.ident.clone().into()))
|
|
}
|
|
|
|
/// If and only if this compound has exactly one field, return a reference
|
|
/// to that field's type.
|
|
pub(crate) fn single_type(&self) -> Option<&Type> {
|
|
if self.fields.len() != 1 {
|
|
None
|
|
} else {
|
|
Some(&self.fields[0].ty)
|
|
}
|
|
}
|
|
|
|
/// Return a [`DynCompound`] refering to `self`, if and only if this
|
|
/// compound has a valid `#[xml(namespace)]` field.
|
|
///
|
|
/// The function and type name refer to this being a precondition for a
|
|
/// valid `namespace = dyn` struct or enum variant.
|
|
pub(crate) fn as_dyn(&self) -> Option<DynCompound<'_>> {
|
|
let (_, ty, member) = self.namespace_field.as_ref()?;
|
|
Some(DynCompound {
|
|
namespace_ty: ty,
|
|
namespace_member: member,
|
|
fields: &self.fields,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Reference to a [`Compound`] which has proven that it has a
|
|
/// `#[xml(namespace)]` field.
|
|
///
|
|
/// This simplifies some checks here and there.
|
|
pub(crate) struct DynCompound<'x> {
|
|
/// The type of the `#[xml(namespace)]` field.
|
|
namespace_ty: &'x Type,
|
|
|
|
/// The member referring to the `#[xml(namespace)]` field.
|
|
namespace_member: &'x Member,
|
|
|
|
/// The fields of the compound.
|
|
fields: &'x [FieldDef],
|
|
}
|
|
|
|
impl<'x> DynCompound<'x> {
|
|
/// Return a reference to the [`Type`] of the field annotated with
|
|
/// `#[xml(namespace)]`.
|
|
pub(crate) fn namespace_ty(&self) -> &'x Type {
|
|
self.namespace_ty
|
|
}
|
|
|
|
/// Build the implementation of
|
|
/// `DynNamespace::namespace(&self) -> &Self::Namespace`.
|
|
pub(crate) fn build_get_namespace(
|
|
&self,
|
|
mut access_field: impl FnMut(Member) -> Expr,
|
|
) -> Result<TokenStream> {
|
|
let member = access_field(self.namespace_member.clone());
|
|
Ok(quote! {
|
|
&#member
|
|
})
|
|
}
|
|
|
|
/// Build the implementation of
|
|
/// `DynNamespace::set_namespace<T: Into<Self::Namespace>>(&mut self, ns: T)`.
|
|
pub(crate) fn build_set_namespace(
|
|
&self,
|
|
input: &Ident,
|
|
mut access_field: impl FnMut(Member) -> Expr,
|
|
) -> Result<TokenStream> {
|
|
let member = access_field(self.namespace_member.clone());
|
|
let mut field_impls = quote! {};
|
|
for field in self.fields {
|
|
let field_impl = field.build_set_namespace(input, &mut access_field);
|
|
field_impls.extend(field_impl);
|
|
}
|
|
Ok(quote! {
|
|
#field_impls
|
|
#member = #input;
|
|
})
|
|
}
|
|
}
|