1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
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()
        }
    }
}