mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
836 lines
29 KiB
Rust
836 lines
29 KiB
Rust
/*!
|
|
# Processing of struct declarations
|
|
|
|
This module contains the main code for implementing the derive macros from
|
|
this crate on `struct` items.
|
|
|
|
It is thus the counterpart to [`crate::enums`].
|
|
*/
|
|
use proc_macro2::{Span, TokenStream};
|
|
|
|
use quote::{quote, quote_spanned};
|
|
use syn::{spanned::Spanned, *};
|
|
|
|
use crate::common::{build_prepare, build_validate, ItemDef};
|
|
use crate::compound::{Compound, DynCompound};
|
|
use crate::error_message::ParentRef;
|
|
use crate::meta::{
|
|
Flag, Name, NameRef, NamespaceRef, NodeFilterMeta, StaticNamespace, XmlCompoundMeta,
|
|
};
|
|
|
|
/// A XML namespace as declared on a struct.
|
|
#[derive(Debug)]
|
|
pub(crate) enum StructNamespace {
|
|
/// The namespace is a static string.
|
|
Static(
|
|
/// The namespace as [`Path`] pointing at the static string.
|
|
StaticNamespace,
|
|
),
|
|
|
|
/// Instead of a fixed namespace, the namespace is dynamic. The allowed
|
|
/// values are determined by a
|
|
/// [`NamespaceField`][`crate::field::namespace::NamespaceField`]
|
|
/// (declared using `#[xml(namespace)]`).
|
|
Dyn {
|
|
/// The `dyn` token from the `#[xml(namespace = dyn)]` meta.
|
|
#[allow(dead_code)]
|
|
dyn_tok: Token![dyn],
|
|
|
|
/// The type of the namespace field.
|
|
ty: Type,
|
|
|
|
/// The member of the namespace field.
|
|
member: Member,
|
|
},
|
|
}
|
|
|
|
/// Represent a selector for element-transparent structs.
|
|
///
|
|
/// See also [`StructInner::Element`].
|
|
#[derive(Debug)]
|
|
pub(crate) enum ElementSelector {
|
|
/// Any element will be accepted.
|
|
///
|
|
/// Corresponds to `#[xml(element)]`.
|
|
Any,
|
|
|
|
/// The element will be matched by XML name only.
|
|
///
|
|
/// Corresponds to `#[xml(element(name = ..))]`.
|
|
ByName(Name),
|
|
|
|
/// The element will be matched by XML namespace only.
|
|
///
|
|
/// Corresponds to `#[xml(element(namespace = ..))]`.
|
|
ByNamespace(StaticNamespace),
|
|
|
|
/// The element will be matched by XML namespace and name..
|
|
///
|
|
/// Corresponds to `#[xml(element(namespace = .., name = ..))]`.
|
|
Qualified {
|
|
/// The XML namespace to match.
|
|
namespace: StaticNamespace,
|
|
|
|
/// The XML name to match.
|
|
name: Name,
|
|
},
|
|
}
|
|
|
|
impl TryFrom<NodeFilterMeta> for ElementSelector {
|
|
type Error = Error;
|
|
|
|
fn try_from(other: NodeFilterMeta) -> Result<Self> {
|
|
let namespace = match other.namespace {
|
|
None => None,
|
|
Some(NamespaceRef::Static(ns)) => Some(ns),
|
|
Some(NamespaceRef::Dyn(ns)) => return Err(Error::new_spanned(
|
|
ns,
|
|
"namespace = dyn cannot be used with element-transparent structs or enum variants."
|
|
)),
|
|
Some(NamespaceRef::Super(ns)) => return Err(Error::new_spanned(
|
|
ns,
|
|
"namespace = super cannot be used with element-transparent structs or enum variants."
|
|
)),
|
|
};
|
|
let name = other.name.map(|x| Name::from(x));
|
|
|
|
match (namespace, name) {
|
|
(Some(namespace), Some(name)) => Ok(Self::Qualified { namespace, name }),
|
|
(Some(namespace), None) => Ok(Self::ByNamespace(namespace)),
|
|
(None, Some(name)) => Ok(Self::ByName(name)),
|
|
(None, None) => Ok(Self::Any),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ElementSelector {
|
|
/// Construct a token stream evaluating to bool.
|
|
///
|
|
/// If the `minidom::Element` in `residual` matches the selector, the
|
|
/// token stream will evaluate to true. Otherwise, it will evaluate to
|
|
/// false.
|
|
pub(crate) fn build_test(&self, residual: &Ident) -> TokenStream {
|
|
match self {
|
|
Self::Any => quote! { true },
|
|
Self::ByName(name) => quote! {
|
|
#residual.name() == #name
|
|
},
|
|
Self::ByNamespace(ns) => quote! {
|
|
#residual.ns() == #ns
|
|
},
|
|
Self::Qualified { namespace, name } => quote! {
|
|
#residual.is(#name, #namespace)
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The inner parts of the struct.
|
|
///
|
|
/// This contains all data necessary for the matching logic, but does not
|
|
/// include validation/preparation of the data. The latter is handled by
|
|
/// [`StructDef`].
|
|
#[derive(Debug)]
|
|
pub(crate) enum StructInner {
|
|
/// Single-field tuple-like struct declared with `#[xml(transparent)]`.
|
|
///
|
|
/// Transparent struct delegate all parsing and serialising to their
|
|
/// single field, which is why they do not need to store a lot of
|
|
/// information and come with extra restrictions, such as:
|
|
///
|
|
/// - no XML namespace can be declared (it is determined by inner type)
|
|
/// - no XML name can be declared (it is determined by inner type)
|
|
/// - the fields must be unnamed
|
|
/// - there must be only exactly one field
|
|
/// - that field has no `#[xml]` attribute
|
|
Transparent {
|
|
/// Type of the only unnamed field.
|
|
ty: Type,
|
|
},
|
|
|
|
/// Single-field tuple-like struct declared with `#[xml(element)]`.
|
|
///
|
|
/// Element-transparent structs take the incoming XML element as-is, and
|
|
/// re-serialise it as-is.
|
|
Element {
|
|
/// Determines the set of acceptable XML elements. Elements which do
|
|
/// not match the selector will not be parsed.
|
|
selector: ElementSelector,
|
|
},
|
|
|
|
/// A compound of fields, *not* declared as transparent.
|
|
///
|
|
/// This can be a unit, tuple-like, or named struct.
|
|
Compound {
|
|
/// The XML namespace to match the struct against.
|
|
namespace: StructNamespace,
|
|
|
|
/// The XML name to match the struct against.
|
|
name: Name,
|
|
|
|
/// The contents of the struct.
|
|
inner: Compound,
|
|
},
|
|
}
|
|
|
|
impl StructInner {
|
|
/// Process the `meta` and `fields` into a [`StructInner`].
|
|
///
|
|
/// The `meta` must be "blank" except for the `transparent`, `namespace`
|
|
/// and `name` fields. If any other field has a non-`None` / non-`Absent`
|
|
/// value, this function panics!
|
|
pub(crate) fn new(meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
|
|
// These must be taken out by the caller.
|
|
assert!(!meta.exhaustive.is_set());
|
|
assert!(meta.validate.is_none());
|
|
assert!(meta.prepare.is_none());
|
|
assert!(meta.normalize_with.is_none());
|
|
assert!(!meta.debug.is_set());
|
|
assert!(!meta.fallback.is_set());
|
|
assert!(meta.attribute.is_none());
|
|
assert!(meta.value.is_none());
|
|
|
|
if let Some(element) = meta.element {
|
|
if let Flag::Present(transparent) = meta.transparent {
|
|
return Err(Error::new(
|
|
transparent,
|
|
"transparent option conflicts with element option. pick one or the other.",
|
|
));
|
|
}
|
|
if let Some(namespace) = meta.namespace {
|
|
return Err(Error::new_spanned(
|
|
namespace,
|
|
"namespace option not allowed on element-transparent structs or enum variants",
|
|
));
|
|
}
|
|
if let Some(name) = meta.name {
|
|
return Err(Error::new_spanned(
|
|
name,
|
|
"name option not allowed on element-transparent structs or enum variants",
|
|
));
|
|
}
|
|
|
|
Self::new_element(element, fields)
|
|
} else if let Flag::Present(_) = meta.transparent {
|
|
if let Some(namespace) = meta.namespace {
|
|
return Err(Error::new_spanned(
|
|
namespace,
|
|
"namespace option not allowed on transparent structs or enum variants",
|
|
));
|
|
}
|
|
if let Some(name) = meta.name {
|
|
return Err(Error::new_spanned(
|
|
name,
|
|
"name option not allowed on transparent structs or enum variants",
|
|
));
|
|
}
|
|
|
|
Self::new_transparent(fields)
|
|
} else {
|
|
let Some(namespace) = meta.namespace else {
|
|
return Err(Error::new(
|
|
meta.span,
|
|
"`namespace` option is required on non-transparent structs or enum variants",
|
|
));
|
|
};
|
|
|
|
let Some(name) = meta.name else {
|
|
return Err(Error::new(
|
|
meta.span,
|
|
"`name` option is required on non-transparent structs or enum variants",
|
|
));
|
|
};
|
|
|
|
Self::new_compound(
|
|
namespace,
|
|
name,
|
|
meta.on_unknown_child,
|
|
meta.on_unknown_attribute,
|
|
fields,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Construct a new transparent struct with the given fields.
|
|
///
|
|
/// This function ensures that only a single, unnamed field is inside the
|
|
/// struct and causes a compile-time error otherwise.
|
|
fn new_transparent(fields: &Fields) -> Result<Self> {
|
|
let field = match fields {
|
|
Fields::Unit => {
|
|
return Err(Error::new(
|
|
Span::call_site(),
|
|
"transparent structs or enum variants must have exactly one field",
|
|
))
|
|
}
|
|
Fields::Named(_) => {
|
|
return Err(Error::new(
|
|
Span::call_site(),
|
|
"transparent structs or enum variants must be tuple-like",
|
|
))
|
|
}
|
|
Fields::Unnamed(fields) => {
|
|
if fields.unnamed.len() == 0 {
|
|
return Err(Error::new(
|
|
Span::call_site(),
|
|
"transparent structs or enum variants must have exactly one field",
|
|
));
|
|
} else if fields.unnamed.len() > 1 {
|
|
return Err(Error::new_spanned(
|
|
&fields.unnamed[1],
|
|
"transparent structs or enum variants must have exactly one field",
|
|
));
|
|
}
|
|
&fields.unnamed[0]
|
|
}
|
|
};
|
|
|
|
for attr in field.attrs.iter() {
|
|
if attr.path().is_ident("xml") {
|
|
return Err(Error::new_spanned(
|
|
attr.path(),
|
|
"the field inside a #[xml(transparent)] struct or enum variant cannot have an #[xml(..)] attribute."
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(Self::Transparent {
|
|
ty: field.ty.clone(),
|
|
})
|
|
}
|
|
|
|
/// Construct a new element-transparent struct with the given fields.
|
|
///
|
|
/// This function ensures that only a single, unnamed field is inside the
|
|
/// struct and causes a compile-time error otherwise.
|
|
fn new_element(node_filter: NodeFilterMeta, fields: &Fields) -> Result<Self> {
|
|
let field = match fields {
|
|
Fields::Unit => {
|
|
return Err(Error::new(
|
|
Span::call_site(),
|
|
"transparent structs or enum variants must have exactly one field",
|
|
))
|
|
}
|
|
Fields::Named(_) => {
|
|
return Err(Error::new(
|
|
Span::call_site(),
|
|
"transparent structs or enum variants must be tuple-like",
|
|
))
|
|
}
|
|
Fields::Unnamed(fields) => {
|
|
if fields.unnamed.len() == 0 {
|
|
return Err(Error::new(
|
|
Span::call_site(),
|
|
"transparent structs or enum variants must have exactly one field",
|
|
));
|
|
} else if fields.unnamed.len() > 1 {
|
|
return Err(Error::new_spanned(
|
|
&fields.unnamed[1],
|
|
"transparent structs or enum variants must have exactly one field",
|
|
));
|
|
}
|
|
&fields.unnamed[0]
|
|
}
|
|
};
|
|
|
|
for attr in field.attrs.iter() {
|
|
if attr.path().is_ident("xml") {
|
|
return Err(Error::new_spanned(
|
|
attr.path(),
|
|
"the field inside a #[xml(transparent)] struct or enum variant cannot have an #[xml(..)] attribute."
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(Self::Element {
|
|
selector: node_filter.try_into()?,
|
|
})
|
|
}
|
|
|
|
/// Construct a new compound-based struct with the given namespace, name
|
|
/// and fields.
|
|
fn new_compound(
|
|
namespace: NamespaceRef,
|
|
name: NameRef,
|
|
on_unknown_child: Option<Ident>,
|
|
on_unknown_attribute: Option<Ident>,
|
|
fields: &Fields,
|
|
) -> Result<Self> {
|
|
let inner = Compound::from_fields(on_unknown_child, on_unknown_attribute, fields)?;
|
|
let namespace_field = inner.namespace_field();
|
|
|
|
let namespace = match namespace {
|
|
NamespaceRef::Static(namespace) => {
|
|
if let Some((span, ..)) = namespace_field {
|
|
return Err(Error::new(
|
|
span,
|
|
"struct or enum variant must be declared with #[xml(namespace = dyn, ..)] to use a #[xml(namespace)] field."
|
|
));
|
|
}
|
|
StructNamespace::Static(namespace)
|
|
}
|
|
NamespaceRef::Dyn(namespace) => {
|
|
if let Some((_, ty, member)) = namespace_field {
|
|
StructNamespace::Dyn {
|
|
dyn_tok: namespace,
|
|
ty: ty.clone(),
|
|
member: member.clone(),
|
|
}
|
|
} else {
|
|
return Err(Error::new_spanned(
|
|
namespace,
|
|
"enum variant or struct declared with #[xml(namespace = dyn)] must have a field annotated with #[xml(namespace)]"
|
|
));
|
|
}
|
|
}
|
|
NamespaceRef::Super(ns) => {
|
|
return Err(Error::new_spanned(
|
|
ns,
|
|
"#[xml(namespace = super)] not allowed on enum variant or struct.",
|
|
));
|
|
}
|
|
};
|
|
|
|
Ok(Self::Compound {
|
|
namespace,
|
|
name: name.into(),
|
|
inner,
|
|
})
|
|
}
|
|
|
|
/// Construct an expression which consumes `residual` and evaluates to
|
|
/// `Result<T, Element>`.
|
|
///
|
|
/// - `struct_name` may contain either the path necessary to construct an
|
|
/// instance of the struct or a nested parent ref. In the latter case,
|
|
/// the struct is constructed as tuple instead of a struct.
|
|
///
|
|
/// - `residual` must be the identifier of the `minidom::Element` to
|
|
/// process.
|
|
///
|
|
/// If the element does not match the selectors of this struct, it is
|
|
/// returned in the `Err` variant for further probing.
|
|
pub(crate) fn build_try_from_element(
|
|
&self,
|
|
struct_name: &ParentRef,
|
|
residual: &Ident,
|
|
) -> Result<TokenStream> {
|
|
match self {
|
|
Self::Transparent { ty } => {
|
|
let cons = match struct_name {
|
|
ParentRef::Named(path) => quote! { #path },
|
|
ParentRef::Unnamed { .. } | ParentRef::Wrapper { .. } => quote! {},
|
|
};
|
|
let ty_from_tree = quote_spanned! {ty.span()=> <#ty as ::xso::FromXml>::from_tree};
|
|
Ok(quote! {
|
|
match #ty_from_tree(#residual) {
|
|
Ok(v) => Ok(#cons (v)),
|
|
Err(::xso::error::Error::TypeMismatch(_, _, #residual)) => Err(#residual),
|
|
Err(other) => return Err(other),
|
|
}
|
|
})
|
|
}
|
|
Self::Element { selector } => {
|
|
let test = selector.build_test(residual);
|
|
let cons = match struct_name {
|
|
ParentRef::Named(path) => quote! { #path },
|
|
ParentRef::Unnamed { .. } | ParentRef::Wrapper { .. } => quote! {},
|
|
};
|
|
Ok(quote! {
|
|
if #test {
|
|
Ok(#cons ( #residual ))
|
|
} else {
|
|
Err(#residual)
|
|
}
|
|
})
|
|
}
|
|
Self::Compound {
|
|
namespace,
|
|
name: xml_name,
|
|
inner,
|
|
} => {
|
|
let namespace_tempname = Ident::new("__struct_namespace", Span::call_site());
|
|
let namespace_expr = Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: namespace_tempname.clone().into(),
|
|
});
|
|
|
|
let body =
|
|
inner.build_try_from_element(struct_name, &namespace_expr, residual, &[])?;
|
|
|
|
match namespace {
|
|
StructNamespace::Dyn { ty, .. } => {
|
|
let ty_from_xml_text = quote_spanned! {ty.span()=> <#ty as ::xso::DynNamespaceEnum>::from_xml_text};
|
|
Ok(quote! {
|
|
match #ty_from_xml_text(&#residual.ns()) {
|
|
Ok(#namespace_tempname) => if #residual.name() == #xml_name {
|
|
Ok(#body)
|
|
} else {
|
|
Err(#residual)
|
|
}
|
|
Err(::xso::error::DynNamespaceError::Invalid) => {
|
|
return Err(::xso::error::Error::ParseError(
|
|
"Invalid namespace"
|
|
));
|
|
}
|
|
Err(::xso::error::DynNamespaceError::Mismatch) => Err(#residual),
|
|
}
|
|
})
|
|
}
|
|
StructNamespace::Static(xml_namespace) => Ok(quote! {
|
|
if #residual.is(#xml_name, #xml_namespace) {
|
|
let #namespace_tempname = #xml_namespace;
|
|
Ok(#body)
|
|
} else {
|
|
Err(#residual)
|
|
}
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Construct an expression which takes the fields as accessed through
|
|
/// `access_field` and converts them into a `minidom::Element`.
|
|
///
|
|
/// - `struct_name` is used primarily for diagnostic messages.s
|
|
///
|
|
/// - `access_field` must be a function which transforms a [`syn::Member`]
|
|
/// referring to a member of the struct to an expression under which the
|
|
/// member can be accessed.
|
|
pub(crate) fn build_into_element(
|
|
&self,
|
|
struct_name: &ParentRef,
|
|
mut access_field: impl FnMut(Member) -> Expr,
|
|
) -> Result<TokenStream> {
|
|
match self {
|
|
Self::Transparent { ty } => {
|
|
let ident = access_field(Member::Unnamed(Index {
|
|
index: 0,
|
|
span: Span::call_site(),
|
|
}));
|
|
let ty_into_tree = quote_spanned! {ty.span()=> <#ty as ::xso::IntoXml>::into_tree};
|
|
Ok(quote! {
|
|
#ty_into_tree(#ident).expect("inner element did not produce any data")
|
|
})
|
|
}
|
|
Self::Element { .. } => {
|
|
let ident = access_field(Member::Unnamed(Index {
|
|
index: 0,
|
|
span: Span::call_site(),
|
|
}));
|
|
Ok(quote! {
|
|
#ident
|
|
})
|
|
}
|
|
Self::Compound {
|
|
namespace,
|
|
name: xml_name,
|
|
inner,
|
|
} => {
|
|
let builder = Ident::new("builder", Span::call_site());
|
|
let (builder_init, namespace_expr) = match namespace {
|
|
StructNamespace::Dyn { ref member, ty, .. } => {
|
|
let expr = access_field(member.clone());
|
|
let ty_into_xml_text = quote_spanned! {ty.span()=> <#ty as ::xso::DynNamespaceEnum>::into_xml_text};
|
|
(
|
|
quote! {
|
|
::xso::exports::minidom::Element::builder(
|
|
#xml_name,
|
|
#ty_into_xml_text(#expr.clone()),
|
|
)
|
|
},
|
|
expr,
|
|
)
|
|
}
|
|
StructNamespace::Static(xml_namespace) => {
|
|
let expr = Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: xml_namespace.clone(),
|
|
});
|
|
(
|
|
quote! {
|
|
::xso::exports::minidom::Element::builder(
|
|
#xml_name,
|
|
#xml_namespace,
|
|
)
|
|
},
|
|
expr,
|
|
)
|
|
}
|
|
};
|
|
|
|
let body = inner.build_into_element(
|
|
struct_name,
|
|
&namespace_expr,
|
|
&builder,
|
|
&mut access_field,
|
|
)?;
|
|
|
|
Ok(quote! {
|
|
{
|
|
let mut #builder = #builder_init;
|
|
let #builder = #body;
|
|
#builder.build()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return an iterator which returns the [`syn::Member`] structs to access
|
|
/// the struct's fields in declaration order.
|
|
pub(crate) fn iter_members(&self) -> Box<dyn Iterator<Item = Member> + '_> {
|
|
match self {
|
|
Self::Transparent { .. } | Self::Element { .. } => Box::new(
|
|
[Member::Unnamed(Index {
|
|
index: 0,
|
|
span: Span::call_site(),
|
|
})]
|
|
.into_iter(),
|
|
),
|
|
Self::Compound { inner, .. } => inner.iter_members(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn as_dyn(&self) -> Option<DynStructInner<'_>> {
|
|
match self {
|
|
Self::Transparent { .. } | Self::Element { .. } => None,
|
|
Self::Compound { ref inner, .. } => inner.as_dyn().map(|x| DynStructInner { inner: x }),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reference to a [`StructInner`] which has proven that the struct is using
|
|
/// namespace = dyn.
|
|
///
|
|
/// This simplifies some checks here and there.
|
|
pub(crate) struct DynStructInner<'x> {
|
|
/// The compound with `namespace = dyn` asserted.
|
|
inner: DynCompound<'x>,
|
|
}
|
|
|
|
impl<'x> DynStructInner<'x> {
|
|
/// Return a reference to the [`Type`] of the field annotated with
|
|
/// `#[xml(namespace)]`.
|
|
pub(crate) fn namespace_ty(&self) -> &'x Type {
|
|
self.inner.namespace_ty()
|
|
}
|
|
|
|
/// Build the implementation of
|
|
/// `DynNamespace::namespace(&self) -> &Self::Namespace`.
|
|
pub(crate) fn build_get_namespace(
|
|
&self,
|
|
access_field: impl FnMut(Member) -> Expr,
|
|
) -> Result<TokenStream> {
|
|
self.inner.build_get_namespace(access_field)
|
|
}
|
|
|
|
/// Build the implementation of
|
|
/// `DynNamespace::set_namespace<T: Into<Self::Namespace>>(&mut self, ns: T)`.
|
|
pub(crate) fn build_set_namespace(
|
|
&self,
|
|
input: &Ident,
|
|
access_field: impl FnMut(Member) -> Expr,
|
|
) -> Result<TokenStream> {
|
|
self.inner.build_set_namespace(input, access_field)
|
|
}
|
|
}
|
|
|
|
/// Create an accessor function for struct fields.
|
|
///
|
|
/// `struct_path` must be the path under which the struct is accessible.
|
|
fn make_accessor(struct_path: Path) -> impl FnMut(Member) -> Expr {
|
|
move |member| {
|
|
Expr::Field(ExprField {
|
|
attrs: Vec::new(),
|
|
dot_token: syn::token::Dot {
|
|
spans: [Span::call_site()],
|
|
},
|
|
base: Box::new(Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: struct_path.clone(),
|
|
})),
|
|
member,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Represent a struct.
|
|
#[derive(Debug)]
|
|
pub(crate) struct StructDef {
|
|
/// The `validate` value, if set on the struct.
|
|
///
|
|
/// This is called after the struct has been otherwise parsed successfully
|
|
/// with the struct 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 struct.
|
|
///
|
|
/// This is called before the struct will be converted back into an XML
|
|
/// element with the struct value as mutable reference as only argument.
|
|
prepare: Option<Path>,
|
|
|
|
/// Structure of the struct.
|
|
inner: StructInner,
|
|
|
|
/// The `debug` flag if set on the struct.
|
|
#[cfg_attr(not(feature = "debug"), allow(dead_code))]
|
|
debug: Flag,
|
|
}
|
|
|
|
impl StructDef {
|
|
/// Construct a new struct from its `#[xml(..)]` attribute and the
|
|
/// fields.
|
|
fn new(mut meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
|
|
if let Flag::Present(fallback) = meta.fallback.take() {
|
|
return Err(syn::Error::new(
|
|
fallback,
|
|
"`fallback` is not allowed on structs",
|
|
));
|
|
}
|
|
|
|
if let Flag::Present(exhaustive) = meta.exhaustive.take() {
|
|
return Err(syn::Error::new(
|
|
exhaustive,
|
|
"`exhaustive` is not allowed on structs",
|
|
));
|
|
}
|
|
|
|
if let Some(attribute) = meta.attribute.take() {
|
|
return Err(syn::Error::new_spanned(
|
|
attribute,
|
|
"`attribute` is not allowed on structs",
|
|
));
|
|
}
|
|
|
|
if let Some(value) = meta.value.take() {
|
|
return Err(syn::Error::new_spanned(
|
|
value,
|
|
"`value` is not allowed on structs",
|
|
));
|
|
}
|
|
|
|
if let Some(normalize_with) = meta.normalize_with.take() {
|
|
return Err(syn::Error::new_spanned(
|
|
normalize_with,
|
|
"`normalize_with` is not allowed on structs",
|
|
));
|
|
}
|
|
|
|
let validate = meta.validate.take();
|
|
let prepare = meta.prepare.take();
|
|
let debug = meta.debug.take();
|
|
|
|
Ok(Self {
|
|
validate,
|
|
prepare,
|
|
debug,
|
|
inner: StructInner::new(meta, fields)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ItemDef for StructDef {
|
|
fn build_try_from_element(
|
|
&self,
|
|
struct_name: &ParentRef,
|
|
residual: &Ident,
|
|
) -> Result<TokenStream> {
|
|
let validate = build_validate(self.validate.as_ref());
|
|
|
|
let try_from_impl = self.inner.build_try_from_element(struct_name, residual)?;
|
|
|
|
let result = quote! {
|
|
{
|
|
let mut result = match #try_from_impl {
|
|
Ok(v) => v,
|
|
Err(residual) => return Err(Self::Error::TypeMismatch("", "", residual)),
|
|
};
|
|
#validate;
|
|
Ok(result)
|
|
}
|
|
};
|
|
#[cfg(feature = "debug")]
|
|
if self.debug.is_set() {
|
|
println!("{}", result);
|
|
}
|
|
Ok(result)
|
|
}
|
|
|
|
fn build_into_element(
|
|
&self,
|
|
struct_name: &ParentRef,
|
|
value_ident: &Ident,
|
|
) -> Result<TokenStream> {
|
|
let prepare = build_prepare(self.prepare.as_ref(), value_ident);
|
|
|
|
let access_field = make_accessor(Path {
|
|
leading_colon: None,
|
|
segments: [PathSegment::from(value_ident.clone())]
|
|
.into_iter()
|
|
.collect(),
|
|
});
|
|
let into_impl = self.inner.build_into_element(struct_name, access_field)?;
|
|
|
|
let result = quote! {
|
|
{
|
|
#prepare
|
|
#into_impl
|
|
}
|
|
};
|
|
#[cfg(feature = "debug")]
|
|
if self.debug.is_set() {
|
|
println!("{}", result);
|
|
}
|
|
Ok(result)
|
|
}
|
|
|
|
fn build_dyn_namespace(&self) -> Result<TokenStream> {
|
|
let dyn_inner = match self.inner.as_dyn() {
|
|
Some(v) => v,
|
|
None => return Err(Error::new(
|
|
Span::call_site(),
|
|
"struct must have `namespace = dyn` and a `#[xml(namespace)]` field to derive DynNamespace"
|
|
)),
|
|
};
|
|
|
|
let set_namespace_input = Ident::new("ns", Span::call_site());
|
|
let mut accessor = make_accessor(Ident::new("self", Span::call_site()).into());
|
|
|
|
let ty = dyn_inner.namespace_ty();
|
|
let namespace_impl = dyn_inner.build_get_namespace(&mut accessor)?;
|
|
let set_namespace_impl =
|
|
dyn_inner.build_set_namespace(&set_namespace_input, &mut accessor)?;
|
|
|
|
Ok(quote! {
|
|
type Namespace = #ty;
|
|
|
|
fn namespace(&self) -> &Self::Namespace {
|
|
#namespace_impl
|
|
}
|
|
|
|
fn set_namespace<T: Into<Self::Namespace>>(&mut self, #set_namespace_input: T) {
|
|
let #set_namespace_input = #set_namespace_input.into();
|
|
#set_namespace_impl
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parse_struct(item: &syn::ItemStruct) -> Result<Box<dyn ItemDef>> {
|
|
let mut meta = XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
|
|
let wrapped_with = meta.wrapped_with.take();
|
|
let span = meta.span;
|
|
let mut def = Box::new(StructDef::new(meta, &item.fields)?) as Box<dyn ItemDef>;
|
|
if let Some(wrapped_with) = wrapped_with {
|
|
def = crate::wrapped::wrap(&span, wrapped_with, &item.ident, def)?;
|
|
}
|
|
Ok(def)
|
|
}
|