From 5e2717e1304d46f8445f9c77428fbc92a40630ed Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Wed, 25 Dec 2024 10:21:09 +0100 Subject: [PATCH] xml: new variant flag and fixes to generic types --- crates/xml/derive/src/de/attrs.rs | 1 + crates/xml/derive/src/de/de_enum.rs | 157 ++++++++++++++++++-------- crates/xml/derive/src/de/de_struct.rs | 5 +- crates/xml/derive/src/de/field.rs | 22 +--- crates/xml/derive/src/de/mod.rs | 17 +++ crates/xml/src/de.rs | 2 - crates/xml/src/value.rs | 15 --- crates/xml/tests/propertyupdate.rs | 57 ++++++++++ 8 files changed, 194 insertions(+), 82 deletions(-) create mode 100644 crates/xml/tests/propertyupdate.rs diff --git a/crates/xml/derive/src/de/attrs.rs b/crates/xml/derive/src/de/attrs.rs index 931c90b..bf5bf34 100644 --- a/crates/xml/derive/src/de/attrs.rs +++ b/crates/xml/derive/src/de/attrs.rs @@ -19,6 +19,7 @@ pub struct VariantAttrs { #[darling(flatten)] pub common: TagAttrs, pub other: Flag, + pub skip_deserializing: Flag, } #[derive(Default, FromDeriveInput, Clone)] diff --git a/crates/xml/derive/src/de/de_enum.rs b/crates/xml/derive/src/de/de_enum.rs index aa595ab..234cef9 100644 --- a/crates/xml/derive/src/de/de_enum.rs +++ b/crates/xml/derive/src/de/de_enum.rs @@ -1,6 +1,6 @@ use core::panic; -use super::attrs::EnumAttrs; +use super::{attrs::EnumAttrs, get_generic_type}; use crate::de::attrs::VariantAttrs; use darling::{FromDeriveInput, FromVariant}; use heck::ToKebabCase; @@ -28,60 +28,127 @@ impl Variant { )) } - pub fn tagged_branch(&self) -> proc_macro2::TokenStream { - let ident = self.ident(); - let variant_name = self.xml_name(); + fn skip_de(&self) -> bool { + self.attrs.skip_deserializing.is_present() + } - match (self.attrs.other.is_present(), &self.variant.fields) { - (_, Fields::Named(_)) => { - panic!( - "struct variants are not supported, please use a tuple variant with a struct" - ) - } - (false, Fields::Unnamed(FieldsUnnamed { unnamed, .. })) => { + fn variant_type(&self) -> syn::Type { + match &self.variant.fields { + Fields::Named(_) => panic!( + "struct variants are not supported, please use a tuple variant with a struct" + ), + Fields::Unit => syn::Type::Path(syn::parse_str("::rustical_xml::Unit").unwrap()), + Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { if unnamed.len() != 1 { panic!("tuple variants should contain exactly one element"); } let field = unnamed.iter().next().unwrap(); - quote! { - #variant_name => { - let val = #field::deserialize(reader, start, empty)?; - Ok(Self::#ident(val)) - } - } - } - (false, Fields::Unit) => { - quote! { - #variant_name => { - // Make sure that content is still consumed - ::rustical_xml::Unit::deserialize(reader, start, empty)?; - Ok(Self::#ident) - } - } - } - (true, Fields::Unnamed(_)) => { - panic!("other for tuple enums not implemented yet") - } - (true, Fields::Unit) => { - quote! { - _ => { - // Make sure that content is still consumed - ::rustical_xml::Unit::deserialize(reader, start, empty)?; - Ok(Self::#ident) - } - } + field.ty.to_owned() } } } - pub fn untagged_branch(&self) -> proc_macro2::TokenStream { + fn is_optional(&self) -> bool { + if let syn::Type::Path(syn::TypePath { path, .. }) = self.variant_type() { + if path.segments.len() != 1 { + return false; + } + let type_ident = &path.segments.first().unwrap().ident; + let option: syn::Ident = syn::parse_str("Option").unwrap(); + return type_ident == &option; + } + false + } + + /// The type to deserialize to + /// - type Option => optional: deserialize with T + /// - flatten Vec: deserialize with T + /// - deserialize with T + pub fn deserializer_type(&self) -> syn::Type { + let ty = self.variant_type(); + if self.is_optional() { + return get_generic_type(&ty).unwrap().to_owned(); + } + ty + } + + pub fn tagged_branch(&self) -> Option { + if self.skip_de() { + return None; + } + let ident = self.ident(); + let variant_name = self.xml_name(); + let deserializer_type = self.deserializer_type(); + + Some( + match ( + self.attrs.other.is_present(), + &self.variant.fields, + self.is_optional(), + ) { + (_, Fields::Named(_), _) => { + panic!( + "struct variants are not supported, please use a tuple variant with a struct" + ) + } + (false, Fields::Unnamed(FieldsUnnamed { unnamed, .. }), true) => { + if unnamed.len() != 1 { + panic!("tuple variants should contain exactly one element"); + } + quote! { + #variant_name => { + let val = Some(<#deserializer_type as ::rustical_xml::XmlDeserialize>::deserialize(reader, start, empty)?); + Ok(Self::#ident(val)) + } + } + } + (false, Fields::Unnamed(FieldsUnnamed { unnamed, .. }), false) => { + if unnamed.len() != 1 { + panic!("tuple variants should contain exactly one element"); + } + quote! { + #variant_name => { + let val = <#deserializer_type as ::rustical_xml::XmlDeserialize>::deserialize(reader, start, empty)?; + Ok(Self::#ident(val)) + } + } + } + (false, Fields::Unit, _) => { + quote! { + #variant_name => { + // Make sure that content is still consumed + ::rustical_xml::Unit::deserialize(reader, start, empty)?; + Ok(Self::#ident) + } + } + } + (true, Fields::Unnamed(_), _) => { + panic!("other for tuple enums not implemented yet") + } + (true, Fields::Unit, _) => { + quote! { + _ => { + // Make sure that content is still consumed + ::rustical_xml::Unit::deserialize(reader, start, empty)?; + Ok(Self::#ident) + } + } + } + }, + ) + } + + pub fn untagged_branch(&self) -> Option { + if self.skip_de() { + return None; + } if self.attrs.other.is_present() { panic!("using the other flag on an untagged variant is futile"); } let ident = self.ident(); - match &self.variant.fields { + Some(match &self.variant.fields { Fields::Named(_) => { panic!( "struct variants are not supported, please use a tuple variant with a struct" @@ -93,7 +160,7 @@ impl Variant { } let field = unnamed.iter().next().unwrap(); quote! { - if let Ok(val) = #field::deserialize(reader, start, empty) { + if let Ok(val) = <#field as ::rustical_xml::XmlDeserialize>::deserialize(reader, start, empty) { return Ok(Self::#ident(val)); } } @@ -106,7 +173,7 @@ impl Variant { } } } - } + }) } } @@ -125,7 +192,7 @@ impl Enum { let variant_branches = self .variants .iter() - .map(|variant| variant.untagged_branch()); + .filter_map(|variant| variant.untagged_branch()); quote! { impl #impl_generics ::rustical_xml::XmlDeserialize for #name #type_generics #where_clause { @@ -146,7 +213,7 @@ impl Enum { let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); let name = &self.ident; - let variant_branches = self.variants.iter().map(Variant::tagged_branch); + let variant_branches = self.variants.iter().filter_map(Variant::tagged_branch); quote! { impl #impl_generics ::rustical_xml::XmlDeserialize for #name #type_generics #where_clause { @@ -246,7 +313,7 @@ impl Enum { Event::Decl(_) => { /* ignore this */ } Event::Comment(_) => { /* ignore this */ } Event::Start(start) | Event::Empty(start) => { - return Self::deserialize(&mut reader, &start, empty); + return ::deserialize(&mut reader, &start, empty); } _ => return Err(::rustical_xml::XmlDeError::UnknownError), }; diff --git a/crates/xml/derive/src/de/de_struct.rs b/crates/xml/derive/src/de/de_struct.rs index 856bc1c..1c34290 100644 --- a/crates/xml/derive/src/de/de_struct.rs +++ b/crates/xml/derive/src/de/de_struct.rs @@ -9,7 +9,7 @@ fn invalid_field_branch(allow: bool) -> proc_macro2::TokenStream { if allow { quote! {} } else { - quote! { return Err(XmlDeError::InvalidFieldName(format!("[{ns:?}]{tag:?}"))) } + quote! { return Err(XmlDeError::InvalidFieldName(format!("[{ns:?}]{tag}", tag = String::from_utf8_lossy(tag)))) } } } @@ -64,6 +64,7 @@ impl NamedStruct { } pub fn impl_de(&self) -> proc_macro2::TokenStream { + let builder_generics = &self.generics; let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); let ident = &self.ident; @@ -99,7 +100,7 @@ impl NamedStruct { let mut buf = Vec::new(); // initialise fields - struct StructBuilder #type_generics #where_clause { + struct StructBuilder #builder_generics { #(#builder_fields),* } diff --git a/crates/xml/derive/src/de/field.rs b/crates/xml/derive/src/de/field.rs index 334d606..c3bdf99 100644 --- a/crates/xml/derive/src/de/field.rs +++ b/crates/xml/derive/src/de/field.rs @@ -1,4 +1,7 @@ -use super::attrs::{ContainerAttrs, FieldAttrs, FieldType}; +use super::{ + attrs::{ContainerAttrs, FieldAttrs, FieldType}, + get_generic_type, +}; use darling::FromField; use heck::ToKebabCase; use quote::quote; @@ -14,23 +17,6 @@ fn wrap_option_if_no_default( } } -fn get_generic_type(ty: &syn::Type) -> Option<&syn::Type> { - if let syn::Type::Path(syn::TypePath { path, .. }) = ty { - if let Some(seg) = path.segments.last() { - if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { - args, - .. - }) = &seg.arguments - { - if let Some(syn::GenericArgument::Type(t)) = &args.first() { - return Some(t); - } - } - } - } - None -} - pub struct Field { pub field: syn::Field, pub attrs: FieldAttrs, diff --git a/crates/xml/derive/src/de/mod.rs b/crates/xml/derive/src/de/mod.rs index ff5395e..59561e9 100644 --- a/crates/xml/derive/src/de/mod.rs +++ b/crates/xml/derive/src/de/mod.rs @@ -6,3 +6,20 @@ mod field; pub use de_enum::Enum; pub use de_struct::NamedStruct; pub use field::Field; + +pub fn get_generic_type(ty: &syn::Type) -> Option<&syn::Type> { + if let syn::Type::Path(syn::TypePath { path, .. }) = ty { + if let Some(seg) = path.segments.last() { + if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + args, + .. + }) = &seg.arguments + { + if let Some(syn::GenericArgument::Type(t)) = &args.first() { + return Some(t); + } + } + } + } + None +} diff --git a/crates/xml/src/de.rs b/crates/xml/src/de.rs index 62e2e04..1baa409 100644 --- a/crates/xml/src/de.rs +++ b/crates/xml/src/de.rs @@ -10,8 +10,6 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum XmlDeError { - #[error(transparent)] - QuickXmlDeError(#[from] quick_xml::de::DeError), #[error(transparent)] QuickXmlError(#[from] quick_xml::Error), #[error(transparent)] diff --git a/crates/xml/src/value.rs b/crates/xml/src/value.rs index a264477..56e79e3 100644 --- a/crates/xml/src/value.rs +++ b/crates/xml/src/value.rs @@ -20,21 +20,6 @@ pub trait Value: Sized { fn deserialize(val: &str) -> Result; } -// impl Value for Option { -// fn serialize(&self) -> String { -// match self { -// Some(inner) => inner.serialize(), -// None => "".to_owned(), -// } -// } -// fn deserialize(val: &str) -> Result { -// match val { -// "" => Ok(None), -// val => Ok(Some(T::deserialize(val)?)), -// } -// } -// } - macro_rules! impl_value_parse { ($t:ty) => { impl Value for $t { diff --git a/crates/xml/tests/propertyupdate.rs b/crates/xml/tests/propertyupdate.rs new file mode 100644 index 0000000..d6d5a75 --- /dev/null +++ b/crates/xml/tests/propertyupdate.rs @@ -0,0 +1,57 @@ +use rustical_xml::{Unparsed, XmlDeserialize, XmlDocument, XmlRootTag}; + +#[test] +fn test_propertyupdate() { + #[derive(XmlDeserialize)] + struct SetPropertyElement { + prop: T, + } + + #[derive(XmlDeserialize)] + struct TagName { + #[xml(ty = "tag_name")] + name: String, + } + + #[derive(XmlDeserialize)] + struct PropertyElement { + #[xml(ty = "untagged")] + property: TagName, + } + + #[derive(XmlDeserialize)] + struct RemovePropertyElement { + prop: PropertyElement, + } + + #[derive(XmlDeserialize)] + enum Operation { + Set(SetPropertyElement), + Remove(RemovePropertyElement), + } + + #[derive(XmlDeserialize, XmlRootTag)] + #[xml(root = b"propertyupdate")] + struct PropertyupdateElement { + #[xml(ty = "untagged", flatten)] + operations: Vec>, + } + + let doc = PropertyupdateElement::::parse_str( + r#" + + + + Hello + + + + + + + + + "#, + ) + .unwrap(); +}