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