xso-proc: add support for non-String typed attributes

This commit is contained in:
Jonas Schäfer 2024-06-25 17:37:03 +02:00
parent 1f679c3af7
commit c0fc7f49cf
4 changed files with 104 additions and 6 deletions

View file

@ -313,3 +313,20 @@ fn prefixed_attribute_roundtrip() {
}; };
roundtrip_full::<PrefixedAttribute>("<attr xmlns='urn:example:ns1' xml:lang='foo'/>"); roundtrip_full::<PrefixedAttribute>("<attr xmlns='urn:example:ns1' xml:lang='foo'/>");
} }
#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
#[xml(namespace = NS1, name = "attr")]
struct RequiredNonStringAttribute {
#[xml(attribute)]
foo: i32,
}
#[test]
fn required_non_string_attribute_roundtrip() {
#[allow(unused_imports)]
use std::{
option::Option::{None, Some},
result::Result::{Err, Ok},
};
roundtrip_full::<RequiredNonStringAttribute>("<attr xmlns='urn:example:ns1' foo='-16'/>");
}

View file

@ -15,6 +15,7 @@ use rxml_validation::NcName;
use crate::error_message::{self, ParentRef}; use crate::error_message::{self, ParentRef};
use crate::meta::{NameRef, NamespaceRef, XmlFieldMeta}; use crate::meta::{NameRef, NamespaceRef, XmlFieldMeta};
use crate::scope::{FromEventsScope, IntoEventsScope}; use crate::scope::{FromEventsScope, IntoEventsScope};
use crate::types::{from_xml_text_fn, into_optional_xml_text_fn};
/// Code slices necessary for declaring and initializing a temporary variable /// Code slices necessary for declaring and initializing a temporary variable
/// for parsing purposes. /// for parsing purposes.
@ -182,6 +183,7 @@ impl FieldDef {
ref xml_namespace, ref xml_namespace,
} => { } => {
let FromEventsScope { ref attrs, .. } = scope; let FromEventsScope { ref attrs, .. } = scope;
let ty = self.ty.clone();
let missing_msg = error_message::on_missing_attribute(container_name, &self.member); let missing_msg = error_message::on_missing_attribute(container_name, &self.member);
@ -192,15 +194,17 @@ impl FieldDef {
}, },
}; };
let from_xml_text = from_xml_text_fn(ty.clone());
return Ok(FieldBuilderPart::Init { return Ok(FieldBuilderPart::Init {
value: FieldTempInit { value: FieldTempInit {
ty: self.ty.clone(),
init: quote! { init: quote! {
match #attrs.remove(#xml_namespace, #xml_name) { match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
::core::option::Option::Some(v) => v, ::core::option::Option::Some(v) => v,
::core::option::Option::None => return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()), ::core::option::Option::None => return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()),
} }
}, },
ty: self.ty.clone(),
}, },
}); });
} }
@ -230,13 +234,15 @@ impl FieldDef {
}, },
}; };
let into_optional_xml_text = into_optional_xml_text_fn(self.ty.clone());
return Ok(FieldIteratorPart::Header { return Ok(FieldIteratorPart::Header {
setter: quote! { setter: quote! {
#attrs.insert( #into_optional_xml_text(#bound_name)?.and_then(|#bound_name| #attrs.insert(
#xml_namespace, #xml_namespace,
#xml_name.to_owned(), #xml_name.to_owned(),
#bound_name, #bound_name,
); ));
}, },
}); });
} }

View file

@ -7,7 +7,7 @@
//! Module with specific [`syn::Type`] constructors. //! Module with specific [`syn::Type`] constructors.
use proc_macro2::Span; use proc_macro2::Span;
use syn::*; use syn::{spanned::Spanned, *};
/// Construct a [`syn::Type`] referring to `::xso::exports::rxml::QName`. /// Construct a [`syn::Type`] referring to `::xso::exports::rxml::QName`.
pub(crate) fn qname_ty(span: Span) -> Type { pub(crate) fn qname_ty(span: Span) -> Type {
@ -40,3 +40,77 @@ pub(crate) fn qname_ty(span: Span) -> Type {
}, },
}) })
} }
/// Construct a [`syn::Expr`] referring to
/// `<#ty as ::xso::FromXmlText>::from_xml_text`.
pub(crate) fn from_xml_text_fn(ty: Type) -> Expr {
let span = ty.span();
Expr::Path(ExprPath {
attrs: Vec::new(),
qself: Some(QSelf {
lt_token: syn::token::Lt { spans: [span] },
ty: Box::new(ty),
position: 2,
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("xso", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("FromXmlText", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("from_xml_text", span),
arguments: PathArguments::None,
},
]
.into_iter()
.collect(),
},
})
}
/// Construct a [`syn::Expr`] referring to
/// `<#ty as ::xso::IntoOptionalXmlText>::into_optional_xml_text`.
pub(crate) fn into_optional_xml_text_fn(ty: Type) -> Expr {
let span = ty.span();
Expr::Path(ExprPath {
attrs: Vec::new(),
qself: Some(QSelf {
lt_token: syn::token::Lt { spans: [span] },
ty: Box::new(ty),
position: 2,
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("xso", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("IntoOptionalXmlText", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("into_optional_xml_text", span),
arguments: PathArguments::None,
},
]
.into_iter()
.collect(),
},
})
}

View file

@ -63,7 +63,8 @@ The following mapping types are defined:
#### `attribute` meta #### `attribute` meta
The `attribute` meta causes the field to be mapped to an XML attribute of the The `attribute` meta causes the field to be mapped to an XML attribute of the
same name. The field must be of type [`String`]. same name. For `FromXml`, the field's type must implement [`FromXmlText`] and
for `IntoXml`, the field's type must implement [`IntoOptionalXmlText`].
The following keys can be used inside the `#[xml(attribute(..))]` meta: The following keys can be used inside the `#[xml(attribute(..))]` meta: