xso-proc: implement support for collections of children
This commit is contained in:
parent
5c3ed1435f
commit
93ba2797be
6 changed files with 395 additions and 50 deletions
|
@ -795,3 +795,22 @@ fn exhaustive_name_switched_enum_roundtrip_variant_2() {
|
||||||
};
|
};
|
||||||
roundtrip_full::<ExhaustiveNameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>")
|
roundtrip_full::<ExhaustiveNameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(namespace = NS1, name = "parent")]
|
||||||
|
struct Children {
|
||||||
|
#[xml(child(n = ..))]
|
||||||
|
foo: Vec<RequiredAttribute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn children_roundtrip() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<Children>(
|
||||||
|
"<parent xmlns='urn:example:ns1'><attr foo='X'/><attr foo='Y'/><attr foo='Z'/></parent>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -13,12 +13,13 @@ use syn::{spanned::Spanned, *};
|
||||||
use rxml_validation::NcName;
|
use rxml_validation::NcName;
|
||||||
|
|
||||||
use crate::error_message::{self, ParentRef};
|
use crate::error_message::{self, ParentRef};
|
||||||
use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
|
use crate::meta::{AmountConstraint, Flag, NameRef, NamespaceRef, XmlFieldMeta};
|
||||||
use crate::scope::{AsItemsScope, FromEventsScope};
|
use crate::scope::{AsItemsScope, FromEventsScope};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
as_optional_xml_text_fn, as_xml_iter_fn, as_xml_text_fn, default_fn, from_events_fn,
|
as_optional_xml_text_fn, as_xml_iter_fn, as_xml_text_fn, default_fn, extend_fn, from_events_fn,
|
||||||
from_xml_builder_ty, from_xml_text_fn, item_iter_ty, option_ty, string_ty,
|
from_xml_builder_ty, from_xml_text_fn, into_iterator_into_iter_fn, into_iterator_item_ty,
|
||||||
text_codec_decode_fn, text_codec_encode_fn,
|
into_iterator_iter_ty, item_iter_ty, option_ty, ref_ty, string_ty, text_codec_decode_fn,
|
||||||
|
text_codec_encode_fn,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Code slices necessary for declaring and initializing a temporary variable
|
/// Code slices necessary for declaring and initializing a temporary variable
|
||||||
|
@ -140,8 +141,8 @@ enum FieldKind {
|
||||||
/// The XML name of the attribute.
|
/// The XML name of the attribute.
|
||||||
xml_name: NameRef,
|
xml_name: NameRef,
|
||||||
|
|
||||||
// Flag indicating whether the value should be defaulted if the
|
/// Flag indicating whether the value should be defaulted if the
|
||||||
// attribute is absent.
|
/// attribute is absent.
|
||||||
default_: Flag,
|
default_: Flag,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -153,9 +154,12 @@ enum FieldKind {
|
||||||
|
|
||||||
/// The field maps to a child
|
/// The field maps to a child
|
||||||
Child {
|
Child {
|
||||||
// Flag indicating whether the value should be defaulted if the
|
/// Flag indicating whether the value should be defaulted if the
|
||||||
// child is absent.
|
/// child is absent.
|
||||||
default_: Flag,
|
default_: Flag,
|
||||||
|
|
||||||
|
/// Number of child elements allowed.
|
||||||
|
amount: AmountConstraint,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +207,26 @@ impl FieldKind {
|
||||||
|
|
||||||
XmlFieldMeta::Text { codec } => Ok(Self::Text { codec }),
|
XmlFieldMeta::Text { codec } => Ok(Self::Text { codec }),
|
||||||
|
|
||||||
XmlFieldMeta::Child { default_ } => Ok(Self::Child { default_ }),
|
XmlFieldMeta::Child { default_, amount } => {
|
||||||
|
if let Some(AmountConstraint::Any(ref amount_span)) = amount {
|
||||||
|
if let Flag::Present(ref flag_span) = default_ {
|
||||||
|
let mut err = Error::new(
|
||||||
|
*flag_span,
|
||||||
|
"`default` has no meaning for child collections",
|
||||||
|
);
|
||||||
|
err.combine(Error::new(
|
||||||
|
*amount_span,
|
||||||
|
"the field is treated as a collection because of this `n` value",
|
||||||
|
));
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::Child {
|
||||||
|
default_,
|
||||||
|
amount: amount.unwrap_or(AmountConstraint::FixedSingle(Span::call_site())),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -345,17 +368,31 @@ impl FieldDef {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldKind::Child { ref default_ } => {
|
FieldKind::Child {
|
||||||
|
ref default_,
|
||||||
|
ref amount,
|
||||||
|
} => {
|
||||||
let FromEventsScope {
|
let FromEventsScope {
|
||||||
ref substate_result,
|
ref substate_result,
|
||||||
..
|
..
|
||||||
} = scope;
|
} = scope;
|
||||||
let field_access = scope.access_field(&self.member);
|
let field_access = scope.access_field(&self.member);
|
||||||
|
|
||||||
let missing_msg = error_message::on_missing_child(container_name, &self.member);
|
let element_ty = match amount {
|
||||||
|
AmountConstraint::FixedSingle(_) => self.ty.clone(),
|
||||||
|
AmountConstraint::Any(_) => into_iterator_item_ty(self.ty.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
let from_events = from_events_fn(self.ty.clone());
|
let from_events = from_events_fn(element_ty.clone());
|
||||||
let from_xml_builder = from_xml_builder_ty(self.ty.clone());
|
let from_xml_builder = from_xml_builder_ty(element_ty.clone());
|
||||||
|
|
||||||
|
let matcher = quote! { #from_events(name, attrs) };
|
||||||
|
let builder = from_xml_builder;
|
||||||
|
|
||||||
|
match amount {
|
||||||
|
AmountConstraint::FixedSingle(_) => {
|
||||||
|
let missing_msg =
|
||||||
|
error_message::on_missing_child(container_name, &self.member);
|
||||||
|
|
||||||
let on_absent = match default_ {
|
let on_absent = match default_ {
|
||||||
Flag::Absent => quote! {
|
Flag::Absent => quote! {
|
||||||
|
@ -374,10 +411,8 @@ impl FieldDef {
|
||||||
init: quote! { ::std::option::Option::None },
|
init: quote! { ::std::option::Option::None },
|
||||||
ty: option_ty(self.ty.clone()),
|
ty: option_ty(self.ty.clone()),
|
||||||
},
|
},
|
||||||
matcher: quote! {
|
matcher,
|
||||||
#from_events(name, attrs)
|
builder,
|
||||||
},
|
|
||||||
builder: from_xml_builder,
|
|
||||||
collect: quote! {
|
collect: quote! {
|
||||||
#field_access = ::std::option::Option::Some(#substate_result);
|
#field_access = ::std::option::Option::Some(#substate_result);
|
||||||
},
|
},
|
||||||
|
@ -389,6 +424,24 @@ impl FieldDef {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
AmountConstraint::Any(_) => {
|
||||||
|
let ty_extend = extend_fn(self.ty.clone(), element_ty.clone());
|
||||||
|
let ty_default = default_fn(self.ty.clone());
|
||||||
|
Ok(FieldBuilderPart::Nested {
|
||||||
|
value: FieldTempInit {
|
||||||
|
init: quote! { #ty_default() },
|
||||||
|
ty: self.ty.clone(),
|
||||||
|
},
|
||||||
|
matcher,
|
||||||
|
builder,
|
||||||
|
collect: quote! {
|
||||||
|
#ty_extend(&mut #field_access, [#substate_result]);
|
||||||
|
},
|
||||||
|
finalize: quote! { #field_access },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,7 +495,10 @@ impl FieldDef {
|
||||||
Ok(FieldIteratorPart::Text { generator })
|
Ok(FieldIteratorPart::Text { generator })
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldKind::Child { default_: _ } => {
|
FieldKind::Child {
|
||||||
|
default_: _,
|
||||||
|
amount: AmountConstraint::FixedSingle(_),
|
||||||
|
} => {
|
||||||
let AsItemsScope { ref lifetime, .. } = scope;
|
let AsItemsScope { ref lifetime, .. } = scope;
|
||||||
|
|
||||||
let as_xml_iter = as_xml_iter_fn(self.ty.clone());
|
let as_xml_iter = as_xml_iter_fn(self.ty.clone());
|
||||||
|
@ -460,6 +516,63 @@ impl FieldDef {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldKind::Child {
|
||||||
|
default_: _,
|
||||||
|
amount: AmountConstraint::Any(_),
|
||||||
|
} => {
|
||||||
|
let AsItemsScope { ref lifetime, .. } = scope;
|
||||||
|
|
||||||
|
// This should give us the type of element stored in the
|
||||||
|
// collection.
|
||||||
|
let element_ty = into_iterator_item_ty(self.ty.clone());
|
||||||
|
|
||||||
|
// And this is the collection type we actually work with --
|
||||||
|
// as_xml_iter uses references after all.
|
||||||
|
let ty = ref_ty(self.ty.clone(), lifetime.clone());
|
||||||
|
|
||||||
|
// as_xml_iter is called on the bare type (not the ref type)
|
||||||
|
let as_xml_iter = as_xml_iter_fn(element_ty.clone());
|
||||||
|
|
||||||
|
// And thus the iterator associated with AsXml is also derived
|
||||||
|
// from the bare type.
|
||||||
|
let item_iter = item_iter_ty(element_ty.clone(), lifetime.clone());
|
||||||
|
|
||||||
|
// But the iterator for iterating over the elements inside the
|
||||||
|
// collection must use the ref type.
|
||||||
|
let element_iter = into_iterator_iter_ty(ty.clone());
|
||||||
|
|
||||||
|
// And likewise the into_iter impl.
|
||||||
|
let into_iter = into_iterator_into_iter_fn(ty.clone());
|
||||||
|
|
||||||
|
let state_ty = Type::Tuple(TypeTuple {
|
||||||
|
paren_token: token::Paren::default(),
|
||||||
|
elems: [element_iter, option_ty(item_iter)].into_iter().collect(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(FieldIteratorPart::Content {
|
||||||
|
value: FieldTempInit {
|
||||||
|
init: quote! {
|
||||||
|
(#into_iter(#bound_name), ::core::option::Option::None)
|
||||||
|
},
|
||||||
|
ty: state_ty,
|
||||||
|
},
|
||||||
|
generator: quote! {
|
||||||
|
loop {
|
||||||
|
if let ::core::option::Option::Some(current) = #bound_name.1.as_mut() {
|
||||||
|
if let ::core::option::Option::Some(item) = current.next() {
|
||||||
|
break ::core::option::Option::Some(item).transpose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let ::core::option::Option::Some(item) = #bound_name.0.next() {
|
||||||
|
#bound_name.1 = ::core::option::Option::Some(#as_xml_iter(item)?)
|
||||||
|
} else {
|
||||||
|
break ::core::result::Result::Ok(::core::option::Option::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -180,6 +180,53 @@ impl quote::ToTokens for NameRef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the amount constraint used with child elements.
|
||||||
|
///
|
||||||
|
/// Currently, this only supports "one" (literal `1`) or "any amount" (`..`).
|
||||||
|
/// In the future, we might want to add support for any range pattern for
|
||||||
|
/// `usize` and any positive integer literal.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum AmountConstraint {
|
||||||
|
/// Equivalent to `1`
|
||||||
|
#[allow(dead_code)]
|
||||||
|
FixedSingle(Span),
|
||||||
|
|
||||||
|
/// Equivalent to `..`.
|
||||||
|
Any(Span),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl syn::parse::Parse for AmountConstraint {
|
||||||
|
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
|
||||||
|
if input.peek(LitInt) && !input.peek2(token::DotDot) && !input.peek2(token::DotDotEq) {
|
||||||
|
let lit: LitInt = input.parse()?;
|
||||||
|
let value: usize = lit.base10_parse()?;
|
||||||
|
if value == 1 {
|
||||||
|
Ok(Self::FixedSingle(lit.span()))
|
||||||
|
} else {
|
||||||
|
Err(Error::new(lit.span(), "only `1` and `..` are allowed here"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let p: PatRange = input.parse()?;
|
||||||
|
if let Some(attr) = p.attrs.first() {
|
||||||
|
return Err(Error::new_spanned(attr, "attributes not allowed here"));
|
||||||
|
}
|
||||||
|
if let Some(start) = p.start.as_ref() {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
start,
|
||||||
|
"only full ranges (`..`) are allowed here",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(end) = p.end.as_ref() {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
end,
|
||||||
|
"only full ranges (`..`) are allowed here",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(Self::Any(p.span()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a boolean flag from a `#[xml(..)]` attribute meta.
|
/// Represents a boolean flag from a `#[xml(..)]` attribute meta.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub(crate) enum Flag {
|
pub(crate) enum Flag {
|
||||||
|
@ -558,6 +605,9 @@ pub(crate) enum XmlFieldMeta {
|
||||||
Child {
|
Child {
|
||||||
/// The `default` flag.
|
/// The `default` flag.
|
||||||
default_: Flag,
|
default_: Flag,
|
||||||
|
|
||||||
|
/// The `n` flag.
|
||||||
|
amount: Option<AmountConstraint>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,6 +744,7 @@ impl XmlFieldMeta {
|
||||||
fn child_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
fn child_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
||||||
if meta.input.peek(syn::token::Paren) {
|
if meta.input.peek(syn::token::Paren) {
|
||||||
let mut default_ = Flag::Absent;
|
let mut default_ = Flag::Absent;
|
||||||
|
let mut amount = None;
|
||||||
meta.parse_nested_meta(|meta| {
|
meta.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident("default") {
|
if meta.path.is_ident("default") {
|
||||||
if default_.is_set() {
|
if default_.is_set() {
|
||||||
|
@ -701,14 +752,21 @@ impl XmlFieldMeta {
|
||||||
}
|
}
|
||||||
default_ = (&meta.path).into();
|
default_ = (&meta.path).into();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else if meta.path.is_ident("n") {
|
||||||
|
if amount.is_some() {
|
||||||
|
return Err(Error::new_spanned(meta.path, "duplicate `n` key"));
|
||||||
|
}
|
||||||
|
amount = Some(meta.value()?.parse()?);
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new_spanned(meta.path, "unsupported key"))
|
Err(Error::new_spanned(meta.path, "unsupported key"))
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
Ok(Self::Child { default_ })
|
Ok(Self::Child { default_, amount })
|
||||||
} else {
|
} else {
|
||||||
Ok(Self::Child {
|
Ok(Self::Child {
|
||||||
default_: Flag::Absent,
|
default_: Flag::Absent,
|
||||||
|
amount: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -622,3 +622,126 @@ pub(crate) fn item_iter_ty(of_ty: Type, lifetime: Lifetime) -> Type {
|
||||||
});
|
});
|
||||||
Type::Path(ty)
|
Type::Path(ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a [`syn::TypePath`] referring to `<#of_ty as IntoIterator>`.
|
||||||
|
fn into_iterator_of(of_ty: Type) -> (Span, TypePath) {
|
||||||
|
let span = of_ty.span();
|
||||||
|
(
|
||||||
|
span,
|
||||||
|
TypePath {
|
||||||
|
qself: Some(QSelf {
|
||||||
|
lt_token: syn::token::Lt { spans: [span] },
|
||||||
|
ty: Box::new(of_ty),
|
||||||
|
position: 3,
|
||||||
|
as_token: Some(syn::token::As { span }),
|
||||||
|
gt_token: syn::token::Gt { spans: [span] },
|
||||||
|
}),
|
||||||
|
path: Path {
|
||||||
|
leading_colon: Some(syn::token::PathSep {
|
||||||
|
spans: [span, span],
|
||||||
|
}),
|
||||||
|
segments: [
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("std", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("iter", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("IntoIterator", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`syn::Type`] referring to
|
||||||
|
/// `<#of_ty as IntoIterator>::IntoIter`.
|
||||||
|
pub(crate) fn into_iterator_iter_ty(of_ty: Type) -> Type {
|
||||||
|
let (span, mut ty) = into_iterator_of(of_ty);
|
||||||
|
ty.path.segments.push(PathSegment {
|
||||||
|
ident: Ident::new("IntoIter", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
});
|
||||||
|
Type::Path(ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`syn::Type`] referring to
|
||||||
|
/// `<#of_ty as IntoIterator>::Item`.
|
||||||
|
pub(crate) fn into_iterator_item_ty(of_ty: Type) -> Type {
|
||||||
|
let (span, mut ty) = into_iterator_of(of_ty);
|
||||||
|
ty.path.segments.push(PathSegment {
|
||||||
|
ident: Ident::new("Item", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
});
|
||||||
|
Type::Path(ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`syn::Expr`] referring to
|
||||||
|
/// `<#of_ty as IntoIterator>::into_iter`.
|
||||||
|
pub(crate) fn into_iterator_into_iter_fn(of_ty: Type) -> Expr {
|
||||||
|
let (span, mut ty) = into_iterator_of(of_ty);
|
||||||
|
ty.path.segments.push(PathSegment {
|
||||||
|
ident: Ident::new("into_iter", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
});
|
||||||
|
Expr::Path(ExprPath {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
qself: ty.qself,
|
||||||
|
path: ty.path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`syn::Expr`] referring to
|
||||||
|
/// `<#of_ty as ::std::iter::Extend>::extend`.
|
||||||
|
pub(crate) fn extend_fn(of_ty: Type, item_ty: Type) -> Expr {
|
||||||
|
let span = of_ty.span();
|
||||||
|
Expr::Path(ExprPath {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
qself: Some(QSelf {
|
||||||
|
lt_token: syn::token::Lt { spans: [span] },
|
||||||
|
ty: Box::new(of_ty),
|
||||||
|
position: 3,
|
||||||
|
as_token: Some(syn::token::As { span }),
|
||||||
|
gt_token: syn::token::Gt { spans: [span] },
|
||||||
|
}),
|
||||||
|
path: Path {
|
||||||
|
leading_colon: Some(syn::token::PathSep {
|
||||||
|
spans: [span, span],
|
||||||
|
}),
|
||||||
|
segments: [
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("std", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("iter", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("Extend", span),
|
||||||
|
arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
|
||||||
|
colon2_token: Some(syn::token::PathSep {
|
||||||
|
spans: [span, span],
|
||||||
|
}),
|
||||||
|
lt_token: syn::token::Lt { spans: [span] },
|
||||||
|
args: [GenericArgument::Type(item_ty)].into_iter().collect(),
|
||||||
|
gt_token: syn::token::Gt { spans: [span] },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("extend", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ Version NEXT:
|
||||||
of text codecs.
|
of text codecs.
|
||||||
* Added
|
* Added
|
||||||
- Support for child elements in derive macros. Child elements may also
|
- Support for child elements in derive macros. Child elements may also
|
||||||
be wrapped in Option or Box.
|
be wrapped in Option or Box or in containers like Vec or HashSet.
|
||||||
- Support for overriding the names of the types generated by the derive
|
- Support for overriding the names of the types generated by the derive
|
||||||
macros.
|
macros.
|
||||||
- Support for deriving FromXml and AsXml on enums.
|
- Support for deriving FromXml and AsXml on enums.
|
||||||
|
|
|
@ -219,17 +219,29 @@ assert_eq!(foo, Foo {
|
||||||
The `child` meta causes the field to be mapped to a child element of the
|
The `child` meta causes the field to be mapped to a child element of the
|
||||||
element.
|
element.
|
||||||
|
|
||||||
|
The following keys can be used inside the `#[xml(child(..))]` meta:
|
||||||
|
|
||||||
| Key | Value type | Description |
|
| Key | Value type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `default` | flag | If present, an absent child will substitute the default value instead of raising an error. |
|
| `default` | flag | If present, an absent child will substitute the default value instead of raising an error. |
|
||||||
|
| `n` | `1` or `..` | If `1`, a single element is parsed. If `..`, a collection is parsed. Defaults to `1`. |
|
||||||
|
|
||||||
The field's type must implement [`FromXml`] in order to derive `FromXml` and
|
When parsing a single child element (i.e. `n = 1` or no `n` value set at all),
|
||||||
|
the field's type must implement [`FromXml`] in order to derive `FromXml` and
|
||||||
[`AsXml`] in order to derive `AsXml`.
|
[`AsXml`] in order to derive `AsXml`.
|
||||||
|
|
||||||
|
When parsing a collection (with `n = ..`), the field's type must implement
|
||||||
|
[`IntoIterator<Item = T>`][`std::iter::IntoIterator`], where `T` must
|
||||||
|
implement [`FromXml`] in order to derive `FromXml` and [`AsXml`] in order to
|
||||||
|
derive `AsXml`. In addition, the field's type must implement
|
||||||
|
[`Extend<T>`][`std::iter::Extend`] to derive `FromXml` and the field's
|
||||||
|
reference type must implement `IntoIterator<Item = &'_ T>` to derive `AsXml`.
|
||||||
|
|
||||||
If `default` is specified and the child is absent in the source, the value
|
If `default` is specified and the child is absent in the source, the value
|
||||||
is generated using [`std::default::Default`], requiring the field type to
|
is generated using [`std::default::Default`], requiring the field type to
|
||||||
implement the `Default` trait for a `FromXml` derivation. `default` has no
|
implement the `Default` trait for a `FromXml` derivation. `default` has no
|
||||||
influence on `AsXml`.
|
influence on `AsXml`. Combining `default` and `n` where `n` is not set to `1`
|
||||||
|
is not supported and will cause a compile-time error.
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
|
@ -242,6 +254,13 @@ struct Child {
|
||||||
some_attr: String,
|
some_attr: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, Debug, PartialEq)]
|
||||||
|
#[xml(namespace = "urn:example", name = "other-child")]
|
||||||
|
struct OtherChild {
|
||||||
|
#[xml(attribute = "some-attr")]
|
||||||
|
some_attr: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(FromXml, Debug, PartialEq)]
|
#[derive(FromXml, Debug, PartialEq)]
|
||||||
#[xml(namespace = "urn:example", name = "parent")]
|
#[xml(namespace = "urn:example", name = "parent")]
|
||||||
struct Parent {
|
struct Parent {
|
||||||
|
@ -250,15 +269,28 @@ struct Parent {
|
||||||
|
|
||||||
#[xml(child)]
|
#[xml(child)]
|
||||||
bar: Child,
|
bar: Child,
|
||||||
|
|
||||||
|
#[xml(child(n = ..))]
|
||||||
|
baz: Vec<OtherChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent: Parent = xso::from_bytes(b"<parent
|
let parent: Parent = xso::from_bytes(b"<parent
|
||||||
xmlns='urn:example'
|
xmlns='urn:example'
|
||||||
foo='hello world!'
|
foo='hello world!'
|
||||||
><child some-attr='within'/></parent>").unwrap();
|
><child
|
||||||
|
some-attr='within'
|
||||||
|
/><other-child
|
||||||
|
some-attr='c1'
|
||||||
|
/><other-child
|
||||||
|
some-attr='c2'
|
||||||
|
/></parent>").unwrap();
|
||||||
assert_eq!(parent, Parent {
|
assert_eq!(parent, Parent {
|
||||||
foo: "hello world!".to_owned(),
|
foo: "hello world!".to_owned(),
|
||||||
bar: Child { some_attr: "within".to_owned() },
|
bar: Child { some_attr: "within".to_owned() },
|
||||||
|
baz: vec! [
|
||||||
|
OtherChild { some_attr: "c1".to_owned() },
|
||||||
|
OtherChild { some_attr: "c2".to_owned() },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue