xso: add support for ignoring unknown children

This commit is contained in:
Jonas Schäfer 2024-10-03 12:51:57 +02:00 committed by Link Mauve
commit 66233b0150
10 changed files with 242 additions and 6 deletions

View file

@ -16,8 +16,8 @@ use crate::meta::NamespaceRef;
use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
use crate::types::{
default_fn, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty, ref_ty,
unknown_attribute_policy_path,
default_fn, discard_builder_ty, feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty,
ref_ty, unknown_attribute_policy_path, unknown_child_policy_path,
};
fn resolve_policy(policy: Option<Ident>, mut enum_ref: Path) -> Expr {
@ -52,6 +52,9 @@ pub(crate) struct Compound {
/// Policy defining how to handle unknown attributes.
unknown_attribute_policy: Expr,
/// Policy defining how to handle unknown children.
unknown_child_policy: Expr,
}
impl Compound {
@ -59,11 +62,16 @@ impl Compound {
pub(crate) fn from_field_defs<I: IntoIterator<Item = Result<FieldDef>>>(
compound_fields: I,
unknown_attribute_policy: Option<Ident>,
unknown_child_policy: Option<Ident>,
) -> Result<Self> {
let unknown_attribute_policy = resolve_policy(
unknown_attribute_policy,
unknown_attribute_policy_path(Span::call_site()),
);
let unknown_child_policy = resolve_policy(
unknown_child_policy,
unknown_child_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));
@ -91,6 +99,7 @@ impl Compound {
Ok(Self {
fields,
unknown_attribute_policy,
unknown_child_policy,
})
}
@ -99,6 +108,7 @@ impl Compound {
compound_fields: &Fields,
container_namespace: &NamespaceRef,
unknown_attribute_policy: Option<Ident>,
unknown_child_policy: Option<Ident>,
) -> Result<Self> {
Self::from_field_defs(
compound_fields.iter().enumerate().map(|(i, field)| {
@ -116,6 +126,7 @@ impl Compound {
FieldDef::from_field(field, index, container_namespace)
}),
unknown_attribute_policy,
unknown_child_policy,
)
}
@ -141,6 +152,7 @@ impl Compound {
} = scope;
let default_state_ident = quote::format_ident!("{}Default", state_prefix);
let discard_state_ident = quote::format_ident!("{}Discard", state_prefix);
let builder_data_ty: Type = TypePath {
qself: None,
path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
@ -334,6 +346,7 @@ impl Compound {
let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
let unknown_child_err = format!("Unknown child in {}.", output_name);
let unknown_child_policy = &self.unknown_child_policy;
let output_cons = match output_name {
ParentRef::Named(ref path) => {
@ -348,14 +361,43 @@ impl Compound {
}
};
let discard_builder_ty = discard_builder_ty(Span::call_site());
let discard_feed = feed_fn(discard_builder_ty.clone());
let child_fallback = match fallback_child_matcher {
Some((_, matcher)) => matcher,
None => quote! {
let _ = (name, attrs);
::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
let _: () = #unknown_child_policy.apply_policy(#unknown_child_err)?;
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
#builder_data_ident,
#substate_data: #discard_builder_ty::new(),
}))
},
};
states.push(State::new_with_builder(
discard_state_ident.clone(),
&builder_data_ident,
&builder_data_ty,
).with_field(
substate_data,
&discard_builder_ty,
).with_mut(substate_data).with_impl(quote! {
match #discard_feed(&mut #substate_data, ev)? {
::core::option::Option::Some(#substate_result) => {
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#default_state_ident {
#builder_data_ident,
}))
}
::core::option::Option::None => {
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#discard_state_ident {
#builder_data_ident,
#substate_data,
}))
}
}
}));
states.push(State::new_with_builder(
default_state_ident.clone(),
builder_data_ident,

View file

@ -47,6 +47,7 @@ impl NameVariant {
builder,
iterator,
on_unknown_attribute,
on_unknown_child,
transparent,
} = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
@ -64,7 +65,12 @@ impl NameVariant {
Ok(Self {
name,
ident: decl.ident.clone(),
inner: Compound::from_fields(&decl.fields, enum_namespace, on_unknown_attribute)?,
inner: Compound::from_fields(
&decl.fields,
enum_namespace,
on_unknown_attribute,
on_unknown_child,
)?,
})
}
@ -267,6 +273,7 @@ impl DynamicVariant {
ref builder,
ref iterator,
on_unknown_attribute: _, // used by StructInner
on_unknown_child: _, // used by StructInner
transparent: _, // used by StructInner
} = meta;
@ -382,6 +389,7 @@ impl EnumInner {
builder,
iterator,
on_unknown_attribute,
on_unknown_child,
transparent,
} = meta;
@ -395,6 +403,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");
reject_key!(on_unknown_child 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, None)?;
let parts = Compound::from_field_defs(field_defs, None, None)?;
Ok(Box::new(ChildField {
default_,

View file

@ -353,6 +353,10 @@ pub(crate) struct XmlCompoundMeta {
/// any.
pub(crate) on_unknown_attribute: Option<Ident>,
/// The value assigned to `on_unknown_child` inside `#[xml(..)]`, if
/// any.
pub(crate) on_unknown_child: Option<Ident>,
/// The exhaustive flag.
pub(crate) exhaustive: Flag,
@ -370,6 +374,7 @@ impl XmlCompoundMeta {
let mut builder = None;
let mut iterator = None;
let mut on_unknown_attribute = None;
let mut on_unknown_child = None;
let mut debug = Flag::Absent;
let mut exhaustive = Flag::Absent;
let mut transparent = Flag::Absent;
@ -402,6 +407,15 @@ impl XmlCompoundMeta {
}
on_unknown_attribute = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("on_unknown_child") {
if on_unknown_child.is_some() {
return Err(Error::new_spanned(
meta.path,
"duplicate `on_unknown_child` key",
));
}
on_unknown_child = 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"));
@ -429,6 +443,7 @@ impl XmlCompoundMeta {
builder,
iterator,
on_unknown_attribute,
on_unknown_child,
exhaustive,
transparent,
})

View file

@ -70,6 +70,7 @@ impl StructInner {
builder,
iterator,
on_unknown_attribute,
on_unknown_child,
transparent,
} = meta;
@ -86,6 +87,7 @@ impl StructInner {
reject_key!(namespace not on "transparent structs");
reject_key!(name not on "transparent structs");
reject_key!(on_unknown_attribute not on "transparent structs");
reject_key!(on_unknown_child not on "transparent structs");
let fields_span = fields.span();
let fields = match fields {
@ -145,7 +147,12 @@ impl StructInner {
};
Ok(Self::Compound {
inner: Compound::from_fields(fields, &xml_namespace, on_unknown_attribute)?,
inner: Compound::from_fields(
fields,
&xml_namespace,
on_unknown_attribute,
on_unknown_child,
)?,
xml_namespace,
xml_name,
})

View file

@ -841,3 +841,52 @@ pub(crate) fn unknown_attribute_policy_path(span: Span) -> Path {
.collect(),
}
}
/// Construct a [`syn::Path`] referring to `::xso::UnknownChildPolicy`.
pub(crate) fn unknown_child_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("UnknownChildPolicy", span),
arguments: PathArguments::None,
},
]
.into_iter()
.collect(),
}
}
/// Construct a [`syn::Type`] referring to `::xso::fromxml::Discard`.
pub(crate) fn discard_builder_ty(span: Span) -> Type {
Type::Path(TypePath {
qself: None,
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("fromxml", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("Discard", span),
arguments: PathArguments::None,
},
]
.into_iter()
.collect(),
},
})
}