-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enums and newtypes support in
AutoJsJson
- Loading branch information
Showing
11 changed files
with
450 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
use proc_macro::TokenStream; | ||
use quote::quote; | ||
use syn::{ext::IdentExt, DataEnum, Fields, Ident}; | ||
|
||
// { | ||
// "Somestring": "foobar" | ||
// } | ||
// | ||
// { | ||
// "Point": { "x": 10, "y": "one" } | ||
// } | ||
// | ||
// { | ||
// "Tuple": ["two", 20] | ||
// } | ||
// | ||
// "Nothing" | ||
pub(super) fn impl_js_json_enum(name: &Ident, data: &DataEnum) -> Result<TokenStream, String> { | ||
// Encoding code for every variant | ||
let mut variant_encodes = vec![]; | ||
|
||
// Encoding code for every simple variant (data-less) | ||
let mut variant_string_decodes = vec![]; | ||
|
||
// Envoding code for every compound variant (with data) | ||
let mut variant_object_decodes = vec![]; | ||
|
||
for variant in &data.variants { | ||
let variant_ident = &variant.ident; | ||
let variant_name = &variant.ident.unraw().to_string(); | ||
match &variant.fields { | ||
// Simple variant | ||
// Enum::Variant <-> "Variant" | ||
Fields::Unit => { | ||
variant_encodes.push(quote! { Self::#variant_ident => #variant_name.to_json(), }); | ||
variant_string_decodes.push(quote! { #variant_name => Ok(Self::#variant_ident), }); | ||
} | ||
|
||
// Compound variant with unnamed field(s) (tuple) | ||
// Enum::Variant(...) <-> "Variant": ... | ||
Fields::Unnamed(fields) => { | ||
// Enum::Variant(T) <-> "Variant": T | ||
if fields.unnamed.len() == 1 { | ||
// Encode | ||
variant_encodes.push(quote! { | ||
Self::#variant_ident(value) => { | ||
vertigo::JsJson::Object(::std::collections::HashMap::from([ | ||
( | ||
#variant_name.to_string(), | ||
value.to_json(), | ||
), | ||
])) | ||
} | ||
}); | ||
|
||
// Decode | ||
variant_object_decodes.push(quote! { | ||
if let Some(value) = compound_variant.get_mut(#variant_name) { | ||
return Ok(Self::#variant_ident( | ||
vertigo::JsJsonDeserialize::from_json(ctx.clone(), value.to_owned())? | ||
)) | ||
} | ||
}); | ||
|
||
// Enum::Variant(T1, T2...) <-> "Variant": [T1, T2, ...] | ||
} else { | ||
// Encode | ||
let (field_idents, field_encodes) = | ||
super::tuple_fields::get_encodes(fields.unnamed.iter()); | ||
|
||
variant_encodes.push(quote! { | ||
Self::#variant_ident(#(#field_idents,)*) => { | ||
vertigo::JsJson::Object(::std::collections::HashMap::from([ | ||
( | ||
#variant_name.to_string(), | ||
vertigo::JsJson::List(vec![ | ||
#(#field_encodes)* | ||
]) | ||
), | ||
])) | ||
} | ||
}); | ||
|
||
// Decode | ||
let fields_number = field_idents.len(); | ||
let field_decodes = super::tuple_fields::get_decodes(field_idents); | ||
|
||
variant_object_decodes.push(quote! { | ||
if let Some(value) = compound_variant.get_mut(#variant_name) { | ||
match value.to_owned() { | ||
vertigo::JsJson::List(fields) => { | ||
if fields.len() != #fields_number { | ||
return Err(ctx.add( | ||
format!("Wrong unmber of fields in tuple for variant {}. Expected {}, got {}", #variant_name, #fields_number, fields.len()) | ||
)); | ||
} | ||
let mut fields_rev = fields.into_iter().rev().collect::<Vec<_>>(); | ||
return Ok(Self::#variant_ident ( | ||
#(#field_decodes)* | ||
)) | ||
}, | ||
x => return Err(ctx.add( | ||
format!("Invalid type {} while decoding enum tuple, expected list", x.typename()) | ||
)), | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
|
||
// Compound variant with named field(s) (anonymous struct) | ||
// Enum::Variant { x: X, y: Y, ...) <-> "Variant": { x: X, y: Y, ... } | ||
Fields::Named(fields) => { | ||
// Encode | ||
let field_idents = fields | ||
.named | ||
.iter() | ||
.filter_map(|field| field.ident.clone()) | ||
.collect::<Vec<_>>(); | ||
|
||
let field_encodes = field_idents | ||
.iter() | ||
.map(|field_ident| { | ||
let field_name = field_ident.unraw().to_string(); | ||
quote! { | ||
(#field_name.to_string(), #field_ident.to_json()), | ||
} | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
variant_encodes.push(quote! { | ||
Self::#variant_ident {#(#field_idents,)*} => { | ||
vertigo::JsJson::Object(::std::collections::HashMap::from([ | ||
( | ||
#variant_name.to_string(), | ||
vertigo::JsJson::Object(::std::collections::HashMap::from([ | ||
#(#field_encodes)* | ||
])) | ||
), | ||
])) | ||
} | ||
}); | ||
|
||
// Decode | ||
let field_decodes = field_idents | ||
.iter() | ||
.map(|field_ident| { | ||
let field_name = field_ident.unraw().to_string(); | ||
quote! { | ||
#field_ident: value.get_property(&ctx, #field_name)?, | ||
} | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
variant_object_decodes.push(quote! { | ||
if let Some(value) = compound_variant.get_mut(#variant_name) { | ||
return Ok(Self::#variant_ident { | ||
#(#field_decodes)* | ||
}) | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
|
||
let result = quote! { | ||
impl vertigo::JsJsonSerialize for #name { | ||
fn to_json(self) -> vertigo::JsJson { | ||
match self { | ||
#(#variant_encodes)* | ||
} | ||
} | ||
} | ||
|
||
impl vertigo::JsJsonDeserialize for #name { | ||
fn from_json( | ||
ctx: vertigo::JsJsonContext, | ||
json: vertigo::JsJson, | ||
) -> Result<Self, vertigo::JsJsonContext> { | ||
match json { | ||
vertigo::JsJson::String(simple_variant) => { | ||
match simple_variant.as_str() { | ||
#(#variant_string_decodes)* | ||
x => Err(ctx.add(format!("Invalid simple variant {x}"))), | ||
} | ||
} | ||
vertigo::JsJson::Object(mut compound_variant) => { | ||
#(#variant_object_decodes)* | ||
Err(ctx.add("Value not matched with any variant".to_string())) | ||
} | ||
x => Err(ctx.add( | ||
format!("Invalid type {} while decoding enum, expected string or object", x.typename()) | ||
)), | ||
} | ||
} | ||
} | ||
}; | ||
|
||
Ok(result.into()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use proc_macro::TokenStream; | ||
use syn::{Data, DeriveInput}; | ||
|
||
mod enums; | ||
mod newtypes; | ||
mod structs; | ||
mod tuple_fields; | ||
|
||
pub(crate) fn impl_js_json_derive(ast: &DeriveInput) -> Result<TokenStream, String> { | ||
let name = &ast.ident; | ||
|
||
match ast.data { | ||
Data::Struct(ref data) => structs::impl_js_json_struct(name, data), | ||
Data::Enum(ref data) => enums::impl_js_json_enum(name, data), | ||
Data::Union(ref _data) => Err("Unions not supported yet".to_string()), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
use proc_macro::TokenStream; | ||
use quote::quote; | ||
use syn::{DataStruct, Ident}; | ||
|
||
pub(super) fn impl_js_json_newtype(name: &Ident, data: &DataStruct) -> Result<TokenStream, String> { | ||
let mut encodes = Vec::new(); | ||
let mut decodes = Vec::new(); | ||
|
||
// Struct(T) <-> T | ||
if data.fields.len() == 1 { | ||
// Encode | ||
encodes.push(quote! { | ||
self.0.to_json() | ||
}); | ||
|
||
// Decode | ||
decodes.push(quote! { | ||
vertigo::JsJsonDeserialize::from_json(ctx, json).map(Self) | ||
}); | ||
|
||
// Struct(T1, T2...) <-> [T1, T2, ...] | ||
} else { | ||
// Encode | ||
let (field_idents, field_encodes) = super::tuple_fields::get_encodes(data.fields.iter()); | ||
|
||
encodes.push(quote! { | ||
let #name (#(#field_idents,)*) = self; | ||
vertigo::JsJson::List(vec![ | ||
#(#field_encodes)* | ||
]) | ||
}); | ||
|
||
// Decode | ||
let fields_number = field_idents.len(); | ||
let field_decodes = super::tuple_fields::get_decodes(field_idents); | ||
let name_str = name.to_string(); | ||
|
||
decodes.push(quote! { | ||
match json { | ||
vertigo::JsJson::List(fields) => { | ||
if fields.len() != #fields_number { | ||
return Err(ctx.add( | ||
format!("Wrong number of fields in tuple for newtype {}. Expected {}, got {}", #name_str, #fields_number, fields.len()) | ||
)); | ||
} | ||
let mut fields_rev = fields.into_iter().rev().collect::<Vec<_>>(); | ||
return Ok(#name( | ||
#(#field_decodes)* | ||
)) | ||
} | ||
x => return Err(ctx.add( | ||
format!("Invalid type {} while decoding newtype tuple, expected list", x.typename()) | ||
)), | ||
} | ||
}); | ||
} | ||
|
||
let result = quote! { | ||
impl vertigo::JsJsonSerialize for #name { | ||
fn to_json(self) -> vertigo::JsJson { | ||
#(#encodes)* | ||
} | ||
} | ||
|
||
impl vertigo::JsJsonDeserialize for #name { | ||
fn from_json( | ||
ctx: vertigo::JsJsonContext, | ||
json: vertigo::JsJson, | ||
) -> Result<Self, vertigo::JsJsonContext> { | ||
#(#decodes)* | ||
} | ||
} | ||
}; | ||
|
||
Ok(result.into()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
use proc_macro2::TokenStream; | ||
use quote::quote; | ||
use syn::spanned::Spanned; | ||
use syn::{punctuated::Iter, Field, Ident}; | ||
|
||
/// Takes tuple fields and returns (generated fields' names, generated encodes) | ||
pub(super) fn get_encodes(fields_iter: Iter<'_, Field>) -> (Vec<Ident>, Vec<TokenStream>) { | ||
let field_idents = fields_iter | ||
.enumerate() | ||
.map(|(n, field)| Ident::new(&format!("f_{}", n), field.span())) | ||
.collect::<Vec<_>>(); | ||
|
||
let field_encodes = field_idents | ||
.iter() | ||
.map(|field_ident| { | ||
quote! { | ||
#field_ident.to_json(), | ||
} | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
(field_idents, field_encodes) | ||
} | ||
|
||
/// Takes tuple fields and returns generated encodes | ||
pub(super) fn get_decodes(field_idents: Vec<Ident>) -> Vec<TokenStream> { | ||
field_idents | ||
.iter() | ||
.map(|_| { | ||
quote! { | ||
vertigo::JsJsonDeserialize::from_json(ctx.clone(), fields_rev.pop().unwrap())?, | ||
} | ||
}) | ||
.collect::<Vec<_>>() | ||
} |
Oops, something went wrong.