xso: add support for dynamic enums

This commit is contained in:
Jonas Schäfer 2024-08-05 16:35:10 +02:00 committed by Link Mauve
commit fb80bb7532
5 changed files with 329 additions and 46 deletions

View file

@ -17,9 +17,11 @@ use crate::compound::Compound;
use crate::error_message::ParentRef;
use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
use crate::state::{AsItemsStateMachine, FromEventsStateMachine};
use crate::structs::StructInner;
use crate::types::{ref_ty, ty_from_ident};
/// The definition of an enum variant, switched on the XML element's name.
/// The definition of an enum variant, switched on the XML element's name,
/// inside a [`NameSwitchedEnum`].
struct NameVariant {
/// The XML name of the element to map the enum variant to.
name: NameRef,
@ -140,6 +142,8 @@ impl NameVariant {
}
}
/// The definition of a enum which switches based on the XML element name,
/// with the XML namespace fixed.
struct NameSwitchedEnum {
/// The XML namespace of the element to map the enum to.
namespace: NamespaceRef,
@ -153,36 +157,10 @@ struct NameSwitchedEnum {
impl NameSwitchedEnum {
fn new<'x, I: IntoIterator<Item = &'x Variant>>(
meta: XmlCompoundMeta,
namespace: NamespaceRef,
exhaustive: Flag,
variant_iter: I,
) -> Result<Self> {
// We destructure here so that we get informed when new fields are
// added and can handle them, either by processing them or raising
// an error if they are present.
let XmlCompoundMeta {
span: meta_span,
qname: QNameRef { namespace, name },
exhaustive,
debug,
builder,
iterator,
transparent,
} = meta;
// These must've been cleared by the caller. Because these being set
// is a programming error (in xso-proc) and not a usage error, we
// assert here instead of using reject_key!.
assert!(builder.is_none());
assert!(iterator.is_none());
assert!(!debug.is_set());
reject_key!(name not on "enums" only on "their variants");
reject_key!(transparent flag not on "enums" only on "structs");
let Some(namespace) = namespace else {
return Err(Error::new(meta_span, "`namespace` is required on enums"));
};
let mut variants = Vec::new();
let mut seen_names = HashMap::new();
for variant in variant_iter {
@ -207,6 +185,7 @@ impl NameSwitchedEnum {
})
}
/// Build the deserialisation statemachine for the name-switched enum.
fn make_from_events_statemachine(
&self,
target_ty_ident: &Ident,
@ -241,6 +220,7 @@ impl NameSwitchedEnum {
Ok(statemachine)
}
/// Build the serialisation statemachine for the name-switched enum.
fn make_as_item_iter_statemachine(
&self,
target_ty_ident: &Ident,
@ -261,10 +241,211 @@ impl NameSwitchedEnum {
}
}
/// The definition of an enum variant in a [`DynamicEnum`].
struct DynamicVariant {
/// The identifier of the enum variant.
ident: Ident,
/// The definition of the struct-like which resembles the enum variant.
inner: StructInner,
}
impl DynamicVariant {
fn new(variant: &Variant) -> Result<Self> {
let ident = variant.ident.clone();
let meta = XmlCompoundMeta::parse_from_attributes(&variant.attrs)?;
// We destructure here so that we get informed when new fields are
// added and can handle them, either by processing them or raising
// an error if they are present.
let XmlCompoundMeta {
span: _,
qname: _, // used by StructInner
ref exhaustive,
ref debug,
ref builder,
ref iterator,
transparent: _, // used by StructInner
} = 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");
let inner = StructInner::new(meta, &variant.fields)?;
Ok(Self { ident, inner })
}
}
/// The definition of an enum where each variant is a completely unrelated
/// possible XML subtree.
struct DynamicEnum {
/// The enum variants.
variants: Vec<DynamicVariant>,
}
impl DynamicEnum {
fn new<'x, I: IntoIterator<Item = &'x Variant>>(variant_iter: I) -> Result<Self> {
let mut variants = Vec::new();
for variant in variant_iter {
variants.push(DynamicVariant::new(variant)?);
}
Ok(Self { variants })
}
/// Build the deserialisation statemachine for the dynamic enum.
fn make_from_events_statemachine(
&self,
target_ty_ident: &Ident,
state_ty_ident: &Ident,
) -> Result<FromEventsStateMachine> {
let mut statemachine = FromEventsStateMachine::new();
for variant in self.variants.iter() {
let submachine = variant.inner.make_from_events_statemachine(
state_ty_ident,
&ParentRef::Named(Path {
leading_colon: None,
segments: [
PathSegment::from(target_ty_ident.clone()),
variant.ident.clone().into(),
]
.into_iter()
.collect(),
}),
&variant.ident.to_string(),
)?;
statemachine.merge(submachine.compile());
}
Ok(statemachine)
}
/// Build the serialisation statemachine for the dynamic enum.
fn make_as_item_iter_statemachine(
&self,
target_ty_ident: &Ident,
state_ty_ident: &Ident,
item_iter_ty_lifetime: &Lifetime,
) -> Result<AsItemsStateMachine> {
let mut statemachine = AsItemsStateMachine::new();
for variant in self.variants.iter() {
let submachine = variant.inner.make_as_item_iter_statemachine(
&ParentRef::Named(Path {
leading_colon: None,
segments: [
PathSegment::from(target_ty_ident.clone()),
variant.ident.clone().into(),
]
.into_iter()
.collect(),
}),
state_ty_ident,
&variant.ident.to_string(),
item_iter_ty_lifetime,
)?;
statemachine.merge(submachine.compile());
}
Ok(statemachine)
}
}
/// The definition of an enum.
enum EnumInner {
/// The enum switches based on the XML name of the element, with the XML
/// namespace fixed.
NameSwitched(NameSwitchedEnum),
/// The enum consists of variants with entirely unrelated XML structures.
Dynamic(DynamicEnum),
}
impl EnumInner {
fn new<'x, I: IntoIterator<Item = &'x Variant>>(
meta: XmlCompoundMeta,
variant_iter: I,
) -> Result<Self> {
// We destructure here so that we get informed when new fields are
// added and can handle them, either by processing them or raising
// an error if they are present.
let XmlCompoundMeta {
span: _,
qname: QNameRef { namespace, name },
exhaustive,
debug,
builder,
iterator,
transparent,
} = meta;
// These must've been cleared by the caller. Because these being set
// is a programming error (in xso-proc) and not a usage error, we
// assert here instead of using reject_key!.
assert!(builder.is_none());
assert!(iterator.is_none());
assert!(!debug.is_set());
reject_key!(name not on "enums" only on "their variants");
reject_key!(transparent flag not on "enums" only on "structs");
if let Some(namespace) = namespace {
Ok(Self::NameSwitched(NameSwitchedEnum::new(
namespace,
exhaustive,
variant_iter,
)?))
} else {
reject_key!(exhaustive flag not on "dynamic enums" only on "name-switched enums");
Ok(Self::Dynamic(DynamicEnum::new(variant_iter)?))
}
}
/// Build the deserialisation statemachine for the enum.
fn make_from_events_statemachine(
&self,
target_ty_ident: &Ident,
state_ty_ident: &Ident,
) -> Result<FromEventsStateMachine> {
match self {
Self::NameSwitched(ref inner) => {
inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
}
Self::Dynamic(ref inner) => {
inner.make_from_events_statemachine(target_ty_ident, state_ty_ident)
}
}
}
/// Build the serialisation statemachine for the enum.
fn make_as_item_iter_statemachine(
&self,
target_ty_ident: &Ident,
state_ty_ident: &Ident,
item_iter_ty_lifetime: &Lifetime,
) -> Result<AsItemsStateMachine> {
match self {
Self::NameSwitched(ref inner) => inner.make_as_item_iter_statemachine(
target_ty_ident,
state_ty_ident,
item_iter_ty_lifetime,
),
Self::Dynamic(ref inner) => inner.make_as_item_iter_statemachine(
target_ty_ident,
state_ty_ident,
item_iter_ty_lifetime,
),
}
}
}
/// Definition of an enum and how to parse it.
pub(crate) struct EnumDef {
/// Implementation of the enum itself
inner: NameSwitchedEnum,
inner: EnumInner,
/// Name of the target type.
target_ty_ident: Ident,
@ -299,7 +480,7 @@ impl EnumDef {
let debug = meta.debug.take().is_set();
Ok(Self {
inner: NameSwitchedEnum::new(meta, variant_iter)?,
inner: EnumInner::new(meta, variant_iter)?,
target_ty_ident: ident.clone(),
builder_ty_ident,
item_iter_ty_ident,

View file

@ -24,7 +24,7 @@ pub const XMLNS_XMLNS: &str = "http://www.w3.org/2000/xmlns/";
macro_rules! reject_key {
($key:ident not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => {
if let Some($key) = $key {
if let Some(ref $key) = $key {
return Err(Error::new_spanned(
$key,
concat!(
@ -43,9 +43,9 @@ macro_rules! reject_key {
};
($key:ident flag not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => {
if let Flag::Present($key) = $key {
if let Flag::Present(ref $key) = $key {
return Err(Error::new(
$key,
*$key,
concat!(
"`",
stringify!($key),

View file

@ -23,7 +23,7 @@ use crate::types::{
/// The inner parts of the struct.
///
/// This contains all data necessary for the matching logic.
enum StructInner {
pub(crate) enum StructInner {
/// Single-field struct declared with `#[xml(transparent)]`.
///
/// Transparent struct delegate all parsing and serialising to their
@ -58,7 +58,7 @@ enum StructInner {
}
impl StructInner {
fn new(meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
pub(crate) fn new(meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
// We destructure here so that we get informed when new fields are
// added and can handle them, either by processing them or raising
// an error if they are present.
@ -150,7 +150,7 @@ impl StructInner {
}
}
fn make_from_events_statemachine(
pub(crate) fn make_from_events_statemachine(
&self,
state_ty_ident: &Ident,
output_name: &ParentRef,
@ -225,7 +225,7 @@ impl StructInner {
}
}
fn make_as_item_iter_statemachine(
pub(crate) fn make_as_item_iter_statemachine(
&self,
input_name: &ParentRef,
state_ty_ident: &Ident,