xso-proc: Add n = 1 flag to element meta
This allows exactly one arbitrary payload in any element, and is handled after every other element with a more specific matcher has been parsed. Both the #[xml(element(n = 1))] meta and its shortcut #[xml(element)] are allowed and treated the exact same way.
This commit is contained in:
parent
057306fba1
commit
c72cf3ddd1
7 changed files with 265 additions and 80 deletions
|
|
@ -67,10 +67,9 @@ impl Field for ChildField {
|
|||
} = scope;
|
||||
|
||||
let from_events = from_events_fn(element_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;
|
||||
let builder = from_xml_builder_ty(element_ty.clone());
|
||||
|
||||
(
|
||||
TokenStream::default(),
|
||||
|
|
|
|||
|
|
@ -13,52 +13,97 @@ use proc_macro2::{Span, TokenStream};
|
|||
use quote::quote;
|
||||
use syn::*;
|
||||
|
||||
use crate::error_message::ParentRef;
|
||||
use crate::error_message::{self, ParentRef};
|
||||
use crate::meta::AmountConstraint;
|
||||
use crate::scope::{AsItemsScope, FromEventsScope};
|
||||
use crate::types::{
|
||||
default_fn, element_ty, from_xml_builder_ty, into_iterator_into_iter_fn, into_iterator_iter_ty,
|
||||
item_iter_ty, option_ty, ref_ty,
|
||||
as_xml_iter_fn, default_fn, element_ty, from_events_fn, from_xml_builder_ty,
|
||||
into_iterator_into_iter_fn, into_iterator_item_ty, into_iterator_iter_ty, item_iter_ty,
|
||||
option_ty, ref_ty,
|
||||
};
|
||||
|
||||
use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher};
|
||||
|
||||
pub(super) struct ElementField;
|
||||
pub(super) struct ElementField {
|
||||
/// Number of child elements allowed.
|
||||
pub(super) amount: AmountConstraint,
|
||||
}
|
||||
|
||||
impl Field for ElementField {
|
||||
fn make_builder_part(
|
||||
&self,
|
||||
scope: &FromEventsScope,
|
||||
_container_name: &ParentRef,
|
||||
container_name: &ParentRef,
|
||||
member: &Member,
|
||||
ty: &Type,
|
||||
) -> Result<FieldBuilderPart> {
|
||||
let element_ty = match self.amount {
|
||||
AmountConstraint::FixedSingle(_) => ty.clone(),
|
||||
AmountConstraint::Any(_) => into_iterator_item_ty(ty.clone()),
|
||||
};
|
||||
|
||||
let FromEventsScope {
|
||||
ref substate_result,
|
||||
..
|
||||
} = scope;
|
||||
|
||||
let from_events = from_events_fn(element_ty.clone());
|
||||
|
||||
let extra_defs = TokenStream::default();
|
||||
let field_access = scope.access_field(member);
|
||||
|
||||
let element_ty = element_ty(Span::call_site());
|
||||
let default_fn = default_fn(ty.clone());
|
||||
let builder = from_xml_builder_ty(element_ty.clone());
|
||||
|
||||
Ok(FieldBuilderPart::Nested {
|
||||
extra_defs: TokenStream::default(),
|
||||
value: FieldTempInit {
|
||||
init: quote! { #default_fn() },
|
||||
ty: ty.clone(),
|
||||
},
|
||||
matcher: NestedMatcher::Fallback(quote! {
|
||||
#builder::new(name, attrs)
|
||||
match self.amount {
|
||||
AmountConstraint::FixedSingle(_) => {
|
||||
let missing_msg = error_message::on_missing_child(container_name, member);
|
||||
let on_absent = quote! {
|
||||
return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
|
||||
};
|
||||
Ok(FieldBuilderPart::Nested {
|
||||
extra_defs,
|
||||
value: FieldTempInit {
|
||||
init: quote! { ::core::option::Option::None },
|
||||
ty: option_ty(ty.clone()),
|
||||
},
|
||||
matcher: NestedMatcher::Selective(quote! {
|
||||
if #field_access.is_some() {
|
||||
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
|
||||
} else {
|
||||
#from_events(name, attrs)
|
||||
}
|
||||
}),
|
||||
builder,
|
||||
collect: quote! {
|
||||
#field_access = ::core::option::Option::Some(#substate_result);
|
||||
},
|
||||
finalize: quote! {
|
||||
match #field_access {
|
||||
::core::option::Option::Some(value) => value,
|
||||
::core::option::Option::None => #on_absent,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
AmountConstraint::Any(_) => Ok(FieldBuilderPart::Nested {
|
||||
extra_defs,
|
||||
value: FieldTempInit {
|
||||
init: quote! { #default_fn() },
|
||||
ty: ty.clone(),
|
||||
},
|
||||
matcher: NestedMatcher::Fallback(quote! {
|
||||
#builder::new(name, attrs)
|
||||
}),
|
||||
builder,
|
||||
collect: quote! {
|
||||
<#ty as ::core::iter::Extend::<#element_ty>>::extend(&mut #field_access, [#substate_result]);
|
||||
},
|
||||
finalize: quote! {
|
||||
#field_access
|
||||
},
|
||||
}),
|
||||
builder,
|
||||
collect: quote! {
|
||||
<#ty as ::core::iter::Extend::<#element_ty>>::extend(&mut #field_access, [#substate_result]);
|
||||
},
|
||||
finalize: quote! {
|
||||
#field_access
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn make_iterator_part(
|
||||
|
|
@ -71,6 +116,15 @@ impl Field for ElementField {
|
|||
) -> Result<FieldIteratorPart> {
|
||||
let AsItemsScope { ref lifetime, .. } = scope;
|
||||
|
||||
let item_ty = match self.amount {
|
||||
AmountConstraint::FixedSingle(_) => ty.clone(),
|
||||
AmountConstraint::Any(_) => {
|
||||
// This should give us the type of element stored in the
|
||||
// collection.
|
||||
into_iterator_item_ty(ty.clone())
|
||||
}
|
||||
};
|
||||
|
||||
let element_ty = element_ty(Span::call_site());
|
||||
let iter_ty = item_iter_ty(element_ty.clone(), lifetime.clone());
|
||||
let element_iter = into_iterator_iter_ty(ref_ty(ty.clone(), lifetime.clone()));
|
||||
|
|
@ -78,33 +132,49 @@ impl Field for ElementField {
|
|||
|
||||
let state_ty = Type::Tuple(TypeTuple {
|
||||
paren_token: token::Paren::default(),
|
||||
elems: [element_iter, option_ty(iter_ty)].into_iter().collect(),
|
||||
elems: [element_iter, option_ty(iter_ty.clone())]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
});
|
||||
|
||||
Ok(FieldIteratorPart::Content {
|
||||
extra_defs: TokenStream::default(),
|
||||
value: FieldTempInit {
|
||||
init: quote! {
|
||||
(#into_iter(#bound_name), ::core::option::Option::None)
|
||||
let extra_defs = TokenStream::default();
|
||||
let as_xml_iter = as_xml_iter_fn(item_ty.clone());
|
||||
let init = quote! { #as_xml_iter(#bound_name)? };
|
||||
let iter_ty = item_iter_ty(item_ty.clone(), lifetime.clone());
|
||||
|
||||
match self.amount {
|
||||
AmountConstraint::FixedSingle(_) => Ok(FieldIteratorPart::Content {
|
||||
extra_defs,
|
||||
value: FieldTempInit { init, ty: iter_ty },
|
||||
generator: quote! {
|
||||
#bound_name.next().transpose()
|
||||
},
|
||||
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();
|
||||
}),
|
||||
AmountConstraint::Any(_) => Ok(FieldIteratorPart::Content {
|
||||
extra_defs,
|
||||
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(
|
||||
<#element_ty as ::xso::AsXml>::as_xml_iter(item)?
|
||||
);
|
||||
} else {
|
||||
break ::core::result::Result::Ok(::core::option::Option::None)
|
||||
}
|
||||
}
|
||||
if let ::core::option::Option::Some(item) = #bound_name.0.next() {
|
||||
#bound_name.1 = ::core::option::Option::Some(
|
||||
<#element_ty as ::xso::AsXml>::as_xml_iter(item)?
|
||||
);
|
||||
} else {
|
||||
break ::core::result::Result::Ok(::core::option::Option::None)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ fn new_field(
|
|||
);
|
||||
err.combine(Error::new(
|
||||
*amount,
|
||||
"`n` was set to a non-1 value here, which enables connection logic",
|
||||
"`n` was set to a non-1 value here, which enables collection logic",
|
||||
));
|
||||
return Err(err);
|
||||
}
|
||||
|
|
@ -406,20 +406,9 @@ fn new_field(
|
|||
}
|
||||
|
||||
#[cfg(feature = "minidom")]
|
||||
XmlFieldMeta::Element { span, amount } => {
|
||||
match amount {
|
||||
Some(AmountConstraint::Any(_)) => (),
|
||||
Some(AmountConstraint::FixedSingle(span)) => {
|
||||
return Err(Error::new(
|
||||
span,
|
||||
"only `n = ..` is supported for #[xml(element)]` currently",
|
||||
))
|
||||
}
|
||||
None => return Err(Error::new(span, "`n` must be set to `..` currently")),
|
||||
}
|
||||
|
||||
Ok(Box::new(ElementField))
|
||||
}
|
||||
XmlFieldMeta::Element { span, amount } => Ok(Box::new(ElementField {
|
||||
amount: amount.unwrap_or(AmountConstraint::FixedSingle(span)),
|
||||
})),
|
||||
|
||||
#[cfg(not(feature = "minidom"))]
|
||||
XmlFieldMeta::Element { span, amount } => {
|
||||
|
|
|
|||
|
|
@ -1035,17 +1035,19 @@ impl XmlFieldMeta {
|
|||
/// Parse a `#[xml(element)]` meta.
|
||||
fn element_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
||||
let mut amount = None;
|
||||
meta.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("n") {
|
||||
if amount.is_some() {
|
||||
return Err(Error::new_spanned(meta.path, "duplicate `n` key"));
|
||||
if meta.input.peek(syn::token::Paren) {
|
||||
meta.parse_nested_meta(|meta| {
|
||||
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 {
|
||||
Err(Error::new_spanned(meta.path, "unsupported key"))
|
||||
}
|
||||
amount = Some(meta.value()?.parse()?);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new_spanned(meta.path, "unsupported key"))
|
||||
}
|
||||
})?;
|
||||
})?;
|
||||
}
|
||||
Ok(Self::Element {
|
||||
span: meta.path.span(),
|
||||
amount,
|
||||
|
|
|
|||
Loading…
Reference in a new issue