xso: implement transparent structs
This commit is contained in:
parent
fea915daf1
commit
fda4a9ff29
6 changed files with 360 additions and 70 deletions
|
|
@ -1570,3 +1570,33 @@ fn element_catchall_roundtrip() {
|
||||||
"<parent xmlns='urn:example:ns1'><child><deeper/></child><child xmlns='urn:example:ns2'/><more-children/><yet-another-child/><child/></parent>",
|
"<parent xmlns='urn:example:ns1'><child><deeper/></child><child xmlns='urn:example:ns2'/><more-children/><yet-another-child/><child/></parent>",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(transparent)]
|
||||||
|
struct TransparentStruct(RequiredAttribute);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transparent_struct_roundtrip() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<TransparentStruct>("<attr xmlns='urn:example:ns1' foo='bar'/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(transparent)]
|
||||||
|
struct TransparentStructNamed {
|
||||||
|
foo: RequiredAttribute,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transparent_struct_named_roundtrip() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<TransparentStructNamed>("<attr xmlns='urn:example:ns1' foo='bar'/>");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ impl NameVariant {
|
||||||
debug,
|
debug,
|
||||||
builder,
|
builder,
|
||||||
iterator,
|
iterator,
|
||||||
|
transparent,
|
||||||
} = 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");
|
||||||
|
|
@ -51,6 +52,7 @@ impl NameVariant {
|
||||||
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");
|
||||||
|
reject_key!(transparent flag not on "named enum variants" only on "structs");
|
||||||
|
|
||||||
let Some(name) = name else {
|
let Some(name) = name else {
|
||||||
return Err(Error::new(meta_span, "`name` is required on enum variants"));
|
return Err(Error::new(meta_span, "`name` is required on enum variants"));
|
||||||
|
|
@ -179,9 +181,11 @@ impl EnumDef {
|
||||||
debug,
|
debug,
|
||||||
builder,
|
builder,
|
||||||
iterator,
|
iterator,
|
||||||
|
transparent,
|
||||||
} = meta;
|
} = meta;
|
||||||
|
|
||||||
reject_key!(name not on "enums" only on "their variants");
|
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 {
|
let Some(namespace) = namespace else {
|
||||||
return Err(Error::new(meta_span, "`namespace` is required on enums"));
|
return Err(Error::new(meta_span, "`namespace` is required on enums"));
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub const XMLNS_XML: &str = "http://www.w3.org/XML/1998/namespace";
|
||||||
pub const XMLNS_XMLNS: &str = "http://www.w3.org/2000/xmlns/";
|
pub const XMLNS_XMLNS: &str = "http://www.w3.org/2000/xmlns/";
|
||||||
|
|
||||||
macro_rules! reject_key {
|
macro_rules! reject_key {
|
||||||
($key:ident not on $not_allowed_on:literal only on $only_allowed_on:literal) => {
|
($key:ident not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => {
|
||||||
if let Some($key) = $key {
|
if let Some($key) = $key {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
$key,
|
$key,
|
||||||
|
|
@ -32,15 +32,17 @@ macro_rules! reject_key {
|
||||||
stringify!($key),
|
stringify!($key),
|
||||||
"` is not allowed on ",
|
"` is not allowed on ",
|
||||||
$not_allowed_on,
|
$not_allowed_on,
|
||||||
|
$(
|
||||||
" (only on ",
|
" (only on ",
|
||||||
$only_allowed_on,
|
$only_allowed_on,
|
||||||
")"
|
")",
|
||||||
|
)?
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($key:ident flag not on $not_allowed_on:literal only on $only_allowed_on:literal) => {
|
($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($key) = $key {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
$key,
|
$key,
|
||||||
|
|
@ -49,9 +51,11 @@ macro_rules! reject_key {
|
||||||
stringify!($key),
|
stringify!($key),
|
||||||
"` is not allowed on ",
|
"` is not allowed on ",
|
||||||
$not_allowed_on,
|
$not_allowed_on,
|
||||||
|
$(
|
||||||
" (only on ",
|
" (only on ",
|
||||||
$only_allowed_on,
|
$only_allowed_on,
|
||||||
")"
|
")",
|
||||||
|
)?
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +256,13 @@ impl Flag {
|
||||||
Self::Present(_) => true,
|
Self::Present(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like `Option::take`, but for flags.
|
||||||
|
pub(crate) fn take(&mut self) -> Self {
|
||||||
|
let mut result = Flag::Absent;
|
||||||
|
core::mem::swap(&mut result, self);
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Spanned> From<T> for Flag {
|
impl<T: Spanned> From<T> for Flag {
|
||||||
|
|
@ -340,6 +351,9 @@ pub(crate) struct XmlCompoundMeta {
|
||||||
|
|
||||||
/// The exhaustive flag.
|
/// The exhaustive flag.
|
||||||
pub(crate) exhaustive: Flag,
|
pub(crate) exhaustive: Flag,
|
||||||
|
|
||||||
|
/// The transparent flag.
|
||||||
|
pub(crate) transparent: Flag,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XmlCompoundMeta {
|
impl XmlCompoundMeta {
|
||||||
|
|
@ -353,6 +367,7 @@ impl XmlCompoundMeta {
|
||||||
let mut iterator = None;
|
let mut iterator = None;
|
||||||
let mut debug = Flag::Absent;
|
let mut debug = Flag::Absent;
|
||||||
let mut exhaustive = Flag::Absent;
|
let mut exhaustive = Flag::Absent;
|
||||||
|
let mut transparent = Flag::Absent;
|
||||||
|
|
||||||
attr.parse_nested_meta(|meta| {
|
attr.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident("debug") {
|
if meta.path.is_ident("debug") {
|
||||||
|
|
@ -379,6 +394,12 @@ impl XmlCompoundMeta {
|
||||||
}
|
}
|
||||||
exhaustive = (&meta.path).into();
|
exhaustive = (&meta.path).into();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else if meta.path.is_ident("transparent") {
|
||||||
|
if transparent.is_set() {
|
||||||
|
return Err(Error::new_spanned(meta.path, "duplicate `transparent` key"));
|
||||||
|
}
|
||||||
|
transparent = (&meta.path).into();
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
match qname.parse_incremental_from_meta(meta)? {
|
match qname.parse_incremental_from_meta(meta)? {
|
||||||
None => Ok(()),
|
None => Ok(()),
|
||||||
|
|
@ -394,6 +415,7 @@ impl XmlCompoundMeta {
|
||||||
builder,
|
builder,
|
||||||
iterator,
|
iterator,
|
||||||
exhaustive,
|
exhaustive,
|
||||||
|
transparent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,296 @@
|
||||||
|
|
||||||
//! Handling of structs
|
//! Handling of structs
|
||||||
|
|
||||||
use proc_macro2::Span;
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::*;
|
use syn::{spanned::Spanned, *};
|
||||||
|
|
||||||
use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
|
use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
|
||||||
use crate::compound::Compound;
|
use crate::compound::Compound;
|
||||||
|
use crate::error_message::ParentRef;
|
||||||
use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
|
use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
|
||||||
use crate::types::{ref_ty, ty_from_ident};
|
use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
|
||||||
|
use crate::types::{
|
||||||
|
as_xml_iter_fn, feed_fn, from_events_fn, from_xml_builder_ty, item_iter_ty, ref_ty,
|
||||||
|
ty_from_ident,
|
||||||
|
};
|
||||||
|
|
||||||
/// Definition of a struct and how to parse it.
|
/// The inner parts of the struct.
|
||||||
pub(crate) struct StructDef {
|
///
|
||||||
|
/// This contains all data necessary for the matching logic.
|
||||||
|
enum StructInner {
|
||||||
|
/// Single-field struct declared with `#[xml(transparent)]`.
|
||||||
|
///
|
||||||
|
/// Transparent struct delegate all parsing and serialising to their
|
||||||
|
/// only field, which is why they do not need to store a lot of
|
||||||
|
/// information and come with extra restrictions, such as:
|
||||||
|
///
|
||||||
|
/// - no XML namespace can be declared (it is determined by inner type)
|
||||||
|
/// - no XML name can be declared (it is determined by inner type)
|
||||||
|
/// - there must be only exactly one field
|
||||||
|
/// - that field has no `#[xml]` attribute
|
||||||
|
Transparent {
|
||||||
|
/// The member identifier of the only field.
|
||||||
|
member: Member,
|
||||||
|
|
||||||
|
/// Type of the only field.
|
||||||
|
ty: Type,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A compound of fields, *not* declared as transparent.
|
||||||
|
///
|
||||||
|
/// This can be a unit, tuple-like, or named struct.
|
||||||
|
Compound {
|
||||||
/// The XML namespace of the element to map the struct to.
|
/// The XML namespace of the element to map the struct to.
|
||||||
namespace: NamespaceRef,
|
xml_namespace: NamespaceRef,
|
||||||
|
|
||||||
/// The XML name of the element to map the struct to.
|
/// The XML name of the element to map the struct to.
|
||||||
name: NameRef,
|
xml_name: NameRef,
|
||||||
|
|
||||||
/// The field(s) of this struct.
|
/// The field(s) of this struct.
|
||||||
inner: Compound,
|
inner: Compound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StructInner {
|
||||||
|
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.
|
||||||
|
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!(exhaustive flag not on "structs" only on "enums");
|
||||||
|
|
||||||
|
if let Flag::Present(_) = transparent {
|
||||||
|
reject_key!(namespace not on "transparent structs");
|
||||||
|
reject_key!(name not on "transparent structs");
|
||||||
|
|
||||||
|
let fields_span = fields.span();
|
||||||
|
let fields = match fields {
|
||||||
|
Fields::Unit => {
|
||||||
|
return Err(Error::new(
|
||||||
|
fields_span,
|
||||||
|
"transparent structs or enum variants must have exactly one field",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Fields::Named(FieldsNamed {
|
||||||
|
named: ref fields, ..
|
||||||
|
})
|
||||||
|
| Fields::Unnamed(FieldsUnnamed {
|
||||||
|
unnamed: ref fields,
|
||||||
|
..
|
||||||
|
}) => fields,
|
||||||
|
};
|
||||||
|
|
||||||
|
if fields.len() != 1 {
|
||||||
|
return Err(Error::new(
|
||||||
|
fields_span,
|
||||||
|
"transparent structs or enum variants must have exactly one field",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let field = &fields[0];
|
||||||
|
for attr in field.attrs.iter() {
|
||||||
|
if attr.meta.path().is_ident("xml") {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
attr,
|
||||||
|
"#[xml(..)] attributes are not allowed inside transparent structs",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let member = match field.ident.as_ref() {
|
||||||
|
Some(v) => Member::Named(v.clone()),
|
||||||
|
None => Member::Unnamed(Index {
|
||||||
|
span: field.ty.span(),
|
||||||
|
index: 0,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let ty = field.ty.clone();
|
||||||
|
Ok(Self::Transparent { ty, member })
|
||||||
|
} else {
|
||||||
|
let Some(xml_namespace) = namespace else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta_span,
|
||||||
|
"`namespace` is required on non-transparent structs",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(xml_name) = name else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta_span,
|
||||||
|
"`name` is required on non-transparent structs",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self::Compound {
|
||||||
|
inner: Compound::from_fields(fields, &xml_namespace)?,
|
||||||
|
xml_namespace,
|
||||||
|
xml_name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_from_events_statemachine(
|
||||||
|
&self,
|
||||||
|
state_ty_ident: &Ident,
|
||||||
|
output_name: &ParentRef,
|
||||||
|
state_prefix: &str,
|
||||||
|
) -> Result<FromEventsSubmachine> {
|
||||||
|
match self {
|
||||||
|
Self::Transparent { ty, member } => {
|
||||||
|
let from_xml_builder_ty = from_xml_builder_ty(ty.clone());
|
||||||
|
let from_events_fn = from_events_fn(ty.clone());
|
||||||
|
let feed_fn = feed_fn(from_xml_builder_ty.clone());
|
||||||
|
|
||||||
|
let output_cons = match output_name {
|
||||||
|
ParentRef::Named(ref path) => quote! {
|
||||||
|
#path { #member: result }
|
||||||
|
},
|
||||||
|
ParentRef::Unnamed { .. } => quote! {
|
||||||
|
( result, )
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let state_name = quote::format_ident!("{}Default", state_prefix);
|
||||||
|
let builder_data_ident = quote::format_ident!("__xso_data");
|
||||||
|
|
||||||
|
// Here, we generate a partial statemachine which really only
|
||||||
|
// proxies the FromXmlBuilder implementation of the inner
|
||||||
|
// type.
|
||||||
|
Ok(FromEventsSubmachine {
|
||||||
|
defs: TokenStream::default(),
|
||||||
|
states: vec![
|
||||||
|
State::new_with_builder(
|
||||||
|
state_name.clone(),
|
||||||
|
&builder_data_ident,
|
||||||
|
&from_xml_builder_ty,
|
||||||
|
)
|
||||||
|
.with_impl(quote! {
|
||||||
|
match #feed_fn(&mut #builder_data_ident, ev)? {
|
||||||
|
::core::option::Option::Some(result) => {
|
||||||
|
::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons))
|
||||||
|
}
|
||||||
|
::core::option::Option::None => {
|
||||||
|
::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
|
||||||
|
#builder_data_ident,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
init: quote! {
|
||||||
|
#from_events_fn(name, attrs).map(|#builder_data_ident| Self::#state_name { #builder_data_ident })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Compound {
|
||||||
|
ref inner,
|
||||||
|
ref xml_namespace,
|
||||||
|
ref xml_name,
|
||||||
|
} => Ok(inner
|
||||||
|
.make_from_events_statemachine(state_ty_ident, output_name, state_prefix)?
|
||||||
|
.with_augmented_init(|init| {
|
||||||
|
quote! {
|
||||||
|
if name.0 != #xml_namespace || name.1 != #xml_name {
|
||||||
|
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
|
||||||
|
name,
|
||||||
|
attrs,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
#init
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_as_item_iter_statemachine(
|
||||||
|
&self,
|
||||||
|
input_name: &ParentRef,
|
||||||
|
state_ty_ident: &Ident,
|
||||||
|
state_prefix: &str,
|
||||||
|
item_iter_ty_lifetime: &Lifetime,
|
||||||
|
) -> Result<AsItemsSubmachine> {
|
||||||
|
match self {
|
||||||
|
Self::Transparent { ty, member } => {
|
||||||
|
let item_iter_ty = item_iter_ty(ty.clone(), item_iter_ty_lifetime.clone());
|
||||||
|
let as_xml_iter_fn = as_xml_iter_fn(ty.clone());
|
||||||
|
|
||||||
|
let state_name = quote::format_ident!("{}Default", state_prefix);
|
||||||
|
let iter_ident = quote::format_ident!("__xso_data");
|
||||||
|
|
||||||
|
let destructure = match input_name {
|
||||||
|
ParentRef::Named(ref path) => quote! {
|
||||||
|
#path { #member: #iter_ident }
|
||||||
|
},
|
||||||
|
ParentRef::Unnamed { .. } => quote! {
|
||||||
|
(#iter_ident, )
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Here, we generate a partial statemachine which really only
|
||||||
|
// proxies the AsXml iterator implementation from the inner
|
||||||
|
// type.
|
||||||
|
Ok(AsItemsSubmachine {
|
||||||
|
defs: TokenStream::default(),
|
||||||
|
states: vec![State::new_with_builder(
|
||||||
|
state_name.clone(),
|
||||||
|
&iter_ident,
|
||||||
|
&item_iter_ty,
|
||||||
|
)
|
||||||
|
.with_mut(&iter_ident)
|
||||||
|
.with_impl(quote! {
|
||||||
|
#iter_ident.next().transpose()?
|
||||||
|
})],
|
||||||
|
destructure,
|
||||||
|
init: quote! {
|
||||||
|
#as_xml_iter_fn(#iter_ident).map(|#iter_ident| Self::#state_name { #iter_ident })?
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Compound {
|
||||||
|
ref inner,
|
||||||
|
ref xml_namespace,
|
||||||
|
ref xml_name,
|
||||||
|
} => Ok(inner
|
||||||
|
.make_as_item_iter_statemachine(
|
||||||
|
input_name,
|
||||||
|
state_ty_ident,
|
||||||
|
state_prefix,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Definition of a struct and how to parse it.
|
||||||
|
pub(crate) struct StructDef {
|
||||||
/// Name of the target type.
|
/// Name of the target type.
|
||||||
target_ty_ident: Ident,
|
target_ty_ident: Ident,
|
||||||
|
|
||||||
|
|
@ -37,47 +307,30 @@ pub(crate) struct StructDef {
|
||||||
|
|
||||||
/// Flag whether debug mode is enabled.
|
/// Flag whether debug mode is enabled.
|
||||||
debug: bool,
|
debug: bool,
|
||||||
|
|
||||||
|
/// The matching logic and contents of the struct.
|
||||||
|
inner: StructInner,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StructDef {
|
impl StructDef {
|
||||||
/// Create a new struct from its name, meta, and fields.
|
/// Create a new struct from its name, meta, and fields.
|
||||||
pub(crate) fn new(ident: &Ident, meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
|
pub(crate) fn new(ident: &Ident, mut meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
|
||||||
// We destructure here so that we get informed when new fields are
|
let builder_ty_ident = match meta.builder.take() {
|
||||||
// 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,
|
|
||||||
} = meta;
|
|
||||||
|
|
||||||
reject_key!(exhaustive flag not on "structs" only on "enums");
|
|
||||||
|
|
||||||
let Some(namespace) = namespace else {
|
|
||||||
return Err(Error::new(meta_span, "`namespace` is required on structs"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(name) = name else {
|
|
||||||
return Err(Error::new(meta_span, "`name` is required on structs"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let builder_ty_ident = match builder {
|
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
|
None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let item_iter_ty_ident = match iterator {
|
let item_iter_ty_ident = match meta.iterator.take() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
|
None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let debug = meta.debug.take();
|
||||||
|
|
||||||
|
let inner = StructInner::new(meta, fields)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: Compound::from_fields(fields, &namespace)?,
|
inner,
|
||||||
namespace,
|
|
||||||
name,
|
|
||||||
target_ty_ident: ident.clone(),
|
target_ty_ident: ident.clone(),
|
||||||
builder_ty_ident,
|
builder_ty_ident,
|
||||||
item_iter_ty_ident,
|
item_iter_ty_ident,
|
||||||
|
|
@ -93,9 +346,6 @@ impl ItemDef for StructDef {
|
||||||
name_ident: &Ident,
|
name_ident: &Ident,
|
||||||
attrs_ident: &Ident,
|
attrs_ident: &Ident,
|
||||||
) -> Result<FromXmlParts> {
|
) -> Result<FromXmlParts> {
|
||||||
let xml_namespace = &self.namespace;
|
|
||||||
let xml_name = &self.name;
|
|
||||||
|
|
||||||
let target_ty_ident = &self.target_ty_ident;
|
let target_ty_ident = &self.target_ty_ident;
|
||||||
let builder_ty_ident = &self.builder_ty_ident;
|
let builder_ty_ident = &self.builder_ty_ident;
|
||||||
let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
|
let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
|
||||||
|
|
@ -107,18 +357,6 @@ impl ItemDef for StructDef {
|
||||||
&Path::from(target_ty_ident.clone()).into(),
|
&Path::from(target_ty_ident.clone()).into(),
|
||||||
"Struct",
|
"Struct",
|
||||||
)?
|
)?
|
||||||
.with_augmented_init(|init| {
|
|
||||||
quote! {
|
|
||||||
if name.0 != #xml_namespace || name.1 != #xml_name {
|
|
||||||
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
|
|
||||||
name,
|
|
||||||
attrs,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
#init
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.compile()
|
.compile()
|
||||||
.render(
|
.render(
|
||||||
vis,
|
vis,
|
||||||
|
|
@ -141,9 +379,6 @@ impl ItemDef for StructDef {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
|
fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
|
||||||
let xml_namespace = &self.namespace;
|
|
||||||
let xml_name = &self.name;
|
|
||||||
|
|
||||||
let target_ty_ident = &self.target_ty_ident;
|
let target_ty_ident = &self.target_ty_ident;
|
||||||
let item_iter_ty_ident = &self.item_iter_ty_ident;
|
let item_iter_ty_ident = &self.item_iter_ty_ident;
|
||||||
let item_iter_ty_lifetime = Lifetime {
|
let item_iter_ty_lifetime = Lifetime {
|
||||||
|
|
@ -183,15 +418,6 @@ impl ItemDef for StructDef {
|
||||||
"Struct",
|
"Struct",
|
||||||
&item_iter_ty_lifetime,
|
&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()
|
.compile()
|
||||||
.render(
|
.render(
|
||||||
vis,
|
vis,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ Version NEXT:
|
||||||
structs.
|
structs.
|
||||||
- Support for collecting all unknown children in a single field as
|
- Support for collecting all unknown children in a single field as
|
||||||
collection of `minidom::Element`.
|
collection of `minidom::Element`.
|
||||||
|
- Support for "transparent" structs (newtype-like patterns for XSO).
|
||||||
|
|
||||||
Version 0.1.2:
|
Version 0.1.2:
|
||||||
2024-07-26 Jonas Schäfer <jonas@zombofant.net>
|
2024-07-26 Jonas Schäfer <jonas@zombofant.net>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ The following keys are defined on structs:
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `namespace` | *string literal* or *path* | The XML element namespace to match. If it is a *path*, it must point at a `&'static str`. |
|
| `namespace` | *string literal* or *path* | The XML element namespace to match. If it is a *path*, it must point at a `&'static str`. |
|
||||||
| `name` | *string literal* or *path* | The XML element name to match. If it is a *path*, it must point at a `&'static NcNameStr`. |
|
| `name` | *string literal* or *path* | The XML element name to match. If it is a *path*, it must point at a `&'static NcNameStr`. |
|
||||||
|
| `transparent` | *flag* | If present, declares the struct as *transparent* struct (see below) |
|
||||||
| `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. |
|
||||||
|
|
||||||
|
|
@ -74,6 +75,12 @@ By default, the builder type uses the type's name suffixed with
|
||||||
`FromXmlBuilder` and the iterator type uses the type's name suffixed with
|
`FromXmlBuilder` and the iterator type uses the type's name suffixed with
|
||||||
`AsXmlIterator`.
|
`AsXmlIterator`.
|
||||||
|
|
||||||
|
If the struct is marked as `transparent`, it must not have a `namespace` or
|
||||||
|
`name` set and it must have exactly one field. That field's type must
|
||||||
|
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
|
### Enum meta
|
||||||
|
|
||||||
The following keys are defined on enums:
|
The following keys are defined on enums:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue