@@ -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,
@@ -81,9 +81,27 @@ implement [`FromXml`] in order to derive `FromXml` and [`AsXml`] in order to
derive `AsXml`. The struct will be (de-)serialised exactly like the type of
that single field. This allows a newtype-like pattern for XSO structs.
-### Enum meta
+### Enums
-The following keys are defined on enums:
+Two different `enum` flavors are supported:
+
+1. [**Name-switched enums**](#name-switched-enum-meta) have a fixed XML
+ namespace they match on and each variant corresponds to a different XML
+ element name within that namespace.
+
+2. [**Dynamic enums**](#dynamic-enum-meta) have entirely unrelated variants.
+
+At the source-code level, they are distinguished by the meta keys which are
+present on the `enum`: The different variants have different sets of mandatory
+keys and can thus be uniquely identified.
+
+#### Name-switched enum meta
+
+Name-switched enums match a fixed XML namespace and then select the enum
+variant based on the XML element's name. Name-switched enums are declared by
+setting the `namespace` key on a `enum` item.
+
+The following keys are defined on name-switched enums:
| Key | Value type | Description |
| --- | --- | --- |
@@ -92,10 +110,10 @@ The following keys are defined on enums:
| `iterator` | optional *ident* | The name to use for the generated iterator type. |
| `exhaustive` | *flag* | If present, the enum considers itself authoritative for its namespace; unknown elements within the namespace are rejected instead of treated as mismatch. |
-All variants of an enum live within the same namespace and are distinguished
-exclusively by their XML name within that namespace. The contents of the XML
-element (including attributes) is not inspected before selecting the variant
-when parsing XML.
+All variants of a name-switched enum live within the same namespace and are
+distinguished exclusively by their XML name within that namespace. The
+contents of the XML element (including attributes) is not inspected before
+selecting the variant when parsing XML.
If *exhaustive* is set and an element is encountered which matches the
namespace of the enum, but matches none of its variants, parsing will fail
@@ -109,7 +127,7 @@ Note that the *exhaustive* flag is orthogonal to the Rust attribute
For details on `builder` and `iterator`, see the [Struct meta](#struct-meta)
documentation above.
-#### Enum variant meta
+##### Name-switched enum variant meta
| Key | Value type | Description |
| --- | --- | --- |
@@ -119,7 +137,7 @@ 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
and cannot be overridden.
-#### Example
+##### Example
```rust
# use xso::FromXml;
@@ -145,6 +163,57 @@ let foo: Foo = xso::from_bytes(b"<b xmlns='urn:example' bar='hello'/>").unwrap()
assert_eq!(foo, Foo::Variant2 { bar: "hello".to_string() });
```
+#### Dynamic enum meta
+
+Dynamic enums select their variants by attempting to match them in declaration
+order. Dynamic enums are declared by not setting the `namespace` key on an
+`enum` item.
+
+The following keys are defined on dynamic enums:
+
+| Key | Value type | Description |
+| --- | --- | --- |
+| `builder` | optional *ident* | The name to use for the generated builder type. |
+| `iterator` | optional *ident* | The name to use for the generated iterator type. |
+
+For details on `builder` and `iterator`, see the [Struct meta](#struct-meta)
+documentation above.
+
+##### Dynamic enum variant meta
+
+Dynamic enum variants are completely independent of one another and thus use
+the same meta structure as structs. See [Struct meta](#struct-meta) for
+details.
+
+The `builder`, `iterator` and `debug` keys cannot be used on dynmaic enum
+variants.
+
+##### Example
+
+```rust
+# use xso::FromXml;
+#[derive(FromXml, Debug, PartialEq)]
+#[xml()]
+enum Foo {
+ #[xml(namespace = "urn:example:ns1", name = "a")]
+ Variant1 {
+ #[xml(attribute)]
+ foo: String,
+ },
+ #[xml(namespace = "urn:example:ns2", name = "b")]
+ Variant2 {
+ #[xml(attribute)]
+ bar: String,
+ },
+}
+
+let foo: Foo = xso::from_bytes(b"<a xmlns='urn:example:ns1' foo='hello'/>").unwrap();
+assert_eq!(foo, Foo::Variant1 { foo: "hello".to_string() });
+
+let foo: Foo = xso::from_bytes(b"<b xmlns='urn:example:ns2' bar='hello'/>").unwrap();
+assert_eq!(foo, Foo::Variant2 { bar: "hello".to_string() });
+```
+
### Field meta
For fields, the *meta* consists of a nested meta inside the `#[xml(..)]` meta,