Skip to main content

jsonrpsee_proc_macros/
render_server.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26
27use std::collections::HashSet;
28
29use super::RpcDescription;
30use crate::{
31    helpers::{generate_where_clause, is_option, may_borrow},
32    rpc_macro::RpcFnArg,
33};
34use proc_macro2::{Span, TokenStream as TokenStream2};
35use quote::{quote, quote_spanned};
36
37impl RpcDescription {
38    pub(super) fn render_server(&self) -> Result<TokenStream2, syn::Error> {
39        let trait_name = quote::format_ident!("{}Server", &self.trait_def.ident);
40        let generics = self.trait_def.generics.clone();
41        let (impl_generics, _, where_clause) = generics.split_for_impl();
42
43        let method_impls = self.render_methods()?;
44        let into_rpc_impl = self.render_into_rpc()?;
45
46        // Doc-comment to be associated with the server.
47        let doc_comment =
48            format!("Server trait implementation for the `{}` RPC API.", self.trait_def.ident);
49
50        let trait_impl = quote! {
51            #[doc = #doc_comment]
52            pub trait #trait_name #impl_generics: Sized + Send + Sync + 'static #where_clause {
53                #method_impls
54                #into_rpc_impl
55            }
56        };
57
58        Ok(trait_impl)
59    }
60
61    fn render_methods(&self) -> Result<TokenStream2, syn::Error> {
62        let methods = self.methods.iter().map(|method| {
63            let docs = &method.docs;
64            let mut method_sig = method.signature.clone();
65            desugar_async(&mut method_sig);
66
67            quote! {
68                #docs
69                #method_sig
70            }
71        });
72
73        let subscriptions = self.subscriptions.iter().map(|sub| {
74            let docs = &sub.docs;
75            let subscription_sink_ty = self.jrps_server_item(quote! { PendingSubscriptionSink });
76
77            // Add `SubscriptionSink` as the second input parameter to the signature.
78            let subscription_sink: syn::FnArg =
79                syn::parse_quote!(subscription_sink: #subscription_sink_ty);
80            let mut sub_sig = sub.signature.clone();
81            sub_sig.sig.inputs.insert(1, subscription_sink);
82            desugar_async(&mut sub_sig);
83
84            quote! {
85                #docs
86                #sub_sig
87            }
88        });
89
90        Ok(quote! {
91            #(#methods)*
92            #(#subscriptions)*
93        })
94    }
95
96    /// Helper that will ignore results of `register_*` method calls, and panic if there have been
97    /// any errors in debug builds.
98    ///
99    /// The debug assert is a safeguard should the contract that guarantees the method names to
100    /// never conflict in the macro be broken in the future.
101    fn handle_register_result(&self, tokens: TokenStream2) -> TokenStream2 {
102        let reexports = self.jrps_server_item(quote! { core::__reexports });
103        quote! {{
104            let _res = #tokens;
105            #[cfg(debug_assertions)]
106            if _res.is_err() {
107                #reexports::panic_fail_register();
108            }
109        }}
110    }
111
112    fn render_into_rpc(&self) -> Result<TokenStream2, syn::Error> {
113        let rpc_module = self.jrps_server_item(quote! { RpcModule });
114
115        let mut registered = HashSet::new();
116        let mut errors = Vec::new();
117        let mut check_name = |name: &str, span: Span| {
118            if registered.contains(name) {
119                let message = format!("{name:?} is already defined");
120                errors.push(quote_spanned!(span => compile_error!(#message);));
121            } else {
122                registered.insert(name.to_string());
123            }
124        };
125
126        let methods = self
127            .methods
128            .iter()
129            .map(|method| {
130                // Rust method to invoke (e.g. `self.<foo>(...)`).
131                let rust_method_name = &method.signature.sig.ident;
132                // Name of the RPC method (e.g. `foo_makeSpam`).
133                let rpc_method_name = self.rpc_identifier(&method.name);
134                // `parsing` is the code associated with parsing structure from the
135                // provided `Params` object.
136                // `params_seq` is the comma-delimited sequence of parameters we're passing to the
137                // rust function called..
138                let (parsing, params_seq) = self.render_params_decoding(&method.params, None);
139
140                let into_response = self.jrps_server_item(quote! { IntoResponse });
141
142                check_name(&rpc_method_name, rust_method_name.span());
143
144                if method.signature.sig.asyncness.is_some() {
145                    self.handle_register_result(quote! {
146                        rpc.register_async_method(#rpc_method_name, |params, context| async move {
147                            #parsing
148                            #into_response::into_response(context.as_ref().#rust_method_name(#params_seq).await)
149                        })
150                    })
151                } else if method.blocking {
152                    self.handle_register_result(quote! {
153                        rpc.register_blocking_method(#rpc_method_name, |params, context| {
154                            #parsing
155                            #into_response::into_response(context.#rust_method_name(#params_seq))
156                        })
157                    })
158                } else {
159                    self.handle_register_result(quote! {
160                        rpc.register_method(#rpc_method_name, |params, context| {
161                            #parsing
162                            #into_response::into_response(context.#rust_method_name(#params_seq))
163                        })
164                    })
165                }
166            })
167            .collect::<Vec<_>>();
168
169        let subscriptions = self
170            .subscriptions
171            .iter()
172            .map(|sub| {
173                // Rust method to invoke (e.g. `self.<foo>(...)`).
174                let rust_method_name = &sub.signature.sig.ident;
175                // Name of the RPC method to subscribe to (e.g. `foo_sub`).
176                let rpc_sub_name = self.rpc_identifier(&sub.name);
177                // Name of `method` in the subscription response.
178                let rpc_notif_name_override = sub.notif_name_override.as_ref().map(|m| self.rpc_identifier(m));
179                // Name of the RPC method to unsubscribe (e.g. `foo_sub`).
180                let rpc_unsub_name = self.rpc_identifier(&sub.unsubscribe);
181                // `parsing` is the code associated with parsing structure from the
182                // provided `Params` object.
183                // `params_seq` is the comma-delimited sequence of parameters.
184                let pending = proc_macro2::Ident::new("pending", rust_method_name.span());
185                let (parsing, params_seq) = self.render_params_decoding(&sub.params, Some(pending));
186                let sub_err = self.jrps_server_item(quote! { SubscriptionCloseResponse });
187                let into_sub_response = self.jrps_server_item(quote! { IntoSubscriptionCloseResponse });
188
189                check_name(&rpc_sub_name, rust_method_name.span());
190                check_name(&rpc_unsub_name, rust_method_name.span());
191
192                let rpc_notif_name = match rpc_notif_name_override {
193                    Some(notif) => {
194                        check_name(&notif, rust_method_name.span());
195                        notif
196                    }
197                    None => rpc_sub_name.clone(),
198                };
199
200                if sub.signature.sig.asyncness.is_some() {
201                    self.handle_register_result(quote! {
202                        rpc.register_subscription(#rpc_sub_name, #rpc_notif_name, #rpc_unsub_name, |params, mut pending, context| async move {
203                            #parsing
204                            #into_sub_response::into_response(context.as_ref().#rust_method_name(pending, #params_seq).await)
205                        })
206                    })
207                } else {
208                    self.handle_register_result(quote! {
209                        rpc.register_subscription_raw(#rpc_sub_name, #rpc_notif_name, #rpc_unsub_name, |params, mut pending, context| {
210                            #parsing
211                            let _ = context.as_ref().#rust_method_name(pending, #params_seq);
212                            #sub_err::None
213                        })
214                    })
215                }
216            })
217            .collect::<Vec<_>>();
218
219        let method_aliases = self
220            .methods
221            .iter()
222            .map(|method| {
223                let rpc_name = self.rpc_identifier(&method.name);
224                let rust_method_name = &method.signature.sig.ident;
225
226                // Rust method to invoke (e.g. `self.<foo>(...)`).
227                let aliases: Vec<TokenStream2> = method
228                    .aliases
229                    .iter()
230                    .map(|alias| {
231                        check_name(alias, rust_method_name.span());
232                        self.handle_register_result(quote! {
233                            rpc.register_alias(#alias, #rpc_name)
234                        })
235                    })
236                    .collect();
237
238                quote!( #(#aliases)* )
239            })
240            .collect::<Vec<_>>();
241
242        let subscription_aliases = self
243            .subscriptions
244            .iter()
245            .map(|method| {
246                let sub_name = self.rpc_identifier(&method.name);
247                let unsub_name = self.rpc_identifier(&method.unsubscribe);
248                let rust_method_name = &method.signature.sig.ident;
249
250                let sub: Vec<TokenStream2> = method
251                    .aliases
252                    .iter()
253                    .map(|alias| {
254                        check_name(alias, rust_method_name.span());
255                        self.handle_register_result(quote! {
256                            rpc.register_alias(#alias, #sub_name)
257                        })
258                    })
259                    .collect();
260                let unsub: Vec<TokenStream2> = method
261                    .unsubscribe_aliases
262                    .iter()
263                    .map(|alias| {
264                        check_name(alias, rust_method_name.span());
265                        self.handle_register_result(quote! {
266                            rpc.register_alias(#alias, #unsub_name)
267                        })
268                    })
269                    .collect();
270
271                quote! (
272                    #(#sub)*
273                    #(#unsub)*
274                )
275            })
276            .collect::<Vec<_>>();
277
278        let doc_comment = "Collects all the methods and subscriptions defined in the trait \
279                                and adds them into a single `RpcModule`.";
280
281        let sub_tys: Vec<syn::Type> =
282            self.subscriptions.clone().into_iter().map(|s| s.item).collect();
283        let where_clause =
284            generate_where_clause(&self.trait_def, &sub_tys, self.server_bounds.as_ref());
285
286        // NOTE(niklasad1): empty where clause is valid rust syntax.
287        Ok(quote! {
288            #[doc = #doc_comment]
289            fn into_rpc(self) -> #rpc_module<Self> where #(#where_clause,)* {
290                let mut rpc = #rpc_module::new(self);
291
292                #(#errors)*
293                #(#methods)*
294                #(#subscriptions)*
295                #(#method_aliases)*
296                #(#subscription_aliases)*
297
298                rpc
299            }
300        })
301    }
302
303    fn render_params_decoding(
304        &self,
305        params: &[RpcFnArg],
306        sub: Option<proc_macro2::Ident>,
307    ) -> (TokenStream2, TokenStream2) {
308        if params.is_empty() {
309            return (TokenStream2::default(), TokenStream2::default());
310        }
311
312        let params_fields_seq = params.iter().map(RpcFnArg::arg_pat);
313        let params_fields = quote! { #(#params_fields_seq),* };
314
315        let reexports = self.jrps_server_item(quote! { core::__reexports });
316
317        let error_ret = if let Some(pending) = &sub {
318            let tokio = quote! { #reexports::tokio };
319            let sub_err = self.jrps_server_item(quote! { SubscriptionCloseResponse });
320            quote! {
321                #tokio::spawn(#pending.reject(e));
322                return #sub_err::None;
323            }
324        } else {
325            let response_payload = self.jrps_server_item(quote! { ResponsePayload });
326            quote! {
327                return #response_payload::error(e);
328            }
329        };
330
331        // Code to decode sequence of parameters from a JSON array.
332        let decode_array = {
333            let decode_fields = params.iter().map(|RpcFnArg { arg_pat, ty, .. }| {
334                let is_option = is_option(ty);
335                let next_method = if is_option { quote!(optional_next) } else { quote!(next) };
336                quote! {
337                    let #arg_pat: #ty = match seq.#next_method() {
338                        Ok(v) => v,
339                        Err(e) => {
340                            #reexports::log_fail_parse(stringify!(#arg_pat), stringify!(#ty), &e, #is_option);
341                            #error_ret
342                        }
343                    };
344                }
345            });
346
347            // Move the raw params into the decoding block so they are freed once decoded, instead
348            // of living as long as an async method or subscription future. Arguments that may
349            // borrow from the params need them to outlive the call.
350            let free_params = (!params.iter().any(|arg| may_borrow(&arg.ty)))
351                .then(|| quote! { let params = params; });
352
353            quote! {
354                #free_params
355                let mut seq = params.sequence();
356                #(#decode_fields);*
357                (#params_fields)
358            }
359        };
360
361        // Parameters are positional, like geth: object (named) params fail to parse as an array.
362        let parsing = quote! {
363            let (#params_fields) = {
364                #decode_array
365            };
366        };
367
368        (parsing, params_fields)
369    }
370}
371
372/// Turns `async fn f(..) -> R` into `fn f(..) -> impl Future<Output = R> + Send`.
373///
374/// The server trait stays usable from the `Send` futures that `RpcModule` registers, without boxing
375/// every call the way `async_trait` does. Implementations can still use `async fn`.
376fn desugar_async(method: &mut syn::TraitItemFn) {
377    let sig = &mut method.sig;
378    if sig.asyncness.take().is_none() {
379        return;
380    }
381    let output = match &sig.output {
382        syn::ReturnType::Default => quote! { () },
383        syn::ReturnType::Type(_, ty) => quote! { #ty },
384    };
385    sig.output = syn::parse_quote! {
386        -> impl ::core::future::Future<Output = #output> + ::core::marker::Send
387    };
388    // A default body becomes the body of the returned future.
389    if let Some(body) = &mut method.default {
390        *body = syn::parse_quote!({ async move #body });
391    }
392}