Detailed changes
@@ -1779,3 +1779,45 @@ fn extract_tuple_to_map_roundtrip() {
"<parent xmlns='urn:example:ns1'><text>hello world</text><text xml:lang='de'>hallo welt</text></parent>",
);
}
+
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = "foo", on_unknown_attribute = Discard)]
+struct IgnoreUnknownAttributes;
+
+#[test]
+fn ignore_unknown_attributes_empty_roundtrip() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::<IgnoreUnknownAttributes>("<foo xmlns='urn:example:ns1'/>");
+}
+
+#[test]
+fn ignore_unknown_attributes_positive() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::<IgnoreUnknownAttributes>("<foo xmlns='urn:example:ns1' fnord='bar'/>") {
+ Ok(IgnoreUnknownAttributes) => (),
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn ignore_unknown_attributes_negative_unexpected_child() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::<IgnoreUnknownAttributes>("<foo xmlns='urn:example:ns1'><coucou/></foo>") {
+ Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e))) => {
+ assert_eq!(e, "Unknown child in IgnoreUnknownAttributes element.");
+ }
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
@@ -15,19 +15,55 @@ use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit,
use crate::meta::NamespaceRef;
use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
-use crate::types::{feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty, ref_ty};
+use crate::types::{
+ default_fn, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty, ref_ty,
+ unknown_attribute_policy_path,
+};
+
+fn resolve_policy(policy: Option<Ident>, mut enum_ref: Path) -> Expr {
+ match policy {
+ Some(ident) => {
+ enum_ref.segments.push(ident.into());
+ Expr::Path(ExprPath {
+ attrs: Vec::new(),
+ qself: None,
+ path: enum_ref,
+ })
+ }
+ None => {
+ let default_fn = default_fn(Type::Path(TypePath {
+ qself: None,
+ path: enum_ref,
+ }));
+ Expr::Call(ExprCall {
+ attrs: Vec::new(),
+ func: Box::new(default_fn),
+ paren_token: token::Paren::default(),
+ args: punctuated::Punctuated::new(),
+ })
+ }
+ }
+}
/// A struct or enum variant's contents.
pub(crate) struct Compound {
/// The fields of this compound.
fields: Vec<FieldDef>,
+
+ /// Policy defining how to handle unknown attributes.
+ unknown_attribute_policy: Expr,
}
impl Compound {
/// Construct a compound from processed field definitions.
pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
compound_fields: I,
+ unknown_attribute_policy: Option<Ident>,
) -> Result<Self> {
+ let unknown_attribute_policy = resolve_policy(
+ unknown_attribute_policy,
+ unknown_attribute_policy_path(Span::call_site()),
+ );
let compound_fields = compound_fields.into_iter();
let size_hint = compound_fields.size_hint();
let mut fields = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
@@ -52,28 +88,35 @@ impl Compound {
fields.push(field);
}
- Ok(Self { fields })
+ Ok(Self {
+ fields,
+ unknown_attribute_policy,
+ })
}
/// Construct a compound from fields.
pub(crate) fn from_fields(
compound_fields: &Fields,
container_namespace: &NamespaceRef,
+ unknown_attribute_policy: Option<Ident>,
) -> Result<Self> {
- Self::from_field_defs(compound_fields.iter().enumerate().map(|(i, field)| {
- let index = match i.try_into() {
- Ok(v) => v,
- // we are converting to u32, are you crazy?!
- // (u32, because syn::Member::Index needs that.)
- Err(_) => {
- return Err(Error::new_spanned(
- field,
- "okay, mate, that are way too many fields. get your life together.",
- ))
- }
- };
- FieldDef::from_field(field, index, container_namespace)
- }))
+ Self::from_field_defs(
+ compound_fields.iter().enumerate().map(|(i, field)| {
+ let index = match i.try_into() {
+ Ok(v) => v,
+ // we are converting to u32, are you crazy?!
+ // (u32, because syn::Member::Index needs that.)
+ Err(_) => {
+ return Err(Error::new_spanned(
+ field,
+ "okay, mate, that are way too many fields. get your life together.",
+ ))
+ }
+ };
+ FieldDef::from_field(field, index, container_namespace)
+ }),
+ unknown_attribute_policy,
+ )
}
/// Make and return a set of states which is used to construct the target
@@ -342,6 +385,8 @@ impl Compound {
}
}));
+ let unknown_attribute_policy = &self.unknown_attribute_policy;
+
Ok(FromEventsSubmachine {
defs: quote! {
#extra_defs
@@ -356,9 +401,7 @@ impl Compound {
#builder_data_init
};
if #attrs.len() > 0 {
- return ::core::result::Result::Err(::xso::error::Error::Other(
- #unknown_attr_err,
- ).into());
+ let _: () = #unknown_attribute_policy.apply_policy(#unknown_attr_err)?;
}
::core::result::Result::Ok(#state_ty_ident::#default_state_ident { #builder_data_ident })
},
@@ -46,6 +46,7 @@ impl NameVariant {
debug,
builder,
iterator,
+ on_unknown_attribute,
transparent,
} = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
@@ -63,7 +64,7 @@ impl NameVariant {
Ok(Self {
name,
ident: decl.ident.clone(),
- inner: Compound::from_fields(&decl.fields, enum_namespace)?,
+ inner: Compound::from_fields(&decl.fields, enum_namespace, on_unknown_attribute)?,
})
}
@@ -265,7 +266,8 @@ impl DynamicVariant {
ref debug,
ref builder,
ref iterator,
- transparent: _, // used by StructInner
+ on_unknown_attribute: _, // used by StructInner
+ transparent: _, // used by StructInner
} = meta;
reject_key!(debug flag not on "enum variants" only on "enums and structs");
@@ -379,6 +381,7 @@ impl EnumInner {
debug,
builder,
iterator,
+ on_unknown_attribute,
transparent,
} = meta;
@@ -391,6 +394,7 @@ impl EnumInner {
reject_key!(name not on "enums" only on "their variants");
reject_key!(transparent flag not on "enums" only on "structs");
+ reject_key!(on_unknown_attribute not on "enums" only on "enum variants and structs");
if let Some(namespace) = namespace {
Ok(Self::NameSwitched(NameSwitchedEnum::new(
@@ -366,7 +366,7 @@ fn new_field(
&xml_namespace,
));
}
- let parts = Compound::from_field_defs(field_defs)?;
+ let parts = Compound::from_field_defs(field_defs, None)?;
Ok(Box::new(ChildField {
default_,
@@ -349,6 +349,10 @@ pub(crate) struct XmlCompoundMeta {
/// The value assigned to `iterator` inside `#[xml(..)]`, if any.
pub(crate) iterator: Option<Ident>,
+ /// The value assigned to `on_unknown_attribute` inside `#[xml(..)]`, if
+ /// any.
+ pub(crate) on_unknown_attribute: Option<Ident>,
+
/// The exhaustive flag.
pub(crate) exhaustive: Flag,
@@ -365,6 +369,7 @@ impl XmlCompoundMeta {
let mut qname = QNameRef::default();
let mut builder = None;
let mut iterator = None;
+ let mut on_unknown_attribute = None;
let mut debug = Flag::Absent;
let mut exhaustive = Flag::Absent;
let mut transparent = Flag::Absent;
@@ -388,6 +393,15 @@ impl XmlCompoundMeta {
}
iterator = Some(meta.value()?.parse()?);
Ok(())
+ } else if meta.path.is_ident("on_unknown_attribute") {
+ if on_unknown_attribute.is_some() {
+ return Err(Error::new_spanned(
+ meta.path,
+ "duplicate `on_unknown_attribute` key",
+ ));
+ }
+ on_unknown_attribute = Some(meta.value()?.parse()?);
+ Ok(())
} else if meta.path.is_ident("exhaustive") {
if exhaustive.is_set() {
return Err(Error::new_spanned(meta.path, "duplicate `exhaustive` key"));
@@ -414,6 +428,7 @@ impl XmlCompoundMeta {
debug,
builder,
iterator,
+ on_unknown_attribute,
exhaustive,
transparent,
})
@@ -69,6 +69,7 @@ impl StructInner {
debug,
builder,
iterator,
+ on_unknown_attribute,
transparent,
} = meta;
@@ -84,6 +85,7 @@ impl StructInner {
if let Flag::Present(_) = transparent {
reject_key!(namespace not on "transparent structs");
reject_key!(name not on "transparent structs");
+ reject_key!(on_unknown_attribute not on "transparent structs");
let fields_span = fields.span();
let fields = match fields {
@@ -143,7 +145,7 @@ impl StructInner {
};
Ok(Self::Compound {
- inner: Compound::from_fields(fields, &xml_namespace)?,
+ inner: Compound::from_fields(fields, &xml_namespace, on_unknown_attribute)?,
xml_namespace,
xml_name,
})
@@ -820,3 +820,24 @@ pub(crate) fn element_ty(span: Span) -> Type {
},
})
}
+
+/// Construct a [`syn::Path`] referring to `::xso::UnknownAttributePolicy`.
+pub(crate) fn unknown_attribute_policy_path(span: Span) -> Path {
+ Path {
+ leading_colon: Some(syn::token::PathSep {
+ spans: [span, span],
+ }),
+ segments: [
+ PathSegment {
+ ident: Ident::new("xso", span),
+ arguments: PathArguments::None,
+ },
+ PathSegment {
+ ident: Ident::new("UnknownAttributePolicy", span),
+ arguments: PathArguments::None,
+ },
+ ]
+ .into_iter()
+ .collect(),
+ }
+}
@@ -47,6 +47,7 @@ such:
- *path*: A Rust path, like `some_crate::foo::Bar`. Note that `foo` on its own
is also a path.
+- *identifier*: A single Rust identifier.
- *string literal*: A string literal, like `"hello world!"`.
- *type*: A Rust type.
- *expression*: A Rust expression.
@@ -67,6 +68,7 @@ The following keys are defined on structs:
| `transparent` | *flag* | If present, declares the struct as *transparent* struct (see below) |
| `builder` | optional *ident* | The name to use for the generated builder type. |
| `iterator` | optional *ident* | The name to use for the generated iterator type. |
+| `on_unknown_attribute` | *identifier* | Name of an [`UnknownAttributePolicy`] member, controlling how unknown attributes are handled. |
Note that the `name` value must be a valid XML element name, without colons.
The namespace prefix, if any, is assigned automatically at serialisation time
@@ -146,6 +148,7 @@ documentation above.
| Key | Value type | Description |
| --- | --- | --- |
| `name` | *string literal* or *path* | The XML element name to match for this variant. If it is a *path*, it must point at a `&'static NcNameStr`. |
+| `on_unknown_attribute` | *identifier* | Name of an [`UnknownAttributePolicy`] member, controlling how unknown attributes are handled. |
Note that the `name` value must be a valid XML element name, without colons.
The namespace prefix, if any, is assigned automatically at serialisation time
@@ -291,6 +291,38 @@ impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
}
}
+/// Control how unknown attributes are handled.
+///
+/// The variants of this enum are referenced in the
+/// `#[xml(on_unknown_attribute = ..)]` which can be used on structs and
+/// enum variants. The specified variant controls how attributes, which are
+/// not handled by any member of the compound, are handled during parsing.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
+pub enum UnknownAttributePolicy {
+ /// All unknown attributes are discarded.
+ Discard,
+
+ /// The first unknown attribute which is encountered generates a fatal
+ /// parsing error.
+ ///
+ /// This is the default policy.
+ #[default]
+ Fail,
+}
+
+impl UnknownAttributePolicy {
+ #[doc(hidden)]
+ /// Implementation of the policy.
+ ///
+ /// This is an internal API and not subject to semver versioning.
+ pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
+ match self {
+ Self::Fail => Err(self::error::Error::Other(msg)),
+ Self::Discard => Ok(()),
+ }
+ }
+}
+
/// Attempt to transform a type implementing [`AsXml`] into another
/// type which implements [`FromXml`].
pub fn transform<T: FromXml, F: AsXml>(from: F) -> Result<T, self::error::Error> {