mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-06-17 21:35:25 +02:00
549 lines
18 KiB
Rust
549 lines
18 KiB
Rust
/*!
|
||
Infrastructure for processing fields of compounds.
|
||
|
||
This module contains the Infrastructure necessary to parse Rust fields from
|
||
XML and convert them back to XML.
|
||
|
||
The main file of the module contains the outward, generic types covering all
|
||
use cases. The actual implementations for all but the trivial field kinds are
|
||
sorted into submodules.
|
||
*/
|
||
#[cfg(doc)]
|
||
pub(crate) mod attribute;
|
||
#[cfg(not(doc))]
|
||
mod attribute;
|
||
|
||
#[cfg(doc)]
|
||
pub(crate) mod child;
|
||
#[cfg(not(doc))]
|
||
mod child;
|
||
|
||
#[cfg(doc)]
|
||
pub(crate) mod element;
|
||
#[cfg(not(doc))]
|
||
mod element;
|
||
|
||
#[cfg(doc)]
|
||
pub(crate) mod flag;
|
||
#[cfg(not(doc))]
|
||
mod flag;
|
||
|
||
#[cfg(doc)]
|
||
pub(crate) mod namespace;
|
||
#[cfg(not(doc))]
|
||
mod namespace;
|
||
|
||
#[cfg(doc)]
|
||
pub(crate) mod text;
|
||
#[cfg(not(doc))]
|
||
mod text;
|
||
|
||
use proc_macro2::{Span, TokenStream};
|
||
|
||
use quote::{quote, quote_spanned};
|
||
use syn::{spanned::Spanned, *};
|
||
|
||
use crate::error_message::ParentRef;
|
||
use crate::meta::{ChildMode, NamespaceRef, StaticNamespace, XmlFieldMeta};
|
||
|
||
use self::attribute::AttributeField;
|
||
use self::child::ChildField;
|
||
use self::element::{ElementField, ElementsField};
|
||
use self::flag::FlagField;
|
||
use self::namespace::NamespaceField;
|
||
use self::text::TextField;
|
||
|
||
pub(crate) trait Field: std::fmt::Debug {
|
||
/// Return true if and only if this field is a field collecting all XML
|
||
/// text of the element.
|
||
fn is_text(&self) -> bool {
|
||
false
|
||
}
|
||
|
||
/// Return true if and only if this field is a field collecting *all* XML
|
||
/// children of the element.
|
||
fn is_child_wildcard(&self) -> bool {
|
||
false
|
||
}
|
||
|
||
/// Return true if and only if this field is a field storing the namespace
|
||
/// of the XML element.
|
||
fn is_namespace(&self) -> bool {
|
||
false
|
||
}
|
||
|
||
/// Construct the code necessary to parse data from a `minidom::Element`
|
||
/// into the field.
|
||
///
|
||
/// - `container_name` should be the identifier or path of the containing
|
||
/// compound and is used to generate runtime error messages.
|
||
///
|
||
/// - `container_namespace_expr` may be an expression under which the
|
||
/// parent compound's `::xmpp_parsers_core::DynNamespaceEnum` value is
|
||
/// obtainable, if any. This is needed for fields and compounds
|
||
/// using `#[xml(namespace = super)]`.
|
||
///
|
||
/// - `tempname` must be an identifier which the implementation can freely
|
||
/// use for a temporary variable related to parsing.
|
||
///
|
||
/// - `member` must be the target struct member identifier or index, used
|
||
/// for error messages.
|
||
///
|
||
/// - `ty` must be the field's type.
|
||
fn build_try_from_element(
|
||
&self,
|
||
container_name: &ParentRef,
|
||
container_namespace_expr: &Expr,
|
||
tempname: Ident,
|
||
member: &Member,
|
||
ty: &Type,
|
||
) -> Result<FieldParsePart>;
|
||
|
||
/// Construct an expression which consumes the identifier `builder`, which
|
||
/// must be a minidom `Builder`, and returns it, modified in such a way
|
||
/// that it contains the field's data.
|
||
///
|
||
/// - `container_name` should be the identifier or path of the containing
|
||
/// compound and is used to generate runtime error messages.
|
||
///
|
||
/// - `container_namespace_expr` may be an expression under which the
|
||
/// parent compound's `::xmpp_parsers_core::DynNamespaceEnum` value is
|
||
/// obtainable, if any. This is needed for fields and compounds
|
||
/// using `#[xml(namespace = super)]`.
|
||
///
|
||
/// - `member` must be the target struct member identifier or index, used
|
||
/// for error messages.
|
||
///
|
||
/// - `ty` must be the field's type.
|
||
///
|
||
/// - `access` must be an expression to consume the field's data. It is
|
||
/// evaluated exactly once.
|
||
fn build_into_element(
|
||
&self,
|
||
container_name: &ParentRef,
|
||
container_namespace_expr: &Expr,
|
||
member: &Member,
|
||
ty: &Type,
|
||
access: Expr,
|
||
) -> Result<TokenStream>;
|
||
|
||
/// Construct the code necessary to update the namespace on the field.
|
||
///
|
||
/// - `input` must be the identifier of the variable holding the new
|
||
/// namespace to set.
|
||
///
|
||
/// - `ty` must be the field's type.
|
||
///
|
||
/// - `access` must be an expression which can be written to, which allows
|
||
/// updating the field's value.
|
||
fn build_set_namespace(&self, input: &Ident, ty: &Type, access: Expr) -> Result<TokenStream>;
|
||
}
|
||
|
||
/// Code slices necessary for parsing a single field.
|
||
#[derive(Default)]
|
||
pub(crate) struct FieldParsePart {
|
||
/// Zero or more statements which check whether an attribute is allowed.
|
||
/// These should be `if` statements where the body consists of `continue`
|
||
/// if and only if that attribute is allowed.
|
||
///
|
||
/// Typically only generated by FieldKind::Attribute.
|
||
pub(crate) attrcheck: TokenStream,
|
||
|
||
/// Zero or more statements to initialize a temporary variable before the
|
||
/// child element parsing loop.
|
||
///
|
||
/// For child-element-related fields, this will generally create a
|
||
/// temporary `Option<..>` or `Vec<..>` or so, into which the target
|
||
/// data is then collected.
|
||
pub(crate) tempinit: TokenStream,
|
||
|
||
/// An expression, which consumes the identifier `residual` and either
|
||
/// returns it unchanged, calls `continue`, or returns with an error.
|
||
///
|
||
/// Generally, this will be some kind of
|
||
/// `if residual.is(..) { .. } else { residual }` construct.
|
||
pub(crate) childiter: TokenStream,
|
||
|
||
/// Zero or more statements which are added to the end of the child
|
||
/// parsing loop.
|
||
///
|
||
/// Only one field may return this. If multiple fields return this, the
|
||
/// derive macro panics. The invariant that only one field may emit this
|
||
/// should already be enforced by `Compound::new_struct`.
|
||
pub(crate) childfallback: Option<TokenStream>,
|
||
|
||
/// The expression which evaluates to the final value of the field.
|
||
pub(crate) value: TokenStream,
|
||
}
|
||
|
||
/// A XML namespace as declared on a field.
|
||
#[derive(Clone, Debug)]
|
||
pub(crate) enum FieldNamespace {
|
||
/// The namespace is a static string.
|
||
Static(
|
||
/// The namespace as [`Path`] pointing at the static string.
|
||
StaticNamespace,
|
||
),
|
||
|
||
/// Instead of a fixed namespace, the namespace of the parent is used.
|
||
///
|
||
/// This is only allowed inside enum variants or structs with a
|
||
/// `#[xml(namespace = dyn)]` declaration, i.e. using the
|
||
/// [`crate::structs::StructNamespace::Dyn`] variant.
|
||
Super(
|
||
/// The `super` token from the `#[xml(namespace = super)]` meta.
|
||
Token![super],
|
||
),
|
||
}
|
||
|
||
impl From<FieldNamespace> for NamespaceRef {
|
||
fn from(other: FieldNamespace) -> Self {
|
||
match other {
|
||
FieldNamespace::Static(ns) => Self::Static(ns),
|
||
FieldNamespace::Super(ns) => Self::Super(ns),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
pub(crate) struct Ignore;
|
||
|
||
impl Field for Ignore {
|
||
fn build_try_from_element(
|
||
&self,
|
||
_container_name: &ParentRef,
|
||
_container_namespace_expr: &Expr,
|
||
_tempname: Ident,
|
||
_member: &Member,
|
||
ty: &Type,
|
||
) -> Result<FieldParsePart> {
|
||
let ty_default = quote_spanned! {ty.span()=> <#ty as std::default::Default>::default};
|
||
Ok(FieldParsePart {
|
||
value: quote! { #ty_default() },
|
||
..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 })
|
||
}
|
||
|
||
fn build_set_namespace(
|
||
&self,
|
||
_input: &Ident,
|
||
_ty: &Type,
|
||
_access: Expr,
|
||
) -> Result<TokenStream> {
|
||
Ok(TokenStream::default())
|
||
}
|
||
}
|
||
|
||
/// Construct a `Field` implementation by processing the options in the meta.
|
||
///
|
||
/// *Note*: Explicit type specifications are rejected by this function.
|
||
/// Those must have been taken out of the `meta` before calling this
|
||
/// function. The compile-time error message emitted by this function is
|
||
/// not very useful in this case, so if you do not want to support
|
||
/// explicit types, you should take them out at the call site and emit
|
||
/// a more useful error there.
|
||
fn new_field(
|
||
span: &Span,
|
||
field_ident: Option<&Ident>,
|
||
field_ty: &Type,
|
||
meta: XmlFieldMeta,
|
||
) -> Result<Box<dyn Field>> {
|
||
match meta {
|
||
XmlFieldMeta::Attribute {
|
||
name,
|
||
default_,
|
||
codec,
|
||
ty,
|
||
} => {
|
||
if let Some(ty) = ty {
|
||
return Err(Error::new_spanned(ty, "cannot set attribute type here"));
|
||
};
|
||
Ok(Box::new(AttributeField::new(
|
||
span,
|
||
field_ident,
|
||
name,
|
||
default_,
|
||
codec,
|
||
)?))
|
||
}
|
||
XmlFieldMeta::Child {
|
||
mode,
|
||
namespace,
|
||
name,
|
||
extract,
|
||
default_,
|
||
skip_if,
|
||
codec,
|
||
} => Ok(Box::new(ChildField::new(
|
||
span, mode, namespace, name, extract, default_, skip_if, codec, field_ty,
|
||
)?)),
|
||
XmlFieldMeta::Text { codec, ty } => {
|
||
if let Some(ty) = ty {
|
||
return Err(Error::new_spanned(ty, "cannot set text type here"));
|
||
};
|
||
Ok(Box::new(TextField::new(span, codec)?))
|
||
}
|
||
XmlFieldMeta::Namespace => Ok(Box::new(NamespaceField::new())),
|
||
XmlFieldMeta::Element {
|
||
namespace,
|
||
name,
|
||
default_,
|
||
} => Ok(Box::new(ElementField::new(
|
||
span, namespace, name, default_,
|
||
)?)),
|
||
XmlFieldMeta::Elements { namespace, name } => {
|
||
Ok(Box::new(ElementsField::new(span, namespace, name)?))
|
||
}
|
||
XmlFieldMeta::Flag { namespace, name } => {
|
||
Ok(Box::new(FlagField::new(span, namespace, name)?))
|
||
}
|
||
XmlFieldMeta::Ignore => Ok(Box::new(Ignore)),
|
||
}
|
||
}
|
||
|
||
/* impl FieldKind {
|
||
/// Return true if the field kind is equal to [`Self::Text`].
|
||
pub(crate) fn is_text(&self) -> bool {
|
||
match self {
|
||
Self::Text { .. } => true,
|
||
_ => false,
|
||
}
|
||
}
|
||
|
||
/// Return true if the field kind is a [`Self::Elements`] which matches
|
||
/// all child elements.
|
||
pub(crate) fn is_collect_wildcard(&self) -> bool {
|
||
match self {
|
||
Self::Elements(ElementsField { selector: None }) => true,
|
||
_ => false,
|
||
}
|
||
}
|
||
} */
|
||
|
||
/// All data necessary to generate code to convert a Rust field to or from
|
||
/// XML.
|
||
#[derive(Debug)]
|
||
pub(crate) struct FieldDef {
|
||
/// The span of the `#[xml]` meta defining this field.
|
||
pub(crate) span: Span,
|
||
|
||
/// The identifier (in a named compound) or index (in an unnamed/tuple-like
|
||
/// compound) of the field.
|
||
pub(crate) ident: Member,
|
||
|
||
/// The type of the field.
|
||
pub(crate) ty: Type,
|
||
|
||
/// The way the field is mapped to XML.
|
||
pub(crate) kind: Box<dyn Field>,
|
||
}
|
||
|
||
fn try_unwrap_option_type(ty: Type) -> Type {
|
||
match ty {
|
||
syn::Type::Path(ty) => {
|
||
if ty.path.segments.len() != 1 {
|
||
return syn::Type::Path(ty);
|
||
}
|
||
let segment = &ty.path.segments[0];
|
||
if segment.ident != "Option" {
|
||
return syn::Type::Path(ty);
|
||
}
|
||
match segment.arguments {
|
||
PathArguments::AngleBracketed(ref args) => {
|
||
if args.args.len() != 1 {
|
||
return syn::Type::Path(ty);
|
||
}
|
||
let arg = &args.args[0];
|
||
match arg {
|
||
// finally found the inner of Option<T>
|
||
GenericArgument::Type(ty) => ty.clone(),
|
||
_ => return syn::Type::Path(ty),
|
||
}
|
||
}
|
||
_ => return syn::Type::Path(ty),
|
||
}
|
||
}
|
||
other => other,
|
||
}
|
||
}
|
||
|
||
impl FieldDef {
|
||
/// Generate a [`FieldDef`] as extract from an [`XmlFieldMeta`]
|
||
/// specification.
|
||
///
|
||
/// This is used to build a [`crate::compound::Compound`] used to parse
|
||
/// the extract. As it would otherwise be an insane amount of duplication,
|
||
/// the compound parsing logic is re-used.
|
||
///
|
||
/// `extract` must be the extract specification parsed from the attribute.
|
||
/// `index` must be the number of the field: all extract fields are
|
||
/// unnumbered.
|
||
///
|
||
/// `single_extract_type` should be the type of the field this extract is
|
||
/// assigned to, in case that field is a `#[xml(child)]` (i.e. a
|
||
/// non-collection field). If given and the user did not specify a type
|
||
/// on the extract, that type is used (instead of the fallback to
|
||
/// `String`).
|
||
///
|
||
/// However, there's one minor catch: If the type matches `Option<T>`
|
||
/// verbatimly (in particular not `std::option::Option<T>`), the inner type
|
||
/// `T` is used instead. This is immensely helpful for optional child
|
||
/// extracts and can be overridden by the user by explicitly specifying a
|
||
/// type.
|
||
fn from_extract(
|
||
span: Span,
|
||
mut extract: XmlFieldMeta,
|
||
index: u32,
|
||
single_extract_type: Option<Type>,
|
||
) -> Result<Self> {
|
||
let string_ty: Type =
|
||
parse_str("::std::string::String").expect("cannot construct string type");
|
||
let ty = extract.determine_type();
|
||
let ty = ty
|
||
.or(single_extract_type.map(try_unwrap_option_type))
|
||
.unwrap_or(string_ty);
|
||
let kind = new_field(
|
||
&span, None, // field_ident is always none here, we use unnamed fields.
|
||
&ty, extract,
|
||
)?;
|
||
Ok(Self {
|
||
span,
|
||
ident: Member::Unnamed(Index {
|
||
index,
|
||
span: Span::call_site(),
|
||
}),
|
||
ty,
|
||
kind,
|
||
})
|
||
}
|
||
|
||
/// Return a reference to the field's type, if and only if it is a
|
||
/// [`NamespaceField`][`crate::field::namespace::NamespaceField`].
|
||
pub(crate) fn namespace_field_type(&self) -> Option<&Type> {
|
||
if self.kind.is_namespace() {
|
||
Some(&self.ty)
|
||
} else {
|
||
None
|
||
}
|
||
}
|
||
|
||
/// Generate a [`FieldDef`] from a [`syn::Field`].
|
||
///
|
||
/// `index` must be the number of the field within the compound, starting
|
||
/// at zero. It is used only for unnamed fields.
|
||
///
|
||
/// This parses the attributes using [`XmlFieldMeta`].
|
||
pub(crate) fn from_field(field: &syn::Field, index: u32) -> Result<Self> {
|
||
let mut meta: Option<(XmlFieldMeta, Span)> = None;
|
||
for attr in field.attrs.iter() {
|
||
if !attr.path().is_ident("xml") {
|
||
continue;
|
||
}
|
||
if meta.is_some() {
|
||
return Err(Error::new_spanned(
|
||
attr,
|
||
"only one #[xml(..)] attribute per field allowed.",
|
||
));
|
||
}
|
||
|
||
meta = Some((XmlFieldMeta::parse_from_attribute(attr)?, attr.span()));
|
||
}
|
||
|
||
let Some((mut meta, span)) = meta else {
|
||
return Err(Error::new_spanned(
|
||
field,
|
||
"exactly one #[xml(..)] attribute per field required.",
|
||
));
|
||
};
|
||
|
||
let ident: Member = match field.ident.as_ref() {
|
||
Some(v) => Member::Named(v.clone()),
|
||
None => Member::Unnamed(Index {
|
||
index,
|
||
span: Span::call_site(),
|
||
}),
|
||
};
|
||
|
||
if let Some(ty) = meta.take_type() {
|
||
return Err(Error::new_spanned(
|
||
ty,
|
||
"specifying the type on struct or enum variant fields is redundant and not allowed",
|
||
));
|
||
}
|
||
|
||
let kind = new_field(&span, field.ident.as_ref(), &field.ty, meta)?;
|
||
|
||
Ok(Self {
|
||
span,
|
||
ident,
|
||
ty: field.ty.clone(),
|
||
kind,
|
||
})
|
||
}
|
||
|
||
/// Construct a [`FieldParsePart`] which creates the field's value from
|
||
/// XML.
|
||
pub(crate) fn build_try_from_element(
|
||
&self,
|
||
container_name: &ParentRef,
|
||
container_namespace_expr: &Expr,
|
||
) -> Result<FieldParsePart> {
|
||
let ident = &self.ident;
|
||
let ty = &self.ty;
|
||
let tempname = quote::format_ident!("__field_init_{}", ident);
|
||
self.kind.build_try_from_element(
|
||
container_name,
|
||
container_namespace_expr,
|
||
tempname,
|
||
ident,
|
||
ty,
|
||
)
|
||
}
|
||
|
||
/// Construct a [`TokenStream`] which propagates the parent's namespace
|
||
/// to all children with `namespace = super` as well as the namespace
|
||
/// field itself.
|
||
pub(crate) fn build_set_namespace(
|
||
&self,
|
||
input: &Ident,
|
||
mut access_field: impl FnMut(Member) -> Expr,
|
||
) -> Result<TokenStream> {
|
||
let member = access_field(self.ident.clone());
|
||
let ty = &self.ty;
|
||
self.kind.build_set_namespace(input, ty, member)
|
||
}
|
||
|
||
/// Construct an expression which consumes the ident `builder` and returns
|
||
/// it, after modifying it to contain the field's data.
|
||
///
|
||
/// The field's data is accessed using the `Expr` returned by
|
||
/// `access_field` when passed the identifier or index of the field.
|
||
///
|
||
/// The caller is responsible to set up the enviroment where the returned
|
||
/// `TokenStream` is used so that the expressions returned by
|
||
/// `access_field` and the `builder` ident are accessible.
|
||
pub(crate) fn build_into_element(
|
||
&self,
|
||
container_name: &ParentRef,
|
||
container_namespace_expr: &Expr,
|
||
mut access_field: impl FnMut(Member) -> Expr,
|
||
) -> Result<TokenStream> {
|
||
let member = &self.ident;
|
||
let ident = access_field(member.clone());
|
||
let ty = &self.ty;
|
||
self.kind
|
||
.build_into_element(container_name, container_namespace_expr, member, ty, ident)
|
||
}
|
||
}
|