1
0
mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git synced 2024-06-09 01:34:03 +02:00
xmpp-rs/parsers-macros/src/field/element.rs
Jonas Schäfer decbeb351f parsers-macros: introduce Field trait and object
This simplifies the code of FieldDef and makes it more extensible for
future ideas.
2024-04-05 15:53:48 +02:00

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