xso: add support for ignoring unknown attributes

This commit is contained in:
Jonas Schäfer 2024-10-03 12:29:10 +02:00 committed by Link Mauve
commit 290460ba9d
9 changed files with 185 additions and 23 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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