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() } } }