1
0
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:
Jonas Schäfer 2024-03-24 16:48:04 +01:00
parent fbac723c4d
commit 55e4287420
7 changed files with 338 additions and 77 deletions

View File

@ -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

View File

@ -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;

View File

@ -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! {

View File

@ -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

View File

@ -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),

View File

@ -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);

View File

@ -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),
}
}