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

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 }))
}