ezjsonrpc_macros/
lib.rs

1#![recursion_limit = "2048"]
2
3extern crate proc_macro;
4
5use proc_macro::TokenStream;
6use proc_macro2::Span;
7use proc_macro_hack::proc_macro_hack;
8use quote::quote;
9
10use syn::*;
11
12#[proc_macro_attribute]
13pub fn jsonrpc_method(attrs: TokenStream, item: TokenStream) -> TokenStream {
14    let method = parse_macro_input!(item as ImplItemMethod);
15
16    let attrs = parse_macro_input!(attrs as AttributeArgs);
17
18    let params = &method
19        .sig
20        .decl
21        .inputs
22        .iter()
23        .filter_map(|x| match *x {
24            FnArg::Captured(ref y) => Some(y),
25            _ => None
26        })
27        .filter_map(|x| match x.pat {
28            Pat::Ident(ref y) => Some(y),
29            _ => None
30        })
31        .map(|x| &x.ident)
32        .collect::<Vec<_>>();
33
34    let jsonrpc_callable_fn_ident = Ident::new(
35        &format!(
36            "jsonrpc_callable_for_{}",
37            method.sig.ident.to_string().trim_left_matches("r#").to_owned()
38        ),
39        Span::call_site()
40    );
41
42    let jsonrpc_method_name_ident = Ident::new(
43        &format!(
44            "JSONRPC_METHOD_NAME_FOR_{}",
45            method.sig.ident.to_string().trim_left_matches("r#").to_owned()
46        ),
47        Span::call_site()
48    );
49
50    let item_fn_ident = &method.sig.ident;
51
52    let mut item_fn_ident_s = item_fn_ident.to_string();
53
54    if let Some(rename) = attrs
55        .iter()
56        .filter_map(|x| match x {
57            NestedMeta::Meta(y) => Some(y),
58            _ => None
59        })
60        .filter_map(|x| match x {
61            Meta::NameValue(y) => Some(y),
62            _ => None
63        })
64        .find(|x| x.ident == "rename")
65        .and_then(|x| match x.lit {
66            Lit::Str(ref y) => Some(y),
67            _ => None
68        })
69    {
70        item_fn_ident_s = rename.value();
71    }
72
73    let mut rename_arg_mappings = attrs
74        .iter()
75        .filter_map(|x| match x {
76            NestedMeta::Meta(y) => Some(y),
77            _ => None
78        })
79        .filter_map(|x| match x {
80            Meta::List(y) => Some(y),
81            _ => None
82        })
83        .filter(|x| x.ident == "rename_args")
84        .flat_map(|x| x.nested.iter())
85        .filter_map(|x| match x {
86            NestedMeta::Meta(y) => Some(y),
87            _ => None
88        })
89        .filter_map(|x| match x {
90            Meta::NameValue(y) => Some(y),
91            _ => None
92        })
93        .filter_map(|x| match x.lit {
94            Lit::Str(ref y) => Some((x.ident.to_string(), y.value())),
95            _ => None
96        })
97        .collect::<std::collections::HashMap<String, String>>();
98
99    let param_names = &params
100        .iter()
101        .map(|id| {
102            let name = id.to_string();
103            rename_arg_mappings.remove(&name).unwrap_or_else(|| name)
104        })
105        .collect::<Vec<_>>();
106
107    let extract_positional = extract_positional(param_names.len());
108    let extract_named = extract_named(param_names.len());
109
110    let jsonrpc_callable_fn: ImplItemMethod = {
111        if param_names.is_empty() {
112            parse_quote! {
113                pub fn #jsonrpc_callable_fn_ident(&self, params: Option<ezjsonrpc::exp::serde_json::Value>) -> Box<ezjsonrpc::exp::futures::Future<Item = ezjsonrpc::exp::serde_json::Value, Error = ezjsonrpc::Error> + Send> {
114                    if params.as_ref()
115                        .map(|x| x.as_object().map(|y| !y.is_empty()).unwrap_or(false) ||
116                            x.as_array().map(|y| !y.is_empty()).unwrap_or(false) )
117                        .unwrap_or(false) {
118                        ezjsonrpc::utils::finish_callable(ezjsonrpc::exp::futures::future::err::<(),_>(ezjsonrpc::Error::invalid_params()))
119                    } else {
120                        ezjsonrpc::utils::finish_callable(self.#item_fn_ident())
121                    }
122                }
123            }
124        } else {
125            parse_quote! {
126                pub fn #jsonrpc_callable_fn_ident(&self, params: Option<ezjsonrpc::exp::serde_json::Value>) -> Box<ezjsonrpc::exp::futures::Future<Item = ezjsonrpc::exp::serde_json::Value, Error = ezjsonrpc::Error> + Send> {
127                    match params {
128                        Some(ezjsonrpc::exp::serde_json::Value::Object(map)) => {
129                            #extract_named
130                            if let Ok((#(#params),*)) = extract(map, #(#param_names),*) {
131                                return ezjsonrpc::utils::finish_callable(self.#item_fn_ident(#(#params),*));
132                            }
133                        },
134                        Some(ezjsonrpc::exp::serde_json::Value::Array(vals)) => {
135                            #extract_positional
136                            if let Ok((#(#params),*)) = extract(vals) {
137                                return ezjsonrpc::utils::finish_callable(self.#item_fn_ident(#(#params),*));
138                            }
139                        },
140                        _ => {}
141                    }
142                    ezjsonrpc::utils::finish_callable(ezjsonrpc::exp::futures::future::err::<(),_>(ezjsonrpc::Error::invalid_params()))
143                }
144            }
145        }
146    };
147
148    let jsonrpc_method_name_fn: ImplItemConst = parse_quote! {
149        pub const #jsonrpc_method_name_ident: &'static str = #item_fn_ident_s;
150    };
151
152    let out = quote! {
153        #method
154        #jsonrpc_callable_fn
155        #jsonrpc_method_name_fn
156    };
157
158    out.into()
159}
160
161#[proc_macro_hack]
162pub fn methods(item: TokenStream) -> TokenStream {
163    use syn::parse::Parser;
164
165    let paths = <punctuated::Punctuated<Path, token::Comma>>::parse_terminated.parse(item).unwrap();
166
167    let parts = paths
168        .iter()
169        .map(|path| {
170            let mut json_callable_fn_path = path.clone();
171            {
172                let mut last_seg = json_callable_fn_path.segments.last_mut().unwrap();
173                last_seg.value_mut().ident = Ident::new(
174                    &format!(
175                        "jsonrpc_callable_for_{}",
176                        last_seg.value().ident.to_string().trim_left_matches("r#").to_owned()
177                    ),
178                    Span::call_site()
179                );
180            }
181            let mut json_method_name_path = path.clone();
182            {
183                let mut last_seg = json_method_name_path.segments.last_mut().unwrap();
184                last_seg.value_mut().ident = Ident::new(
185                    &format!(
186                        "JSONRPC_METHOD_NAME_FOR_{}",
187                        last_seg.value().ident.to_string().trim_left_matches("r#").to_owned()
188                    ),
189                    Span::call_site()
190                );
191            }
192            quote!(
193                #json_method_name_path => Some(#json_callable_fn_path),
194            )
195        })
196        .collect::<Vec<_>>();
197
198    let out = quote!(
199        |method: &str| {
200            match method {
201                #(#parts)*
202                _ => None
203            }
204        }
205    );
206
207    out.into()
208}
209
210fn extract_positional(up_to: usize) -> proc_macro2::TokenStream {
211    let tys =
212        (0..up_to).map(|i| Ident::new(&format!("T{}", i), Span::call_site())).collect::<Vec<_>>();
213    let gen = tys
214        .iter()
215        .map(|x| quote!(#x: ezjsonrpc::exp::serde::de::DeserializeOwned))
216        .collect::<Vec<_>>();
217
218    let ts =
219        (0..up_to).map(|i| Ident::new(&format!("t{}", i), Span::call_site())).collect::<Vec<_>>();
220
221    let mut ts_rev = ts.clone();
222    ts_rev.reverse();
223
224    let exprs = (0..up_to)
225        .map(|_| quote!(ezjsonrpc::exp::serde_json::from_value(vals.pop().unwrap())?))
226        .collect::<Vec<_>>();
227
228    quote! {
229        fn extract<#(#gen),*>(mut vals: Vec<ezjsonrpc::exp::serde_json::Value>) -> Result<(#(#tys),*), ezjsonrpc::exp::serde_json::Error> {
230            if vals.len() != #up_to {
231                return Err(ezjsonrpc::exp::serde::de::Error::custom("Invalid vec"));
232            }
233            let (#(#ts_rev),*) = (#(#exprs),*);
234            Ok((#(#ts),*))
235        }
236    }
237}
238
239fn extract_named(up_to: usize) -> proc_macro2::TokenStream {
240    let tys =
241        (0..up_to).map(|i| Ident::new(&format!("T{}", i), Span::call_site())).collect::<Vec<_>>();
242    let gen = tys
243        .iter()
244        .map(|x| quote!(#x: ezjsonrpc::exp::serde::de::DeserializeOwned))
245        .collect::<Vec<_>>();
246
247    let ts =
248        (0..up_to).map(|i| Ident::new(&format!("t{}", i), Span::call_site())).collect::<Vec<_>>();
249
250    let names =
251        (0..up_to).map(|i| Ident::new(&format!("n{}", i), Span::call_site())).collect::<Vec<_>>();
252
253    let names_and_tys = names.iter().map(|x| quote!(#x: &'static str)).collect::<Vec<_>>();
254
255    let mains = ts
256        .iter()
257        .zip(names.iter())
258        .map(|(t, n)| {
259            quote! {
260                let #t = if let Some(val) = map.remove(#n) {
261                    ezjsonrpc::exp::serde_json::from_value(val)?
262                } else {
263                    return Err(ezjsonrpc::exp::serde::de::Error::custom("Invalid map"));
264                };
265            }
266        })
267        .collect::<Vec<_>>();
268
269    quote! {
270        fn extract<#(#gen),*>(mut map: ezjsonrpc::exp::serde_json::Map<String, ezjsonrpc::exp::serde_json::Value>, #(#names_and_tys),*)
271            -> Result<(#(#tys),*), ezjsonrpc::exp::serde_json::Error> {
272            #(#mains)*
273            Ok((#(#ts),*))
274        }
275    }
276}