Skip to main content

jsonrpsee_proc_macros/
lib.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
27//! # jsonrpsee-proc-macros
28//!
29//! Vendored from [jsonrpsee](https://github.com/paritytech/jsonrpsee), Copyright (c) 2019-2021
30//! Parity Technologies (UK) Ltd., licensed under the MIT License (see
31//! `crates/rpc/jsonrpsee/LICENSE`).
32
33#![cfg_attr(not(test), warn(unused_crate_dependencies))]
34#![cfg_attr(docsrs, feature(doc_cfg))]
35
36use proc_macro::TokenStream;
37use rpc_macro::RpcDescription;
38
39mod attributes;
40mod helpers;
41mod render_server;
42mod rpc_macro;
43pub(crate) mod visitor;
44
45/// Main RPC macro.
46///
47/// ## Description
48///
49/// This macro generates the server implementation of an RPC trait, a trait mostly equivalent to
50/// the input, with the following differences:
51///
52/// - The trait will have one additional (already implemented) method, `into_rpc`, which turns any
53///   object that implements the server trait into an `RpcModule`.
54/// - For subscription methods:
55///   - There will be one additional argument inserted right after `&self`: `subscription_sink:
56///     SubscriptionSink`. It should be used to accept or reject a subscription and send data back
57///     to the subscribers.
58///   - The return type of the subscription method must implement `IntoSubscriptionCloseResponse`.
59///
60/// For the `Foo` trait, the server trait will be named `FooServer`, which must be implemented on
61/// your type.
62///
63/// Note: you need to import the `jsonrpsee` façade crate in your code for the macro to work
64/// properly.
65///
66/// Parameters are positional: they must be sent as a JSON array, like geth expects. Requests with
67/// object (named) parameters are answered with an invalid params error.
68///
69/// ## Prerequisites
70///
71/// - Implementors of the server trait must be `Sync`, `Send`, `Sized` and `'static`. If you want to
72///   implement this trait on some type that is not thread-safe, consider using `Arc<RwLock<..>>`.
73///
74/// ## Examples
75///
76/// Below you can find examples of the macro usage along with the code
77/// that generated for it by the macro.
78///
79/// ```ignore
80/// #[rpc(server, namespace = "foo", namespace_separator = ".")]
81/// pub trait Rpc {
82///     #[method(name = "foo")]
83///     async fn async_method(&self, param_a: u8, param_b: String) -> u16;
84///     #[method(name = "bar")]
85///     fn sync_method(&self) -> String;
86///
87///     #[subscription(name = "subscribe", item = String)]
88///     async fn sub(&self) -> SubscriptionResult;
89/// }
90/// ```
91///
92/// Server code that will be generated:
93///
94/// ```ignore
95/// pub trait RpcServer {
96///     // RPC methods are normal methods and can be either sync or async. Async methods return a
97///     // `Send` future, implementations can use `async fn`.
98///     fn async_method(&self, param_a: u8, param_b: String) -> impl Future<Output = u16> + Send;
99///     fn sync_method(&self) -> String;
100///
101///     // Note that `pending_subscription_sink` was added automatically.
102///     fn sub(&self, pending: PendingSubscriptionSink) -> impl Future<Output = SubscriptionResult> + Send;
103///
104///     fn into_rpc(self) -> Result<Self, jsonrpsee::core::Error> {
105///         // Actual implementation stripped, but inside we will create
106///         // a module with one method and one subscription
107///     }
108/// }
109/// ```
110///
111/// ## Attributes
112///
113/// ### `rpc` attribute
114///
115/// `rpc` attribute is applied to a trait in order to turn it into an RPC implementation.
116///
117/// **Arguments:**
118///
119/// - `server` (mandatory): generate `<Trait>Server` trait for the server implementation.
120/// - `namespace`: add a prefix to all the methods and subscriptions in this RPC. For example, with
121///   namespace `foo` and method `spam`, the resulting method name will be `foo_spam`.
122/// - `namespace_separator`: customize the separator used between namespace and method name.
123///   Defaults to `_`. For example, `namespace = "foo", namespace_separator = "."` results in method
124///   names like `foo.bar` instead of `foo_bar`.
125/// - `server_bounds`: replace *all* auto-generated trait bounds with the user-defined ones for the
126///   server implementation.
127///
128/// **Trait requirements:**
129///
130/// A trait wrapped with the `rpc` attribute **must not**:
131///
132/// - have associated types or constants;
133/// - have Rust methods not marked with either the `method` or `subscription` attribute;
134/// - be empty.
135///
136/// ### `method` attribute
137///
138/// `method` attribute is used to define an RPC method.
139///
140/// **Arguments:**
141///
142/// - `name` (mandatory): name of the RPC method. Does not have to be the same as the Rust method
143///   name.
144/// - `aliases`: list of name aliases for the RPC method as a comma separated string. Aliases are
145///   processed ignoring the namespace, so add the complete name, including the namespace.
146/// - `blocking`: when set method execution will always spawn on a dedicated thread. Only usable
147///   with non-`async` methods.
148///
149/// **Method requirements:**
150///
151/// A Rust method marked with the `method` attribute, **may**:
152///
153/// - be either `async` or not;
154/// - have input parameters or not;
155/// - have a return value or not (in the latter case, it will be considered a notification method).
156///
157/// ### `subscription` attribute
158///
159/// `subscription` attribute is used to define a publish/subscribe interface according to the [ethereum pubsub specification](https://geth.ethereum.org/docs/rpc/pubsub)
160///
161/// **Arguments:**
162///
163/// - `name` (mandatory): name of the RPC method. Does not have to be the same as the Rust method
164///   name.
165/// - `unsubscribe` (optional): name of the RPC method to unsubscribe from the subscription. Must
166///   not be the same as `name`. This is generated for you if the subscription name starts with
167///   `subscribe`.
168/// - `aliases` (optional): aliases for `name`. Aliases are processed ignoring the namespace, so add
169///   the complete name, including the namespace.
170/// - `unsubscribe_aliases` (optional): Similar to `aliases` but for `unsubscribe`.
171/// - `item` (mandatory): type of items yielded by the subscription. Note that it must be the type,
172///   not string.
173///
174/// **Method requirements:**
175///
176/// Rust method marked with the `subscription` attribute **must**:
177///
178/// - be asynchronous;
179/// - return a type that implements `jsonrpsee::server::IntoSubscriptionCloseResponse`.
180///
181/// Rust method marked with `subscription` attribute **may**:
182///
183/// - have input parameters or not.
184///
185/// ## Full workflow example
186///
187/// ```rust
188/// //! Example of using proc macro to generate a working server.
189///
190/// use std::net::SocketAddr;
191///
192/// use jsonrpsee::server::ServerBuilder;
193///
194/// // RPC is put into a separate module to clearly show names of generated entities.
195/// mod rpc_impl {
196///     use jsonrpsee::proc_macros::rpc;
197///     use jsonrpsee::server::{PendingSubscriptionSink, SubscriptionMessage, IntoSubscriptionCloseResponse, SubscriptionCloseResponse};
198///     use jsonrpsee::core::{RpcResult, SubscriptionResult, to_json_raw_value};
199///
200///     pub enum CloseResponse {
201///         None,
202///         Failed,
203///     }
204///
205///     impl IntoSubscriptionCloseResponse for CloseResponse {
206///         fn into_response(self) -> SubscriptionCloseResponse {
207///            match self {
208///                // Do not send a close response when the subscription is terminated.
209///                CloseResponse::None => SubscriptionCloseResponse::None,
210///                // Send a close response as an ordinary subscription notification
211///                // when the subscription is terminated.
212///                CloseResponse::Failed => {
213///                     let err = to_json_raw_value(&"Failed").unwrap();
214///                     SubscriptionCloseResponse::Notif(err.into())
215///                }
216///            }
217///         }
218///     }
219///
220///     // Generate the server implementation, prepend all the methods with `foo_` prefix.
221///     #[rpc(server, namespace = "foo")]
222///     pub trait MyRpc {
223///         #[method(name = "foo")]
224///         async fn async_method(
225///             &self,
226///             param_a: u8,
227///             param_b: String
228///         ) -> RpcResult<u16>;
229///
230///         #[method(name = "bar")]
231///         fn sync_method(&self) -> RpcResult<u16>;
232///
233///         /// Methods can have default implementations, which implementors may leave out.
234///         #[method(name = "baz")]
235///         async fn default_method(&self, param: u16) -> RpcResult<u16> {
236///             Ok(param + self.sync_method()?)
237///         }
238///
239///         /// Override the `foo_sub` and use `foo_subNotif` for the notifications.
240///         ///
241///         /// The item field indicates which type goes into result field below.
242///         ///
243///         /// The notification format:
244///         ///
245///         /// ```
246///         /// {
247///         ///     "jsonrpc":"2.0",
248///         ///     "method":"foo_subNotif",
249///         ///     "params":["subscription":"someID", "result":"some string"]
250///         /// }
251///         /// ```
252///         #[subscription(name = "sub" => "subNotif", unsubscribe = "unsub", item = String)]
253///         async fn sub_override_notif_method(&self) -> SubscriptionResult;
254///
255///         /// Use the same method name for both the `subscribe call` and `notifications`
256///         ///
257///         /// The unsubscribe method name is generated here `foo_unsubscribe`
258///         /// Thus the `unsubscribe attribute` is not needed unless a custom unsubscribe method name is wanted.
259///         ///
260///         /// The notification format:
261///         ///
262///         /// ```
263///         /// {
264///         ///     "jsonrpc":"2.0",
265///         ///     "method":"foo_subscribe",
266///         ///     "params":["subscription":"someID", "result":"some string"]
267///         /// }
268///         /// ```
269///         #[subscription(name = "subscribe", item = String)]
270///         async fn sub(&self) -> SubscriptionResult;
271///
272///         #[subscription(name = "sub_custom_close_msg", unsubscribe = "unsub_custom_close_msg", item = String)]
273///         async fn sub_custom_close_msg(&self) -> CloseResponse;
274///     }
275///
276///     // Structure that will implement the `MyRpcServer` trait.
277///     // It can have fields, if required, as long as it's still `Send + Sync + 'static`.
278///     pub struct RpcServerImpl;
279///
280///     // Note that the trait name we use is `MyRpcServer`, not `MyRpc`!
281///     impl MyRpcServer for RpcServerImpl {
282///         async fn async_method(&self, _param_a: u8, _param_b: String) -> RpcResult<u16> {
283///             Ok(42)
284///         }
285///
286///         fn sync_method(&self) -> RpcResult<u16> {
287///             Ok(10)
288///         }
289///
290///         // The stream API can be used to pipe items from the underlying stream
291///         // as subscription responses.
292///         async fn sub_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult {
293///             let mut sink = pending.accept().await?;
294///             let msg = to_json_raw_value(&"Response_A").unwrap();
295///             sink.send(msg).await?;
296///             Ok(())
297///         }
298///
299///         // Send out two values on the subscription.
300///         async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult {
301///             let sink = pending.accept().await?;
302///
303///             let msg1 = to_json_raw_value(&"Response_A").unwrap();
304///             let msg2 = to_json_raw_value(&"Response_B").unwrap();
305///
306///             sink.send(msg1).await?;
307///             sink.send(msg2).await?;
308///
309///             Ok(())
310///         }
311///
312///         // If one doesn't want sent out a close message when a subscription terminates or treat
313///         // errors as subscription error notifications then it's possible to implement
314///         // `IntoSubscriptionCloseResponse` for customized behavior.
315///         async fn sub_custom_close_msg(&self, pending: PendingSubscriptionSink) -> CloseResponse {
316///             let Ok(sink) = pending.accept().await else {
317///                 return CloseResponse::None;
318///             };
319///
320///             let msg = to_json_raw_value(&"Response_A").unwrap();
321///
322///             if sink.send(msg).await.is_ok() {
323///                 CloseResponse::Failed
324///             } else {
325///                 CloseResponse::None
326///             }
327///         }
328///     }
329/// }
330///
331/// // Use the generated server implementation.
332/// use rpc_impl::{MyRpcServer, RpcServerImpl};
333///
334/// pub async fn server() -> SocketAddr {
335///     let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap();
336///     let addr = server.local_addr().unwrap();
337///     let server_handle = server.start(RpcServerImpl.into_rpc());
338///
339///     // `into_rpc()` method was generated inside of the `RpcServer` trait under the hood.
340///     tokio::spawn(server_handle.stopped());
341///
342///     addr
343/// }
344///
345/// #[tokio::main]
346/// async fn main() {
347///     // The generated `RpcModule` can also be called directly without a transport.
348///     let module = RpcServerImpl.into_rpc();
349///     let (res, _) =
350///         module.raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_bar","id":1}"#, 1).await.unwrap();
351///     assert_eq!(res, r#"{"jsonrpc":"2.0","id":1,"result":10}"#);
352///
353///     server().await;
354/// }
355/// ```
356#[proc_macro_attribute]
357pub fn rpc(attr: TokenStream, item: TokenStream) -> TokenStream {
358    let rebuilt_rpc_attribute = syn::Attribute {
359        pound_token: syn::token::Pound::default(),
360        style: syn::AttrStyle::Outer,
361        bracket_token: syn::token::Bracket::default(),
362        meta: syn::Meta::List(syn::MetaList {
363            path: syn::Ident::new("rpc", proc_macro2::Span::call_site()).into(),
364            delimiter: syn::MacroDelimiter::Paren(
365                syn::token::Paren(proc_macro2::Span::call_site()),
366            ),
367            tokens: attr.into(),
368        }),
369    };
370    match rpc_impl(rebuilt_rpc_attribute, item) {
371        Ok(tokens) => tokens,
372        Err(err) => err.to_compile_error(),
373    }
374    .into()
375}
376
377/// Convenience form of `rpc` that may use `?` for error handling to avoid boilerplate.
378fn rpc_impl(
379    attr: syn::Attribute,
380    item: TokenStream,
381) -> Result<proc_macro2::TokenStream, syn::Error> {
382    let trait_data: syn::ItemTrait = syn::parse(item)?;
383    let rpc = RpcDescription::from_item(attr, trait_data)?;
384    rpc.render()
385}