mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-06-18 13:45:57 +02:00
parsers-macros: implement support for extended child destructuring
This commit is contained in:
parent
c0ea48f22f
commit
e8e6b12fd2
|
@ -759,3 +759,35 @@ impl UnknownAttributePolicy {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to support destructuring of child structs beyond what the
|
||||
/// `extract(..)` attribute can deliver.
|
||||
///
|
||||
/// This trait can only be sensibly implemented on types which implement both
|
||||
/// `FromXml` and `IntoXml`. However, as there may be corner cases where only
|
||||
/// one of these other traits is needed, they're not strictly included in the
|
||||
/// trait bounds.
|
||||
///
|
||||
/// When used as value for `codec = ..` inside a `#[xml(child(..))]` or
|
||||
/// `#[xml(children(..))]` field attribute, the field is destructured using
|
||||
/// the trait implementations of `FromXml` / `IntoXml` and then converted
|
||||
/// to the actual field's type by invoking the `ElementCodec<T>` methods, with
|
||||
/// `T` being the field type.
|
||||
pub trait ElementCodec<T> {
|
||||
/// Transform the destructured value further toward the field type.
|
||||
fn decode(value: Self) -> T;
|
||||
|
||||
/// Transform the field type back to something which can be structured
|
||||
/// into XML.
|
||||
fn encode(value: T) -> Self;
|
||||
}
|
||||
|
||||
impl<T> ElementCodec<T> for T {
|
||||
fn decode(value: Self) -> Self {
|
||||
value
|
||||
}
|
||||
|
||||
fn encode(value: Self) -> Self {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,6 +252,11 @@ pub(crate) struct ChildField {
|
|||
/// 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>,
|
||||
|
||||
/// If set, it must point to a type. The `FromXml`/`IntoXml`
|
||||
/// implementations of that type will be used instead, and the type must
|
||||
/// implement `ElementCodec<T>`, where `T` is the type of the field.
|
||||
codec: Option<Path>,
|
||||
}
|
||||
|
||||
impl ChildField {
|
||||
|
@ -283,6 +288,7 @@ impl ChildField {
|
|||
extract: Vec<Box<XmlFieldMeta>>,
|
||||
default_: FlagOr<Path>,
|
||||
skip_if: Option<Path>,
|
||||
codec: Option<Path>,
|
||||
field_type: &Type,
|
||||
) -> Result<Self> {
|
||||
if extract.len() > 0 {
|
||||
|
@ -308,6 +314,12 @@ impl ChildField {
|
|||
"name must be specified on extracted fields",
|
||||
));
|
||||
};
|
||||
if let Some(codec) = codec {
|
||||
return Err(Error::new_spanned(
|
||||
codec,
|
||||
"codec = .. cannot be combined with extract(..)",
|
||||
));
|
||||
}
|
||||
let single_extract_type = match mode {
|
||||
ChildMode::Single => {
|
||||
if extract.len() > 1 {
|
||||
|
@ -332,6 +344,7 @@ impl ChildField {
|
|||
skip_if,
|
||||
default_,
|
||||
super_namespace: Flag::Absent,
|
||||
codec: None,
|
||||
})
|
||||
} else {
|
||||
let super_namespace = match namespace {
|
||||
|
@ -356,6 +369,7 @@ impl ChildField {
|
|||
default_,
|
||||
skip_if,
|
||||
super_namespace,
|
||||
codec,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -450,14 +464,23 @@ impl ChildField {
|
|||
::std::cmp::PartialEq::eq(&#container_namespace_expr, residual.ns().as_str())
|
||||
},
|
||||
};
|
||||
let codec_ty = match self.codec {
|
||||
Some(ty) => Type::Path(TypePath {
|
||||
qself: None,
|
||||
path: ty,
|
||||
}),
|
||||
None => ty.clone(),
|
||||
};
|
||||
let field_ty = ty;
|
||||
Ok(FieldParsePart {
|
||||
tempinit: quote! {
|
||||
let mut #tempname: Option<#ty> = None;
|
||||
let mut #tempname: Option<#field_ty> = None;
|
||||
},
|
||||
childiter: quote! {
|
||||
let mut residual = if #ns_test {
|
||||
match <#ty as ::xmpp_parsers_core::FromXml>::from_tree(residual) {
|
||||
match <#codec_ty as ::xmpp_parsers_core::FromXml>::from_tree(residual) {
|
||||
Ok(v) => {
|
||||
let v = <#codec_ty as ::xmpp_parsers_core::ElementCodec::<#field_ty>>::decode(v);
|
||||
if #tempname.is_some() {
|
||||
return Err(::xmpp_parsers_core::error::Error::ParseError(#duperr));
|
||||
}
|
||||
|
@ -474,7 +497,7 @@ impl ChildField {
|
|||
value: quote! {
|
||||
if let Some(v) = #tempname {
|
||||
v
|
||||
} else if let Some(v) = <#ty as ::xmpp_parsers_core::FromXml>::absent() {
|
||||
} else if let Some(v) = <#codec_ty as ::xmpp_parsers_core::FromXml>::absent().map(<#codec_ty as ::xmpp_parsers_core::ElementCodec::<#field_ty>>::decode) {
|
||||
v
|
||||
} else {
|
||||
#on_missing
|
||||
|
@ -524,13 +547,21 @@ impl ChildField {
|
|||
<#ty as IntoIterator>::Item
|
||||
})
|
||||
.expect("failed to construct item type");
|
||||
let codec_ty = match self.codec {
|
||||
Some(ty) => Type::Path(TypePath {
|
||||
qself: None,
|
||||
path: ty,
|
||||
}),
|
||||
None => item_ty.clone(),
|
||||
};
|
||||
Ok(FieldParsePart {
|
||||
tempinit: quote! {
|
||||
let mut #tempname = <#ty as std::default::Default>::default();
|
||||
},
|
||||
childiter: quote! {
|
||||
let mut residual = match <#item_ty as ::xmpp_parsers_core::FromXml>::from_tree(residual) {
|
||||
let mut residual = match <#codec_ty as ::xmpp_parsers_core::FromXml>::from_tree(residual) {
|
||||
Ok(item) => {
|
||||
let item = <#codec_ty as ::xmpp_parsers_core::ElementCodec::<#item_ty>>::decode(item);
|
||||
<#ty as ::std::iter::Extend<#item_ty>>::extend(&mut #tempname, [item]);
|
||||
continue;
|
||||
},
|
||||
|
@ -625,15 +656,25 @@ impl ChildField {
|
|||
}
|
||||
})
|
||||
}
|
||||
None => Ok(quote! {
|
||||
{
|
||||
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,
|
||||
None => {
|
||||
let codec_ty = match self.codec {
|
||||
Some(ty) => Type::Path(TypePath {
|
||||
qself: None,
|
||||
path: ty,
|
||||
}),
|
||||
None => ty.clone(),
|
||||
};
|
||||
let field_ty = ty;
|
||||
Ok(quote! {
|
||||
{
|
||||
let #temp_ident = #ident;
|
||||
match #skip_map.and_then(|#temp_ident| <#codec_ty as ::xmpp_parsers_core::IntoXml>::into_tree(<#codec_ty as ::xmpp_parsers_core::ElementCodec::<#field_ty>>::encode(#temp_ident))) {
|
||||
Some(#temp_ident) => builder.append(::xmpp_parsers_core::exports::minidom::Node::Element(#temp_ident)),
|
||||
None => builder,
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
},
|
||||
ChildMode::Collection => match self.extract {
|
||||
Some(extract) => {
|
||||
|
@ -657,11 +698,24 @@ impl ChildField {
|
|||
)
|
||||
})
|
||||
}
|
||||
None => Ok(quote! {
|
||||
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))
|
||||
}))
|
||||
}),
|
||||
None => {
|
||||
let item_ty: Type = syn::parse2(quote! {
|
||||
<#ty as IntoIterator>::Item
|
||||
})
|
||||
.expect("failed to construct item type");
|
||||
let codec_ty = match self.codec {
|
||||
Some(ty) => Type::Path(TypePath {
|
||||
qself: None,
|
||||
path: ty,
|
||||
}),
|
||||
None => item_ty.clone(),
|
||||
};
|
||||
Ok(quote! {
|
||||
builder.append_all(#ident.into_iter().filter_map(|#temp_ident| {
|
||||
#skip_map.map(<#codec_ty as ::xmpp_parsers_core::ElementCodec::<#item_ty>>::encode).and_then(::xmpp_parsers_core::IntoXml::into_tree).map(|el| ::xmpp_parsers_core::exports::minidom::Node::Element(el))
|
||||
}))
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,8 +196,9 @@ impl FieldKind {
|
|||
extract,
|
||||
default_,
|
||||
skip_if,
|
||||
codec,
|
||||
} => Ok(FieldKind::Child(ChildField::new(
|
||||
span, mode, namespace, name, extract, default_, skip_if, field_ty,
|
||||
span, mode, namespace, name, extract, default_, skip_if, codec, field_ty,
|
||||
)?)),
|
||||
XmlFieldMeta::Text { codec, ty } => {
|
||||
if let Some(ty) = ty {
|
||||
|
|
|
@ -824,6 +824,9 @@ pub(crate) enum XmlFieldMeta {
|
|||
|
||||
/// Contents of the `skip_if = ..` option.
|
||||
skip_if: Option<Path>,
|
||||
|
||||
/// Contents of the `codec = ..` option.
|
||||
codec: Option<Path>,
|
||||
},
|
||||
|
||||
/// Maps the field to the compounds' XML element's namespace.
|
||||
|
@ -921,6 +924,7 @@ impl XmlFieldMeta {
|
|||
Vec<Box<XmlFieldMeta>>,
|
||||
FlagOr<Path>,
|
||||
Option<Path>,
|
||||
Option<Path>,
|
||||
)> {
|
||||
if meta.input.peek(token::Paren) {
|
||||
let mut name: Option<NameRef> = None;
|
||||
|
@ -928,6 +932,7 @@ impl XmlFieldMeta {
|
|||
let mut extract: Vec<Box<XmlFieldMeta>> = Vec::new();
|
||||
let mut skip_if: Option<Path> = None;
|
||||
let mut default_ = FlagOr::Absent;
|
||||
let mut codec: Option<Path> = None;
|
||||
meta.parse_nested_meta(|meta| {
|
||||
parse_meta!(
|
||||
meta,
|
||||
|
@ -936,18 +941,20 @@ impl XmlFieldMeta {
|
|||
ParseExtracts(&mut extract),
|
||||
ParseFlagOr("default", &mut default_),
|
||||
ParseValue("skip_if", &mut skip_if),
|
||||
ParseValue("codec", &mut codec),
|
||||
)
|
||||
})?;
|
||||
Ok((namespace, name, extract, default_, skip_if))
|
||||
Ok((namespace, name, extract, default_, skip_if, codec))
|
||||
} else {
|
||||
Ok((None, None, Vec::new(), FlagOr::Absent, None))
|
||||
Ok((None, None, Vec::new(), FlagOr::Absent, None, 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_, skip_if) = Self::child_common_from_meta(meta)?;
|
||||
let (namespace, name, extract, default_, skip_if, codec) =
|
||||
Self::child_common_from_meta(meta)?;
|
||||
Ok(Self::Child {
|
||||
mode: ChildMode::Single,
|
||||
namespace,
|
||||
|
@ -955,13 +962,15 @@ impl XmlFieldMeta {
|
|||
extract,
|
||||
default_,
|
||||
skip_if,
|
||||
codec,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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_, skip_if) = Self::child_common_from_meta(meta)?;
|
||||
let (namespace, name, extract, default_, skip_if, codec) =
|
||||
Self::child_common_from_meta(meta)?;
|
||||
if let Some(default_) = default_.into_span() {
|
||||
return Err(syn::Error::new(default_, "default cannot be used on #[xml(children)] (it is implied, the default is the empty container)"));
|
||||
}
|
||||
|
@ -972,6 +981,7 @@ impl XmlFieldMeta {
|
|||
extract,
|
||||
default_: FlagOr::Absent,
|
||||
skip_if,
|
||||
codec,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
|||
|
||||
use crate::core::{
|
||||
error::{DynNamespaceError, Error},
|
||||
Base64, DynNamespace, DynNamespaceEnum, FromXml, IntoXml,
|
||||
Base64, DynNamespace, DynNamespaceEnum, ElementCodec, FromXml, IntoXml,
|
||||
};
|
||||
use crate::Element;
|
||||
|
||||
|
@ -1121,3 +1121,57 @@ fn ignore_unknown_attributes_rejects_children() {
|
|||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = self::TEST_NS1, name = "single")]
|
||||
pub struct SingleEl {
|
||||
#[xml(text)]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl ElementCodec<String> for SingleEl {
|
||||
fn decode(value: Self) -> String {
|
||||
value.value
|
||||
}
|
||||
|
||||
fn encode(value: String) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = self::TEST_NS1, name = "multi")]
|
||||
pub struct MultiEl {
|
||||
#[xml(text)]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl ElementCodec<String> for MultiEl {
|
||||
fn decode(value: Self) -> String {
|
||||
value.value
|
||||
}
|
||||
|
||||
fn encode(value: String) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
||||
#[xml(namespace = self::TEST_NS1, name = "element-codec")]
|
||||
pub struct ElementCodecTest {
|
||||
#[xml(child(codec = SingleEl))]
|
||||
pub single: String,
|
||||
|
||||
#[xml(children(codec = MultiEl))]
|
||||
pub multi: Vec<String>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn element_codec_roundtrip() {
|
||||
match crate::util::test::parse_str::<ElementCodecTest>(
|
||||
"<element-codec xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><single>text 1</single><multi>text 2</multi><multi>text 3</multi></element-codec>",
|
||||
) {
|
||||
Ok(_) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user