From c0fc7f49cfa762f0bd7c5a3385c91014e7d6b516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Tue, 25 Jun 2024 17:37:03 +0200 Subject: [PATCH] xso-proc: add support for non-String typed attributes --- parsers/src/util/macro_tests.rs | 17 ++++++++ xso-proc/src/field.rs | 14 ++++-- xso-proc/src/types.rs | 76 ++++++++++++++++++++++++++++++++- xso/src/from_xml_doc.md | 3 +- 4 files changed, 104 insertions(+), 6 deletions(-) diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs index aed19c834886097e0ff920e456074d18d9e60bd9..3b5ad692e9f83830345908111454180380824eb4 100644 --- a/parsers/src/util/macro_tests.rs +++ b/parsers/src/util/macro_tests.rs @@ -313,3 +313,20 @@ fn prefixed_attribute_roundtrip() { }; roundtrip_full::(""); } + +#[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::(""); +} diff --git a/xso-proc/src/field.rs b/xso-proc/src/field.rs index f9b6dcd1501b6b4c34d3e9ea5068af011641a479..13f214539fa01a36eb1656e1ad477e46dc808796 100644 --- a/xso-proc/src/field.rs +++ b/xso-proc/src/field.rs @@ -15,6 +15,7 @@ use rxml_validation::NcName; use crate::error_message::{self, ParentRef}; use crate::meta::{NameRef, NamespaceRef, XmlFieldMeta}; 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 /// for parsing purposes. @@ -182,6 +183,7 @@ impl FieldDef { ref xml_namespace, } => { let FromEventsScope { ref attrs, .. } = scope; + let ty = self.ty.clone(); 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 { value: FieldTempInit { - ty: self.ty.clone(), 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::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 { setter: quote! { - #attrs.insert( + #into_optional_xml_text(#bound_name)?.and_then(|#bound_name| #attrs.insert( #xml_namespace, #xml_name.to_owned(), #bound_name, - ); + )); }, }); } diff --git a/xso-proc/src/types.rs b/xso-proc/src/types.rs index d39d92d103892cab5ae9bd790a3019b0c7aa2838..358d5328bb9d9326f33e9a39d26557e7f8611f43 100644 --- a/xso-proc/src/types.rs +++ b/xso-proc/src/types.rs @@ -7,7 +7,7 @@ //! Module with specific [`syn::Type`] constructors. use proc_macro2::Span; -use syn::*; +use syn::{spanned::Spanned, *}; /// Construct a [`syn::Type`] referring to `::xso::exports::rxml::QName`. 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(), + }, + }) +} diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md index d813a6456afd590235e76f78dd87c68e477ff752..d9dbd3ce7066c6a50c07cf032d282d59c4d5da63 100644 --- a/xso/src/from_xml_doc.md +++ b/xso/src/from_xml_doc.md @@ -63,7 +63,8 @@ The following mapping types are defined: #### `attribute` meta 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: