xso: implement support for enums
This commit is contained in:
parent
3a56b2bb10
commit
4845715add
9 changed files with 594 additions and 8 deletions
|
@ -617,3 +617,120 @@ fn renamed_types_get_renamed() {
|
|||
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||
#[xml(namespace = NS1, name = "elem")]
|
||||
struct LintTest_;
|
||||
|
||||
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||
#[xml(namespace = NS1)]
|
||||
enum NameSwitchedEnum {
|
||||
#[xml(name = "a")]
|
||||
Variant1 {
|
||||
#[xml(attribute)]
|
||||
foo: String,
|
||||
},
|
||||
#[xml(name = "b")]
|
||||
Variant2 {
|
||||
#[xml(text)]
|
||||
foo: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_switched_enum_positive_variant_1() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<NameSwitchedEnum>("<a xmlns='urn:example:ns1' foo='hello'/>") {
|
||||
Ok(NameSwitchedEnum::Variant1 { foo }) => {
|
||||
assert_eq!(foo, "hello");
|
||||
}
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_switched_enum_positive_variant_2() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<NameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>") {
|
||||
Ok(NameSwitchedEnum::Variant2 { foo }) => {
|
||||
assert_eq!(foo, "hello");
|
||||
}
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_switched_enum_negative_name_mismatch() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<NameSwitchedEnum>("<x xmlns='urn:example:ns1'>hello</x>") {
|
||||
Err(xso::error::FromElementError::Mismatch { .. }) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_switched_enum_negative_namespace_mismatch() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<NameSwitchedEnum>("<b xmlns='urn:example:ns2'>hello</b>") {
|
||||
Err(xso::error::FromElementError::Mismatch { .. }) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_switched_enum_roundtrip_variant_1() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
roundtrip_full::<NameSwitchedEnum>("<a xmlns='urn:example:ns1' foo='hello'/>")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_switched_enum_roundtrip_variant_2() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
roundtrip_full::<NameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>")
|
||||
}
|
||||
|
||||
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||
#[xml(namespace = NS1, builder = RenamedEnumBuilder, iterator = RenamedEnumIter)]
|
||||
enum RenamedEnumTypes {
|
||||
#[xml(name = "elem")]
|
||||
A,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_enum_types_roundtrip() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
roundtrip_full::<RenamedEnumTypes>("<elem xmlns='urn:example:ns1'/>")
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_comparisons)]
|
||||
fn renamed_enum_types_get_renamed() {
|
||||
// these merely serve as a test that the types are declared with the names
|
||||
// given in the attributes.
|
||||
assert!(std::mem::size_of::<RenamedEnumBuilder>() >= 0);
|
||||
assert!(std::mem::size_of::<RenamedEnumIter>() >= 0);
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ impl Compound {
|
|||
/// `rxml::QName` is in scope.
|
||||
pub(crate) fn make_as_item_iter_statemachine(
|
||||
&self,
|
||||
input_name: &Path,
|
||||
input_name: &ParentRef,
|
||||
state_prefix: &str,
|
||||
lifetime: &Lifetime,
|
||||
) -> Result<AsItemsSubmachine> {
|
||||
|
@ -430,11 +430,13 @@ impl Compound {
|
|||
}),
|
||||
);
|
||||
|
||||
let ParentRef::Named(input_path) = input_name;
|
||||
|
||||
Ok(AsItemsSubmachine {
|
||||
defs: TokenStream::default(),
|
||||
states,
|
||||
destructure: quote! {
|
||||
#input_name { #destructure }
|
||||
#input_path { #destructure }
|
||||
},
|
||||
init: quote! {
|
||||
Self::#element_head_start_state_ident { #dummy_ident: ::std::marker::PhantomData, #name_ident: name.1, #ns_ident: name.0, #start_init }
|
||||
|
|
319
xso-proc/src/enums.rs
Normal file
319
xso-proc/src/enums.rs
Normal file
|
@ -0,0 +1,319 @@
|
|||
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Handling of enums
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::*;
|
||||
|
||||
use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
|
||||
use crate::compound::Compound;
|
||||
use crate::error_message::ParentRef;
|
||||
use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
|
||||
use crate::state::{AsItemsStateMachine, FromEventsStateMachine};
|
||||
|
||||
/// The definition of an enum variant, switched on the XML element's name.
|
||||
struct NameVariant {
|
||||
/// The XML name of the element to map the enum variant to.
|
||||
name: NameRef,
|
||||
|
||||
/// The name of the variant
|
||||
ident: Ident,
|
||||
|
||||
/// The field(s) of this struct.
|
||||
inner: Compound,
|
||||
}
|
||||
|
||||
impl NameVariant {
|
||||
/// Construct a new name-selected variant from its declaration.
|
||||
fn new(decl: &Variant) -> Result<Self> {
|
||||
let meta = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
|
||||
|
||||
if let Some(namespace) = meta.namespace {
|
||||
return Err(Error::new_spanned(
|
||||
namespace,
|
||||
"`namespace` is not allowed on enum variants (only on enums and structs)",
|
||||
));
|
||||
}
|
||||
|
||||
let Some(name) = meta.name else {
|
||||
return Err(Error::new(meta.span, "`name` is required on enum variants"));
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
ident: decl.ident.clone(),
|
||||
inner: Compound::from_fields(&decl.fields)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_from_events_statemachine(
|
||||
&self,
|
||||
enum_ident: &Ident,
|
||||
state_ty_ident: &Ident,
|
||||
) -> Result<FromEventsStateMachine> {
|
||||
let xml_name = &self.name;
|
||||
|
||||
Ok(self
|
||||
.inner
|
||||
.make_from_events_statemachine(
|
||||
state_ty_ident,
|
||||
&ParentRef::Named(Path {
|
||||
leading_colon: None,
|
||||
segments: [
|
||||
PathSegment::from(enum_ident.clone()),
|
||||
self.ident.clone().into(),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
}),
|
||||
&self.ident.to_string(),
|
||||
)?
|
||||
.with_augmented_init(|init| {
|
||||
quote! {
|
||||
if name.1 != #xml_name {
|
||||
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
|
||||
name,
|
||||
attrs,
|
||||
})
|
||||
} else {
|
||||
#init
|
||||
}
|
||||
}
|
||||
})
|
||||
.compile())
|
||||
}
|
||||
|
||||
fn make_as_item_iter_statemachine(
|
||||
&self,
|
||||
xml_namespace: &NamespaceRef,
|
||||
enum_ident: &Ident,
|
||||
item_iter_ty_lifetime: &Lifetime,
|
||||
) -> Result<AsItemsStateMachine> {
|
||||
let xml_name = &self.name;
|
||||
|
||||
Ok(self
|
||||
.inner
|
||||
.make_as_item_iter_statemachine(
|
||||
&ParentRef::Named(Path {
|
||||
leading_colon: None,
|
||||
segments: [
|
||||
PathSegment::from(enum_ident.clone()),
|
||||
self.ident.clone().into(),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
}),
|
||||
&self.ident.to_string(),
|
||||
&item_iter_ty_lifetime,
|
||||
)?
|
||||
.with_augmented_init(|init| {
|
||||
quote! {
|
||||
let name = (
|
||||
::xso::exports::rxml::Namespace::from(#xml_namespace),
|
||||
::std::borrow::Cow::Borrowed(#xml_name),
|
||||
);
|
||||
#init
|
||||
}
|
||||
})
|
||||
.compile())
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of an enum and how to parse it.
|
||||
pub(crate) struct EnumDef {
|
||||
/// The XML namespace of the element to map the enum to.
|
||||
namespace: NamespaceRef,
|
||||
|
||||
/// The variants of the enum.
|
||||
variants: Vec<NameVariant>,
|
||||
|
||||
/// Name of the target type.
|
||||
target_ty_ident: Ident,
|
||||
|
||||
/// Name of the builder type.
|
||||
builder_ty_ident: Ident,
|
||||
|
||||
/// Name of the iterator type.
|
||||
item_iter_ty_ident: Ident,
|
||||
|
||||
/// Flag whether debug mode is enabled.
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl EnumDef {
|
||||
/// Create a new enum from its name, meta, and variants.
|
||||
pub(crate) fn new<'x, I: IntoIterator<Item = &'x Variant>>(
|
||||
ident: &Ident,
|
||||
meta: XmlCompoundMeta,
|
||||
variant_iter: I,
|
||||
) -> Result<Self> {
|
||||
if let Some(name) = meta.name {
|
||||
return Err(Error::new_spanned(
|
||||
name,
|
||||
"`name` is not allowed on enums (only on their variants)",
|
||||
));
|
||||
}
|
||||
|
||||
let Some(namespace) = meta.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 {
|
||||
let variant = NameVariant::new(variant)?;
|
||||
if let Some(other) = seen_names.get(&variant.name) {
|
||||
return Err(Error::new_spanned(
|
||||
variant.name,
|
||||
format!(
|
||||
"duplicate `name` in enum: variants {} and {} have the same XML name",
|
||||
other, variant.ident
|
||||
),
|
||||
));
|
||||
}
|
||||
seen_names.insert(variant.name.clone(), variant.ident.clone());
|
||||
variants.push(variant);
|
||||
}
|
||||
|
||||
let builder_ty_ident = match meta.builder {
|
||||
Some(v) => v,
|
||||
None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
|
||||
};
|
||||
|
||||
let item_iter_ty_ident = match meta.iterator {
|
||||
Some(v) => v,
|
||||
None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
namespace,
|
||||
variants,
|
||||
target_ty_ident: ident.clone(),
|
||||
builder_ty_ident,
|
||||
item_iter_ty_ident,
|
||||
debug: meta.debug.is_set(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemDef for EnumDef {
|
||||
fn make_from_events_builder(
|
||||
&self,
|
||||
vis: &Visibility,
|
||||
name_ident: &Ident,
|
||||
attrs_ident: &Ident,
|
||||
) -> Result<FromXmlParts> {
|
||||
let xml_namespace = &self.namespace;
|
||||
let target_ty_ident = &self.target_ty_ident;
|
||||
let builder_ty_ident = &self.builder_ty_ident;
|
||||
let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
|
||||
|
||||
let mut statemachine = FromEventsStateMachine::new();
|
||||
for variant in self.variants.iter() {
|
||||
statemachine
|
||||
.merge(variant.make_from_events_statemachine(target_ty_ident, &state_ty_ident)?);
|
||||
}
|
||||
|
||||
statemachine.set_pre_init(quote! {
|
||||
if name.0 != #xml_namespace {
|
||||
return ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
|
||||
name,
|
||||
attrs,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let defs = statemachine.render(
|
||||
vis,
|
||||
builder_ty_ident,
|
||||
&state_ty_ident,
|
||||
&TypePath {
|
||||
qself: None,
|
||||
path: target_ty_ident.clone().into(),
|
||||
}
|
||||
.into(),
|
||||
)?;
|
||||
|
||||
Ok(FromXmlParts {
|
||||
defs,
|
||||
from_events_body: quote! {
|
||||
#builder_ty_ident::new(#name_ident, #attrs_ident)
|
||||
},
|
||||
builder_ty_ident: builder_ty_ident.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
|
||||
let target_ty_ident = &self.target_ty_ident;
|
||||
let item_iter_ty_ident = &self.item_iter_ty_ident;
|
||||
let item_iter_ty_lifetime = Lifetime {
|
||||
apostrophe: Span::call_site(),
|
||||
ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
|
||||
};
|
||||
let item_iter_ty = Type::Path(TypePath {
|
||||
qself: None,
|
||||
path: Path {
|
||||
leading_colon: None,
|
||||
segments: [PathSegment {
|
||||
ident: item_iter_ty_ident.clone(),
|
||||
arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
|
||||
colon2_token: None,
|
||||
lt_token: token::Lt {
|
||||
spans: [Span::call_site()],
|
||||
},
|
||||
args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
gt_token: token::Gt {
|
||||
spans: [Span::call_site()],
|
||||
},
|
||||
}),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
});
|
||||
let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
|
||||
|
||||
let mut statemachine = AsItemsStateMachine::new();
|
||||
for variant in self.variants.iter() {
|
||||
statemachine.merge(variant.make_as_item_iter_statemachine(
|
||||
&self.namespace,
|
||||
target_ty_ident,
|
||||
&item_iter_ty_lifetime,
|
||||
)?);
|
||||
}
|
||||
|
||||
let defs = statemachine.render(
|
||||
vis,
|
||||
&TypePath {
|
||||
qself: None,
|
||||
path: target_ty_ident.clone().into(),
|
||||
}
|
||||
.into(),
|
||||
&state_ty_ident,
|
||||
&item_iter_ty_lifetime,
|
||||
&item_iter_ty,
|
||||
)?;
|
||||
|
||||
Ok(AsXmlParts {
|
||||
defs,
|
||||
as_xml_iter_body: quote! {
|
||||
#item_iter_ty_ident::new(self)
|
||||
},
|
||||
item_iter_ty,
|
||||
item_iter_ty_lifetime,
|
||||
})
|
||||
}
|
||||
|
||||
fn debug(&self) -> bool {
|
||||
self.debug
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ use syn::*;
|
|||
|
||||
mod common;
|
||||
mod compound;
|
||||
mod enums;
|
||||
mod error_message;
|
||||
mod field;
|
||||
mod meta;
|
||||
|
@ -41,12 +42,17 @@ use common::{AsXmlParts, FromXmlParts, ItemDef};
|
|||
///
|
||||
/// If the item is of an unsupported variant, an appropriate error is
|
||||
/// returned.
|
||||
fn parse_struct(item: Item) -> Result<(Visibility, Ident, structs::StructDef)> {
|
||||
fn parse_struct(item: Item) -> Result<(Visibility, Ident, Box<dyn ItemDef>)> {
|
||||
match item {
|
||||
Item::Struct(item) => {
|
||||
let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
|
||||
let def = structs::StructDef::new(&item.ident, meta, &item.fields)?;
|
||||
Ok((item.vis, item.ident, def))
|
||||
Ok((item.vis, item.ident, Box::new(def)))
|
||||
}
|
||||
Item::Enum(item) => {
|
||||
let meta = meta::XmlCompoundMeta::parse_from_attributes(&item.attrs)?;
|
||||
let def = enums::EnumDef::new(&item.ident, meta, &item.variants)?;
|
||||
Ok((item.vis, item.ident, Box::new(def)))
|
||||
}
|
||||
other => Err(Error::new_spanned(other, "cannot derive on this item")),
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
//! This module is concerned with parsing attributes from the Rust "meta"
|
||||
//! annotations on structs, enums, enum variants and fields.
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{meta::ParseNestedMeta, spanned::Spanned, *};
|
||||
|
@ -56,7 +58,7 @@ impl quote::ToTokens for NamespaceRef {
|
|||
}
|
||||
|
||||
/// Value for the `#[xml(name = .. )]` attribute.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum NameRef {
|
||||
/// The XML name is specified as a string literal.
|
||||
Literal {
|
||||
|
@ -71,6 +73,38 @@ pub(crate) enum NameRef {
|
|||
Path(Path),
|
||||
}
|
||||
|
||||
impl Hash for NameRef {
|
||||
fn hash<H: Hasher>(&self, h: &mut H) {
|
||||
match self {
|
||||
Self::Literal { ref value, .. } => value.hash(h),
|
||||
Self::Path(ref path) => path.hash(h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NameRef {
|
||||
fn eq(&self, other: &NameRef) -> bool {
|
||||
match self {
|
||||
Self::Literal {
|
||||
value: ref my_value,
|
||||
..
|
||||
} => match other {
|
||||
Self::Literal {
|
||||
value: ref other_value,
|
||||
..
|
||||
} => my_value == other_value,
|
||||
_ => false,
|
||||
},
|
||||
Self::Path(ref my_path) => match other {
|
||||
Self::Path(ref other_path) => my_path == other_path,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for NameRef {}
|
||||
|
||||
impl syn::parse::Parse for NameRef {
|
||||
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
|
||||
if input.peek(syn::LitStr) {
|
||||
|
|
|
@ -176,6 +176,7 @@ impl FromEventsSubmachine {
|
|||
state_defs,
|
||||
advance_match_arms,
|
||||
variants: vec![FromEventsEntryPoint { init: self.init }],
|
||||
pre_init: TokenStream::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -369,6 +370,9 @@ pub(crate) struct FromEventsStateMachine {
|
|||
/// Extra items which are needed for the state machine implementation.
|
||||
defs: TokenStream,
|
||||
|
||||
/// Extra code run during pre-init phase.
|
||||
pre_init: TokenStream,
|
||||
|
||||
/// A sequence of enum variant declarations, separated and terminated by
|
||||
/// commas.
|
||||
state_defs: TokenStream,
|
||||
|
@ -390,6 +394,36 @@ pub(crate) struct FromEventsStateMachine {
|
|||
}
|
||||
|
||||
impl FromEventsStateMachine {
|
||||
/// Create a new, empty state machine.
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
defs: TokenStream::default(),
|
||||
state_defs: TokenStream::default(),
|
||||
advance_match_arms: TokenStream::default(),
|
||||
pre_init: TokenStream::default(),
|
||||
variants: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge another state machine into this state machine.
|
||||
///
|
||||
/// This *discards* the other state machine's pre-init code.
|
||||
pub(crate) fn merge(&mut self, other: FromEventsStateMachine) {
|
||||
self.defs.extend(other.defs);
|
||||
self.state_defs.extend(other.state_defs);
|
||||
self.advance_match_arms.extend(other.advance_match_arms);
|
||||
self.variants.extend(other.variants);
|
||||
}
|
||||
|
||||
/// Set additional code to inject at the head of the `new` method for the
|
||||
/// builder.
|
||||
///
|
||||
/// This can be used to do preliminary checks and is commonly used with
|
||||
/// specifically-formed init codes on the variants.
|
||||
pub(crate) fn set_pre_init(&mut self, code: TokenStream) {
|
||||
self.pre_init = code;
|
||||
}
|
||||
|
||||
/// Render the state machine as a token stream.
|
||||
///
|
||||
/// The token stream contains the following pieces:
|
||||
|
@ -411,9 +445,10 @@ impl FromEventsStateMachine {
|
|||
state_defs,
|
||||
advance_match_arms,
|
||||
variants,
|
||||
pre_init,
|
||||
} = self;
|
||||
|
||||
let mut init_body = TokenStream::default();
|
||||
let mut init_body = pre_init;
|
||||
for variant in variants {
|
||||
let FromEventsEntryPoint { init } = variant;
|
||||
init_body.extend(quote! {
|
||||
|
@ -550,6 +585,24 @@ pub(crate) struct AsItemsStateMachine {
|
|||
}
|
||||
|
||||
impl AsItemsStateMachine {
|
||||
/// Create a new, empty state machine.
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
defs: TokenStream::default(),
|
||||
state_defs: TokenStream::default(),
|
||||
advance_match_arms: TokenStream::default(),
|
||||
variants: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge another state machine into this state machine.
|
||||
pub(crate) fn merge(&mut self, other: AsItemsStateMachine) {
|
||||
self.defs.extend(other.defs);
|
||||
self.state_defs.extend(other.state_defs);
|
||||
self.advance_match_arms.extend(other.advance_match_arms);
|
||||
self.variants.extend(other.variants);
|
||||
}
|
||||
|
||||
/// Render the state machine as a token stream.
|
||||
///
|
||||
/// The token stream contains the following pieces:
|
||||
|
@ -588,7 +641,7 @@ impl AsItemsStateMachine {
|
|||
let mut match_arms = TokenStream::default();
|
||||
for AsItemsEntryPoint { destructure, init } in variants {
|
||||
match_arms.extend(quote! {
|
||||
#destructure => #init,
|
||||
#destructure => { #init }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ impl ItemDef for StructDef {
|
|||
let defs = self
|
||||
.inner
|
||||
.make_as_item_iter_statemachine(
|
||||
&target_ty_ident.clone().into(),
|
||||
&Path::from(target_ty_ident.clone()).into(),
|
||||
"Struct",
|
||||
&item_iter_ty_lifetime,
|
||||
)?
|
||||
|
|
|
@ -5,6 +5,7 @@ Version NEXT:
|
|||
be wrapped in Option or Box.
|
||||
- Support for overriding the names of the types generated by the derive
|
||||
macros.
|
||||
- Support for deriving FromXml and AsXml on enums.
|
||||
|
||||
Version 0.1.2:
|
||||
2024-07-26 Jonas Schäfer <jonas@zombofant.net>
|
||||
|
|
|
@ -71,6 +71,60 @@ By default, the builder type uses the type's name suffixed with
|
|||
`FromXmlBuilder` and the iterator type uses the type's name suffixed with
|
||||
`AsXmlIterator`.
|
||||
|
||||
### Enum meta
|
||||
|
||||
The following keys are defined on enums:
|
||||
|
||||
| Key | Value type | Description |
|
||||
| --- | --- | --- |
|
||||
| `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. |
|
||||
| `iterator` | optional *ident* | The name to use for the generated iterator type. |
|
||||
|
||||
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.
|
||||
|
||||
For details on `builder` and `iterator`, see the [Struct meta](#struct-meta)
|
||||
documentation above.
|
||||
|
||||
#### Enum variant meta
|
||||
|
||||
| Key | Value type | Description |
|
||||
| --- | --- | --- |
|
||||
| `name` | *string literal* or *path* | The XML element name to match for this variant. If it is a *path*, it must point at a `&'static NcNameStr`. |
|
||||
|
||||
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
|
||||
|
||||
```rust
|
||||
# use xso::FromXml;
|
||||
#[derive(FromXml, Debug, PartialEq)]
|
||||
#[xml(namespace = "urn:example")]
|
||||
enum Foo {
|
||||
#[xml(name = "a")]
|
||||
Variant1 {
|
||||
#[xml(attribute)]
|
||||
foo: String,
|
||||
},
|
||||
#[xml(name = "b")]
|
||||
Variant2 {
|
||||
#[xml(attribute)]
|
||||
bar: String,
|
||||
},
|
||||
}
|
||||
|
||||
let foo: Foo = xso::from_bytes(b"<a xmlns='urn:example' foo='hello'/>").unwrap();
|
||||
assert_eq!(foo, Foo::Variant1 { foo: "hello".to_string() });
|
||||
|
||||
let foo: Foo = xso::from_bytes(b"<b xmlns='urn:example' 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,
|
||||
|
|
Loading…
Reference in a new issue