Add support for deserializing adjacently tagged enums

This commit is contained in:
etwyniel
2019-11-18 14:57:36 +01:00
parent 8dd5d9f959
commit 263cd46234
4 changed files with 157 additions and 8 deletions

View File

@@ -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<TagType> {
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<Ta
NestedMeta::Meta(Meta::NameValue(value)) => {
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<Ta
return Err(Error::new_spanned(meta, "unsupported attribute"));
}
}
if let Some(ty) = tag_type {
return Ok(ty);
}
Ok(tag_type.unwrap_or(TagType::External))
match (tag, content) {
(None, None) => 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.

108
src/de.rs
View File

@@ -12,6 +12,7 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result<TokenStream
let tag_type = attr::tag_type(&input.attrs, &enumeration)?;
match &tag_type {
TagType::External => 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<TokenStream
}
}
pub fn deserialize_adjacent(input: &DeriveInput, enumeration: &DataEnum, tag: &str, content: &str) -> Result<TokenStream> {
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::<Result<Vec<_>>>()?;
let struct_names = struct_variants
.iter()
.map(|variant| {
Ident::new(
&format!("__{}_{}_Struct", ident, variant.ident),
Span::call_site(),
)
})
.collect::<Vec<_>>();
let structs = struct_variants
.iter()
.zip(struct_names.iter())
.map(|(variant, ident)| variant_as_struct(variant, ident, &input.ident))
.collect::<Result<Vec<_>>>()?;
let unit_variant_idents = unit_variants.iter().map(|v| &v.ident).collect::<Vec<_>>();
let unit_variant_names = unit_variants
.iter()
.cloned()
.map(attr::name_of_variant)
.collect::<Result<Vec<_>>>()?;
Ok(quote! {
const _: () = {
struct __Visitor {
__out: miniserde::export::Option<#ident>,
}
impl miniserde::Deserialize for #ident {
fn begin(__out: &mut miniserde::export::Option<Self>) -> &mut dyn miniserde::de::Visitor {
unsafe {
&mut *{
__out
as *mut miniserde::export::Option<Self>
as *mut __Visitor
}
}
}
}
impl miniserde::de::Visitor for __Visitor {
fn map(&mut self) -> miniserde::Result<miniserde::export::Box<dyn miniserde::de::Map + '_>> {
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<String>,
__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(<String as miniserde::Deserialize>::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<TokenStream> {
let ident = &input.ident;

View File

@@ -13,6 +13,7 @@ enum TagType {
External,
Internal(String),
Untagged,
Adjacent { tag: String, content: String },
}
#[proc_macro_derive(Serialize_enum, attributes(serde))]

View File

@@ -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<Adjacent> = json::from_str(example).unwrap();
let expected = [A(21), B(42, "everything".to_string()), C { x: 2 }, D];
assert_eq!(actual, expected);
}