mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
262 lines
7.7 KiB
Rust
262 lines
7.7 KiB
Rust
/*!
|
|
# Wrapping of any item into a single-child struct
|
|
|
|
This module provides a wrapper around any [`ItemDef`] which wraps the contents
|
|
into an XML element with a given namespace and name. No other children or
|
|
attributes are allowed on that element.
|
|
|
|
This implements `#[xml(.., wrapped_with(..))]`.
|
|
*/
|
|
use proc_macro2::{Span, TokenStream};
|
|
|
|
use quote::quote;
|
|
use syn::*;
|
|
|
|
use crate::common::ItemDef;
|
|
use crate::compound::Compound;
|
|
use crate::error_message::ParentRef;
|
|
use crate::field::{Field, FieldDef, FieldParsePart};
|
|
use crate::meta::{NamespaceRef, NodeFilterMeta};
|
|
use crate::structs::{StructInner, StructNamespace};
|
|
|
|
/// The [`Field`] implementation to handle the actual type.
|
|
#[derive(Debug)]
|
|
struct WrappedField {
|
|
/// The name of the type to parse.
|
|
ty_ident: Ident,
|
|
|
|
/// The implementation of the type to parse.
|
|
inner: Box<dyn ItemDef>,
|
|
}
|
|
|
|
impl Field for WrappedField {
|
|
fn build_try_from_element(
|
|
&self,
|
|
_container_name: &ParentRef,
|
|
_container_namespace_expr: &Expr,
|
|
tempname: Ident,
|
|
_member: &Member,
|
|
_ty: &Type,
|
|
) -> Result<FieldParsePart> {
|
|
let ty = &self.ty_ident;
|
|
let enum_ref = ParentRef::from(Path::from(self.ty_ident.clone()));
|
|
let enum_ref_s = enum_ref.to_string();
|
|
let missingerr = quote! {
|
|
concat!("Required child missing in ", #enum_ref_s, ".")
|
|
};
|
|
let duperr = quote! {
|
|
concat!("Only one child allowed in ", #enum_ref_s, ".")
|
|
};
|
|
let try_from_impl = self.inner.build_try_from_element(
|
|
&(Path::from(self.ty_ident.clone()).into()),
|
|
&Ident::new("residual", Span::call_site()),
|
|
)?;
|
|
Ok(FieldParsePart {
|
|
tempinit: quote! {
|
|
let mut #tempname: Option<#ty> = None;
|
|
},
|
|
childiter: quote! {
|
|
residual = match #try_from_impl {
|
|
Ok(v) => {
|
|
if #tempname.is_some() {
|
|
return Err(::xso::error::Error::ParseError(#duperr));
|
|
}
|
|
#tempname = Some(v);
|
|
continue;
|
|
}
|
|
Err(::xso::error::Error::TypeMismatch(_, _, residual)) => residual,
|
|
Err(other) => return Err(other),
|
|
};
|
|
},
|
|
value: quote! {
|
|
if let Some(v) = #tempname {
|
|
v
|
|
} else {
|
|
return Err(Error::ParseError(#missingerr))
|
|
}
|
|
},
|
|
..FieldParsePart::default()
|
|
})
|
|
}
|
|
|
|
fn build_into_element(
|
|
&self,
|
|
_container_name: &ParentRef,
|
|
_container_namespace_expr: &Expr,
|
|
_member: &Member,
|
|
_ty: &Type,
|
|
access: Expr,
|
|
) -> Result<TokenStream> {
|
|
let tempname = Ident::new("data", Span::call_site());
|
|
let into_impl = self
|
|
.inner
|
|
.build_into_element(&(Path::from(self.ty_ident.clone()).into()), &tempname)?;
|
|
Ok(quote! {
|
|
{
|
|
let mut #tempname = #access;
|
|
let el = #into_impl;
|
|
builder.append(::xso::exports::minidom::Node::Element(el))
|
|
}
|
|
})
|
|
}
|
|
|
|
fn build_set_namespace(
|
|
&self,
|
|
_input: &Ident,
|
|
_ty: &Type,
|
|
_access: Expr,
|
|
) -> Result<TokenStream> {
|
|
// we don't allow deriving DynNamesace.
|
|
unreachable!()
|
|
}
|
|
}
|
|
|
|
/// The wrapper item.
|
|
#[derive(Debug)]
|
|
pub(crate) struct Wrapped {
|
|
/// The actual work is done (similar to how extracts are handled) by a
|
|
/// virtual struct.
|
|
///
|
|
/// This struct is tuple-style and has a single field, implemented using
|
|
/// [`WrappedField`]. That field does the deserialisation of the child
|
|
/// element, while the `StructInner` here does the matching on the parent
|
|
/// element and ensures that no stray data is inside of that.
|
|
inner: StructInner,
|
|
}
|
|
|
|
impl ItemDef for Wrapped {
|
|
fn build_try_from_element(
|
|
&self,
|
|
item_name: &ParentRef,
|
|
residual: &Ident,
|
|
) -> Result<TokenStream> {
|
|
let try_from_impl = self
|
|
.inner
|
|
.build_try_from_element(&item_name.wrapper(), residual)?;
|
|
|
|
Ok(quote! {
|
|
match #try_from_impl {
|
|
Ok(v) => Ok(v.0),
|
|
Err(residual) => Err(::xso::error::Error::TypeMismatch("", "", residual)),
|
|
}
|
|
})
|
|
}
|
|
|
|
fn build_into_element(
|
|
&self,
|
|
item_name: &ParentRef,
|
|
value_ident: &Ident,
|
|
) -> Result<TokenStream> {
|
|
let into_impl = self
|
|
.inner
|
|
.build_into_element(&item_name.wrapper(), |member| {
|
|
Expr::Field(ExprField {
|
|
attrs: Vec::new(),
|
|
base: Box::new(Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: value_ident.clone().into(),
|
|
})),
|
|
dot_token: token::Dot {
|
|
spans: [Span::call_site()],
|
|
},
|
|
member,
|
|
})
|
|
})?;
|
|
let result = quote! {
|
|
let #value_ident = (#value_ident,);
|
|
#into_impl
|
|
};
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
fn build_dyn_namespace(&self) -> Result<TokenStream> {
|
|
return Err(Error::new(
|
|
Span::call_site(),
|
|
"namespace = dyn cannot be combined with wrapped(..)",
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Wrap any struct or enum into another XML element.
|
|
///
|
|
/// - `span` is used for error purposes and should point somewhere in the
|
|
/// vicinity of the wrapping specification.
|
|
///
|
|
/// - `meta` must contain the actual wrapping specification (namespace and
|
|
/// name of the outer field).
|
|
///
|
|
/// - `ty_ident` must be the identifier of the *wrapped* type.
|
|
///
|
|
/// - `inner` must be the implementation of the wrapped type.
|
|
pub(crate) fn wrap(
|
|
span: &Span,
|
|
meta: NodeFilterMeta,
|
|
ty_ident: &Ident,
|
|
inner: Box<dyn ItemDef>,
|
|
) -> Result<Box<Wrapped>> {
|
|
let namespace = match meta.namespace {
|
|
Some(NamespaceRef::Static(ns)) => ns,
|
|
Some(NamespaceRef::Dyn(ns)) => {
|
|
return Err(Error::new_spanned(
|
|
ns,
|
|
"dyn namespace is not supported for wrappers",
|
|
))
|
|
}
|
|
Some(NamespaceRef::Super(ns)) => {
|
|
return Err(Error::new_spanned(
|
|
ns,
|
|
"super namespace is not supported for wrappers",
|
|
))
|
|
}
|
|
None => {
|
|
return Err(Error::new(
|
|
*span,
|
|
"namespace is required for wrappers (inside `#[xml(.., wrapped_with(..))]`)",
|
|
))
|
|
}
|
|
};
|
|
let name = match meta.name {
|
|
Some(name) => name.into(),
|
|
None => {
|
|
return Err(Error::new(
|
|
*span,
|
|
"name is required for wrappers (inside `#[xml(.., wrapped_with(..))]`)",
|
|
))
|
|
}
|
|
};
|
|
|
|
let field = WrappedField {
|
|
ty_ident: ty_ident.clone(),
|
|
inner,
|
|
};
|
|
|
|
let inner = Compound::new(
|
|
None,
|
|
None,
|
|
[Ok(FieldDef {
|
|
span: *span,
|
|
ident: Member::Unnamed(Index {
|
|
index: 0,
|
|
span: *span,
|
|
}),
|
|
// the type is only passed down to the Field impl, where
|
|
// we will not use it.
|
|
ty: Type::Never(TypeNever {
|
|
bang_token: token::Not { spans: [*span] },
|
|
}),
|
|
kind: Box::new(field),
|
|
})]
|
|
.into_iter(),
|
|
)?;
|
|
|
|
let inner = StructInner::Compound {
|
|
namespace: StructNamespace::Static(namespace),
|
|
name,
|
|
inner,
|
|
};
|
|
|
|
Ok(Box::new(Wrapped { inner }))
|
|
}
|