mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-06-09 01:34:03 +02:00
decbeb351f
This simplifies the code of FieldDef and makes it more extensible for future ideas.
306 lines
10 KiB
Rust
306 lines
10 KiB
Rust
//! Infrastructure for parsing fields from child elements without
|
|
//! destructuring their contents.
|
|
use proc_macro2::{Span, TokenStream};
|
|
|
|
use quote::{quote, quote_spanned};
|
|
use syn::{spanned::Spanned, *};
|
|
|
|
use crate::error_message::{self, ParentRef};
|
|
use crate::meta::{FlagOr, Name, NameRef, NamespaceRef, StaticNamespace};
|
|
use crate::structs::ElementSelector;
|
|
|
|
use super::{Field, FieldParsePart};
|
|
|
|
/// A field parsed from an XML child, without destructuring it into Rust
|
|
/// data structures.
|
|
///
|
|
/// Maps to `#[xml(element)]`.
|
|
#[derive(Debug)]
|
|
pub(crate) struct ElementField {
|
|
/// Logic to select matching child elements.
|
|
selector: ElementSelector,
|
|
|
|
/// If set, the field value will be generated using
|
|
/// [`std::default::Default`] or the given callable if no matching child
|
|
/// element is encountered during parsing. If unset, an error is generated
|
|
/// instead and parsing of the parent element fails.
|
|
default_: FlagOr<Path>,
|
|
}
|
|
|
|
impl ElementField {
|
|
/// Construct a new `#[xml(element)]` field.
|
|
///
|
|
/// `namespace` must be a [`NamespaceRef::Static`] describing the
|
|
/// XML namespace of the target child element. Otherwise, a compile-time
|
|
/// error is returned.
|
|
///
|
|
/// `name` must be a [`NameRef`] describing the XML name of the target
|
|
/// child element. Otherwise, a compile-time error is returned.
|
|
///
|
|
/// `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,
|
|
namespace: Option<NamespaceRef>,
|
|
name: Option<NameRef>,
|
|
default_: FlagOr<Path>,
|
|
) -> Result<Self> {
|
|
let namespace = match namespace {
|
|
None => None,
|
|
Some(NamespaceRef::Static(ns)) => Some(ns),
|
|
Some(NamespaceRef::Dyn(ns)) => {
|
|
return Err(Error::new_spanned(
|
|
ns,
|
|
"dynamic namespaces cannot be used with #[xml(element)]",
|
|
))
|
|
}
|
|
Some(NamespaceRef::Super(ns)) => {
|
|
return Err(Error::new_spanned(
|
|
ns,
|
|
"collected elements cannot refer to the parent namespace",
|
|
))
|
|
}
|
|
};
|
|
let name = name.map(Name::from);
|
|
let selector = match (namespace, name) {
|
|
(Some(namespace), Some(name)) => ElementSelector::Qualified { namespace, name },
|
|
(Some(namespace), None) => ElementSelector::ByNamespace(namespace),
|
|
(None, Some(name)) => ElementSelector::ByName(name),
|
|
(None, None) => ElementSelector::Any,
|
|
};
|
|
Ok(Self { selector, default_ })
|
|
}
|
|
}
|
|
|
|
impl Field for ElementField {
|
|
fn build_try_from_element(
|
|
&self,
|
|
container_name: &ParentRef,
|
|
_container_namespace_expr: &Expr,
|
|
tempname: Ident,
|
|
member: &Member,
|
|
ty: &Type,
|
|
) -> Result<FieldParsePart> {
|
|
let test = self
|
|
.selector
|
|
.build_test(&Ident::new("residual", Span::call_site()));
|
|
let missingerr = error_message::on_missing_child(container_name, member);
|
|
let duperr = error_message::on_duplicate_child(container_name, member);
|
|
let ty_span = ty.span();
|
|
let ty_default = quote_spanned! {ty_span=> <#ty as std::default::Default>::default};
|
|
let ty_from_element = quote_spanned! {ty_span=> <#ty as From<::xmpp_parsers_core::exports::minidom::Element>>::from};
|
|
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()
|
|
}
|
|
}
|
|
};
|
|
Ok(FieldParsePart {
|
|
tempinit: quote! {
|
|
let mut #tempname: Option<::xmpp_parsers_core::exports::minidom::Element> = None;
|
|
},
|
|
childiter: quote! {
|
|
residual = if #test {
|
|
if #tempname.is_some() {
|
|
return Err(::xmpp_parsers_core::error::Error::ParseError(#duperr));
|
|
}
|
|
#tempname = Some(residual);
|
|
continue;
|
|
} else {
|
|
residual
|
|
};
|
|
},
|
|
value: quote! {
|
|
if let Some(v) = #tempname {
|
|
#ty_from_element(v)
|
|
} else {
|
|
#on_missing
|
|
}
|
|
},
|
|
..FieldParsePart::default()
|
|
})
|
|
}
|
|
|
|
fn build_into_element(
|
|
&self,
|
|
_container_name: &ParentRef,
|
|
_container_namespace_expr: &Expr,
|
|
_member: &Member,
|
|
ty: &Type,
|
|
access: Expr,
|
|
) -> Result<TokenStream> {
|
|
Ok(quote! {
|
|
match <#ty as Into<Option<::xmpp_parsers_core::exports::minidom::Element>>>::into(#access) {
|
|
Some(v) => builder.append(::xmpp_parsers_core::exports::minidom::Node::Element(v)),
|
|
None => builder
|
|
}
|
|
})
|
|
}
|
|
|
|
fn build_set_namespace(
|
|
&self,
|
|
_input: &Ident,
|
|
_ty: &Type,
|
|
_access: Expr,
|
|
) -> Result<TokenStream> {
|
|
Ok(TokenStream::default())
|
|
}
|
|
}
|
|
|
|
/// A field parsed from a XML children, without destructuring them into Rust
|
|
/// data structures.
|
|
///
|
|
/// Maps to `#[xml(elements)]`.
|
|
#[derive(Debug)]
|
|
pub(crate) struct ElementsField {
|
|
/// Selector to choose the child elements to collect.
|
|
///
|
|
/// - If `None`, *all* children are collected. This is equivalent to
|
|
/// `#[xml(elements)]` on a field and may only occur once in a compound.
|
|
/// - If `Some((ns, None))`, all children matching the namespace,
|
|
/// irrespective of the XML name, are collected.
|
|
/// - If `Some((ns, Some(name)))`, only children matching the namespace
|
|
/// and name are collected.
|
|
pub(super) selector: Option<(StaticNamespace, Option<Name>)>,
|
|
}
|
|
|
|
impl ElementsField {
|
|
/// Construct a new `#[xml(elements)]`.
|
|
///
|
|
/// `namespace` and `name` are optional selectors for XML child elements
|
|
/// to match. If `namespace` is not set, `name` must not be set either,
|
|
/// or a compile-time error is returned.
|
|
///
|
|
/// `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,
|
|
namespace: Option<NamespaceRef>,
|
|
name: Option<NameRef>,
|
|
) -> Result<Self> {
|
|
let namespace = match namespace {
|
|
None => {
|
|
if let Some(name) = name {
|
|
return Err(Error::new_spanned(
|
|
name,
|
|
"#[xml(elements(..))] cannot be used with an unnamespaced name",
|
|
));
|
|
}
|
|
None
|
|
}
|
|
Some(NamespaceRef::Static(ns)) => Some(ns),
|
|
Some(NamespaceRef::Dyn(ns)) => {
|
|
return Err(Error::new_spanned(
|
|
ns,
|
|
"dynamic namespaces cannot be used with #[xml(elements)]",
|
|
))
|
|
}
|
|
Some(NamespaceRef::Super(ns)) => {
|
|
return Err(Error::new_spanned(
|
|
ns,
|
|
"collected elements cannot refer to the parent namespace",
|
|
))
|
|
}
|
|
};
|
|
|
|
Ok(Self {
|
|
selector: namespace.map(|x| (x, name.map(|x| x.into()))),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Field for ElementsField {
|
|
fn is_child_wildcard(&self) -> bool {
|
|
match self.selector {
|
|
None => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn build_try_from_element(
|
|
&self,
|
|
_container_name: &ParentRef,
|
|
_container_namespace_expr: &Expr,
|
|
tempname: Ident,
|
|
_member: &Member,
|
|
_ty: &Type,
|
|
) -> Result<FieldParsePart> {
|
|
match self.selector {
|
|
Some((ref field_namespace, ref field_name)) => {
|
|
let childiter = match field_name {
|
|
// namespace match only
|
|
None => quote! {
|
|
residual = if residual.has_ns(#field_namespace) {
|
|
#tempname.push(residual);
|
|
continue;
|
|
} else {
|
|
residual
|
|
};
|
|
},
|
|
Some(field_name) => quote! {
|
|
residual = if residual.is(#field_name, #field_namespace) {
|
|
#tempname.push(residual);
|
|
continue;
|
|
} else {
|
|
residual
|
|
};
|
|
},
|
|
};
|
|
Ok(FieldParsePart {
|
|
tempinit: quote! {
|
|
let mut #tempname: Vec<::xmpp_parsers_core::exports::minidom::Element> = Vec::new();
|
|
},
|
|
childiter,
|
|
value: quote! { #tempname },
|
|
..FieldParsePart::default()
|
|
})
|
|
}
|
|
None => Ok(FieldParsePart {
|
|
tempinit: quote! {
|
|
let mut #tempname: Vec<::xmpp_parsers_core::exports::minidom::Element> = Vec::new();
|
|
},
|
|
childfallback: Some(quote! {
|
|
#tempname.push(residual);
|
|
}),
|
|
value: quote! { #tempname },
|
|
..FieldParsePart::default()
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn build_into_element(
|
|
&self,
|
|
_container_name: &ParentRef,
|
|
_container_namespace_expr: &Expr,
|
|
_member: &Member,
|
|
_ty: &Type,
|
|
access: Expr,
|
|
) -> Result<TokenStream> {
|
|
Ok(quote! {
|
|
builder.append_all(#access.into_iter().map(|elem| ::xmpp_parsers_core::exports::minidom::Node::Element(elem)))
|
|
})
|
|
}
|
|
|
|
fn build_set_namespace(
|
|
&self,
|
|
_input: &Ident,
|
|
_ty: &Type,
|
|
_access: Expr,
|
|
) -> Result<TokenStream> {
|
|
Ok(TokenStream::default())
|
|
}
|
|
}
|