1use proc_macro::TokenStream;
2use syn::{parse_macro_input, Data, DeriveInput, DataStruct, Fields, FieldsNamed, Type};
3
4#[macro_use]
5extern crate quote;
6extern crate proc_macro;
7
8fn title_case(s: &str) -> String {
9 let mut c = s.chars();
10 match c.next() {
11 None => String::new(),
12 Some(f) => f.to_uppercase().chain(c).collect(),
13 }
14}
15
16struct FieldMeta {
17 t: Type,
18 model_getter: String,
19 struct_name: String,
20}
21
22#[proc_macro_derive(Model)]
23pub fn derive_model(input: TokenStream) -> TokenStream {
24 let ops = vec!["equals", "contains"];
25 let input = parse_macro_input!(input as DeriveInput);
26 let model_name = input.ident;
27
28 let queries_struct_name = format_ident!("{}Queries", model_name);
29 let operation_enum_name = format_ident!("{}Operation", model_name);
30 let client_fn_name = format_ident!("{}", model_name.to_string().to_ascii_lowercase());
31 let client_trait_name = format_ident!("{}Client", model_name);
32
33 let fields = if let Data::Struct(DataStruct {
34 fields: Fields::Named(FieldsNamed {
35 ref named,
36 ..
37 }),
38 ..
39 }) = input.data {
40 named
41 } else {
42 unimplemented!()
43 };
44
45 let field_metas = fields.iter().map(|f| {
46 let field_name = f.ident.as_ref().unwrap();
47 let t = f.ty.clone();
48
49 FieldMeta {
50 t,
51 model_getter: field_name.to_string(),
52 struct_name: format!("{}{}Field", model_name.to_string(), title_case(&field_name.to_string()))
53 }
54 }).collect::<Vec<FieldMeta>>();
55
56 let field_struct_declarations = field_metas.iter().map(|meta| {
57 let field_struct_name = format_ident!("{}", meta.struct_name);
58 quote! {
59 pub struct #field_struct_name {}
60 }
61 });
62
63 let field_struct_getters = field_metas.iter().map(|meta| {
64 let model_getter = format_ident!("{}", meta.model_getter);
65 let struct_name = format_ident!("{}", meta.struct_name);
66
67 quote! {
68 pub fn #model_getter() -> #struct_name {
69 #struct_name { }
70 }
71 }
72 });
73
74 let field_struct_impls = field_metas.iter().map(|meta| {
75 let field_struct_name = format_ident!("{}", meta.struct_name);
76
77 let field_ops = ops.iter().map(|&op| {
78 let op_fn_name = format_ident!("{}", op);
79 let op_enum_case = format_ident!("{}{}", title_case(&meta.model_getter), title_case(op));
80 let field_type = &meta.t;
81
82 quote! {
83 pub fn #op_fn_name(&self, v: #field_type) -> #operation_enum_name {
84 #operation_enum_name::#op_enum_case(v)
85 }
86 }
87 });
88
89 quote! {
90 impl #field_struct_name {
91 #(#field_ops)*
92 }
93 }
94 });
95
96 let operation_enum_cases = field_metas.iter().map(|meta| {
97 let t = &meta.t;
98 let field_name = &title_case(&meta.model_getter);
99
100 let field_cases = ops.iter().map(|&op| {
101 let case_name = format_ident!("{}{}", field_name, title_case(op));
102
103 quote! {
104 #case_name(#t)
105 }
106 });
107
108 quote! {
109 #(#field_cases),*
110 }
111 });
112
113 let m = quote! {
114 #[derive(Debug)]
115 pub enum #operation_enum_name {
116 #(#operation_enum_cases),*
117 }
118
119 #(#field_struct_declarations)*
120
121 #(#field_struct_impls)*
122
123 impl Account {
124 #(#field_struct_getters)*
125 }
126
127 pub struct #queries_struct_name { }
128
129 impl #queries_struct_name {
130 fn find_one(&self, operations: Vec<#operation_enum_name>) -> Result<#model_name, ()> {
131 Err(())
132 }
133 }
134
135 trait #client_trait_name {
136 fn #client_fn_name(&self) -> #queries_struct_name;
137 }
138
139 impl #client_trait_name for Client {
140 fn #client_fn_name(&self) -> #queries_struct_name {
141 #queries_struct_name { }
142 }
143 }
144 };
145
146 TokenStream::from(m)
147}