From 1cdef76c2e57dee59bc36b724638c0db7d11e8db Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Thu, 20 May 2021 20:15:20 +0100 Subject: [PATCH] Support for serde::Deserialize --- enumscribe_derive/src/lib.rs | 164 ++++++++++++++++++----- enumscribe_examples/examples/airports.rs | 10 +- 2 files changed, 135 insertions(+), 39 deletions(-) diff --git a/enumscribe_derive/src/lib.rs b/enumscribe_derive/src/lib.rs index 87f2d23..e7be8b8 100644 --- a/enumscribe_derive/src/lib.rs +++ b/enumscribe_derive/src/lib.rs @@ -9,7 +9,7 @@ use syn::{Data, DataEnum, DeriveInput}; use error::{MacroError, MacroResult}; -use crate::enums::{Variant, VariantType}; +use crate::enums::{Variant, VariantType, Enum}; mod enums; mod attribute; @@ -131,12 +131,12 @@ fn gen_unscribe_impl( trait_return_type: TokenStream2, named_fn: F, other_fn: G, - other_missing_fn: E + other_missing_fn: E, ) -> TokenStream -where - F: Fn(TokenStream2) -> TokenStream2, - G: Fn(TokenStream2) -> TokenStream2, - E: Fn(&Ident) -> MacroResult + where + F: Fn(TokenStream2) -> TokenStream2, + G: Fn(TokenStream2) -> TokenStream2, + E: Fn(&Ident) -> MacroResult { let input: DeriveInput = syn::parse(input) .expect("failed to parse input"); @@ -146,12 +146,38 @@ where let enum_ident = &input.ident; + let to_unscribe_ident = quote! { __enumscribe_to_unscribe }; + + let main_match = proc_try!(gen_unscribe_match( + enum_ident, &parsed_enum, &to_unscribe_ident, named_fn, other_fn, other_missing_fn + )); + + (quote! { + impl #trait_ident for #enum_ident { + fn #trait_fn_name(#to_unscribe_ident: &str) -> #trait_return_type { + #main_match + } + } + }).into() +} + +fn gen_unscribe_match( + enum_ident: &Ident, + parsed_enum: &Enum, + match_against: &TokenStream2, + named_fn: F, + other_fn: G, + other_missing_fn: E, +) -> MacroResult + where + F: Fn(TokenStream2) -> TokenStream2, + G: Fn(TokenStream2) -> TokenStream2, + E: Fn(&Ident) -> MacroResult +{ let mut other_arm = None; let mut case_sensitive_arms = Vec::new(); let mut case_insensitive_arms = Vec::new(); - let to_unscribe_ident = quote! { __enumscribe_to_unscribe }; - for variant in parsed_enum.variants.iter() { let variant_ident = &variant.data.ident; @@ -178,7 +204,7 @@ where } VariantType::Other { field_name } => { - let unscribe_value = quote! { <_ as ::std::convert::Into<_>>::into(#to_unscribe_ident) }; + let unscribe_value = quote! { <_ as ::std::convert::Into<_>>::into(#match_against) }; let constructed_variant = match field_name { None => quote! { @@ -198,14 +224,14 @@ where let other_arm = match other_arm { Some(other_arm) => other_arm, - None => proc_try!(other_missing_fn(enum_ident)) + None => other_missing_fn(enum_ident)? }; let case_insensitive_match = if case_insensitive_arms.is_empty() { None } else { Some(quote! { - let __enumscribe_unscribe_lowercase = #to_unscribe_ident.to_lowercase(); + let __enumscribe_unscribe_lowercase = #match_against.to_lowercase(); match __enumscribe_unscribe_lowercase.as_str() { #(#case_insensitive_arms,)* #other_arm, @@ -215,13 +241,13 @@ where let main_match = match (case_sensitive_arms.is_empty(), case_insensitive_match) { (true, None) => quote! { - match #to_unscribe_ident { + match #match_against { #other_arm, } }, (false, None) => quote! { - match #to_unscribe_ident { + match #match_against { #(#case_sensitive_arms,)* #other_arm, } @@ -229,45 +255,42 @@ where (true, Some(case_insensitive_match)) => { case_insensitive_match - }, + } (false, Some(case_insensitive_match)) => quote! { - match #to_unscribe_ident { + match #match_against { #(#case_sensitive_arms,)* _ => { #case_insensitive_match }, } } }; - (quote! { - impl #trait_ident for #enum_ident { - fn #trait_fn_name(#to_unscribe_ident: &str) -> #trait_return_type { - #main_match - } - } - }).into() + Ok(main_match) } #[proc_macro_derive(ScribeStaticStr, attributes(enumscribe))] pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { gen_scribe_impl( input, + quote! { ::enumscribe::ScribeStaticStr }, quote! { &'static str }, + |_, _, name| Ok(quote! { #name }), + |variant, enum_ident, _| Err(MacroError::new(format!( "cannot derive ScribeStaticStr for {} because the variant {} is marked as {}, so \ there is no &'static str associated with it\n\ hint: try deriving ScribeCowStr instead", - enum_ident.to_string(), variant.data.ident.to_string(), OTHER + enum_ident, variant.data.ident, OTHER ), variant.span)), + |variant, enum_ident| MacroError::new(format!( "cannot derive ScribeStaticStr for {} because the variant {} is marked as {}\n\ explanation: since {} is ignored, it cannot be guaranteed that the enum can \ always be successfully converted to a String\n\ hint: try deriving TryScribeStaticStr instead", - enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, - variant.data.ident.to_string(), + enum_ident, variant.data.ident, IGNORE, variant.data.ident ), variant.span), ) } @@ -276,17 +299,21 @@ pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { gen_try_scribe_impl( input, + quote! { ::enumscribe::TryScribeStaticStr }, quote! { ::std::option::Option<&'static str> }, + |_, _, name| Ok(quote! { ::std::option::Option::Some(#name) }), + |variant, enum_ident, _| Err(MacroError::new(format!( "cannot derive TryScribeStaticStr for {} because the variant {} is marked as {}, so \ there is no &'static str associated with it\n\ hint: try deriving TryScribeCowStr instead", - enum_ident.to_string(), variant.data.ident.to_string(), OTHER + enum_ident, variant.data.ident, OTHER ), variant.span)), + quote! { ::std::option::Option::None }, ) } @@ -295,21 +322,24 @@ pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { pub fn derive_scribe_string(input: TokenStream) -> TokenStream { gen_scribe_impl( input, + quote! { ::enumscribe::ScribeString }, quote! { ::std::string::String }, + |_, _, name| Ok(quote! { <_ as ::std::borrow::ToOwned>::to_owned(#name) }), + |_, _, field| Ok(quote! { <_ as ::std::convert::Into<::std::string::String>>::into(#field) }), + |variant, enum_ident| MacroError::new(format!( "cannot derive ScribeString for {} because the variant {} is marked as {}\n\ explanation: since {} is ignored, it cannot be guaranteed that the enum can \ always be successfully converted to a String\n\ hint: try deriving TryScribeString instead", - enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, - variant.data.ident.to_string(), + enum_ident, variant.data.ident, IGNORE, variant.data.ident ), variant.span), ) } @@ -318,18 +348,22 @@ pub fn derive_scribe_string(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream { gen_try_scribe_impl( input, + quote! { ::enumscribe::TryScribeString }, quote! { ::std::option::Option<::std::string::String> }, + |_, _, name| Ok(quote! { ::std::option::Option::Some( <_ as ::std::borrow::ToOwned>::to_owned(#name) ) }), + |_, _, field| Ok(quote! { ::std::option::Option::Some( <_ as ::std::convert::Into<::std::string::String>>::into(#field) ) }), + quote! { ::std::option::Option::None }, ) } @@ -338,23 +372,26 @@ pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream { pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { gen_scribe_impl( input, + quote! { ::enumscribe::ScribeCowStr }, quote! { ::std::borrow::Cow<'static, str> }, + |_, _, name| Ok(quote! { ::std::borrow::Cow::Borrowed(#name) }), + |_, _, field| Ok(quote! { ::std::borrow::Cow::Owned( <_ as ::std::convert::Into<::std::string::String>>::into(#field) ) }), + |variant, enum_ident| MacroError::new(format!( "cannot derive ScribeCowStr for {} because the variant {} is marked as {}\n\ explanation: since {} is ignored, it cannot be guaranteed that the enum can \ always be successfully converted to a String\n\ hint: try deriving TryScribeCowStr instead", - enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, - variant.data.ident.to_string(), + enum_ident, variant.data.ident, IGNORE, variant.data.ident ), variant.span), ) } @@ -363,13 +400,16 @@ pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { gen_try_scribe_impl( input, + quote! { ::enumscribe::TryScribeCowStr }, quote! { ::std::option::Option<::std::borrow::Cow<'static, str>> }, + |_, _, name| Ok(quote! { ::std::option::Option::Some( ::std::borrow::Cow::Borrowed(#name) ) }), + |_, _, field| Ok(quote! { ::std::option::Option::Some( ::std::borrow::Cow::Owned( @@ -377,6 +417,7 @@ pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { ) ) }), + quote! { ::std::option::Option::None }, ) } @@ -399,8 +440,8 @@ pub fn derive_unscribe(input: TokenStream) -> TokenStream { explanation: since there is no {} variant, it cannot be guaranteed that every string \ can be successfully converted to a variant of {}\n\ hint: either introduce an {} variant, or try deriving TryUnscribe instead", - enum_ident.to_string(), OTHER, OTHER, enum_ident.to_string(), OTHER - ), enum_ident.span())) + enum_ident, OTHER, OTHER, enum_ident, OTHER + ), enum_ident.span())), ) } @@ -417,7 +458,7 @@ pub fn derive_try_unscribe(input: TokenStream) -> TokenStream { |constructed_other_variant| quote! { ::std::option::Option::Some(#constructed_other_variant) }, - |_| Ok(quote! { _ => ::std::option::Option::None }) + |_| Ok(quote! { _ => ::std::option::Option::None }), ) } @@ -448,7 +489,7 @@ pub fn derive_enum_serialize(input: TokenStream) -> TokenStream { #enum_ident::#variant_ident #constructor_tokens => #serializer_ident.serialize_str(#name) }) - }, + } VariantType::Other { field_name } => { match field_name { @@ -457,7 +498,7 @@ pub fn derive_enum_serialize(input: TokenStream) -> TokenStream { #enum_ident::#variant_ident { #field_name } => #serializer_ident.serialize_str(&#field_name) }) - }, + } None => { let field_name = quote! { __enumscribe_other_inner }; match_arms.push(quote! { @@ -501,7 +542,58 @@ pub fn derive_enum_serialize(input: TokenStream) -> TokenStream { #[cfg(feature = "serde")] #[proc_macro_derive(EnumDeserialize, attributes(enumscribe))] pub fn derive_enum_deserialize(input: TokenStream) -> TokenStream { - todo!() + let input: DeriveInput = syn::parse(input) + .expect("failed to parse input"); + + let enum_data = proc_try!(get_enum_data(&input)); + let parsed_enum = proc_try!(enums::parse_enum(enum_data)); + + let enum_ident = &input.ident; + + let deserializer_ident = quote! { __enumscribe_deserializer }; + let deserialized_string_ident = quote! { __enumscribe_deserialized_string }; + let deserialized_str_ident = quote! { __enumscribe_deserialized_str }; + + let variant_strings = parsed_enum.variants.iter() + .filter(|variant| match &variant.v_type { + VariantType::Ignore => false, + _ => true + }) + .map(|variant| variant.data.ident.to_string()) + .collect::>(); + + let main_match = proc_try!(gen_unscribe_match( + enum_ident, + &parsed_enum, + &deserialized_str_ident, + |constructed_named_variant| quote! { + ::std::result::Result::Ok(#constructed_named_variant) + }, + |constructed_other_variant| quote! { + ::std::result::Result::Ok(#constructed_other_variant) + }, + |_| Ok(quote! { + __enumscribe_deserialize_base_case => ::std::result::Result::Err( + ::serde::de::Error::unknown_variant( + __enumscribe_deserialize_base_case, + &[#(#variant_strings),*] + ) + ) + }), + )); + + (quote! { + impl<'de> ::serde::Deserialize<'de> for #enum_ident { + fn deserialize(#deserializer_ident: D) -> ::std::result::Result + where D: ::serde::Deserializer<'de> + { + let #deserialized_string_ident = <::std::string::String as ::serde::Deserialize<'_>> + ::deserialize(#deserializer_ident)?; + let #deserialized_str_ident = #deserialized_string_ident.as_str(); + #main_match + } + } + }).into() } fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> { diff --git a/enumscribe_examples/examples/airports.rs b/enumscribe_examples/examples/airports.rs index eb61445..7bdae98 100644 --- a/enumscribe_examples/examples/airports.rs +++ b/enumscribe_examples/examples/airports.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use enumscribe::*; -#[derive(TryScribeCowStr, Unscribe, EnumSerialize, Eq, PartialEq, Debug)] +#[derive(TryScribeCowStr, Unscribe, EnumSerialize, EnumDeserialize, Eq, PartialEq, Debug)] enum Airport { #[enumscribe(str = "LHR")] Heathrow, @@ -16,7 +16,7 @@ enum Airport { Other(String), } -#[derive(Serialize, Eq, PartialEq, Debug)] +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] struct AirportInfo { airport: Airport, info: String, @@ -41,6 +41,10 @@ fn main() { airport: Airport::Gatwick(), info: "It's somewhere in London, innit".to_owned() }; - println!("{}", serde_json::to_string(&info).unwrap()); + + println!(); + + let info_str = r#"{ "airport": "lgw", "info": "Fun Gatwick fact: I'm fresh out of Gatwick facts" }"#; + println!("{:?}", serde_json::from_str::(info_str).unwrap()); }