easy_jsonrpc_proc_macro_mw/
lib.rs

1// Provides a Handler implementation for input trait
2// Provides client helpers for handler implementaion
3
4#![recursion_limit = "256"]
5
6extern crate proc_macro;
7use heck::SnakeCase;
8use proc_macro2::{self, Span, TokenStream};
9use quote::{quote, quote_spanned};
10use syn::{
11    parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Paren, ArgSelfRef, FnArg,
12    FnDecl, Ident, ItemTrait, MethodSig, Pat, PatIdent, ReturnType, TraitItem, Type, TypeTuple,
13};
14
15/// Generate a Handler implementation and client helpers for trait input.
16///
17/// Example usage:
18///
19/// ```rust,no_run
20/// #[rpc]
21/// trait MyApi {
22///     fn my_method(&self, a: usize);
23///     fn my_other_method(&self) -> bool {}
24/// }
25/// ```
26///
27/// Generated code:
28///
29/// ```
30/// impl Handler for &dyn MyApi {
31///    ..
32/// }
33///
34/// pub enum my_api {}
35///
36/// impl my_api {
37///     fn my_method(arg0: usize) -> Result<BoundMethod<'static, ()>, ArgSerializeError> {
38///          ..
39///     }
40///
41///     fn my_other_method() -> Result<BoundMethod<'static, bool>, ArgSerializeError> {
42///          ..
43///     }
44/// }
45/// ```
46#[proc_macro_attribute]
47pub fn rpc(_: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
48    let trait_def = parse_macro_input!(item as ItemTrait);
49    let server_impl = raise_if_err(impl_server(&trait_def));
50    let client_impl = raise_if_err(impl_client(&trait_def));
51    proc_macro::TokenStream::from(quote! {
52        #trait_def
53        #server_impl
54        #client_impl
55    })
56}
57
58// if Ok, return token stream, else report error
59fn raise_if_err(res: Result<TokenStream, Rejections>) -> TokenStream {
60    match res {
61        Ok(stream) => stream,
62        Err(rej) => rej.raise(),
63    }
64}
65
66// generate a Handler implementation for &dyn Trait
67fn impl_server(tr: &ItemTrait) -> Result<TokenStream, Rejections> {
68    let trait_name = &tr.ident;
69    let methods: Vec<&MethodSig> = trait_methods(&tr)?;
70
71    let handlers = methods.iter().map(|method| {
72        let method_literal = method.ident.to_string();
73        let method_return_type_span = return_type_span(&method);
74        let handler = add_handler(trait_name, method)?;
75        let try_serialize = quote_spanned! {
76            method_return_type_span =>
77                easy_jsonrpc_mw::try_serialize(&result)
78        };
79        Ok(quote! { #method_literal => {
80            let result = #handler;
81            #try_serialize
82        }})
83    });
84    let handlers: Vec<TokenStream> = partition(handlers)?;
85
86    Ok(quote! {
87        impl easy_jsonrpc_mw::Handler for dyn #trait_name {
88            fn handle(&self, method: &str, params: easy_jsonrpc_mw::Params)
89                      -> Result<easy_jsonrpc_mw::Value, easy_jsonrpc_mw::Error> {
90                match method {
91                    #(#handlers,)*
92                    _ => Err(easy_jsonrpc_mw::Error::method_not_found()),
93                }
94            }
95        }
96    })
97}
98
99fn impl_client(tr: &ItemTrait) -> Result<TokenStream, Rejections> {
100    let trait_name = &tr.ident;
101    let methods: Vec<&MethodSig> = trait_methods(&tr)?;
102    let mod_name = Ident::new(&trait_name.to_string().to_snake_case(), Span::call_site());
103    let method_impls = methods
104        .iter()
105        .map(|method| impl_client_method(*method))
106        .collect::<Result<Vec<TokenStream>, Rejections>>()?;
107
108    Ok(quote! {
109        // We originally used "mod" here. The problem was that modules can't access the
110        // namespace of their parent if their parent is a fn.
111        /// Helper module for jsonrpc clients. Automatically generated by easy-jsonrpc.
112        pub enum #mod_name {}
113        impl #mod_name {
114            #(#method_impls)*
115        }
116    })
117}
118
119fn impl_client_method(method: &MethodSig) -> Result<TokenStream, Rejections> {
120    let method_name = &method.ident;
121    let method_name_literal = &method_name.to_string();
122    let args = get_args(&method.decl)?;
123    let fn_definition_args: &Vec<_> = &args
124        .iter()
125        .enumerate()
126        .map(|(i, (name, typ))| {
127            let arg_num_name = Ident::new(&format!("arg{}", i), name.span());
128            quote! {#arg_num_name: #typ}
129        })
130        .collect();
131    let args_serialize: &Vec<_> = &args
132        .iter()
133        .enumerate()
134        .map(|(i, (name, _))| {
135            let arg_num_name = Ident::new(&format!("arg{}", i), name.span());
136            quote! {
137                easy_jsonrpc_mw::serde_json::to_value(#arg_num_name).map_err(|_| easy_jsonrpc_mw::ArgSerializeError)?
138            }
139        })
140        .collect();
141    let return_typ = return_type(&method);
142
143    Ok(quote! {
144        /// Request generator for jsonrpc clients. Automatically generated by easy-jsonrpc.
145        pub fn #method_name ( #(#fn_definition_args,)* )
146                                 -> Result<easy_jsonrpc_mw::BoundMethod<'static, #return_typ>, easy_jsonrpc_mw::ArgSerializeError> {
147            Ok(easy_jsonrpc_mw::BoundMethod::new(
148                #method_name_literal,
149                vec![ #(#args_serialize),* ],
150            ))
151        }
152    })
153}
154
155fn return_type_span(method: &MethodSig) -> Span {
156    let return_type = match &method.decl.output {
157        ReturnType::Default => None,
158        ReturnType::Type(_, typ) => Some(typ),
159    };
160    return_type
161        .map(|typ| typ.span())
162        .unwrap_or_else(|| method.decl.output.span().clone())
163}
164
165fn return_type(method: &MethodSig) -> Type {
166    match &method.decl.output {
167        ReturnType::Default => Type::Tuple(TypeTuple {
168            paren_token: Paren {
169                span: method.decl.output.span(),
170            },
171            elems: Punctuated::new(),
172        }),
173        ReturnType::Type(_, typ) => *typ.clone(),
174    }
175}
176
177// return all methods in the trait, or reject if trait contains an item that is not a method
178fn trait_methods<'a>(tr: &'a ItemTrait) -> Result<Vec<&'a MethodSig>, Rejections> {
179    let methods = partition(tr.items.iter().map(|item| match item {
180        TraitItem::Method(method) => Ok(&method.sig),
181        other => Err(Rejection::create(other.span(), Reason::TraitNotStrictlyMethods).into()),
182    }))?;
183    partition(methods.iter().map(|method| {
184        if method.ident.to_string().starts_with("rpc.") {
185            Err(Rejection::create(method.ident.span(), Reason::ReservedMethodPrefix).into())
186        } else {
187            Ok(())
188        }
189    }))?;
190    Ok(methods)
191}
192
193// generate code that parses rpc arguments and calls the given method
194fn add_handler(trait_name: &Ident, method: &MethodSig) -> Result<TokenStream, Rejections> {
195    let method_name = &method.ident;
196    let args = get_args(&method.decl)?;
197    let arg_name_literals = args.iter().map(|(id, _)| id.to_string());
198    let parse_args = args.iter().enumerate().map(|(index, (ident, ty))| {
199        let argname_literal = format!("\"{}\"", ident);
200        // non-lexical lifetimes make it possible to create a reference to an anonymous owned value
201        let prefix = match ty {
202            syn::Type::Reference(_) => quote! { & },
203            _ => quote! {},
204        };
205        quote_spanned! { ty.span() => #prefix {
206            let next_arg = ordered_args.next().expect(
207                "RPC method Got too few args. This is a bug." // checked in get_rpc_args
208            );
209            easy_jsonrpc_mw::serde_json::from_value(next_arg).map_err(|_| {
210                easy_jsonrpc_mw::InvalidArgs::InvalidArgStructure {
211                    name: #argname_literal,
212                    index: #index,
213                }.into()
214            })?
215        }}
216    });
217
218    Ok(quote! {{
219        let mut args: Vec<easy_jsonrpc_mw::Value> =
220            params.get_rpc_args(&[#(#arg_name_literals),*])
221                .map_err(|a| a.into())?;
222        let mut ordered_args = args.drain(..);
223        let res = <dyn #trait_name>::#method_name(self, #(#parse_args),*); // call the target procedure
224        debug_assert_eq!(ordered_args.next(), None); // parse_args must consume ordered_args
225        res
226    }})
227}
228
229// Get the name and type of each argument from method. Skip the first argument, which must be &self.
230// If the first argument is not &self, an error will be returned.
231fn get_args<'a>(method: &'a FnDecl) -> Result<Vec<(&'a Ident, &'a Type)>, Rejections> {
232    let mut inputs = method.inputs.iter();
233    match inputs.next() {
234        Some(FnArg::SelfRef(ArgSelfRef {
235            mutability: None, ..
236        })) => Ok(()),
237        Some(a) => Err(Rejection::create(a.span(), Reason::FirstArgumentNotSelfRef)),
238        None => Err(Rejection::create(
239            method.inputs.span(),
240            Reason::FirstArgumentNotSelfRef,
241        )),
242    }?;
243    partition(inputs.map(as_jsonrpc_arg))
244}
245
246// If all Ok, return Vec of successful values, otherwise return all Rejections.
247fn partition<K, I: Iterator<Item = Result<K, Rejections>>>(iter: I) -> Result<Vec<K>, Rejections> {
248    let (min, _) = iter.size_hint();
249    let mut oks: Vec<K> = Vec::with_capacity(min);
250    let mut errs: Vec<Rejection> = Vec::new();
251    for i in iter {
252        match i {
253            Ok(ok) => oks.push(ok),
254            Err(Rejections { first, rest }) => {
255                errs.push(first);
256                errs.extend(rest);
257            }
258        }
259    }
260    match errs.first() {
261        Some(first) => Err(Rejections {
262            first: *first,
263            rest: errs[1..].to_vec(),
264        }),
265        None => Ok(oks),
266    }
267}
268
269// Attempt to extract name and type from arg
270fn as_jsonrpc_arg(arg: &FnArg) -> Result<(&Ident, &Type), Rejections> {
271    let arg = match arg {
272        FnArg::Captured(captured) => Ok(captured),
273        a => Err(Rejection::create(a.span(), Reason::ConcreteTypesRequired)),
274    }?;
275    let ty = &arg.ty;
276    let pat_ident = match &arg.pat {
277        Pat::Ident(pat_ident) => Ok(pat_ident),
278        a => Err(Rejection::create(a.span(), Reason::PatternMatchedArg)),
279    }?;
280    let ident = match pat_ident {
281        PatIdent {
282            by_ref: Some(r), ..
283        } => Err(Rejection::create(r.span(), Reason::ReferenceArg)),
284        PatIdent {
285            mutability: Some(m),
286            ..
287        } => Err(Rejection::create(m.span(), Reason::MutableArg)),
288        PatIdent {
289            subpat: Some((l, _)),
290            ..
291        } => Err(Rejection::create(l.span(), Reason::PatternMatchedArg)),
292        PatIdent {
293            ident,
294            by_ref: None,
295            mutability: None,
296            subpat: None,
297        } => Ok(ident),
298    }?;
299    Ok((&ident, &ty))
300}
301
302// returned when macro input is invalid
303#[derive(Clone, Copy)]
304struct Rejection {
305    span: Span,
306    reason: Reason,
307}
308
309// reason for a rejection, reason is comminicated to user when a rejection is returned
310#[derive(Clone, Copy)]
311enum Reason {
312    FirstArgumentNotSelfRef,
313    PatternMatchedArg,
314    ConcreteTypesRequired,
315    TraitNotStrictlyMethods,
316    ReservedMethodPrefix,
317    ReferenceArg,
318    MutableArg,
319}
320
321// Rustc often reports whole batches of errors at once. We can do the same by returning lists of
322// Rejections when appropriate.
323struct Rejections {
324    first: Rejection, // must contain least one rejection
325    rest: Vec<Rejection>,
326}
327
328impl Rejections {
329    // report all contained rejections
330    fn raise(self) -> TokenStream {
331        let Rejections { first, mut rest } = self;
332        let first_err = first.raise();
333        let rest_err = rest.drain(..).map(Rejection::raise);
334        quote! {
335            #first_err
336            #(#rest_err)*
337        }
338    }
339}
340
341// syn's neat error reporting capabilities let us provide helpful errors like the following:
342//
343// ```
344// error: expected type, found `{`
345// --> src/main.rs:1:14
346//   |
347// 1 | fn main() -> {
348//   |              ^
349// ```
350impl Rejection {
351    fn create(span: Span, reason: Reason) -> Self {
352        Rejection { span, reason }
353    }
354
355    // generate a compile_err!() from self
356    fn raise(self) -> TokenStream {
357        let description = match self.reason {
358            Reason::FirstArgumentNotSelfRef => "First argument to jsonrpc method must be &self.",
359            Reason::PatternMatchedArg => {
360                "Pattern matched arguments are not supported in jsonrpc methods."
361            }
362            Reason::ConcreteTypesRequired => {
363                "Arguments and return values must have concrete types."
364            }
365            Reason::TraitNotStrictlyMethods => {
366                "Macro 'jsonrpc_server' expects trait definition containing methods only."
367            }
368            Reason::ReservedMethodPrefix => {
369                "The prefix 'rpc.' is reserved https://www.jsonrpc.org/specification#request_object"
370            }
371            Reason::ReferenceArg => "Reference arguments not supported in jsonrpc macro.",
372            Reason::MutableArg => "Mutable arguments not supported in jsonrpc macro.",
373        };
374
375        syn::Error::new(self.span, description).to_compile_error()
376    }
377}
378
379impl From<Rejection> for Rejections {
380    fn from(first: Rejection) -> Rejections {
381        Rejections {
382            first,
383            rest: vec![],
384        }
385    }
386}