Add support for deserializing internally tagged enums
This commit is contained in:
115
src/de.rs
115
src/de.rs
@@ -13,6 +13,7 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result<TokenStream
|
||||
match &tag_type {
|
||||
TagType::External => deserialize_external(input, enumeration),
|
||||
TagType::Adjacent { tag, content } => deserialize_adjacent(input, enumeration, tag, content),
|
||||
TagType::Internal(tag) => deserialize_internal(input, enumeration, tag),
|
||||
_ => Err(Error::new(
|
||||
Span::call_site(),
|
||||
"Only externally tagged enums are supported",
|
||||
@@ -20,6 +21,120 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result<TokenStream
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_internal(input: &DeriveInput, enumeration: &DataEnum, tag: &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,
|
||||
__map: None,
|
||||
__out: &mut self.__out,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
struct __State<'a> {
|
||||
#(#[allow(non_snake_case)] #struct_names: miniserde::export::Option<#struct_names>,)*
|
||||
__tag: miniserde::export::Option<String>,
|
||||
__map: miniserde::export::Option<miniserde::export::Box<dyn miniserde::de::Map + 'a>>,
|
||||
__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> {
|
||||
if k == #tag {
|
||||
return Ok(<String as miniserde::Deserialize>::begin(&mut self.__tag));
|
||||
}
|
||||
if self.__map.is_none() {
|
||||
let tag = self.__tag.as_ref().ok_or(miniserde::Error)?;
|
||||
self.__map.replace(match tag.as_ref() {
|
||||
#(#struct_variant_names => <#struct_names as miniserde::Deserialize>::begin(
|
||||
unsafe {&mut *(&mut self.#struct_names as *mut miniserde::export::Option<#struct_names>)}
|
||||
).map()?,)*
|
||||
_ => return miniserde::export::Err(miniserde::Error),
|
||||
});
|
||||
}
|
||||
self.__map.as_mut().ok_or(miniserde::Error)?.key(k)
|
||||
}
|
||||
|
||||
fn finish(&mut self) -> miniserde::Result<()> {
|
||||
let tag = self.__tag.take().ok_or(miniserde::Error)?;
|
||||
match tag.as_str() {
|
||||
#(#unit_variant_names => {
|
||||
self.__out.replace(#ident::#unit_variant_idents);
|
||||
return miniserde::export::Ok(());
|
||||
})*
|
||||
_ => (),
|
||||
}
|
||||
self.__map.take().ok_or(miniserde::Error)?.finish()?;
|
||||
match tag.as_str() {
|
||||
#(#struct_variant_names => {
|
||||
self.__out.replace(self.#struct_names.take().ok_or(miniserde::Error)?.as_enum());
|
||||
miniserde::export::Ok(())
|
||||
})*
|
||||
_ => miniserde::export::Err(miniserde::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
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<_>) =
|
||||
|
||||
@@ -39,3 +39,22 @@ fn test_adjacent() {
|
||||
let expected = [A(21), B(42, "everything".to_string()), C { x: 2 }, D];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_internal() {
|
||||
#[derive(Deserialize_enum, Debug, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
enum Internal {
|
||||
#[serde(rename = "renamedB")]
|
||||
B,
|
||||
C {
|
||||
x: i32,
|
||||
},
|
||||
D,
|
||||
}
|
||||
use Internal::*;
|
||||
let example = r#"[{"type":"renamedB"},{"type":"C","x":2},{"type":"D"}]"#;
|
||||
let actual: Vec<Internal> = json::from_str(example).unwrap();
|
||||
let expected = [B, C { x: 2 }, D];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user