From 81567f15e86c08a24efa83a1cd9cf484ef3ef0ab Mon Sep 17 00:00:00 2001 From: etwyniel Date: Mon, 18 Nov 2019 09:38:51 +0100 Subject: [PATCH] Add partial deserialization support Only externally tagged, only named fields or unit variants --- Cargo.toml | 2 +- src/de.rs | 167 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 17 +++++ tests/deserialize.rs | 23 ++++++ 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/de.rs create mode 100644 tests/deserialize.rs diff --git a/Cargo.toml b/Cargo.toml index c92e622..7ff5122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ proc-macro = true miniserde = "0.1" [dependencies] -syn = { version = "1.0", features = ["derive"] } +syn = { version = "1.0", features = ["derive", "full"] } quote = "1.0" proc-macro2 = "1.0" miniserde = "0.1" # TODO: remove diff --git a/src/de.rs b/src/de.rs new file mode 100644 index 0000000..f44dd9e --- /dev/null +++ b/src/de.rs @@ -0,0 +1,167 @@ +use crate::attr; +use crate::TagType; +use crate::bound; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{DataEnum, DeriveInput, Ident, Error, Fields, FieldsNamed, FieldsUnnamed, Result, parse_quote, Variant}; + +pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result { + let tag_type = attr::tag_type(&input.attrs, &enumeration)?; + match &tag_type { + TagType::External => deserialize_external(input, enumeration), + _ => return Err(Error::new(Span::call_site(), "Only externally tagged enums are supported")), + } +} + +pub fn deserialize_external(input: &DeriveInput, enumeration: &DataEnum) -> Result { + let ident = &input.ident; + let names = enumeration + .variants + .iter() + .map(attr::name_of_variant) + .collect::>>()?; + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let wrapper_generics = bound::with_lifetime_bound(&input.generics, "'__a"); + let (wrapper_impl_generics, wrapper_ty_generics, _) = wrapper_generics.split_for_impl(); + let bound = parse_quote!(miniserde::Deserialize); + let bounded_where_clause = bound::where_clause_with_bound(&input.generics, bound); + + let (struct_variants, other_variants): (Vec<_>, Vec<_>) = enumeration + .variants + .iter() + .partition(|v| if let Fields::Named(_) = &v.fields {true} else {false}); + let (tuple_variants, unit_variants): (Vec<_>, Vec<_>) = other_variants + .into_iter() + .partition(|v| if let Fields::Unnamed(_) = &v.fields {true} else {false}); + + let struct_names = struct_variants + .iter() + .map(|variant| Ident::new(&format!("__{}_{}_Struct", input.ident, variant.ident), Span::call_site())) + .collect::>(); + let variant_names = struct_variants + .iter() + .map(|variant| &variant.ident) + .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() + .filter(|v| if let Fields::Unit = &v.fields {true} else {false}) + .map(|v| &v.ident) + .collect::>(); + let unit_variant_names = unit_variants + .iter() + .cloned() + .filter(|v| if let Fields::Unit = &v.fields {true} else {false}) + .map(attr::name_of_variant) + .collect::>>()?; + + Ok(quote!{ + const _: () = { + struct __Visitor #impl_generics #where_clause { + __out: miniserde::export::Option<#ident #ty_generics>, + } + + impl #impl_generics miniserde::Deserialize for #ident #ty_generics #bounded_where_clause { + fn begin(__out: &mut miniserde::export::Option) -> &mut dyn miniserde::de::Visitor { + unsafe { + &mut *{ + __out + as *mut miniserde::export::Option + as *mut __Visitor #ty_generics + } + } + } + } + + impl #impl_generics miniserde::de::Visitor for __Visitor #ty_generics #bounded_where_clause { + fn map(&mut self) -> miniserde::Result> { + Ok(miniserde::export::Box::new(__State{ + __out: &mut self.__out, + #(#variant_names: None,)* + })) + } + + fn string(&mut self, s: &str) -> miniserde::Result<()> { + match s { + #(#unit_variant_names => { + self.__out = Some(#ident::#unit_variant_idents); + Ok(()) + })* + _ => Err(miniserde::Error) + } + } + } + + #[allow(non_snake_case)] + struct __State #wrapper_impl_generics #where_clause { + #(#variant_names: miniserde::export::Option<#struct_names>,)* + __out: &'__a mut miniserde::export::Option<#ident #ty_generics>, + } + + #(#structs)* + + impl #wrapper_impl_generics miniserde::de::Map for __State #wrapper_ty_generics #bounded_where_clause { + fn key(&mut self, k: &miniserde::export::str) -> miniserde::Result<&mut dyn miniserde::de::Visitor> { + match k { + #( + #names => miniserde::export::Ok(#struct_names::begin(&mut self.#variant_names)), + )* + _ => miniserde::export::Ok(miniserde::de::Visitor::ignore()), + } + } + + fn finish(&mut self) -> miniserde::Result<()> { + #( + if let Some(val) = self.#variant_names.take() { + *self.__out = miniserde::export::Some(val.as_enum()); + return miniserde::export::Ok(()); + } + )* + miniserde::export::Err(miniserde::Error) + } + } + }; + }) +} + +pub fn variant_as_struct(variant: &Variant, ident: &Ident, enum_ident: &Ident) -> Result { + let variant_ident = &variant.ident; + let as_enum = match &variant.fields { + Fields::Named(fields) => { + let fieldname = fields.named.iter().map(|f| &f.ident).collect::>(); + let fieldty = fields.named.iter().map(|f| &f.ty); + quote!{ + #enum_ident::#variant_ident { + #( + #fieldname: self.#fieldname, + )* + } + } + } + _ => quote!(unimplemented!()), + }; + let as_struct = syn::ItemStruct { + attrs: variant.attrs.clone(), + vis: syn::Visibility::Inherited, + struct_token: Default::default(), + ident: ident.clone(), + generics: Default::default(), + fields: variant.fields.clone(), + semi_token: None, + }; + Ok(quote!{ + #[derive(Deserialize)] + #as_struct + + impl #ident { + fn as_enum(self) -> #enum_ident { + #as_enum + } + } + }) +} diff --git a/src/lib.rs b/src/lib.rs index a5f9dd0..fc33d5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ extern crate proc_macro; pub(crate) mod attr; mod ser; +mod de; mod bound; use std::convert::From; @@ -29,3 +30,19 @@ pub fn derive_serialize(tokens: proc_macro::TokenStream) -> proc_macro::TokenStr .unwrap_or_else(|err| err.to_compile_error()) .into() } + +#[proc_macro_derive(Deserialize_enum, attributes(serde))] +pub fn derive_deserialize(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(tokens as DeriveInput); + let en = match &input.data { + Data::Enum(en) => en, + _ => { + return Error::new_spanned(input, "Serialize_enum can only be applied to enums") + .to_compile_error() + .into() + } + }; + de::derive(&input, &en) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/tests/deserialize.rs b/tests/deserialize.rs new file mode 100644 index 0000000..f47b33e --- /dev/null +++ b/tests/deserialize.rs @@ -0,0 +1,23 @@ +use miniserde::{json, Deserialize}; +use miniserde_enum::Deserialize_enum; + +#[test] +fn test_external() { + #[derive(Deserialize_enum, Debug, PartialEq)] + enum External { + // A(i32), + // #[serde(rename = "renamedB")] + // B(i32, String), + C { + x: i32, + }, + // D, + } + use External::*; + // let example = r#"[{"A":21},{"renamedB":[42,"everything"]},{"C":{"x":2}},"D"]"#; + let example = r#"[{"C":{"x":2}}]"#; + let actual: Vec = json::from_str(example).unwrap(); + // let expected = [A(21), B(42, "everything".to_string()), C { x: 2 }, D]; + let expected = vec![C { x: 2 }]; + assert_eq!(actual, expected); +}