mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-06-17 21:35:25 +02:00
parsers-macros: add support for skip_if on child/children fields
This commit is contained in:
parent
3983e0d705
commit
953de151c8
|
@ -278,6 +278,12 @@ The following field kinds are available:
|
|||
|
||||
- `extract(..)` (required): Specification of data to extract. See below
|
||||
for options.
|
||||
- `skip_if`: If set, this must be the path to a callable. That callable is
|
||||
invoked with a reference to the field's type at serialisation time. If
|
||||
the callable returns true, the field is omitted from the output
|
||||
completely.
|
||||
|
||||
This should often be combined with `default`.
|
||||
- `default`: If the child is not present, the field value will be created
|
||||
using [`Default::default`]. For `extract(..)`, this is even required when
|
||||
using `Option<..>` as a field type.
|
||||
|
|
|
@ -239,6 +239,11 @@ pub(crate) struct ChildField {
|
|||
/// [`std::default::Default`] if no matching child can be found, instead
|
||||
/// of aborting parsing with an error.
|
||||
default_on_missing: Flag,
|
||||
|
||||
/// If set, it must point to a function. That function will be called with
|
||||
/// an immutable reference to the field's value and must return a boolean.
|
||||
/// If that boolean is true, the child will not be emitted.
|
||||
skip_if: Option<Path>,
|
||||
}
|
||||
|
||||
impl ChildField {
|
||||
|
@ -270,6 +275,7 @@ impl ChildField {
|
|||
name: Option<NameRef>,
|
||||
extract: Vec<Box<XmlFieldMeta>>,
|
||||
default_on_missing: Flag,
|
||||
skip_if: Option<Path>,
|
||||
field_type: &Type,
|
||||
) -> Result<Self> {
|
||||
if extract.len() > 0 {
|
||||
|
@ -316,6 +322,7 @@ impl ChildField {
|
|||
extract,
|
||||
single_extract_type,
|
||||
)?),
|
||||
skip_if,
|
||||
default_on_missing,
|
||||
})
|
||||
} else {
|
||||
|
@ -335,6 +342,7 @@ impl ChildField {
|
|||
mode,
|
||||
extract: None,
|
||||
default_on_missing,
|
||||
skip_if,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -518,10 +526,19 @@ impl ChildField {
|
|||
ty: Type,
|
||||
container_namespace_expr: &Expr,
|
||||
) -> Result<TokenStream> {
|
||||
let temp_ident = Ident::new("__data", Span::call_site());
|
||||
let skip_map = match self.skip_if {
|
||||
Some(callable) => quote! {
|
||||
match #callable(&#temp_ident) {
|
||||
false => Some(#temp_ident),
|
||||
true => None,
|
||||
}
|
||||
},
|
||||
None => quote! { Some(#temp_ident) },
|
||||
};
|
||||
match self.mode {
|
||||
ChildMode::Single => match self.extract {
|
||||
Some(extract) => {
|
||||
let temp_ident = Ident::new("__data", Span::call_site());
|
||||
let temp_expr = Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
|
@ -537,16 +554,19 @@ impl ChildField {
|
|||
&temp_expr,
|
||||
)?;
|
||||
Ok(quote! {
|
||||
match Option::<#inner_ty>::from(#ident) {
|
||||
match Option::<#inner_ty>::from(#ident).and_then(|#temp_ident| #skip_map) {
|
||||
Some(#temp_ident) => builder.append(::xmpp_parsers_core::exports::minidom::Node::Element(#assemble)),
|
||||
None => builder,
|
||||
}
|
||||
})
|
||||
}
|
||||
None => Ok(quote! {
|
||||
match <#ty as ::xmpp_parsers_core::IntoXml>::into_tree(#ident) {
|
||||
Some(el) => builder.append(::xmpp_parsers_core::exports::minidom::Node::Element(el)),
|
||||
None => builder,
|
||||
{
|
||||
let #temp_ident = #ident;
|
||||
match #skip_map.and_then(|#temp_ident| <#ty as ::xmpp_parsers_core::IntoXml>::into_tree(#temp_ident)) {
|
||||
Some(#temp_ident) => builder.append(::xmpp_parsers_core::exports::minidom::Node::Element(#temp_ident)),
|
||||
None => builder,
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
@ -558,20 +578,23 @@ impl ChildField {
|
|||
&Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: None,
|
||||
path: Path::from(Ident::new("data", Span::call_site())),
|
||||
path: temp_ident.clone().into(),
|
||||
}),
|
||||
)?;
|
||||
Ok(quote! {
|
||||
builder.append_all(
|
||||
#ident.into_iter().map(|data| {
|
||||
#assemble
|
||||
#ident.into_iter().filter_map(|#temp_ident| {
|
||||
match #skip_map {
|
||||
Some(#temp_ident) => Some(#assemble),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
None => Ok(quote! {
|
||||
builder.append_all(#ident.into_iter().filter_map(|item| {
|
||||
::xmpp_parsers_core::IntoXml::into_tree(item).map(|el| ::xmpp_parsers_core::exports::minidom::Node::Element(el))
|
||||
builder.append_all(#ident.into_iter().filter_map(|#temp_ident| {
|
||||
#skip_map.and_then(::xmpp_parsers_core::IntoXml::into_tree).map(|el| ::xmpp_parsers_core::exports::minidom::Node::Element(el))
|
||||
}))
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -195,6 +195,7 @@ impl FieldKind {
|
|||
name,
|
||||
extract,
|
||||
default_on_missing,
|
||||
skip_if,
|
||||
} => Ok(FieldKind::Child(ChildField::new(
|
||||
span,
|
||||
mode,
|
||||
|
@ -202,6 +203,7 @@ impl FieldKind {
|
|||
name,
|
||||
extract,
|
||||
default_on_missing,
|
||||
skip_if,
|
||||
field_ty,
|
||||
)?)),
|
||||
XmlFieldMeta::Text { codec, ty } => {
|
||||
|
|
|
@ -705,6 +705,9 @@ pub(crate) enum XmlFieldMeta {
|
|||
|
||||
/// Presence of the `default` flag.
|
||||
default_on_missing: Flag,
|
||||
|
||||
/// Contents of the `skip_if = ..` option.
|
||||
skip_if: Option<Path>,
|
||||
},
|
||||
|
||||
/// Maps the field to the compounds' XML element's namespace.
|
||||
|
@ -801,11 +804,13 @@ impl XmlFieldMeta {
|
|||
Option<NameRef>,
|
||||
Vec<Box<XmlFieldMeta>>,
|
||||
Flag,
|
||||
Option<Path>,
|
||||
)> {
|
||||
if meta.input.peek(token::Paren) {
|
||||
let mut name: Option<NameRef> = None;
|
||||
let mut namespace: Option<NamespaceRef> = None;
|
||||
let mut extract: Vec<Box<XmlFieldMeta>> = Vec::new();
|
||||
let mut skip_if: Option<Path> = None;
|
||||
let mut default_on_missing = Flag::Absent;
|
||||
meta.parse_nested_meta(|meta| {
|
||||
parse_meta!(
|
||||
|
@ -814,31 +819,35 @@ impl XmlFieldMeta {
|
|||
ParseValue("namespace", &mut namespace),
|
||||
ParseExtracts(&mut extract),
|
||||
ParseFlag("default", &mut default_on_missing),
|
||||
ParseValue("skip_if", &mut skip_if),
|
||||
)
|
||||
})?;
|
||||
Ok((namespace, name, extract, default_on_missing))
|
||||
Ok((namespace, name, extract, default_on_missing, skip_if))
|
||||
} else {
|
||||
Ok((None, None, Vec::new(), Flag::Absent))
|
||||
Ok((None, None, Vec::new(), Flag::Absent, None))
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes a `#[xml(child)]` meta, creating a [`Self::Child`]
|
||||
/// variant with [`ChildMode::Single`].
|
||||
fn child_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
|
||||
let (namespace, name, extract, default_on_missing) = Self::child_common_from_meta(meta)?;
|
||||
let (namespace, name, extract, default_on_missing, skip_if) =
|
||||
Self::child_common_from_meta(meta)?;
|
||||
Ok(Self::Child {
|
||||
mode: ChildMode::Single,
|
||||
namespace,
|
||||
name,
|
||||
extract,
|
||||
default_on_missing,
|
||||
skip_if,
|
||||
})
|
||||
}
|
||||
|
||||
/// Processes a `#[xml(children)]` meta, creating a [`Self::Child`]
|
||||
/// variant with [`ChildMode::Collection`].
|
||||
fn children_from_meta(meta: meta::ParseNestedMeta<'_>) -> Result<Self> {
|
||||
let (namespace, name, extract, default_on_missing) = Self::child_common_from_meta(meta)?;
|
||||
let (namespace, name, extract, default_on_missing, skip_if) =
|
||||
Self::child_common_from_meta(meta)?;
|
||||
if let Flag::Present(default_on_missing) = default_on_missing {
|
||||
return Err(syn::Error::new(default_on_missing, "default cannot be used on #[xml(children)] (it is implied, the default is the empty container)"));
|
||||
}
|
||||
|
@ -848,6 +857,7 @@ impl XmlFieldMeta {
|
|||
name,
|
||||
extract,
|
||||
default_on_missing: Flag::Absent,
|
||||
skip_if,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -975,3 +975,28 @@ fn element_qualified_negative_name() {
|
|||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_zero(v: &i8) -> bool {
|
||||
*v == 0
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = self::TEST_NS1, name = "skip-if")]
|
||||
pub struct ChildExtractSkipIf {
|
||||
#[xml(child(namespace = self::TEST_NS1, name = "foo", extract(text), default, skip_if = is_zero))]
|
||||
pub value: i8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_extract_skip_if_roundtrip_absent() {
|
||||
crate::util::test::roundtrip_full::<ChildExtractSkipIf>(
|
||||
"<skip-if xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_extract_skip_if_roundtrip_present() {
|
||||
crate::util::test::roundtrip_full::<ChildExtractSkipIf>(
|
||||
"<skip-if xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo>10</foo></skip-if>",
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user