mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-06-18 21:55:57 +02:00
parsers-macros: implement #[xml(namespace = super)]
on extracted fields
This expands the usability of these fields to structs with `#[xml(namespace = dyn)]`.
This commit is contained in:
parent
fbac723c4d
commit
55e4287420
|
@ -195,7 +195,16 @@ The following field kinds are available:
|
|||
`IntoOptionalXmlText` (for [`IntoXml`]).
|
||||
|
||||
- `name = ..` (required): The XML name of the child to match.
|
||||
- `namespace = ..` (required): The XML namespace of the child to match.
|
||||
- `namespace = ..` (required): The XML namespace of the child to match. This
|
||||
can be one of the following:
|
||||
|
||||
- A path referring to a `&'static str` `static` which contains the
|
||||
namespace URI of the XML element represented by the struct.
|
||||
- `super`: Only usable inside compounds with `#[xml(namespace = dyn)]`,
|
||||
using `#[xml(namespace = super)]` on an extracted field allows to match
|
||||
the field's child's namespace with the dynamically determined namespace
|
||||
of the parent (both during serialisation and during deserialisation).
|
||||
|
||||
- `extract(..)` (required): Specification of data to extract. This must
|
||||
contain one of:
|
||||
|
||||
|
@ -532,7 +541,11 @@ impl<A: FromXmlText + IntoXmlText, B: FromXmlText + IntoXmlText> XmlDataItem<(St
|
|||
/// where the same element occurs in multiple different namespaces. The
|
||||
/// namespace is then kept in the field and is thus available at serialisation
|
||||
/// time.
|
||||
pub trait DynNamespaceEnum: Sized {
|
||||
///
|
||||
/// This trait depends on `PartialEq<str>`; this is used to compare a value
|
||||
/// against the value of an `xmlns` attribute without requiring to go through
|
||||
/// (potentially costly and also error-inducing) [`Self::from_xml_text`].
|
||||
pub trait DynNamespaceEnum: Sized + PartialEq<str> {
|
||||
/// Parse the namespace from the `xmlns` attribute data.
|
||||
///
|
||||
/// This should return [`Mismatch`][`crate::error::DynNamespaceError`] for
|
||||
|
|
|
@ -37,6 +37,14 @@ pub(crate) enum CompoundNamespace {
|
|||
/// The member of the namespace field.
|
||||
member: Member,
|
||||
},
|
||||
|
||||
/// Instead of a fixed namespace, the namespace is the same as the parent
|
||||
/// element.
|
||||
///
|
||||
/// This is only ever generated internally: structs, enums, and enum
|
||||
/// variants cannot be declared with a `super` namespace (it doesn't make
|
||||
/// sense).
|
||||
Super(Token![super]),
|
||||
}
|
||||
|
||||
/// An struct or enum variant's contents.
|
||||
|
@ -242,12 +250,7 @@ impl Compound {
|
|||
));
|
||||
}
|
||||
}
|
||||
Some(NamespaceRef::Super(namespace)) => {
|
||||
return Err(Error::new_spanned(
|
||||
namespace,
|
||||
"only fields can refer to the parent namespace",
|
||||
))
|
||||
}
|
||||
Some(NamespaceRef::Super(ns)) => Some(CompoundNamespace::Super(ns)),
|
||||
};
|
||||
|
||||
Ok(Self::Struct {
|
||||
|
@ -354,11 +357,18 @@ impl Compound {
|
|||
///
|
||||
/// Otherwise, `T` is a tuple of the fields inside the compound in
|
||||
/// declaration order.
|
||||
///
|
||||
/// If the compound's namespace is `super`, the `parent_namespace_expr`
|
||||
/// must be an expression which can be evaluated in order to obtain a
|
||||
/// value implementing `::xmpp_parsers_core::DynNamespaceEnum`. That value
|
||||
/// must reflect the parent element's namespace, as it is used to check if
|
||||
/// the element at hand is in the parent's namespace.
|
||||
pub(crate) fn build_try_from_element(
|
||||
self,
|
||||
name: &ParentRef,
|
||||
item_namespace: Option<&CompoundNamespace>,
|
||||
residual: &Ident,
|
||||
parent_namespace_expr: Option<&Expr>,
|
||||
) -> Result<TokenStream> {
|
||||
match self {
|
||||
Self::Transparent { ty } => Ok(quote! {
|
||||
|
@ -384,6 +394,19 @@ impl Compound {
|
|||
return Err(::xmpp_parsers_core::error::Error::ParseError(concat!("Unknown child in ", #xml_name, " element.")));
|
||||
};
|
||||
let namespace_tempname = quote::format_ident!("__namespace_field");
|
||||
let namespace_expr = match namespace {
|
||||
CompoundNamespace::Static(ns) => Some(Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: ns.clone().into(),
|
||||
})),
|
||||
CompoundNamespace::Dyn { .. } => Some(Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: namespace_tempname.clone().into(),
|
||||
})),
|
||||
CompoundNamespace::Super(_) => parent_namespace_expr.cloned(),
|
||||
};
|
||||
let mut had_fallback: bool = false;
|
||||
|
||||
for field in fields {
|
||||
|
@ -394,7 +417,11 @@ impl Compound {
|
|||
attrcheck: field_attrcheck,
|
||||
value,
|
||||
childfallback: field_childfallback,
|
||||
} = field.build_try_from_element(name, &namespace_tempname)?;
|
||||
} = field.build_try_from_element(
|
||||
name,
|
||||
&namespace_tempname,
|
||||
namespace_expr.as_ref(),
|
||||
)?;
|
||||
|
||||
attrcheck = quote! { #attrcheck #field_attrcheck };
|
||||
tempinit = quote! { #tempinit #field_tempinit };
|
||||
|
@ -468,6 +495,21 @@ impl Compound {
|
|||
Err(::xmpp_parsers_core::error::DynNamespaceError::Mismatch) => Err(#residual),
|
||||
}
|
||||
}),
|
||||
CompoundNamespace::Super(ns) => match parent_namespace_expr {
|
||||
Some(expr) => Ok(quote! {
|
||||
if ::std::cmp::PartialEq::eq(&#expr, #residual.ns().as_str()) && #residual.name() == #xml_name {
|
||||
#body
|
||||
} else {
|
||||
Err(#residual)
|
||||
}
|
||||
}),
|
||||
None => {
|
||||
return Err(Error::new_spanned(
|
||||
ns,
|
||||
"#[xml(namespace = super)] cannot be used here.",
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -488,11 +530,18 @@ impl Compound {
|
|||
/// `item_namespace` must be `Some`; otherwise, a compile-time error is
|
||||
/// generated pointing out that enum variants must have a namespace set
|
||||
/// if their enum has no namespace set.
|
||||
///
|
||||
/// If the compound's namespace is `super`, the `parent_namespace_expr`
|
||||
/// must be an expression which can be evaluated in order to obtain a
|
||||
/// value implementing `::xmpp_parsers_core::DynNamespaceEnum`. That value
|
||||
/// must reflect the parent element's namespace, as it is used to obtain
|
||||
/// the namespace of the compound's own element.
|
||||
pub(crate) fn build_into_element(
|
||||
self,
|
||||
item_name: &ParentRef,
|
||||
item_namespace: Option<&CompoundNamespace>,
|
||||
mut access_field: impl FnMut(Member) -> Expr,
|
||||
parent_namespace_expr: Option<&Expr>,
|
||||
) -> Result<TokenStream> {
|
||||
match self {
|
||||
Self::Transparent { ty: _ } => {
|
||||
|
@ -510,30 +559,59 @@ impl Compound {
|
|||
fields,
|
||||
} => {
|
||||
let xml_name = name;
|
||||
let builder_init = match Self::need_namespace(
|
||||
let (builder_init, namespace_expr) = match Self::need_namespace(
|
||||
item_name,
|
||||
namespace.as_ref(),
|
||||
item_namespace,
|
||||
)? {
|
||||
CompoundNamespace::Static(xml_namespace) => {
|
||||
CompoundNamespace::Static(xml_namespace) => (
|
||||
quote! {
|
||||
::xmpp_parsers_core::exports::minidom::Element::builder(#xml_name, #xml_namespace)
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: xml_namespace.clone(),
|
||||
})),
|
||||
),
|
||||
CompoundNamespace::Dyn { member, ty, .. } => {
|
||||
let ident = access_field(member.clone());
|
||||
quote! {
|
||||
::xmpp_parsers_core::exports::minidom::Element::builder(
|
||||
#xml_name,
|
||||
<#ty as ::xmpp_parsers_core::DynNamespaceEnum>::into_xml_text(#ident),
|
||||
)
|
||||
}
|
||||
(
|
||||
quote! {
|
||||
::xmpp_parsers_core::exports::minidom::Element::builder(
|
||||
#xml_name,
|
||||
<#ty as ::xmpp_parsers_core::DynNamespaceEnum>::into_xml_text(#ident.clone()),
|
||||
)
|
||||
},
|
||||
Some(syn::parse2(quote! { #ident.clone() })?),
|
||||
)
|
||||
}
|
||||
CompoundNamespace::Super(ns) => match parent_namespace_expr {
|
||||
Some(expr) => (
|
||||
quote! {
|
||||
::xmpp_parsers_core::exports::minidom::Element::builder(
|
||||
#xml_name,
|
||||
::xmpp_parsers_core::DynNamespaceEnum::into_xml_text(#expr.clone()),
|
||||
)
|
||||
},
|
||||
Some(expr.clone()),
|
||||
),
|
||||
None => {
|
||||
return Err(Error::new_spanned(
|
||||
ns,
|
||||
"#[xml(namespace = super)] cannot be used here.",
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let mut build = quote! {};
|
||||
for field in fields {
|
||||
let field_build = field.build_into_element(item_name, &mut access_field)?;
|
||||
let field_build = field.build_into_element(
|
||||
item_name,
|
||||
&mut access_field,
|
||||
namespace_expr.as_ref(),
|
||||
)?;
|
||||
build = quote! {
|
||||
#build
|
||||
builder = #field_build;
|
||||
|
|
|
@ -189,6 +189,7 @@ impl EnumDef {
|
|||
match namespace {
|
||||
Some(CompoundNamespace::Static(_)) => (),
|
||||
None => return Err(syn::Error::new(meta.span, "fallback variants can only be used on enums with a static namespace set. use #[xml(namespace = ..)] on the enum itself.")),
|
||||
Some(CompoundNamespace::Super(_)) => unreachable!(),
|
||||
Some(CompoundNamespace::Dyn { dyn_tok, .. })=> return Err(syn::Error::new_spanned(dyn_tok, "fallback variants cannot be combined with dynamic namespaces")),
|
||||
}
|
||||
}
|
||||
|
@ -199,6 +200,7 @@ impl EnumDef {
|
|||
match namespace {
|
||||
Some(CompoundNamespace::Static(_)) => (),
|
||||
None => return Err(syn::Error::new_spanned(exhaustive, "exhaustive enums must have a static namespace set. use #[xml(namespace = ..)] on the enum itself.")),
|
||||
Some(CompoundNamespace::Super(_)) => unreachable!(),
|
||||
Some(CompoundNamespace::Dyn{ dyn_tok, .. }) => return Err(syn::Error::new_spanned(dyn_tok, "exhaustive enums cannot be combined with dynamic namespaces")),
|
||||
}
|
||||
}
|
||||
|
@ -267,6 +269,7 @@ impl EnumDef {
|
|||
.into()),
|
||||
xml_namespace.as_ref(),
|
||||
residual,
|
||||
None,
|
||||
)?;
|
||||
|
||||
iter = quote! {
|
||||
|
@ -343,6 +346,7 @@ impl EnumDef {
|
|||
&(Path::from(ty_ident.clone()).into()),
|
||||
self.namespace.as_ref(),
|
||||
map_ident,
|
||||
None,
|
||||
)?;
|
||||
|
||||
matchers = quote! {
|
||||
|
|
|
@ -7,9 +7,9 @@ use syn::*;
|
|||
|
||||
use crate::compound::Compound;
|
||||
use crate::error_message::{self, ParentRef};
|
||||
use crate::meta::{ExtractMeta, Flag, NameRef, NamespaceRef, StaticNamespace, XmlCompoundMeta};
|
||||
use crate::meta::{ExtractMeta, Flag, NameRef, NamespaceRef, XmlCompoundMeta};
|
||||
|
||||
use super::{ChildMode, FieldDef, FieldParsePart};
|
||||
use super::{ChildMode, FieldDef, FieldNamespace, FieldParsePart};
|
||||
|
||||
/// Definition of a child data extraction.
|
||||
///
|
||||
|
@ -38,13 +38,13 @@ impl ExtractDef {
|
|||
/// order they are extracted.
|
||||
fn new(
|
||||
span: Span,
|
||||
namespace: StaticNamespace,
|
||||
namespace: FieldNamespace,
|
||||
name: NameRef,
|
||||
parts: Vec<ExtractMeta>,
|
||||
) -> Result<Self> {
|
||||
let meta = XmlCompoundMeta {
|
||||
span: span.clone(),
|
||||
namespace: Some(NamespaceRef::Static(namespace.clone())),
|
||||
namespace: Some(namespace.clone().into()),
|
||||
name: Some(name.clone()),
|
||||
fallback: Flag::Absent,
|
||||
transparent: Flag::Absent,
|
||||
|
@ -104,14 +104,21 @@ impl ExtractDef {
|
|||
/// them as "limitations" in the API/attribute reference).
|
||||
///
|
||||
/// It works nicely though.
|
||||
///
|
||||
/// 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,
|
||||
name: &ParentRef,
|
||||
residual: &Ident,
|
||||
target_ty: Option<&Type>,
|
||||
parent_namespace_expr: Option<&Expr>,
|
||||
) -> Result<TokenStream> {
|
||||
let nfields = self.parts.field_count().unwrap();
|
||||
|
||||
let ns_ident = Ident::new("__extract_namespace", Span::call_site());
|
||||
|
||||
let repack = if nfields == 1 {
|
||||
if let Some(target_ty) = target_ty {
|
||||
// special case: single-field extract on a #[child(..)] is
|
||||
|
@ -137,12 +144,38 @@ impl ExtractDef {
|
|||
quote! { data }
|
||||
};
|
||||
|
||||
let build = self.parts.build_try_from_element(name, None, residual)?;
|
||||
// we reassign and wrap the parent namespace here in order to avoid
|
||||
// any issues with (not yet implemented) nested extractions.
|
||||
let (ns_rename, parent_namespace_expr) =
|
||||
if let Some(parent_namespace_expr) = parent_namespace_expr {
|
||||
(
|
||||
quote! {
|
||||
let #ns_ident = (#parent_namespace_expr).clone();
|
||||
},
|
||||
Some(Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: ns_ident.into(),
|
||||
})),
|
||||
)
|
||||
} else {
|
||||
(quote! {}, None)
|
||||
};
|
||||
|
||||
let build = self.parts.build_try_from_element(
|
||||
name,
|
||||
None,
|
||||
residual,
|
||||
parent_namespace_expr.as_ref(),
|
||||
)?;
|
||||
|
||||
Ok(quote! {
|
||||
match #build {
|
||||
Ok(data) => Ok(#repack),
|
||||
Err(e) => Err(e),
|
||||
{
|
||||
#ns_rename
|
||||
match #build {
|
||||
Ok(data) => Ok(#repack),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -163,20 +196,42 @@ impl ExtractDef {
|
|||
///
|
||||
/// Otherwise, the value is used nearly as-is and forwarded to the
|
||||
/// implementation returned by [`Compound::build_into_element`].
|
||||
///
|
||||
/// 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,
|
||||
name: &ParentRef,
|
||||
field: &Expr,
|
||||
target_ty: Option<&Type>,
|
||||
parent_namespace_expr: Option<&Expr>,
|
||||
) -> Result<TokenStream> {
|
||||
let nfields = self.parts.field_count().unwrap();
|
||||
|
||||
let repack = if nfields == 1 {
|
||||
quote! { let data = (data,); }
|
||||
let ident = Ident::new("__extract_data", Span::call_site());
|
||||
let ns_ident = Ident::new("__extract_namespace", Span::call_site());
|
||||
let mut repack = if nfields == 1 {
|
||||
quote! { let #ident = (#ident,); }
|
||||
} else {
|
||||
quote! { let data = data; }
|
||||
quote! { let #ident = #ident; }
|
||||
};
|
||||
|
||||
// we reassign and wrap the parent namespace here in order to avoid
|
||||
// any issues with (not yet implemented) nested extractions.
|
||||
let parent_namespace_expr = if let Some(parent_namespace_expr) = parent_namespace_expr {
|
||||
repack = quote! {
|
||||
#repack
|
||||
let #ns_ident = (#parent_namespace_expr).clone();
|
||||
};
|
||||
Some(Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: ns_ident.into(),
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ident = Ident::new("data", Span::call_site());
|
||||
|
||||
let eval = if nfields == 1 {
|
||||
if let Some(target_ty) = target_ty {
|
||||
|
@ -205,25 +260,30 @@ impl ExtractDef {
|
|||
quote! { Some(#field) }
|
||||
};
|
||||
|
||||
let build = self.parts.build_into_element(name, None, |member| {
|
||||
Expr::Field(ExprField {
|
||||
attrs: Vec::new(),
|
||||
dot_token: syn::token::Dot {
|
||||
spans: [Span::call_site()],
|
||||
},
|
||||
base: Box::new(Expr::Path(ExprPath {
|
||||
let build = self.parts.build_into_element(
|
||||
name,
|
||||
None,
|
||||
|member| {
|
||||
Expr::Field(ExprField {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: Path::from(ident.clone()),
|
||||
})),
|
||||
member,
|
||||
})
|
||||
})?;
|
||||
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,
|
||||
})
|
||||
},
|
||||
parent_namespace_expr.as_ref(),
|
||||
)?;
|
||||
|
||||
Ok(quote! {
|
||||
{
|
||||
match #eval {
|
||||
Some(data) => {
|
||||
Some(#ident) => {
|
||||
#repack
|
||||
Some(#build)
|
||||
}
|
||||
|
@ -291,19 +351,14 @@ impl ChildField {
|
|||
"namespace must be specified on extracted fields",
|
||||
))
|
||||
}
|
||||
Some(NamespaceRef::Static(ns)) => ns,
|
||||
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)) => {
|
||||
return Err(Error::new_spanned(
|
||||
ns,
|
||||
"extracted fields cannot refer to parent namespaces",
|
||||
))
|
||||
}
|
||||
Some(NamespaceRef::Super(ns)) => FieldNamespace::Super(ns),
|
||||
};
|
||||
let Some(name) = name else {
|
||||
return Err(Error::new(
|
||||
|
@ -368,12 +423,18 @@ impl ChildField {
|
|||
/// - `ty` must be the field's type. If this field is not an extract, the
|
||||
/// type's `FromXml` implementation is used to destructure matching XML
|
||||
/// element(s).
|
||||
///
|
||||
/// - `parent_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)]`.
|
||||
pub(super) fn build_try_from_element(
|
||||
self,
|
||||
name: &ParentRef,
|
||||
tempname: Ident,
|
||||
ident: Member,
|
||||
ty: Type,
|
||||
parent_namespace_expr: Option<&Expr>,
|
||||
) -> Result<FieldParsePart> {
|
||||
match self.mode {
|
||||
ChildMode::Single => {
|
||||
|
@ -394,6 +455,7 @@ impl ChildField {
|
|||
&name.child(ident),
|
||||
&Ident::new("residual", Span::call_site()),
|
||||
Some(&ty),
|
||||
parent_namespace_expr,
|
||||
)?;
|
||||
|
||||
Ok(FieldParsePart {
|
||||
|
@ -458,6 +520,7 @@ impl ChildField {
|
|||
&name.child(ident),
|
||||
&Ident::new("residual", Span::call_site()),
|
||||
None,
|
||||
parent_namespace_expr,
|
||||
)?;
|
||||
let item_ty: Type = syn::parse2(quote! {
|
||||
<#ty as IntoIterator>::Item
|
||||
|
@ -515,18 +578,27 @@ impl ChildField {
|
|||
/// - `ident` must be an expression to consume the field's data. It is
|
||||
/// evaluated exactly once.
|
||||
/// - `ty` must be the field's type.
|
||||
/// - `parent_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)]`.
|
||||
pub(super) fn build_into_element(
|
||||
self,
|
||||
name: &ParentRef,
|
||||
member: Member,
|
||||
ident: Expr,
|
||||
ty: Type,
|
||||
parent_namespace_expr: Option<&Expr>,
|
||||
) -> Result<TokenStream> {
|
||||
match self.mode {
|
||||
ChildMode::Single => match self.extract {
|
||||
Some(extract) => {
|
||||
let assemble =
|
||||
extract.build_assemble(&name.child(member), &ident, Some(&ty))?;
|
||||
let assemble = extract.build_assemble(
|
||||
&name.child(member),
|
||||
&ident,
|
||||
Some(&ty),
|
||||
parent_namespace_expr,
|
||||
)?;
|
||||
Ok(quote! {
|
||||
match #assemble {
|
||||
Some(el) => builder.append(::xmpp_parsers_core::exports::minidom::Node::Element(el)),
|
||||
|
@ -551,6 +623,7 @@ impl ChildField {
|
|||
path: Path::from(Ident::new("data", Span::call_site())),
|
||||
}),
|
||||
None,
|
||||
parent_namespace_expr,
|
||||
)?;
|
||||
let item_ty: Type = syn::parse2(quote! {
|
||||
<#ty as IntoIterator>::Item
|
||||
|
|
|
@ -21,7 +21,7 @@ use syn::{spanned::Spanned, *};
|
|||
|
||||
use crate::compound::Compound;
|
||||
use crate::error_message::ParentRef;
|
||||
use crate::meta::{ChildMode, ExtractMeta, Flag, XmlFieldMeta};
|
||||
use crate::meta::{ChildMode, ExtractMeta, Flag, NamespaceRef, StaticNamespace, XmlFieldMeta};
|
||||
|
||||
use self::attribute::AttributeField;
|
||||
use self::child::ChildField;
|
||||
|
@ -66,6 +66,36 @@ pub(crate) struct FieldParsePart {
|
|||
pub(crate) value: TokenStream,
|
||||
}
|
||||
|
||||
/// A XML namespace as declared on a field.
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "debug", derive(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 compounds with a `#[xml(namespace = dyn)]`
|
||||
/// declaration, i.e. using the
|
||||
/// [`crate::compound::CompoundNamespace::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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumeration of possible XML data ↔ Rust field mappings.
|
||||
///
|
||||
/// This matches the processed `#[xml(..)]` metas on the corresponding enum
|
||||
|
@ -301,13 +331,16 @@ impl FieldDef {
|
|||
self,
|
||||
name: &ParentRef,
|
||||
namespace_tempname: &Ident,
|
||||
parent_namespace_expr: Option<&Expr>,
|
||||
) -> Result<FieldParsePart> {
|
||||
let ident = self.ident;
|
||||
let ty = self.ty;
|
||||
let tempname = quote::format_ident!("__field_init_{}", ident);
|
||||
match self.kind {
|
||||
FieldKind::Attribute(field) => field.build_try_from_element(name, tempname, ident, ty),
|
||||
FieldKind::Child(field) => field.build_try_from_element(name, tempname, ident, ty),
|
||||
FieldKind::Child(field) => {
|
||||
field.build_try_from_element(name, tempname, ident, ty, parent_namespace_expr)
|
||||
}
|
||||
FieldKind::Text => Ok(FieldParsePart {
|
||||
tempinit: quote! {
|
||||
let #tempname: #ty = <#ty as ::xmpp_parsers_core::FromXmlText>::from_xml_text(&residual.text())?;
|
||||
|
@ -341,6 +374,7 @@ impl FieldDef {
|
|||
self,
|
||||
name: &ParentRef,
|
||||
mut access_field: impl FnMut(Member) -> Expr,
|
||||
parent_namespace_expr: Option<&Expr>,
|
||||
) -> Result<TokenStream> {
|
||||
let member = self.ident.clone();
|
||||
let ident = access_field(self.ident);
|
||||
|
@ -357,7 +391,9 @@ impl FieldDef {
|
|||
}
|
||||
}
|
||||
}),
|
||||
FieldKind::Child(field) => field.build_into_element(name, member, ident, ty),
|
||||
FieldKind::Child(field) => {
|
||||
field.build_into_element(name, member, ident, ty, parent_namespace_expr)
|
||||
}
|
||||
FieldKind::Element(field) => field.build_into_element(ident, ty),
|
||||
FieldKind::Elements(field) => field.build_into_element(ident, ty),
|
||||
FieldKind::Flag(field) => field.build_into_element(ident, ty),
|
||||
|
|
|
@ -71,6 +71,7 @@ pub(crate) fn try_from_element(item: syn::ItemStruct) -> Result<proc_macro2::Tok
|
|||
&(Path::from(ident.clone()).into()),
|
||||
None,
|
||||
&Ident::new("residual", Span::call_site()),
|
||||
None,
|
||||
)?;
|
||||
let validate = if let Some(validate) = def.validate {
|
||||
quote! {
|
||||
|
@ -114,27 +115,30 @@ pub(crate) fn into_element(item: syn::ItemStruct) -> Result<proc_macro2::TokenSt
|
|||
let meta = XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
|
||||
let ident = item.ident;
|
||||
let def = StructDef::new(meta, &item.fields)?;
|
||||
let into_impl =
|
||||
def.inner
|
||||
.build_into_element(&(Path::from(ident.clone()).into()), None, |member| {
|
||||
Expr::Field(ExprField {
|
||||
let into_impl = def.inner.build_into_element(
|
||||
&(Path::from(ident.clone()).into()),
|
||||
None,
|
||||
|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(),
|
||||
dot_token: syn::token::Dot {
|
||||
spans: [Span::call_site()],
|
||||
qself: None,
|
||||
path: Path {
|
||||
leading_colon: None,
|
||||
segments: [PathSegment::from(Ident::new("other", Span::call_site()))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
base: Box::new(Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: Path {
|
||||
leading_colon: None,
|
||||
segments: [PathSegment::from(Ident::new("other", Span::call_site()))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
})),
|
||||
member,
|
||||
})
|
||||
})?;
|
||||
})),
|
||||
member,
|
||||
})
|
||||
},
|
||||
None,
|
||||
)?;
|
||||
let prepare = if let Some(prepare) = def.prepare {
|
||||
quote! {
|
||||
let _: () = #prepare(&mut other);
|
||||
|
|
|
@ -531,6 +531,15 @@ impl DynNamespaceEnum for NamespaceEnum {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for NamespaceEnum {
|
||||
fn eq(&self, rhs: &str) -> bool {
|
||||
match self {
|
||||
Self::Ns1 => rhs == TEST_NS1,
|
||||
Self::Ns2 => rhs == TEST_NS2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = dyn, name = "dynamic-namespace")]
|
||||
pub struct WithDynamicNamespace {
|
||||
|
@ -614,3 +623,47 @@ fn enum_with_dynamic_namespace_roundtrip_2_2() {
|
|||
"<variant-2 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/>",
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = dyn, name = "dynamic-extract")]
|
||||
pub struct ExtractFieldWithSuper {
|
||||
#[xml(namespace)]
|
||||
pub namespace: NamespaceEnum,
|
||||
|
||||
#[xml(child(namespace = super, name = "contents", extract(text)))]
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_field_with_super_roundtrip_1() {
|
||||
crate::util::test::roundtrip_full::<ExtractFieldWithSuper>(
|
||||
"<dynamic-extract xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><contents>foo</contents></dynamic-extract>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_field_with_super_roundtrip_2() {
|
||||
crate::util::test::roundtrip_full::<ExtractFieldWithSuper>(
|
||||
"<dynamic-extract xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><contents>foo</contents></dynamic-extract>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_field_with_super_negative() {
|
||||
match crate::util::test::parse_str::<ExtractFieldWithSuper>(
|
||||
"<dynamic-extract xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><contents xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'>foo</contents></dynamic-extract>",
|
||||
) {
|
||||
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_field_with_super_negative_wrong_name() {
|
||||
match crate::util::test::parse_str::<ExtractFieldWithSuper>(
|
||||
"<dynamic-extract xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><quak>foo</quak></dynamic-extract>",
|
||||
) {
|
||||
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user