diff --git a/src/attr.rs b/src/attr.rs index 8eaaaad..35f0650 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -3,6 +3,8 @@ use syn::{Attribute, DataEnum, Error, Field, Fields, Lit, Meta, NestedMeta, Resu pub(crate) fn tag_type(attrs: &[Attribute], enumeration: &DataEnum) -> Result { let mut tag_type = None; + let mut tag = None; + let mut content = None; for attr in attrs { if !attr.path.is_ident("serde") { @@ -19,16 +21,18 @@ pub(crate) fn tag_type(attrs: &[Attribute], enumeration: &DataEnum) -> Result { if value.path.is_ident("tag") { if let Lit::Str(s) = &value.lit { - if tag_type.is_some() { + if tag.is_some() { return Err(Error::new_spanned(meta, "duplicate tag attribute")); } - for fields in enumeration.variants.iter().map(|v| &v.fields) { - if let Fields::Unnamed(_) = fields { - return Err(Error::new_spanned(meta, - "enums containing tuple variants cannot be internally tagged")); - } + tag = Some(s.value()); + continue; + } + } else if value.path.is_ident("content") { + if let Lit::Str(s) = &value.lit { + if content.is_some() { + return Err(Error::new_spanned(meta, "duplicate content attribute")); } - tag_type = Some(TagType::Internal(s.value())); + content = Some(s.value()); continue; } } @@ -47,8 +51,24 @@ pub(crate) fn tag_type(attrs: &[Attribute], enumeration: &DataEnum) -> Result Ok(TagType::External), + (Some(tag), None) => { + for fields in enumeration.variants.iter().map(|v| &v.fields) { + if let Fields::Unnamed(_) = fields { + return Err(Error::new_spanned(fields, + "enums containing tuple variants cannot be internally tagged")); + } + } + Ok(TagType::Internal(tag)) + } + (Some(tag), Some(content)) => Ok(TagType::Adjacent { tag, content }), + _ => Err(Error::new_spanned(&attrs[0], "Invalid enum representation.")) + } } /// Find the value of a #[serde(rename = "...")] attribute. diff --git a/src/de.rs b/src/de.rs index 30114e6..f2caf9a 100644 --- a/src/de.rs +++ b/src/de.rs @@ -12,6 +12,7 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result deserialize_external(input, enumeration), + TagType::Adjacent { tag, content } => deserialize_adjacent(input, enumeration, tag, content), _ => Err(Error::new( Span::call_site(), "Only externally tagged enums are supported", @@ -19,6 +20,113 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result Result { + let ident = &input.ident; + let (unit_variants, struct_variants): (Vec<_>, Vec<_>) = + enumeration.variants.iter().partition(|v| { + if let Fields::Unit = &v.fields { + true + } else { + false + } + }); + let struct_variant_names = struct_variants + .iter() + .cloned() + .map(attr::name_of_variant) + .collect::>>()?; + let struct_names = struct_variants + .iter() + .map(|variant| { + Ident::new( + &format!("__{}_{}_Struct", ident, variant.ident), + Span::call_site(), + ) + }) + .collect::>(); + let structs = struct_variants + .iter() + .zip(struct_names.iter()) + .map(|(variant, ident)| variant_as_struct(variant, ident, &input.ident)) + .collect::>>()?; + let unit_variant_idents = unit_variants.iter().map(|v| &v.ident).collect::>(); + let unit_variant_names = unit_variants + .iter() + .cloned() + .map(attr::name_of_variant) + .collect::>>()?; + Ok(quote! { + const _: () = { + struct __Visitor { + __out: miniserde::export::Option<#ident>, + } + + impl miniserde::Deserialize for #ident { + fn begin(__out: &mut miniserde::export::Option) -> &mut dyn miniserde::de::Visitor { + unsafe { + &mut *{ + __out + as *mut miniserde::export::Option + as *mut __Visitor + } + } + } + } + + impl miniserde::de::Visitor for __Visitor { + fn map(&mut self) -> miniserde::Result> { + Ok(miniserde::export::Box::new(__State { + #(#struct_names: None,)* + __tag: None, + __out: &mut self.__out, + })) + } + } + + struct __State<'a> { + #(#[allow(non_snake_case)] #struct_names: miniserde::export::Option<#struct_names>,)* + __tag: Option, + __out: &'a mut miniserde::export::Option<#ident>, + } + + #(#structs)* + + impl<'a> miniserde::de::Map for __State<'a> { + fn key(&mut self, k: &miniserde::export::str) -> miniserde::Result<&mut dyn miniserde::de::Visitor> { + match k { + #tag => Ok(::begin(&mut self.__tag)), + #content => { + match self.__tag.as_ref().map(|s| s.as_str()) { + #(Some(#struct_variant_names) => Ok(<#struct_names as miniserde::Deserialize>::begin(&mut self.#struct_names)),)* + _ => miniserde::export::Err(miniserde::Error), + } + } + _ => miniserde::export::Err(miniserde::Error), + } + } + + fn finish(&mut self) -> miniserde::Result<()> { + match self.__tag.as_ref().map(|s| s.as_str()) { + #(Some(#unit_variant_names) => { + self.__out.replace(#ident::#unit_variant_idents); + Ok(()) + })* + #(Some(#struct_variant_names) => { + if let Some(val) = self.#struct_names.take() { + self.__out.replace(val.as_enum()); + miniserde::export::Ok(()) + } else { + miniserde::export::Err(miniserde::Error) + } + })* + _ => miniserde::export::Err(miniserde::Error), + } + } + } + }; + }) +} + pub fn deserialize_external(input: &DeriveInput, enumeration: &DataEnum) -> Result { let ident = &input.ident; diff --git a/src/lib.rs b/src/lib.rs index 23f408e..fb1c819 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ enum TagType { External, Internal(String), Untagged, + Adjacent { tag: String, content: String }, } #[proc_macro_derive(Serialize_enum, attributes(serde))] diff --git a/tests/deserialize.rs b/tests/deserialize.rs index 4138af5..460254d 100644 --- a/tests/deserialize.rs +++ b/tests/deserialize.rs @@ -19,3 +19,23 @@ fn test_external() { let expected = [A(21), B(42, "everything".to_string()), C { x: 2 }, D]; assert_eq!(actual, expected); } + +#[test] +fn test_adjacent() { + #[derive(Deserialize_enum, Debug, PartialEq)] + #[serde(tag = "type", content = "content")] + enum Adjacent { + A(i32), + #[serde(rename = "renamedB")] + B(i32, String), + C { + x: i32, + }, + D, + } + use Adjacent::*; + let example = r#"[{"type":"A","content":21},{"type":"renamedB","content":[42,"everything"]},{"type":"C","content":{"x":2}},{"type":"D"}]"#; + let actual: Vec = json::from_str(example).unwrap(); + let expected = [A(21), B(42, "everything".to_string()), C { x: 2 }, D]; + assert_eq!(actual, expected); +}