xso: implement exhaustive enums
These more closely mirror how enums work currently with the macros. Non-exhaustive enums may be useful though and kind of were the natural thing to implement.
This commit is contained in:
parent
a20caf839f
commit
c028c3b91a
6 changed files with 127 additions and 2 deletions
|
|
@ -734,3 +734,64 @@ fn renamed_enum_types_get_renamed() {
|
||||||
assert!(std::mem::size_of::<RenamedEnumBuilder>() >= 0);
|
assert!(std::mem::size_of::<RenamedEnumBuilder>() >= 0);
|
||||||
assert!(std::mem::size_of::<RenamedEnumIter>() >= 0);
|
assert!(std::mem::size_of::<RenamedEnumIter>() >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(namespace = NS1, exhaustive)]
|
||||||
|
enum ExhaustiveNameSwitchedEnum {
|
||||||
|
#[xml(name = "a")]
|
||||||
|
Variant1 {
|
||||||
|
#[xml(attribute)]
|
||||||
|
foo: String,
|
||||||
|
},
|
||||||
|
#[xml(name = "b")]
|
||||||
|
Variant2 {
|
||||||
|
#[xml(text)]
|
||||||
|
foo: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exhaustive_name_switched_enum_negative_name_mismatch() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<ExhaustiveNameSwitchedEnum>("<x xmlns='urn:example:ns1'>hello</x>") {
|
||||||
|
Err(xso::error::FromElementError::Invalid { .. }) => (),
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exhaustive_name_switched_enum_negative_namespace_mismatch() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
match parse_str::<ExhaustiveNameSwitchedEnum>("<b xmlns='urn:example:ns2'>hello</b>") {
|
||||||
|
Err(xso::error::FromElementError::Mismatch { .. }) => (),
|
||||||
|
other => panic!("unexpected result: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exhaustive_name_switched_enum_roundtrip_variant_1() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<ExhaustiveNameSwitchedEnum>("<a xmlns='urn:example:ns1' foo='hello'/>")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exhaustive_name_switched_enum_roundtrip_variant_2() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<ExhaustiveNameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,14 @@ impl NameVariant {
|
||||||
span: meta_span,
|
span: meta_span,
|
||||||
namespace,
|
namespace,
|
||||||
name,
|
name,
|
||||||
|
exhaustive,
|
||||||
debug,
|
debug,
|
||||||
builder,
|
builder,
|
||||||
iterator,
|
iterator,
|
||||||
} = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
|
} = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
|
||||||
|
|
||||||
reject_key!(debug flag not on "enum variants" only on "enums and structs");
|
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!(namespace not on "enum variants" only on "enums and structs");
|
reject_key!(namespace not on "enum variants" only on "enums and structs");
|
||||||
reject_key!(builder not on "enum variants" only on "enums and structs");
|
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!(iterator not on "enum variants" only on "enums and structs");
|
||||||
|
|
@ -142,6 +144,9 @@ pub(crate) struct EnumDef {
|
||||||
/// The variants of the enum.
|
/// The variants of the enum.
|
||||||
variants: Vec<NameVariant>,
|
variants: Vec<NameVariant>,
|
||||||
|
|
||||||
|
/// Flag indicating whether the enum is exhaustive.
|
||||||
|
exhaustive: bool,
|
||||||
|
|
||||||
/// Name of the target type.
|
/// Name of the target type.
|
||||||
target_ty_ident: Ident,
|
target_ty_ident: Ident,
|
||||||
|
|
||||||
|
|
@ -169,6 +174,7 @@ impl EnumDef {
|
||||||
span: meta_span,
|
span: meta_span,
|
||||||
namespace,
|
namespace,
|
||||||
name,
|
name,
|
||||||
|
exhaustive,
|
||||||
debug,
|
debug,
|
||||||
builder,
|
builder,
|
||||||
iterator,
|
iterator,
|
||||||
|
|
@ -210,6 +216,7 @@ impl EnumDef {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
namespace,
|
namespace,
|
||||||
variants,
|
variants,
|
||||||
|
exhaustive: exhaustive.is_set(),
|
||||||
target_ty_ident: ident.clone(),
|
target_ty_ident: ident.clone(),
|
||||||
builder_ty_ident,
|
builder_ty_ident,
|
||||||
item_iter_ty_ident,
|
item_iter_ty_ident,
|
||||||
|
|
@ -245,6 +252,15 @@ impl ItemDef for EnumDef {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if self.exhaustive {
|
||||||
|
let mismatch_err = format!("This is not a {} element.", target_ty_ident);
|
||||||
|
statemachine.set_fallback(quote! {
|
||||||
|
::core::result::Result::Err(::xso::error::FromEventsError::Invalid(
|
||||||
|
::xso::error::Error::Other(#mismatch_err),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let defs = statemachine.render(
|
let defs = statemachine.render(
|
||||||
vis,
|
vis,
|
||||||
builder_ty_ident,
|
builder_ty_ident,
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,9 @@ pub(crate) struct XmlCompoundMeta {
|
||||||
|
|
||||||
/// The value assigned to `iterator` inside `#[xml(..)]`, if any.
|
/// The value assigned to `iterator` inside `#[xml(..)]`, if any.
|
||||||
pub(crate) iterator: Option<Ident>,
|
pub(crate) iterator: Option<Ident>,
|
||||||
|
|
||||||
|
/// The exhaustive flag.
|
||||||
|
pub(crate) exhaustive: Flag,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XmlCompoundMeta {
|
impl XmlCompoundMeta {
|
||||||
|
|
@ -248,6 +251,7 @@ impl XmlCompoundMeta {
|
||||||
let mut builder = None;
|
let mut builder = None;
|
||||||
let mut iterator = None;
|
let mut iterator = None;
|
||||||
let mut debug = Flag::Absent;
|
let mut debug = Flag::Absent;
|
||||||
|
let mut exhaustive = Flag::Absent;
|
||||||
|
|
||||||
attr.parse_nested_meta(|meta| {
|
attr.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident("name") {
|
if meta.path.is_ident("name") {
|
||||||
|
|
@ -280,6 +284,12 @@ impl XmlCompoundMeta {
|
||||||
}
|
}
|
||||||
iterator = Some(meta.value()?.parse()?);
|
iterator = Some(meta.value()?.parse()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else if meta.path.is_ident("exhaustive") {
|
||||||
|
if exhaustive.is_set() {
|
||||||
|
return Err(Error::new_spanned(meta.path, "duplicate `exhaustive` key"));
|
||||||
|
}
|
||||||
|
exhaustive = (&meta.path).into();
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new_spanned(meta.path, "unsupported key"))
|
Err(Error::new_spanned(meta.path, "unsupported key"))
|
||||||
}
|
}
|
||||||
|
|
@ -292,6 +302,7 @@ impl XmlCompoundMeta {
|
||||||
debug,
|
debug,
|
||||||
builder,
|
builder,
|
||||||
iterator,
|
iterator,
|
||||||
|
exhaustive,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,7 @@ impl FromEventsSubmachine {
|
||||||
advance_match_arms,
|
advance_match_arms,
|
||||||
variants: vec![FromEventsEntryPoint { init: self.init }],
|
variants: vec![FromEventsEntryPoint { init: self.init }],
|
||||||
pre_init: TokenStream::default(),
|
pre_init: TokenStream::default(),
|
||||||
|
fallback: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,6 +374,12 @@ pub(crate) struct FromEventsStateMachine {
|
||||||
/// Extra code run during pre-init phase.
|
/// Extra code run during pre-init phase.
|
||||||
pre_init: TokenStream,
|
pre_init: TokenStream,
|
||||||
|
|
||||||
|
/// Code to run as fallback if none of the branches matched the start
|
||||||
|
/// event.
|
||||||
|
///
|
||||||
|
/// If absent, a `FromEventsError::Mismatch` is generated.
|
||||||
|
fallback: Option<TokenStream>,
|
||||||
|
|
||||||
/// A sequence of enum variant declarations, separated and terminated by
|
/// A sequence of enum variant declarations, separated and terminated by
|
||||||
/// commas.
|
/// commas.
|
||||||
state_defs: TokenStream,
|
state_defs: TokenStream,
|
||||||
|
|
@ -402,6 +409,7 @@ impl FromEventsStateMachine {
|
||||||
advance_match_arms: TokenStream::default(),
|
advance_match_arms: TokenStream::default(),
|
||||||
pre_init: TokenStream::default(),
|
pre_init: TokenStream::default(),
|
||||||
variants: Vec::new(),
|
variants: Vec::new(),
|
||||||
|
fallback: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -409,6 +417,7 @@ impl FromEventsStateMachine {
|
||||||
///
|
///
|
||||||
/// This *discards* the other state machine's pre-init code.
|
/// This *discards* the other state machine's pre-init code.
|
||||||
pub(crate) fn merge(&mut self, other: FromEventsStateMachine) {
|
pub(crate) fn merge(&mut self, other: FromEventsStateMachine) {
|
||||||
|
assert!(other.fallback.is_none());
|
||||||
self.defs.extend(other.defs);
|
self.defs.extend(other.defs);
|
||||||
self.state_defs.extend(other.state_defs);
|
self.state_defs.extend(other.state_defs);
|
||||||
self.advance_match_arms.extend(other.advance_match_arms);
|
self.advance_match_arms.extend(other.advance_match_arms);
|
||||||
|
|
@ -424,6 +433,14 @@ impl FromEventsStateMachine {
|
||||||
self.pre_init = code;
|
self.pre_init = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the fallback code to use if none of the branches matches the start
|
||||||
|
/// event.
|
||||||
|
///
|
||||||
|
/// By default, a `FromEventsError::Mismatch` is generated.
|
||||||
|
pub(crate) fn set_fallback(&mut self, code: TokenStream) {
|
||||||
|
self.fallback = Some(code);
|
||||||
|
}
|
||||||
|
|
||||||
/// Render the state machine as a token stream.
|
/// Render the state machine as a token stream.
|
||||||
///
|
///
|
||||||
/// The token stream contains the following pieces:
|
/// The token stream contains the following pieces:
|
||||||
|
|
@ -446,6 +463,7 @@ impl FromEventsStateMachine {
|
||||||
advance_match_arms,
|
advance_match_arms,
|
||||||
variants,
|
variants,
|
||||||
pre_init,
|
pre_init,
|
||||||
|
fallback,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut init_body = pre_init;
|
let mut init_body = pre_init;
|
||||||
|
|
@ -460,6 +478,12 @@ impl FromEventsStateMachine {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fallback = fallback.unwrap_or_else(|| {
|
||||||
|
quote! {
|
||||||
|
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let output_ty_ref = make_ty_ref(output_ty);
|
let output_ty_ref = make_ty_ref(output_ty);
|
||||||
|
|
||||||
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 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);
|
||||||
|
|
@ -520,7 +544,7 @@ impl FromEventsStateMachine {
|
||||||
) -> ::core::result::Result<Self, ::xso::error::FromEventsError> {
|
) -> ::core::result::Result<Self, ::xso::error::FromEventsError> {
|
||||||
#init_body
|
#init_body
|
||||||
{ let _ = &mut attrs; }
|
{ let _ = &mut attrs; }
|
||||||
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
|
#fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use syn::*;
|
||||||
|
|
||||||
use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
|
use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
|
||||||
use crate::compound::Compound;
|
use crate::compound::Compound;
|
||||||
use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
|
use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, XmlCompoundMeta};
|
||||||
|
|
||||||
/// Definition of a struct and how to parse it.
|
/// Definition of a struct and how to parse it.
|
||||||
pub(crate) struct StructDef {
|
pub(crate) struct StructDef {
|
||||||
|
|
@ -48,11 +48,14 @@ impl StructDef {
|
||||||
span: meta_span,
|
span: meta_span,
|
||||||
namespace,
|
namespace,
|
||||||
name,
|
name,
|
||||||
|
exhaustive,
|
||||||
debug,
|
debug,
|
||||||
builder,
|
builder,
|
||||||
iterator,
|
iterator,
|
||||||
} = meta;
|
} = meta;
|
||||||
|
|
||||||
|
reject_key!(exhaustive flag not on "structs" only on "enums");
|
||||||
|
|
||||||
let Some(namespace) = namespace else {
|
let Some(namespace) = namespace else {
|
||||||
return Err(Error::new(meta_span, "`namespace` is required on structs"));
|
return Err(Error::new(meta_span, "`namespace` is required on structs"));
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -81,12 +81,22 @@ The following keys are defined on enums:
|
||||||
| `namespace` | *string literal* or *path* | The XML element namespace to match for this enum. If it is a *path*, it must point at a `&'static str`. |
|
| `namespace` | *string literal* or *path* | The XML element namespace to match for this enum. If it is a *path*, it must point at a `&'static str`. |
|
||||||
| `builder` | optional *ident* | The name to use for the generated builder type. |
|
| `builder` | optional *ident* | The name to use for the generated builder type. |
|
||||||
| `iterator` | optional *ident* | The name to use for the generated iterator type. |
|
| `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
|
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
|
exclusively by their XML name within that namespace. The contents of the XML
|
||||||
element (including attributes) is not inspected before selecting the variant
|
element (including attributes) is not inspected before selecting the variant
|
||||||
when parsing XML.
|
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
|
||||||
|
with an error. If *exhaustive* is *not* set, in such a situation, parsing
|
||||||
|
would attempt to continue with other siblings of the enum, attempting to find
|
||||||
|
a handler for that element.
|
||||||
|
|
||||||
|
Note that the *exhaustive* flag is orthogonal to the Rust attribute
|
||||||
|
`#[non_exhaustive]`.
|
||||||
|
|
||||||
For details on `builder` and `iterator`, see the [Struct meta](#struct-meta)
|
For details on `builder` and `iterator`, see the [Struct meta](#struct-meta)
|
||||||
documentation above.
|
documentation above.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue