mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-06-18 21:55:57 +02:00
This allows containers to reject items based on their content, without having to resort to a panic. The use case is containers which are polymorphic, but don't support different item types, as encountered e.g. in XEP-0060 PubSub publish vs. retract events.
703 lines
29 KiB
Rust
703 lines
29 KiB
Rust
//! Infrastructure for parsing fields from child elements while destructuring
|
|
//! their contents.
|
|
use proc_macro2::{Span, TokenStream};
|
|
|
|
use quote::{quote, quote_spanned};
|
|
use syn::{spanned::Spanned, *};
|
|
|
|
use crate::compound::Compound;
|
|
use crate::error_message::{self, ParentRef};
|
|
use crate::meta::{Flag, FlagOr, NameRef, NamespaceRef, XmlFieldMeta};
|
|
|
|
use super::{ChildMode, Field, FieldDef, FieldNamespace, FieldParsePart};
|
|
|
|
/// Definition of a child data extraction.
|
|
///
|
|
/// This is used to implement fields annotated with
|
|
/// `#[xml(child(.., extract(..))]` or `#[xml(children(.., extract(..)))]`.
|
|
#[derive(Debug)]
|
|
pub(super) struct ExtractDef {
|
|
namespace: FieldNamespace,
|
|
|
|
name: NameRef,
|
|
|
|
/// Compound which contains the arguments of the `extract(..)` attribute,
|
|
/// transformed into a struct with unnamed fields.
|
|
///
|
|
/// This is used to generate the parsing/serialisation code, by
|
|
/// essentially "declaring" a shim struct, as if it were a real Rust
|
|
/// struct, and using the result of the parsing process directly for the
|
|
/// field on which the `extract(..)` option was used, instead of putting
|
|
/// it into a Rust struct.
|
|
parts: Compound,
|
|
}
|
|
|
|
impl ExtractDef {
|
|
fn expand_namespace(&self, container_namespace_expr: &Expr) -> Expr {
|
|
match self.namespace {
|
|
FieldNamespace::Static(ref ns) => Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: ns.clone().into(),
|
|
}),
|
|
FieldNamespace::Super(_) => container_namespace_expr.clone(),
|
|
}
|
|
}
|
|
|
|
/// Construct an `ExtractDef`.
|
|
///
|
|
/// The `namespace` and `name` identify the XML element this `ExtractDef`
|
|
/// works on, i.e. the child element to match.
|
|
///
|
|
/// `parts` contains the pieces of data to extract from the child in the
|
|
/// order they are extracted.
|
|
///
|
|
/// Finally, `single_extract_type` should be passed if the extract is used
|
|
/// in the context of a `#[xml(child)]` field (i.e. not for a container)
|
|
/// and it should then be the type of that field. This allows defaulting
|
|
/// the type of the extract's field to that type if it has not been
|
|
/// specified explicitly by the user.
|
|
fn new(
|
|
span: Span,
|
|
namespace: FieldNamespace,
|
|
name: NameRef,
|
|
parts: Vec<Box<XmlFieldMeta>>,
|
|
mut single_extract_type: Option<Type>,
|
|
) -> Result<Self> {
|
|
if parts.len() != 1 {
|
|
single_extract_type = None;
|
|
}
|
|
let parts = Compound::new(
|
|
None,
|
|
None,
|
|
parts.into_iter().enumerate().map(|(i, x)| {
|
|
FieldDef::from_extract(span.clone(), *x, i as u32, single_extract_type.take())
|
|
}),
|
|
)?;
|
|
Ok(Self {
|
|
namespace,
|
|
name,
|
|
parts,
|
|
})
|
|
}
|
|
|
|
/// Construct a token stream containing an expression which tries to
|
|
/// process the child element at the identifier `residual`.
|
|
///
|
|
/// The value of the expression is a `Result<T, Element>`. If the
|
|
/// element in `residual` does not match the XML namespace and name of
|
|
/// this extract definition, it is returned as `Err(#residual)`.
|
|
///
|
|
/// Otherwise, the element is destructured according to the extraction
|
|
/// specification contained in `self`. If this extract consists of a
|
|
/// single field, that field is returned after an unbounded call to
|
|
/// `Into::into`. The call to `into` allows assignment to `Option<T>`.
|
|
///
|
|
/// If the extract consists of more than one field, these fields are
|
|
/// returned as tuple without any further conversion.
|
|
///
|
|
/// The `parent_namespace_expr`, if given, is evaluated, cloned and
|
|
/// latched into a local variable (in the generated code) and that is
|
|
/// passed on to the inner call to [`Compound::build_into_element`].
|
|
fn build_extract(
|
|
&self,
|
|
container_name: &ParentRef,
|
|
container_namespace_expr: &Expr,
|
|
residual: &Ident,
|
|
) -> Result<TokenStream> {
|
|
let namespace_expr = self.expand_namespace(container_namespace_expr);
|
|
let xml_name = &self.name;
|
|
|
|
let nfields = self.parts.field_count();
|
|
|
|
let repack = if nfields == 1 {
|
|
quote! { data.0.into() }
|
|
} else {
|
|
quote! { data }
|
|
};
|
|
|
|
let parse =
|
|
self.parts
|
|
.build_try_from_element(container_name, &namespace_expr, residual, &[])?;
|
|
|
|
let test_expr = match self.namespace {
|
|
FieldNamespace::Static(ref xml_namespace) => quote! {
|
|
#residual.is(#xml_name, #xml_namespace)
|
|
},
|
|
FieldNamespace::Super(_) => quote! {
|
|
::std::cmp::PartialEq::eq(&#namespace_expr, #residual.ns().as_str()) && #residual.name() == #xml_name
|
|
},
|
|
};
|
|
|
|
Ok(quote! {
|
|
if #test_expr {
|
|
let data = #parse;
|
|
Ok(#repack)
|
|
} else {
|
|
Err(#residual)
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Construct a token stream containing an expression evaluating to a
|
|
/// `Option<minidom::Element>`.
|
|
///
|
|
/// This is the reverse operation of
|
|
/// [`build_extract`][`Self::build_extract`], and like in that function,
|
|
/// there are weird edge cases in here.
|
|
///
|
|
/// `field` must be the expression which contains the extracted field's
|
|
/// data. It is evaluated exactly once.
|
|
///
|
|
/// The `parent_namespace_expr`, if given, is evaluated, cloned and
|
|
/// latched into a local variable (in the generated code) and that is
|
|
/// passed on to the inner call to [`Compound::build_into_element`].
|
|
fn build_assemble(
|
|
&self,
|
|
container_name: &ParentRef,
|
|
container_namespace_expr: &Expr,
|
|
field: &Expr,
|
|
) -> Result<TokenStream> {
|
|
let xml_namespace = self.expand_namespace(container_namespace_expr);
|
|
let xml_name = &self.name;
|
|
|
|
let nfields = self.parts.field_count();
|
|
|
|
let ident = Ident::new("__extract_data", Span::call_site());
|
|
|
|
let repack = if nfields == 1 {
|
|
quote! { let #ident = (#ident,); }
|
|
} else {
|
|
quote! { let #ident = #ident; }
|
|
};
|
|
|
|
let builder = Ident::new("builder", Span::call_site());
|
|
|
|
let builder_init = match self.namespace {
|
|
FieldNamespace::Static(ref xml_namespace) => quote! {
|
|
::xmpp_parsers_core::exports::minidom::Element::builder(
|
|
#xml_name,
|
|
#xml_namespace
|
|
)
|
|
},
|
|
FieldNamespace::Super(_) => quote! {
|
|
::xmpp_parsers_core::exports::minidom::Element::builder(
|
|
#xml_name,
|
|
::xmpp_parsers_core::DynNamespaceEnum::into_xml_text(#xml_namespace.clone()),
|
|
)
|
|
},
|
|
};
|
|
|
|
let build =
|
|
self.parts
|
|
.build_into_element(container_name, &xml_namespace, &builder, |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: Path::from(ident.clone()),
|
|
})),
|
|
member,
|
|
})
|
|
})?;
|
|
|
|
Ok(quote! {
|
|
{
|
|
let #ident = #field;
|
|
#repack
|
|
let #builder = #builder_init;
|
|
let #builder = #build;
|
|
#builder.build()
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Return the type of the only field, if this extract has exactly one
|
|
/// field, or None otherwise.
|
|
fn inner_type(&self) -> Option<&Type> {
|
|
self.parts.single_type()
|
|
}
|
|
}
|
|
|
|
/// A field parsed from an XML child, destructured into a Rust data structure.
|
|
///
|
|
/// Maps to `#[xml(child)]` and `#[xml(children)]`.
|
|
#[derive(Debug)]
|
|
pub(crate) struct ChildField {
|
|
/// Determines whether one or more matching child elements are expected.
|
|
///
|
|
/// This is basically the difference between `#[xml(child(..))]` and
|
|
/// `#[xml(children(..))]`.
|
|
mode: ChildMode,
|
|
|
|
/// If set, the field's value will be obtained by destructuring the child
|
|
/// element using the given [`ExtractDef`], instead of parsing it using
|
|
/// `FromXml`.
|
|
extract: Option<ExtractDef>,
|
|
|
|
/// If set, `extract` must be None, the child's type must implement
|
|
/// `DynNamespace` and the compound must use `namespace = dyn`.
|
|
super_namespace: Flag,
|
|
|
|
/// If set, the field's value will be generated using
|
|
/// [`std::default::Default`] or the given path if no matching child can
|
|
/// be found, instead of aborting parsing with an error.
|
|
default_: FlagOr<Path>,
|
|
|
|
/// If set, it must point to a function. That function will be called with
|
|
/// an immutable reference to the field's value and must return a boolean.
|
|
/// If that boolean is true, the child will not be emitted.
|
|
skip_if: Option<Path>,
|
|
|
|
/// If set, it must point to a type. The `FromXml`/`IntoXml`
|
|
/// implementations of that type will be used instead, and the type must
|
|
/// implement `ElementCodec<T>`, where `T` is the type of the field.
|
|
codec: Option<Path>,
|
|
}
|
|
|
|
impl ChildField {
|
|
/// Construct a new `#[xml(child)]` or `#[xml(children)]` field.
|
|
///
|
|
/// `mode` distinguishes between `#[xml(child(..))]` and
|
|
/// `#[xml(children(..))]` fields.
|
|
///
|
|
/// If the child is going to be extracted, it `namespace` and `name` must
|
|
/// identify the target child's XML namespace and name and `extract` must
|
|
/// be the extraction parts to process.
|
|
///
|
|
/// Otherwise, if no extract is intended, `namespace` and `name` must be
|
|
/// `None` and `extract` must be empty.
|
|
///
|
|
/// The `default_` flag stored, see [`Self::default_`] for semantics.
|
|
///
|
|
/// `field_type` must be the type of the field. It is used to configure
|
|
/// the extract correctly, if it is specified and the mode is single.
|
|
///
|
|
/// `attr_span` is used for emitting error messages when no better span
|
|
/// can be constructed. This should point at the `#[xml(..)]` meta of the
|
|
/// field or another closely-related object.
|
|
pub(super) fn new(
|
|
attr_span: &Span,
|
|
mode: ChildMode,
|
|
namespace: Option<NamespaceRef>,
|
|
name: Option<NameRef>,
|
|
extract: Vec<Box<XmlFieldMeta>>,
|
|
default_: FlagOr<Path>,
|
|
skip_if: Option<Path>,
|
|
codec: Option<Path>,
|
|
field_type: &Type,
|
|
) -> Result<Self> {
|
|
if extract.len() > 0 {
|
|
let namespace = match namespace {
|
|
None => {
|
|
return Err(Error::new(
|
|
attr_span.clone(),
|
|
"namespace must be specified on extracted fields",
|
|
))
|
|
}
|
|
Some(NamespaceRef::Static(ns)) => FieldNamespace::Static(ns),
|
|
Some(NamespaceRef::Dyn(ns)) => {
|
|
return Err(Error::new_spanned(
|
|
ns,
|
|
"extracted fields cannot use dynamic namespaces",
|
|
))
|
|
}
|
|
Some(NamespaceRef::Super(ns)) => FieldNamespace::Super(ns),
|
|
};
|
|
let Some(name) = name else {
|
|
return Err(Error::new(
|
|
attr_span.clone(),
|
|
"name must be specified on extracted fields",
|
|
));
|
|
};
|
|
if let Some(codec) = codec {
|
|
return Err(Error::new_spanned(
|
|
codec,
|
|
"codec = .. cannot be combined with extract(..)",
|
|
));
|
|
}
|
|
let single_extract_type = match mode {
|
|
ChildMode::Single => {
|
|
if extract.len() > 1 {
|
|
return Err(Error::new(
|
|
attr_span.clone(),
|
|
"extracting multiple texts from children is only on collection fields",
|
|
));
|
|
};
|
|
Some(field_type.clone())
|
|
}
|
|
ChildMode::Collection => None,
|
|
};
|
|
Ok(Self {
|
|
mode,
|
|
extract: Some(ExtractDef::new(
|
|
attr_span.clone(),
|
|
namespace,
|
|
name.into(),
|
|
extract,
|
|
single_extract_type,
|
|
)?),
|
|
skip_if,
|
|
default_,
|
|
super_namespace: Flag::Absent,
|
|
codec: None,
|
|
})
|
|
} else {
|
|
let super_namespace = match namespace {
|
|
None => Flag::Absent,
|
|
Some(NamespaceRef::Super(ns)) => Flag::Present(ns.span),
|
|
Some(namespace) => {
|
|
return Err(Error::new_spanned(
|
|
namespace,
|
|
"namespace declaration not allowed on non-extracted child fields",
|
|
));
|
|
}
|
|
};
|
|
if let Some(name) = name {
|
|
return Err(Error::new_spanned(
|
|
name,
|
|
"name declaration not allowed on non-extracted child fields",
|
|
));
|
|
}
|
|
Ok(Self {
|
|
mode,
|
|
extract: None,
|
|
default_,
|
|
skip_if,
|
|
super_namespace,
|
|
codec,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Field for ChildField {
|
|
fn build_try_from_element(
|
|
&self,
|
|
container_name: &ParentRef,
|
|
container_namespace_expr: &Expr,
|
|
tempname: Ident,
|
|
member: &Member,
|
|
ty: &Type,
|
|
) -> Result<FieldParsePart> {
|
|
let ty_span = ty.span();
|
|
let ty_default = quote_spanned! {ty_span=> <#ty as std::default::Default>::default};
|
|
match self.mode {
|
|
ChildMode::Single => {
|
|
let missingerr = error_message::on_missing_child(container_name, &member);
|
|
let duperr = error_message::on_duplicate_child(container_name, &member);
|
|
let on_missing = match self.default_ {
|
|
FlagOr::Absent => {
|
|
quote! {
|
|
return Err(::xmpp_parsers_core::error::Error::ParseError(#missingerr));
|
|
}
|
|
}
|
|
FlagOr::Present(_) => {
|
|
quote! {
|
|
#ty_default()
|
|
}
|
|
}
|
|
FlagOr::Value { ref value, .. } => {
|
|
quote! {
|
|
#value()
|
|
}
|
|
}
|
|
};
|
|
match self.extract {
|
|
Some(ref extract) => {
|
|
let extract = extract.build_extract(
|
|
&container_name.child(member.clone()),
|
|
container_namespace_expr,
|
|
&Ident::new("residual", Span::call_site()),
|
|
)?;
|
|
|
|
Ok(FieldParsePart {
|
|
tempinit: quote! {
|
|
let mut #tempname: Option<#ty> = None;
|
|
},
|
|
childiter: quote! {
|
|
residual = match #extract {
|
|
Ok(v) => {
|
|
if #tempname.is_some() {
|
|
return Err(::xmpp_parsers_core::error::Error::ParseError(#duperr));
|
|
}
|
|
#tempname = Some(v);
|
|
continue;
|
|
},
|
|
Err(residual) => residual,
|
|
};
|
|
},
|
|
value: quote! {
|
|
if let Some(v) = #tempname {
|
|
v
|
|
} else {
|
|
#on_missing
|
|
}
|
|
},
|
|
..FieldParsePart::default()
|
|
})
|
|
}
|
|
None => {
|
|
let ns_test = match self.super_namespace {
|
|
Flag::Absent => quote! { true },
|
|
Flag::Present(_) => quote! {
|
|
::std::cmp::PartialEq::eq(&#container_namespace_expr, residual.ns().as_str())
|
|
},
|
|
};
|
|
let codec_ty = match self.codec {
|
|
Some(ref ty) => Type::Path(TypePath {
|
|
qself: None,
|
|
path: ty.clone(),
|
|
}),
|
|
None => ty.clone(),
|
|
};
|
|
let field_ty = ty;
|
|
let codec_ty_span = codec_ty.span();
|
|
let codec_ty_from_tree = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::FromXml>::from_tree};
|
|
let codec_ty_absent = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::FromXml>::absent};
|
|
let codec_ty_decode = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::ElementCodec::<#field_ty>>::decode};
|
|
Ok(FieldParsePart {
|
|
tempinit: quote! {
|
|
let mut #tempname: Option<#field_ty> = None;
|
|
},
|
|
childiter: quote! {
|
|
let mut residual = if #ns_test {
|
|
match #codec_ty_from_tree(residual) {
|
|
Ok(v) => {
|
|
let v = #codec_ty_decode(v);
|
|
if #tempname.is_some() {
|
|
return Err(::xmpp_parsers_core::error::Error::ParseError(#duperr));
|
|
}
|
|
#tempname = Some(v);
|
|
continue
|
|
}
|
|
Err(::xmpp_parsers_core::error::Error::TypeMismatch(_, _, e)) => e,
|
|
Err(other) => return Err(other),
|
|
}
|
|
} else {
|
|
residual
|
|
};
|
|
},
|
|
value: quote! {
|
|
if let Some(v) = #tempname {
|
|
v
|
|
} else if let Some(v) = #codec_ty_absent().map(#codec_ty_decode) {
|
|
v
|
|
} else {
|
|
#on_missing
|
|
}
|
|
},
|
|
..FieldParsePart::default()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
ChildMode::Collection => {
|
|
let item_ty: Type = syn::parse2(quote_spanned! {ty_span=>
|
|
<#ty as IntoIterator>::Item
|
|
})
|
|
.expect("failed to construct item type");
|
|
let ty_try_extend = quote_spanned! {ty_span=> <#ty as ::xmpp_parsers_core::TryExtend<#item_ty>>::try_extend};
|
|
match self.extract {
|
|
Some(ref extract) => {
|
|
let extract = extract.build_extract(
|
|
&container_name.child(member.clone()),
|
|
container_namespace_expr,
|
|
&Ident::new("residual", Span::call_site()),
|
|
)?;
|
|
Ok(FieldParsePart {
|
|
tempinit: quote! {
|
|
let mut #tempname = #ty_default();
|
|
},
|
|
childiter: quote! {
|
|
residual = match #extract {
|
|
Ok(v) => {
|
|
#ty_try_extend(&mut #tempname, [v])?;
|
|
continue;
|
|
},
|
|
Err(residual) => residual,
|
|
};
|
|
},
|
|
value: quote! { #tempname },
|
|
..FieldParsePart::default()
|
|
})
|
|
}
|
|
None => {
|
|
if let Flag::Present(span) = self.super_namespace {
|
|
return Err(Error::new(
|
|
span,
|
|
"#[xml(namespace = dyn)] not supported for #[xml(children)]",
|
|
));
|
|
}
|
|
let codec_ty = match self.codec {
|
|
Some(ref ty) => Type::Path(TypePath {
|
|
qself: None,
|
|
path: ty.clone(),
|
|
}),
|
|
None => item_ty.clone(),
|
|
};
|
|
let codec_ty_span = codec_ty.span();
|
|
let codec_ty_from_tree = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::FromXml>::from_tree};
|
|
let codec_ty_decode = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::ElementCodec::<#item_ty>>::decode};
|
|
Ok(FieldParsePart {
|
|
tempinit: quote! {
|
|
let mut #tempname = #ty_default();
|
|
},
|
|
childiter: quote! {
|
|
let mut residual = match #codec_ty_from_tree(residual) {
|
|
Ok(item) => {
|
|
let item = #codec_ty_decode(item);
|
|
#ty_try_extend(&mut #tempname, [item])?;
|
|
continue;
|
|
},
|
|
Err(::xmpp_parsers_core::error::Error::TypeMismatch(_, _, e)) => e,
|
|
Err(other) => return Err(other),
|
|
};
|
|
},
|
|
value: quote! { #tempname },
|
|
..FieldParsePart::default()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_set_namespace(&self, input: &Ident, ty: &Type, access: Expr) -> Result<TokenStream> {
|
|
match self.mode {
|
|
ChildMode::Single => match self.extract {
|
|
Some(_) => Ok(quote! {}),
|
|
None => match self.super_namespace {
|
|
Flag::Absent => Ok(quote! {}),
|
|
Flag::Present(_) => {
|
|
let ty_span = ty.span();
|
|
// using quote_spanned in this way here causes the "the trait `DynNamespace` is not implemented for `..`" error message appear on member_ty instead of on the derive macro invocation.
|
|
let method = quote_spanned! {ty_span=> <#ty as ::xmpp_parsers_core::DynNamespace>::set_namespace};
|
|
Ok(quote! {
|
|
#method(&mut #access, #input.clone());
|
|
})
|
|
}
|
|
},
|
|
},
|
|
_ => Ok(quote! {}),
|
|
}
|
|
}
|
|
|
|
fn build_into_element(
|
|
&self,
|
|
container_name: &ParentRef,
|
|
container_namespace_expr: &Expr,
|
|
member: &Member,
|
|
ty: &Type,
|
|
access: Expr,
|
|
) -> Result<TokenStream> {
|
|
let temp_ident = Ident::new("__data", Span::call_site());
|
|
let skip_map = match self.skip_if {
|
|
Some(ref callable) => quote! {
|
|
match #callable(&#temp_ident) {
|
|
false => Some(#temp_ident),
|
|
true => None,
|
|
}
|
|
},
|
|
None => quote! { Some(#temp_ident) },
|
|
};
|
|
match self.mode {
|
|
ChildMode::Single => match self.extract {
|
|
Some(ref extract) => {
|
|
let temp_expr = Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: temp_ident.clone().into(),
|
|
});
|
|
let inner_ty = extract
|
|
.inner_type()
|
|
.expect("child extract can only have one field!")
|
|
.clone();
|
|
let assemble = extract.build_assemble(
|
|
&container_name.child(member.clone()),
|
|
container_namespace_expr,
|
|
&temp_expr,
|
|
)?;
|
|
Ok(quote! {
|
|
match Option::<#inner_ty>::from(#access).and_then(|#temp_ident| #skip_map) {
|
|
Some(#temp_ident) => builder.append(::xmpp_parsers_core::exports::minidom::Node::Element(#assemble)),
|
|
None => builder,
|
|
}
|
|
})
|
|
}
|
|
None => {
|
|
let codec_ty = match self.codec {
|
|
Some(ref ty) => Type::Path(TypePath {
|
|
qself: None,
|
|
path: ty.clone(),
|
|
}),
|
|
None => ty.clone(),
|
|
};
|
|
let field_ty = ty;
|
|
let codec_ty_span = codec_ty.span();
|
|
let codec_ty_into_tree = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::IntoXml>::into_tree};
|
|
let codec_ty_encode = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::ElementCodec::<#field_ty>>::encode};
|
|
Ok(quote! {
|
|
{
|
|
let #temp_ident = #access;
|
|
match #skip_map.map(#codec_ty_encode).and_then(#codec_ty_into_tree) {
|
|
Some(#temp_ident) => builder.append(::xmpp_parsers_core::exports::minidom::Node::Element(#temp_ident)),
|
|
None => builder,
|
|
}
|
|
}
|
|
})
|
|
}
|
|
},
|
|
ChildMode::Collection => match self.extract {
|
|
Some(ref extract) => {
|
|
let assemble = extract.build_assemble(
|
|
&container_name.child(member.clone()),
|
|
container_namespace_expr,
|
|
&Expr::Path(ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path: temp_ident.clone().into(),
|
|
}),
|
|
)?;
|
|
Ok(quote! {
|
|
builder.append_all(
|
|
#access.into_iter().filter_map(|#temp_ident| {
|
|
match #skip_map {
|
|
Some(#temp_ident) => Some(#assemble),
|
|
None => None,
|
|
}
|
|
})
|
|
)
|
|
})
|
|
}
|
|
None => {
|
|
let ty_span = ty.span();
|
|
let item_ty: Type = syn::parse2(quote_spanned! {ty_span=>
|
|
<#ty as IntoIterator>::Item
|
|
})
|
|
.expect("failed to construct item type");
|
|
let codec_ty = match self.codec {
|
|
Some(ref ty) => Type::Path(TypePath {
|
|
qself: None,
|
|
path: ty.clone(),
|
|
}),
|
|
None => item_ty.clone(),
|
|
};
|
|
let codec_ty_span = codec_ty.span();
|
|
let codec_ty_into_tree = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::IntoXml>::into_tree};
|
|
let codec_ty_encode = quote_spanned! {codec_ty_span=> <#codec_ty as ::xmpp_parsers_core::ElementCodec::<#item_ty>>::encode};
|
|
Ok(quote! {
|
|
builder.append_all(#access.into_iter().filter_map(|#temp_ident| {
|
|
#skip_map.map(#codec_ty_encode).and_then(#codec_ty_into_tree).map(|el| ::xmpp_parsers_core::exports::minidom::Node::Element(el))
|
|
}))
|
|
})
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|