Add support for deserializing adjacently tagged enums
This commit is contained in:
36
src/attr.rs
36
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<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
108
src/de.rs
@@ -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;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ enum TagType {
|
||||
External,
|
||||
Internal(String),
|
||||
Untagged,
|
||||
Adjacent { tag: String, content: String },
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Serialize_enum, attributes(serde))]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user