extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{
self,
Data,
DataStruct,
DeriveInput,
Error,
Field,
Fields,
export::Span,
LitStr,
punctuated::Punctuated,
spanned::Spanned,
token::Comma
};
fn extract_struct_fields<'a>(data: &'a Data, span: &Span) -> Result<&'a DataStruct, Error> {
match data {
Data::Struct(struct_fields) => Ok(struct_fields),
_ => Err( Error::new(span.clone(), "cannot derive json type for non structs"))
}
}
fn extract_named_fields<'a>(data_struct: &'a DataStruct,
span: &Span) -> Result<Option<&'a Punctuated<Field, Comma>>, Error> {
match data_struct.fields {
Fields::Named(ref named_fields) => Ok(Some(&named_fields.named)),
Fields::Unnamed(_) => Err( Error::new(span.clone(), "cannot derive json type for a tuple struct")),
Fields::Unit => Ok(None)
}
}
fn fields_to_json(data: &Data, span: &Span) -> Result<String, Error> {
let data_struct = extract_struct_fields(data, span)?;
if let Some(named_fields) = extract_named_fields(data_struct, span)? {
let mut fields = Vec::new();
for field in named_fields {
let ident = field.ident
.as_ref()
.ok_or(Error::new(span.clone(), "cannot derive json type for unnamed field"))?
.to_string();
let ty = field.ty
.clone()
.into_token_stream()
.to_string()
.replace(" ", "");
fields.push(format!("{{\"{}\": \"{}\"}}", ident, ty));
fields.push(String::from(", "));
}
let _ = fields.pop();
let mut json = String::from("[");
for field in fields.iter() {
json.push_str(&*field);
}
json.push(']');
Ok(json)
}
else {
Ok("[]".to_owned())
}
}
fn impl_json_type_macro(ast: &DeriveInput) -> Result<TokenStream, Error> {
let name = &ast.ident;
let json_fields = fields_to_json(&ast.data, &ast.span())?;
let format_json = format!("{{\"name\": {}, fields: {}}}", name.to_string(), json_fields);
let json = LitStr::new(&*format_json, Span::call_site());
let gen = quote! {
impl json_type::JsonType for #name {
fn json_type() -> &'static str {
#json
}
}
};
Ok(gen.into())
}
#[proc_macro_derive(JsonType)]
pub fn json_type_derive(input: TokenStream) -> TokenStream {
match syn::parse(input) {
Ok(ast) => {
impl_json_type_macro(&ast)
.unwrap_or_else(|e| e.to_compile_error().into())
},
Err(e) => {
e.to_compile_error().into()
}
}
}