From 39beee2f69229ebe4fd87a230eae901c7ef64807 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:35:19 +0100 Subject: [PATCH] xml: Also implement unit variants --- crates/xml/derive/src/attrs.rs | 1 + crates/xml/derive/src/lib.rs | 16 ++++++- crates/xml/derive/src/xml_enum.rs | 71 +++++++++++++++++++++++++++++++ crates/xml/src/lib.rs | 1 + crates/xml/tests/enum_variants.rs | 16 ++++++- 5 files changed, 102 insertions(+), 3 deletions(-) diff --git a/crates/xml/derive/src/attrs.rs b/crates/xml/derive/src/attrs.rs index 63dc26d..6f34495 100644 --- a/crates/xml/derive/src/attrs.rs +++ b/crates/xml/derive/src/attrs.rs @@ -22,6 +22,7 @@ pub struct VariantAttrs { #[darling(attributes(xml))] pub struct EnumAttrs { pub untagged: Flag, + pub unit_variants_name: Option, } #[derive(Default, FromDeriveInput, Clone)] diff --git a/crates/xml/derive/src/lib.rs b/crates/xml/derive/src/lib.rs index 6be7312..8d5bbac 100644 --- a/crates/xml/derive/src/lib.rs +++ b/crates/xml/derive/src/lib.rs @@ -67,9 +67,21 @@ pub fn derive_enum_variants(input: proc_macro::TokenStream) -> proc_macro::Token let input = parse_macro_input!(input as DeriveInput); match &input.data { - syn::Data::Struct(_) => panic!("Struct not supported, use XmlRootTag instead"), + syn::Data::Struct(_) => panic!("Struct not supported"), syn::Data::Enum(e) => Enum::parse(&input, e).impl_enum_variants(), - syn::Data::Union(_) => panic!("Union not supported as root"), + syn::Data::Union(_) => panic!("Union not supported"), + } + .into() +} + +#[proc_macro_derive(EnumUnitVariants, attributes(xml))] +pub fn derive_enum_unit_variants(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + match &input.data { + syn::Data::Struct(_) => panic!("Struct not supported"), + syn::Data::Enum(e) => Enum::parse(&input, e).impl_enum_unit_variants(), + syn::Data::Union(_) => panic!("Union not supported"), } .into() } diff --git a/crates/xml/derive/src/xml_enum.rs b/crates/xml/derive/src/xml_enum.rs index 5fc06b9..0d0191b 100644 --- a/crates/xml/derive/src/xml_enum.rs +++ b/crates/xml/derive/src/xml_enum.rs @@ -2,6 +2,7 @@ use super::{attrs::EnumAttrs, Variant}; use crate::attrs::VariantAttrs; use core::panic; use darling::{FromDeriveInput, FromVariant}; +use proc_macro2::Span; use quote::quote; use syn::{DataEnum, DeriveInput}; @@ -238,4 +239,74 @@ impl Enum { } } } + + pub fn impl_enum_unit_variants(&self) -> proc_macro2::TokenStream { + let ident = &self.ident; + if self.attrs.untagged.is_present() { + panic!("EnumUnitVariants not implemented for untagged enums"); + } + let unit_enum_ident = if let Some(name) = &self.attrs.unit_variants_name { + syn::Ident::new(name, Span::call_site()) + } else { + panic!("unit_variants_name not set"); + }; + + let tagged_variants: Vec<_> = self + .variants + .iter() + .filter(|variant| !variant.attrs.other.is_present()) + .collect(); + + let variant_outputs: Vec<_> = tagged_variants + .iter() + .map(|variant| { + let ns = match &variant.attrs.common.ns { + Some(ns) => quote! { Some(#ns) }, + None => quote! { None }, + }; + let b_xml_name = variant.xml_name().value(); + let xml_name = String::from_utf8_lossy(&b_xml_name); + quote! {(#ns, #xml_name)} + }) + .collect(); + + let variant_idents: Vec<_> = tagged_variants + .iter() + .map(|variant| &variant.variant.ident) + .collect(); + + let unit_to_output_branches = + variant_idents + .iter() + .zip(&variant_outputs) + .map(|(variant_ident, out)| { + quote! { #unit_enum_ident::#variant_ident => #out } + }); + + let from_enum_to_unit_branches = variant_idents.iter().map(|variant_ident| { + quote! { #ident::#variant_ident { .. } => #unit_enum_ident::#variant_ident } + }); + + quote! { + enum #unit_enum_ident { + #(#variant_idents),* + } + + impl From<#unit_enum_ident> for (Option<::quick_xml::name::Namespace<'static>>, &'static str) { + fn from(val: #unit_enum_ident) -> Self { + match val { + #(#unit_to_output_branches),* + } + } + } + + impl From<#ident> for #unit_enum_ident { + fn from(val: #ident) -> Self { + match val { + #(#from_enum_to_unit_branches),* + } + } + } + } + } } diff --git a/crates/xml/src/lib.rs b/crates/xml/src/lib.rs index 24d7bbc..e0d5428 100644 --- a/crates/xml/src/lib.rs +++ b/crates/xml/src/lib.rs @@ -14,6 +14,7 @@ pub use se::XmlSerialize; pub use se::XmlSerializeRoot; pub use unparsed::Unparsed; pub use value::{ParseValueError, ValueDeserialize, ValueSerialize}; +pub use xml_derive::EnumUnitVariants; pub use xml_derive::EnumVariants; pub use xml_derive::XmlRootTag; diff --git a/crates/xml/tests/enum_variants.rs b/crates/xml/tests/enum_variants.rs index 2c96a66..d8957a0 100644 --- a/crates/xml/tests/enum_variants.rs +++ b/crates/xml/tests/enum_variants.rs @@ -1,5 +1,6 @@ use quick_xml::name::Namespace; use rustical_xml::EnumVariants; +use xml_derive::EnumUnitVariants; pub const NS_DAV: Namespace = Namespace(b"DAV:"); pub const NS_DAVPUSH: Namespace = Namespace(b"https://bitfire.at/webdav-push"); @@ -14,7 +15,8 @@ enum ExtensionProp { Hello, } -#[derive(EnumVariants)] +#[derive(EnumVariants, EnumUnitVariants)] +#[xml(unit_variants_name = "CalendarPropName")] enum CalendarProp { // WebDAV (RFC 2518) #[xml(ns = "NS_DAV")] @@ -60,3 +62,15 @@ fn test_enum_untagged_variants() { ] ); } + +#[test] +fn test_enum_unit_variants() { + let displayname: (Option, &str) = CalendarPropName::Displayname.into(); + assert_eq!(displayname, (Some(NS_DAV), "displayname")); + let topic: (Option, &str) = CalendarPropName::Topic.into(); + assert_eq!(topic, (None, "topic")); + + let propname: CalendarPropName = CalendarProp::Displayname(None).into(); + let displayname: (Option, &str) = propname.into(); + assert_eq!(displayname, (Some(NS_DAV), "displayname")); +}