xso: add support for post-deserialization callback

This commit is contained in:
Jonas Schäfer 2025-04-05 13:38:05 +02:00
commit 8e81c41f59
8 changed files with 193 additions and 1 deletions

View file

@ -50,6 +50,7 @@ impl NameVariant {
on_unknown_child,
transparent,
discard,
deserialize_callback,
} = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
reject_key!(debug flag not on "enum variants" only on "enums and structs");
@ -58,6 +59,7 @@ impl NameVariant {
reject_key!(builder not on "enum variants" only on "enums and structs");
reject_key!(iterator not on "enum variants" only on "enums and structs");
reject_key!(transparent flag not on "named enum variants" only on "structs");
reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
let Some(name) = name else {
return Err(Error::new(meta_span, "`name` is required on enum variants"));
@ -278,12 +280,14 @@ impl DynamicVariant {
on_unknown_child: _, // used by StructInner
transparent: _, // used by StructInner
discard: _, // used by StructInner
ref deserialize_callback,
} = meta;
reject_key!(debug flag not on "enum variants" only on "enums and structs");
reject_key!(exhaustive flag not on "enum variants" only on "enums");
reject_key!(builder not on "enum variants" only on "enums and structs");
reject_key!(iterator not on "enum variants" only on "enums and structs");
reject_key!(deserialize_callback not on "enum variants" only on "enums and structs");
let inner = StructInner::new(meta, &variant.fields)?;
Ok(Self { ident, inner })
@ -395,6 +399,7 @@ impl EnumInner {
on_unknown_child,
transparent,
discard,
deserialize_callback,
} = meta;
// These must've been cleared by the caller. Because these being set
@ -403,6 +408,7 @@ impl EnumInner {
assert!(builder.is_none());
assert!(iterator.is_none());
assert!(!debug.is_set());
assert!(deserialize_callback.is_none());
reject_key!(name not on "enums" only on "their variants");
reject_key!(transparent flag not on "enums" only on "structs");
@ -476,6 +482,9 @@ pub(crate) struct EnumDef {
/// Flag whether debug mode is enabled.
debug: bool,
/// Optional validator function to call.
deserialize_callback: Option<Path>,
}
impl EnumDef {
@ -496,6 +505,7 @@ impl EnumDef {
};
let debug = meta.debug.take().is_set();
let deserialize_callback = meta.deserialize_callback.take();
Ok(Self {
inner: EnumInner::new(meta, variant_iter)?,
@ -503,6 +513,7 @@ impl EnumDef {
builder_ty_ident,
item_iter_ty_ident,
debug,
deserialize_callback,
})
}
}
@ -530,6 +541,7 @@ impl ItemDef for EnumDef {
path: target_ty_ident.clone().into(),
}
.into(),
self.deserialize_callback.as_ref(),
)?;
Ok(FromXmlParts {

View file

@ -303,6 +303,7 @@ impl ExtractDef {
&from_xml_builder_ty_ident,
&state_ty_ident,
&self.parts.to_tuple_ty().into(),
None,
)?;
let from_xml_builder_ty = ty_from_ident(from_xml_builder_ty_ident.clone()).into();

View file

@ -447,6 +447,9 @@ pub(crate) struct XmlCompoundMeta {
/// Items to discard.
pub(crate) discard: Vec<DiscardSpec>,
/// The value assigned to `deserialize_callback` inside `#[xml(..)]`, if any.
pub(crate) deserialize_callback: Option<Path>,
}
impl XmlCompoundMeta {
@ -464,6 +467,7 @@ impl XmlCompoundMeta {
let mut exhaustive = Flag::Absent;
let mut transparent = Flag::Absent;
let mut discard = Vec::new();
let mut deserialize_callback = None;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("debug") {
@ -520,6 +524,15 @@ impl XmlCompoundMeta {
Ok(())
})?;
Ok(())
} else if meta.path.is_ident("deserialize_callback") {
if deserialize_callback.is_some() {
return Err(Error::new_spanned(
meta.path,
"duplicate `deserialize_callback` key",
));
}
deserialize_callback = Some(meta.value()?.parse()?);
Ok(())
} else {
match qname.parse_incremental_from_meta(meta)? {
None => Ok(()),
@ -539,6 +552,7 @@ impl XmlCompoundMeta {
exhaustive,
transparent,
discard,
deserialize_callback,
})
}

View file

@ -456,6 +456,7 @@ impl FromEventsStateMachine {
builder_ty_ident: &Ident,
state_ty_ident: &Ident,
output_ty: &Type,
validate_fn: Option<&Path>,
) -> Result<TokenStream> {
let Self {
defs,
@ -488,6 +489,18 @@ impl FromEventsStateMachine {
let docstr = format!("Build a {0} from XML events.\n\nThis type is generated using the [`macro@xso::FromXml`] derive macro and implements [`xso::FromEventsBuilder`] for {0}.", output_ty_ref);
let validate_call = match validate_fn {
None => quote! {
// needed to avoid unused_mut warning.
let _ = &mut value;
},
Some(validate_fn) => {
quote! {
#validate_fn(&mut value)?;
}
}
};
Ok(quote! {
#defs
@ -529,7 +542,10 @@ impl FromEventsStateMachine {
fn feed(&mut self, ev: ::xso::exports::rxml::Event) -> ::core::result::Result<::core::option::Option<Self::Output>, ::xso::error::Error> {
let inner = self.0.take().expect("feed called after completion");
match inner.advance(ev)? {
::core::ops::ControlFlow::Continue(value) => ::core::result::Result::Ok(::core::option::Option::Some(value)),
::core::ops::ControlFlow::Continue(mut value) => {
#validate_call
::core::result::Result::Ok(::core::option::Option::Some(value))
}
::core::ops::ControlFlow::Break(st) => {
self.0 = ::core::option::Option::Some(st);
::core::result::Result::Ok(::core::option::Option::None)

View file

@ -73,6 +73,7 @@ impl StructInner {
on_unknown_child,
transparent,
discard,
deserialize_callback,
} = meta;
// These must've been cleared by the caller. Because these being set
@ -81,6 +82,7 @@ impl StructInner {
assert!(builder.is_none());
assert!(iterator.is_none());
assert!(!debug.is_set());
assert!(deserialize_callback.is_none());
reject_key!(exhaustive flag not on "structs" only on "enums");
@ -322,6 +324,9 @@ pub(crate) struct StructDef {
/// The matching logic and contents of the struct.
inner: StructInner,
/// Optional validator function to call.
deserialize_callback: Option<Path>,
}
impl StructDef {
@ -338,6 +343,7 @@ impl StructDef {
};
let debug = meta.debug.take();
let deserialize_callback = meta.deserialize_callback.take();
let inner = StructInner::new(meta, fields)?;
@ -347,6 +353,7 @@ impl StructDef {
builder_ty_ident,
item_iter_ty_ident,
debug: debug.is_set(),
deserialize_callback,
})
}
}
@ -379,6 +386,7 @@ impl ItemDef for StructDef {
path: target_ty_ident.clone().into(),
}
.into(),
self.deserialize_callback.as_ref(),
)?;
Ok(FromXmlParts {