mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
xso_proc: add options to override behavior for unknown things
This commit is contained in:
parent
970a48e07b
commit
457ae7ea69
|
@ -1073,3 +1073,51 @@ fn nested_dyn_namespace_serializes_inner_with_correct_namespace() {
|
|||
assert_eq!(v2.namespace, NamespaceEnum::Ns2);
|
||||
assert_eq!(v2.inner.namespace, NamespaceEnum::Ns2);
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = self::TEST_NS1, name = "ignore-unknown-children", on_unknown_child = Ignore)]
|
||||
pub struct IgnoreUnknownChildren;
|
||||
|
||||
#[test]
|
||||
fn ignore_unknown_children_ignores_children() {
|
||||
match crate::util::test::parse_str::<IgnoreUnknownChildren>(
|
||||
"<ignore-unknown-children xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo/></ignore-unknown-children>",
|
||||
) {
|
||||
Ok(_) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_unknown_children_rejects_attributes() {
|
||||
match crate::util::test::parse_str::<IgnoreUnknownChildren>(
|
||||
"<ignore-unknown-children xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' foo='bar'/>",
|
||||
) {
|
||||
Err(Error::ParseError(msg)) if msg.find("Unknown attribute").is_some() => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = self::TEST_NS1, name = "ignore-unknown-attributes", on_unknown_attribute = Ignore)]
|
||||
pub struct IgnoreUnknownAttributes;
|
||||
|
||||
#[test]
|
||||
fn ignore_unknown_attributes_ignores_attributes() {
|
||||
match crate::util::test::parse_str::<IgnoreUnknownAttributes>(
|
||||
"<ignore-unknown-attributes xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' foo='bar'/>",
|
||||
) {
|
||||
Ok(_) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_unknown_attributes_rejects_children() {
|
||||
match crate::util::test::parse_str::<IgnoreUnknownAttributes>(
|
||||
"<ignore-unknown-attributes xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo/></ignore-unknown-attributes>",
|
||||
) {
|
||||
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,58 @@ use syn::{spanned::Spanned, *};
|
|||
use crate::error_message::ParentRef;
|
||||
use crate::field::{FieldDef, FieldParsePart};
|
||||
|
||||
/// Expand the given identifier to resolve as member of the
|
||||
/// `UnknownChildPolicy` enum. If the identifier is absent, invoke
|
||||
/// `UnknownChildPolicy::default()` instead.
|
||||
fn default_on_unknown_child(p: Option<Ident>) -> Expr {
|
||||
match p {
|
||||
Some(v) => Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: Path {
|
||||
leading_colon: Some(token::PathSep {
|
||||
spans: [Span::call_site(), Span::call_site()],
|
||||
}),
|
||||
segments: [
|
||||
PathSegment::from(Ident::new("xso", Span::call_site())),
|
||||
PathSegment::from(Ident::new("UnknownChildPolicy", Span::call_site())),
|
||||
PathSegment::from(v),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
}),
|
||||
None => syn::parse2(quote! { ::xso::UnknownChildPolicy::default() })
|
||||
.expect("failed to construct default unknown child policy"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand the given identifier to resolve as member of the
|
||||
/// `UnknownAttributePolicy` enum. If the identifier is absent, invoke
|
||||
/// `UnknownAttributePolicy::default()` instead.
|
||||
fn default_on_unknown_attribute(p: Option<Ident>) -> Expr {
|
||||
match p {
|
||||
Some(v) => Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: Path {
|
||||
leading_colon: Some(token::PathSep {
|
||||
spans: [Span::call_site(), Span::call_site()],
|
||||
}),
|
||||
segments: [
|
||||
PathSegment::from(Ident::new("xso", Span::call_site())),
|
||||
PathSegment::from(Ident::new("UnknownAttributePolicy", Span::call_site())),
|
||||
PathSegment::from(v),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
}),
|
||||
None => syn::parse2(quote! { ::xso::UnknownAttributePolicy::default() })
|
||||
.expect("failed to construct default unknown attribute policy"),
|
||||
}
|
||||
}
|
||||
|
||||
/// An struct or enum variant's contents.
|
||||
///
|
||||
/// This struct is used to generate the parsing/serialisation loop, but most
|
||||
|
@ -26,11 +78,23 @@ pub(crate) struct Compound {
|
|||
/// Information about the field annotated with `#[xml(namespace)]`, if
|
||||
/// any.
|
||||
namespace_field: Option<(Span, Type, Member)>,
|
||||
|
||||
/// Member of the `UnknownChildPolicy` enum to use when handling unknown
|
||||
/// children.
|
||||
on_unknown_child: Expr,
|
||||
|
||||
/// Member of the `UnknownAttributePolicy` enum to use when handling
|
||||
/// unknown attributes.
|
||||
on_unknown_attribute: Expr,
|
||||
}
|
||||
|
||||
impl Compound {
|
||||
/// Construct a new compound from an iterator of [`FieldDef`] structs.
|
||||
pub(crate) fn new<T: Iterator<Item = Result<FieldDef>>>(input: T) -> Result<Self> {
|
||||
pub(crate) fn new<T: Iterator<Item = Result<FieldDef>>>(
|
||||
on_unknown_child: Option<Ident>,
|
||||
on_unknown_attribute: Option<Ident>,
|
||||
input: T,
|
||||
) -> Result<Self> {
|
||||
let mut fields = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
|
||||
let mut text_field: Option<Span> = None;
|
||||
let mut namespace_field: Option<(Span, Type, Member)> = None;
|
||||
|
@ -74,6 +138,8 @@ impl Compound {
|
|||
Ok(Self {
|
||||
fields,
|
||||
namespace_field,
|
||||
on_unknown_child: default_on_unknown_child(on_unknown_child),
|
||||
on_unknown_attribute: default_on_unknown_attribute(on_unknown_attribute),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -81,8 +147,14 @@ impl Compound {
|
|||
///
|
||||
/// This a convenience wrapper around [`Self::new`], converting the
|
||||
/// [`syn::Field`] structs to [`FieldDef`].
|
||||
pub(crate) fn from_fields(fields: &Fields) -> Result<Self> {
|
||||
pub(crate) fn from_fields(
|
||||
on_unknown_child: Option<Ident>,
|
||||
on_unknown_attribute: Option<Ident>,
|
||||
fields: &Fields,
|
||||
) -> Result<Self> {
|
||||
Self::new(
|
||||
on_unknown_child,
|
||||
on_unknown_attribute,
|
||||
fields.iter().enumerate().map(|(i, field)| {
|
||||
FieldDef::from_field(field, i.try_into().expect("too many fields"))
|
||||
}),
|
||||
|
@ -130,6 +202,8 @@ impl Compound {
|
|||
forgive_attributes: &[&str],
|
||||
) -> Result<TokenStream> {
|
||||
let readable_name = container_name.to_string();
|
||||
let on_unknown_child = self.on_unknown_child;
|
||||
let on_unknown_attribute = self.on_unknown_attribute;
|
||||
|
||||
let mut init = quote! {};
|
||||
let mut tupinit = quote! {};
|
||||
|
@ -143,7 +217,7 @@ impl Compound {
|
|||
let mut tempinit = quote! {};
|
||||
let mut childiter = quote! {};
|
||||
let mut childfallback = quote! {
|
||||
return Err(::xso::error::Error::ParseError(concat!("Unknown child in ", #readable_name, ".")));
|
||||
#on_unknown_child.trigger(concat!("Unknown child in ", #readable_name, "."))?;
|
||||
};
|
||||
let mut had_fallback: bool = false;
|
||||
|
||||
|
@ -196,7 +270,7 @@ impl Compound {
|
|||
{
|
||||
for (key, _) in #residual.attrs() {
|
||||
#attrcheck
|
||||
return Err(::xso::error::Error::ParseError(concat!("Unknown attribute in ", #readable_name, ".")));
|
||||
#on_unknown_attribute.trigger(concat!("Unknown attribute in ", #readable_name, "."))?;
|
||||
}
|
||||
|
||||
#tempinit
|
||||
|
|
|
@ -150,7 +150,11 @@ impl StrMatchedVariant {
|
|||
value: name,
|
||||
ident: variant.ident.clone(),
|
||||
fallback,
|
||||
inner: Compound::from_fields(&variant.fields)?,
|
||||
inner: Compound::from_fields(
|
||||
meta.on_unknown_child,
|
||||
meta.on_unknown_attribute,
|
||||
&variant.fields,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -179,7 +183,11 @@ impl StrMatchedVariant {
|
|||
value,
|
||||
ident: variant.ident.clone(),
|
||||
fallback,
|
||||
inner: Compound::from_fields(&variant.fields)?,
|
||||
inner: Compound::from_fields(
|
||||
meta.on_unknown_child,
|
||||
meta.on_unknown_attribute,
|
||||
&variant.fields,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,9 +67,13 @@ impl ExtractDef {
|
|||
if parts.len() != 1 {
|
||||
single_extract_type = None;
|
||||
}
|
||||
let parts = Compound::new(parts.into_iter().enumerate().map(|(i, x)| {
|
||||
FieldDef::from_extract(span.clone(), *x, i as u32, single_extract_type.take())
|
||||
}))?;
|
||||
let parts = Compound::new(
|
||||
None,
|
||||
None,
|
||||
parts.into_iter().enumerate().map(|(i, x)| {
|
||||
FieldDef::from_extract(span.clone(), *x, i as u32, single_extract_type.take())
|
||||
}),
|
||||
)?;
|
||||
Ok(Self {
|
||||
namespace,
|
||||
name,
|
||||
|
|
|
@ -502,6 +502,14 @@ pub(crate) struct XmlCompoundMeta {
|
|||
|
||||
/// The options set inside `element`, if any.
|
||||
pub(crate) element: Option<NodeFilterMeta>,
|
||||
|
||||
/// Member of the `UnknownChildPolicy` enum to use when handling unknown
|
||||
/// children.
|
||||
pub(crate) on_unknown_child: Option<Ident>,
|
||||
|
||||
/// Member of the `UnknownAttributePolicy` enum to use when handling
|
||||
/// unknown attributes.
|
||||
pub(crate) on_unknown_attribute: Option<Ident>,
|
||||
}
|
||||
|
||||
impl XmlCompoundMeta {
|
||||
|
@ -520,6 +528,8 @@ impl XmlCompoundMeta {
|
|||
normalize_with: None,
|
||||
debug: Flag::Absent,
|
||||
element: None,
|
||||
on_unknown_attribute: None,
|
||||
on_unknown_child: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,6 +550,8 @@ impl XmlCompoundMeta {
|
|||
let mut prepare: Option<Path> = None;
|
||||
let mut normalize_with: Option<Path> = None;
|
||||
let mut element: Option<NodeFilterMeta> = None;
|
||||
let mut on_unknown_attribute: Option<Ident> = None;
|
||||
let mut on_unknown_child: Option<Ident> = None;
|
||||
|
||||
attr.parse_nested_meta(|meta| {
|
||||
parse_meta!(
|
||||
|
@ -557,6 +569,8 @@ impl XmlCompoundMeta {
|
|||
ParseValue("normalize_with", &mut normalize_with),
|
||||
ParseValue("normalise_with", &mut normalize_with),
|
||||
ParseNodeFilter("element", &mut element),
|
||||
ParseValue("on_unknown_attribute", &mut on_unknown_attribute),
|
||||
ParseValue("on_unknown_child", &mut on_unknown_child),
|
||||
)
|
||||
})?;
|
||||
|
||||
|
@ -582,6 +596,8 @@ impl XmlCompoundMeta {
|
|||
debug,
|
||||
normalize_with,
|
||||
element,
|
||||
on_unknown_child,
|
||||
on_unknown_attribute,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -241,7 +241,13 @@ impl StructInner {
|
|||
));
|
||||
};
|
||||
|
||||
Self::new_compound(namespace, name, fields)
|
||||
Self::new_compound(
|
||||
namespace,
|
||||
name,
|
||||
meta.on_unknown_child,
|
||||
meta.on_unknown_attribute,
|
||||
fields,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,8 +349,14 @@ impl StructInner {
|
|||
|
||||
/// Construct a new compound-based struct with the given namespace, name
|
||||
/// and fields.
|
||||
fn new_compound(namespace: NamespaceRef, name: NameRef, fields: &Fields) -> Result<Self> {
|
||||
let inner = Compound::from_fields(fields)?;
|
||||
fn new_compound(
|
||||
namespace: NamespaceRef,
|
||||
name: NameRef,
|
||||
on_unknown_child: Option<Ident>,
|
||||
on_unknown_attribute: Option<Ident>,
|
||||
fields: &Fields,
|
||||
) -> Result<Self> {
|
||||
let inner = Compound::from_fields(on_unknown_child, on_unknown_attribute, fields)?;
|
||||
let namespace_field = inner.namespace_field();
|
||||
|
||||
let namespace = match namespace {
|
||||
|
|
|
@ -113,6 +113,20 @@ preserved, if the container preserves sort order on insertion.
|
|||
|
||||
`validate` is allowed and will be called.
|
||||
|
||||
- `on_unknown_child = ..` may be set to the identifier of a member of the
|
||||
[`UnknownChildPolicy`] enum (i.e. for example `on_unknown_child = Ignore`).
|
||||
This configures the behavior when unknown child elements are encountered.
|
||||
See [`UnknownChildPolicy`] for details.
|
||||
|
||||
Has no effect on grandchildren.
|
||||
|
||||
- `on_unknown_attribute = ..` may be set to the identifier of a member of the
|
||||
[`UnknownAttributePolicy`] enum (i.e. for example
|
||||
`on_unknown_attribute = Ignore`). This configures the behavior when unknown
|
||||
attributes are encountered. See [`UnknownAttributePolicy`] for details.
|
||||
|
||||
Has no effect on children.
|
||||
|
||||
## Enums
|
||||
|
||||
Enums come in multiple flavors. All flavors have the following attributes:
|
||||
|
@ -680,3 +694,68 @@ pub trait DynNamespace {
|
|||
/// fields referring to child structs with `namespace = super`.
|
||||
fn set_namespace<T: Into<Self::Namespace>>(&mut self, ns: T);
|
||||
}
|
||||
|
||||
/// Configure how unknown child elements are handled.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum UnknownChildPolicy {
|
||||
/// Fail parsing with a fatal error.
|
||||
#[cfg_attr(not(feature = "disable-validation"), default)]
|
||||
Fail,
|
||||
|
||||
/// Ignore and discard any unknown child elements.
|
||||
#[cfg_attr(feature = "disable-validation", default)]
|
||||
Ignore,
|
||||
}
|
||||
|
||||
impl UnknownChildPolicy {
|
||||
// TODO: once we have a more suitable error message structure, we should
|
||||
// not pass the entire message down to this function.
|
||||
/// Return a result describing the result of triggering the child policy.
|
||||
///
|
||||
/// In other words, this returns an error iff the policy is set to
|
||||
/// `Fail`.
|
||||
///
|
||||
/// **Note:** This function is not to be considered part of the public
|
||||
/// API! It is only marked `pub` because it needs to be called from
|
||||
/// macro-generated code! Its signature and behavior may change without
|
||||
/// notice and without major version bump at any time.
|
||||
pub fn trigger(&self, msg: &'static str) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::Fail => Err(error::Error::ParseError(msg)),
|
||||
Self::Ignore => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure how unknown attributes are handled.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum UnknownAttributePolicy {
|
||||
/// Fail parsing with a fatal error.
|
||||
#[cfg_attr(not(feature = "disable-validation"), default)]
|
||||
Fail,
|
||||
|
||||
/// Ignore and discard any unknown attributes.
|
||||
#[cfg_attr(feature = "disable-validation", default)]
|
||||
Ignore,
|
||||
}
|
||||
|
||||
impl UnknownAttributePolicy {
|
||||
// TODO: once we have a more suitable error message structure, we should
|
||||
// not pass the entire message down to this function.
|
||||
/// Return a result describing the result of triggering the attribute
|
||||
/// policy.
|
||||
///
|
||||
/// In other words, this returns an error iff the policy is set to
|
||||
/// `Fail`.
|
||||
///
|
||||
/// **Note:** This function is not to be considered part of the public
|
||||
/// API! It is only marked `pub` because it needs to be called from
|
||||
/// macro-generated code! Its signature and behavior may change without
|
||||
/// notice and without major version bump at any time.
|
||||
pub fn trigger(&self, msg: &'static str) -> Result<(), error::Error> {
|
||||
match self {
|
||||
Self::Fail => Err(error::Error::ParseError(msg)),
|
||||
Self::Ignore => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue