From 46dac988824a0b37361198ec40e9b0ad64bf9bfe Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Thu, 20 May 2021 18:56:54 +0100 Subject: [PATCH] Support for serde::Serialize --- enumscribe/Cargo.toml | 4 ++ enumscribe/src/lib.rs | 2 +- enumscribe_derive/Cargo.toml | 5 +- enumscribe_derive/src/lib.rs | 86 +++++++++++++++++++++++- enumscribe_examples/Cargo.toml | 2 + enumscribe_examples/examples/airports.rs | 35 +++++++--- 6 files changed, 120 insertions(+), 14 deletions(-) diff --git a/enumscribe/Cargo.toml b/enumscribe/Cargo.toml index 6591ea1..0df160a 100644 --- a/enumscribe/Cargo.toml +++ b/enumscribe/Cargo.toml @@ -9,3 +9,7 @@ description = "Procedural macros for converting between enums and strings" [dependencies] enumscribe_derive = { version = "0.1.0", path = "../enumscribe_derive" } + +[features] +default = ["serde"] +serde = ["enumscribe_derive/serde"] diff --git a/enumscribe/src/lib.rs b/enumscribe/src/lib.rs index 57741f8..7dc88a5 100644 --- a/enumscribe/src/lib.rs +++ b/enumscribe/src/lib.rs @@ -6,8 +6,8 @@ //! | `ignore` used? | `other` used? | Conversion to string | Conversion from string | //! |----------------|---------------|----------------------|------------------------| //! | No | No | [ScribeStaticStr] | [TryUnscribe] | -//! | Yes | No | [TryScribeStaticStr] | [TryUnscribe] | //! | No | Yes | [ScribeCowStr] | [Unscribe] | +//! | Yes | No | [TryScribeStaticStr] | [TryUnscribe] | //! | Yes | Yes | [TryScribeCowStr] | [Unscribe] | //! //! There are also [ScribeString] and [TryScribeString] traits which can be used in the same diff --git a/enumscribe_derive/Cargo.toml b/enumscribe_derive/Cargo.toml index d9ed919..b546a6a 100644 --- a/enumscribe_derive/Cargo.toml +++ b/enumscribe_derive/Cargo.toml @@ -15,6 +15,5 @@ proc-macro2 = "1.0" syn = "1.0" quote = "1.0" -[dev-dependencies] -serde = "1.0" -serde_json = "1.0" +[features] +serde = [] diff --git a/enumscribe_derive/src/lib.rs b/enumscribe_derive/src/lib.rs index a9e30f2..87f2d23 100644 --- a/enumscribe_derive/src/lib.rs +++ b/enumscribe_derive/src/lib.rs @@ -2,7 +2,6 @@ //! to strings and vice-versa. use proc_macro::TokenStream; -use std::iter; use proc_macro2::Ident; use quote::quote; @@ -10,7 +9,7 @@ use syn::{Data, DataEnum, DeriveInput}; use error::{MacroError, MacroResult}; -use crate::enums::{Variant, VariantType, VariantConstructor}; +use crate::enums::{Variant, VariantType}; mod enums; mod attribute; @@ -422,6 +421,89 @@ pub fn derive_try_unscribe(input: TokenStream) -> TokenStream { ) } +#[cfg(feature = "serde")] +#[proc_macro_derive(EnumSerialize, attributes(enumscribe))] +pub fn derive_enum_serialize(input: TokenStream) -> TokenStream { + 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 serializer_ident = quote! { __enumscribe_serializer }; + + let mut match_arms = Vec::new(); + let mut ignore_variant = false; + + for variant in parsed_enum.variants.iter() { + let variant_ident = &variant.data.ident; + + match &variant.v_type { + VariantType::Ignore => ignore_variant = true, + + VariantType::Named { name, constructor, .. } => { + let constructor_tokens = constructor.empty(); + match_arms.push(quote! { + #enum_ident::#variant_ident #constructor_tokens => + #serializer_ident.serialize_str(#name) + }) + }, + + VariantType::Other { field_name } => { + match field_name { + Some(field_name) => { + match_arms.push(quote! { + #enum_ident::#variant_ident { #field_name } => + #serializer_ident.serialize_str(&#field_name) + }) + }, + None => { + let field_name = quote! { __enumscribe_other_inner }; + match_arms.push(quote! { + #enum_ident::#variant_ident(#field_name) => + #serializer_ident.serialize_str(&#field_name) + }) + } + } + } + } + } + + let ignore_arm = if ignore_variant { + let err_string = format!( + "attempted to serialize an unserializable variant of {}", + enum_ident + ); + quote! { + _ => ::std::result::Result::Err( + ::serde::ser::Error::custom(#err_string) + ) + } + } else { + quote! {} + }; + + (quote! { + impl ::serde::Serialize for #enum_ident { + fn serialize(&self, #serializer_ident: S) -> ::std::result::Result + where S: ::serde::Serializer + { + match self { + #(#match_arms,)* + #ignore_arm + } + } + } + }).into() +} + +#[cfg(feature = "serde")] +#[proc_macro_derive(EnumDeserialize, attributes(enumscribe))] +pub fn derive_enum_deserialize(input: TokenStream) -> TokenStream { + todo!() +} + fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> { let enum_data = match &input.data { Data::Enum(enum_data) => enum_data, diff --git a/enumscribe_examples/Cargo.toml b/enumscribe_examples/Cargo.toml index a60d093..00499f1 100644 --- a/enumscribe_examples/Cargo.toml +++ b/enumscribe_examples/Cargo.toml @@ -8,3 +8,5 @@ license = "MIT" [dev-dependencies] enumscribe = { path = "../enumscribe" } enumscribe_derive = { path = "../enumscribe_derive" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/enumscribe_examples/examples/airports.rs b/enumscribe_examples/examples/airports.rs index 16f9cf3..eb61445 100644 --- a/enumscribe_examples/examples/airports.rs +++ b/enumscribe_examples/examples/airports.rs @@ -1,6 +1,8 @@ +use serde::{Deserialize, Serialize}; + use enumscribe::*; -#[derive(TryScribeCowStr, TryUnscribe, Eq, PartialEq, Debug)] +#[derive(TryScribeCowStr, Unscribe, EnumSerialize, Eq, PartialEq, Debug)] enum Airport { #[enumscribe(str = "LHR")] Heathrow, @@ -10,18 +12,35 @@ enum Airport { Luton {}, #[enumscribe(str = "BHX", case_insensitive, ignore)] BirminghamInternational, - // #[enumscribe(other)] - // Other(String), + #[enumscribe(other)] + Other(String), +} + +#[derive(Serialize, Eq, PartialEq, Debug)] +struct AirportInfo { + airport: Airport, + info: String, } fn main() { let luton = Airport::Luton {}; println!("Hello, {:?}!", luton.try_scribe()); - // let other = Airport::Other("Dedicated EasyJet-only airport".to_owned()); - // println!("Hello, {:?}!", other.try_scribe()); + let other = Airport::Other("Dedicated EasyJet-only airport".to_owned()); + println!("Hello, {:?}!", other.try_scribe()); + + println!(); + + println!("{:?}", Airport::unscribe("LHR")); + println!("{:?}", Airport::unscribe("lhr")); + println!("{:?}", Airport::unscribe("lgw")); + + println!(); + + let info = AirportInfo { + airport: Airport::Gatwick(), + info: "It's somewhere in London, innit".to_owned() + }; - println!("{:?}", Airport::try_unscribe("LHR")); - println!("{:?}", Airport::try_unscribe("lhr")); - println!("{:?}", Airport::try_unscribe("lgw")); + println!("{}", serde_json::to_string(&info).unwrap()); }