use syn::Expr;
use quote::{quote, format_ident};
use darling::FromMeta;
pub fn derive_typedesc(input: synstructure::Structure) -> proc_macro2::TokenStream {
match input.ast().data {
syn::Data::Struct(..) => derive_typedesc_struct(input),
syn::Data::Enum(..) => derive_typedesc_enum(input),
_ => panic!("impossible to derive a TypeDesc for unions")
}
}
pub fn derive_typedesc_struct(input: synstructure::Structure) -> proc_macro2::TokenStream {
let name = &input.ast().ident;
let vis = &input.ast().vis;
let const_name = format_ident!("_DERIVE_TypeDesc_{}", name);
let struct_name = format_ident!("{}Type", name);
let mut statics = Vec::new();
let mut member_to_json = Vec::new();
let mut member_from_json = Vec::new();
let mut descr_members = Vec::new();
let mut descr_optional = Vec::new();
for binding in input.variants()[0].bindings() {
let ident = binding.ast().ident.as_ref().unwrap_or_else(
|| panic!("TypeDesc cannot be derived for tuple structs"));
let ident_str = ident.to_string();
let dtype_static = format_ident!("STRUCT_FIELD_{}", ident_str);
let mut dtype = None;
for attr in &binding.ast().attrs {
if attr.path.segments[0].ident == "datatype" {
dtype = attr.parse_meta().ok();
}
}
let dtype = dtype.unwrap_or_else(
|| panic!("member {} has no valid datatype attribute", ident_str));
let dtype = String::from_meta(&dtype).unwrap_or_else(
|e| panic!("member {} has no valid datatype attribute: {}", ident_str, e));
let dtype_expr = syn::parse_str::<Expr>(&dtype).unwrap_or_else(
|_| panic!("member {} has no valid datatype attribute", ident_str));
let mut is_option_type = false;
if let syn::Type::Path(ref ptype) = binding.ast().ty {
if ptype.path.segments[0].ident == "Option" {
is_option_type = true;
}
}
statics.push(quote! {
static ref #dtype_static: typedesc_type!(#dtype_expr) = #dtype_expr;
});
if is_option_type {
member_to_json.push(quote! {
if let Some(member) = val.#ident {
let json_member = #dtype_static.to_json(member)
.map_err(|e| e.amend(concat!("in ", #ident_str)))?;
map.insert(#ident_str.into(), json_member);
}
});
member_from_json.push(quote! {
#ident: match obj.get(#ident_str) {
None => None,
Some(val) => Some(#dtype_static.from_json(val)
.map_err(|e| e.amend(concat!("in ", #ident_str)))?),
},
});
} else {
member_to_json.push(quote! {
let json_member = #dtype_static.to_json(val.#ident)
.map_err(|e| e.amend(concat!("in ", #ident_str)))?;
map.insert(#ident_str.into(), json_member);
});
member_from_json.push(quote! {
#ident: #dtype_static.from_json(
obj.get(#ident_str).ok_or_else(
|| Error::bad_value(concat!("missing ", #ident_str, " in object")))?
).map_err(|e| e.amend(concat!("in ", #ident_str)))?,
});
}
descr_members.push(quote! { #ident_str: #dtype_static.type_json(), });
descr_optional.push(quote! { #ident_str, });
}
let generated = quote! {
#vis struct #struct_name;
#[allow(non_upper_case_globals)]
const #const_name: () = {
use serde_json::{json, Value, map::Map};
use lazy_static::lazy_static;
use crate::secop_core::errors::Error;
use crate::secop_core::types::TypeDesc;
lazy_static! {
#( #statics )*
}
impl TypeDesc for #struct_name {
type Repr = #name;
fn type_json(&self) -> Value {
json!({"type": "struct",
"members": { #( #descr_members )* },
"optional": [ #( #descr_optional )* ]})
}
fn to_json(&self, val: Self::Repr) -> std::result::Result<Value, Error> {
let mut map = Map::new();
#( #member_to_json )*
Ok(Value::Object(map))
}
fn from_json(&self, val: &Value) -> std::result::Result<Self::Repr, Error> {
if let Some(obj) = val.as_object() {
Ok(#name { #( #member_from_json )* })
} else {
Err(Error::bad_value("expected object"))
}
}
}
};
};
generated
}
pub fn derive_typedesc_enum(input: synstructure::Structure) -> proc_macro2::TokenStream {
let name = &input.ast().ident;
let vis = &input.ast().vis;
let const_name = format_ident!("_DERIVE_TypeDesc_{}", name);
let struct_name = format_ident!("{}Type", name);
let mut descr_members = Vec::new();
let mut str_arms = Vec::new();
let mut int_arms = Vec::new();
let mut discr = -1i64;
for variant in input.variants() {
let ident = &variant.ast().ident;
let ident_str = ident.to_string();
if variant.ast().fields != &syn::Fields::Unit {
panic!("enum member {} cannot have data associated with it", ident);
}
if let Some((_, dis)) = variant.ast().discriminant {
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(i), .. }) = dis {
discr = i.base10_parse().unwrap();
} else {
panic!("explicit enum discriminants can only be integer literals");
}
} else {
discr += 1;
}
descr_members.push(quote! { #ident_str: #discr, });
str_arms.push(quote! { #ident_str => Ok(#name::#ident), });
int_arms.push(quote! { #discr => Ok(#name::#ident), });
}
let generated = quote! {
#vis struct #struct_name;
#[allow(non_upper_case_globals)]
const #const_name: () = {
use serde_json::{json, Value};
use crate::secop_core::errors::Error;
use crate::secop_core::types::TypeDesc;
impl TypeDesc for #struct_name {
type Repr = #name;
fn type_json(&self) -> Value {
json!({"type": "enum", "members": { #( #descr_members )* }})
}
fn to_json(&self, val: Self::Repr) -> std::result::Result<Value, Error> {
Ok(json!(val as i64))
}
fn from_json(&self, val: &Value) -> std::result::Result<Self::Repr, Error> {
if let Some(s) = val.as_str() {
match s {
#( #str_arms )*
_ => Err(Error::bad_value(
format!("{:?} is not an enum member", s)))
}
} else if let Some(i) = val.as_i64() {
match i {
#( #int_arms )*
_ => Err(Error::bad_value(
format!("{:?} is not an enum member", i)))
}
} else {
Err(Error::bad_value("expected string or integer"))
}
}
}
};
};
generated
}