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")]
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/// - `server_bounds`: replace *all* auto-generated trait bounds with the user-defined ones for the server
151///   implementation.
152/// - `client_bounds`: replace *all* auto-generated trait bounds with the user-defined ones for the client
153///   implementation.
154///
155/// **Trait requirements:**
156///
157/// A trait wrapped with the `rpc` attribute **must not**:
158///
159/// - have associated types or constants;
160/// - have Rust methods not marked with either the `method` or `subscription` attribute;
161/// - be empty.
162///
163/// At least one of the `server` or `client` flags must be provided, otherwise the compilation will err.
164///
165/// ### `method` attribute
166///
167/// `method` attribute is used to define an RPC method.
168///
169/// **Arguments:**
170///
171/// - `name` (mandatory): name of the RPC method. Does not have to be the same as the Rust method name.
172/// - `aliases`: list of name aliases for the RPC method as a comma separated string.
173///              Aliases are processed ignoring the namespace, so add the complete name, including the
174///              namespace.
175/// - `blocking`: when set method execution will always spawn on a dedicated thread. Only usable with non-`async` methods.
176/// - `param_kind`: kind of structure to use for parameter passing. Can be "array" or "map", defaults to "array".
177///
178/// **Method requirements:**
179///
180/// A Rust method marked with the `method` attribute, **may**:
181///
182/// - be either `async` or not;
183/// - have input parameters or not;
184/// - have a return value or not (in the latter case, it will be considered a notification method).
185///
186/// ### `subscription` attribute
187///
188/// `subscription` attribute is used to define a publish/subscribe interface according to the [ethereum pubsub specification](https://geth.ethereum.org/docs/rpc/pubsub)
189///
190/// **Arguments:**
191///
192/// - `name` (mandatory): name of the RPC method. Does not have to be the same as the Rust method name.
193/// - `unsubscribe` (optional): name of the RPC method to unsubscribe from the subscription. Must not be the same as `name`.
194///                             This is generated for you if the subscription name starts with `subscribe`.
195/// - `aliases` (optional): aliases for `name`. Aliases are processed ignoring the namespace,
196///                         so add the complete name, including the namespace.
197/// - `unsubscribe_aliases` (optional): Similar to `aliases` but for `unsubscribe`.
198/// - `item` (mandatory): type of items yielded by the subscription. Note that it must be the type, not string.
199/// - `param_kind`: kind of structure to use for parameter passing. Can be "array" or "map", defaults to "array".
200///
201/// **Method requirements:**
202///
203/// Rust method marked with the `subscription` attribute **must**:
204///
205/// - be asynchronous;
206/// - return a type that implements `jsonrpsee::server::IntoSubscriptionCloseResponse`.
207///
208/// Rust method marked with `subscription` attribute **may**:
209///
210/// - have input parameters or not.
211///
212/// ### `argument` attribute
213///
214/// `argument` attribute is used to modify a function argument.
215///
216/// **Arguments:**
217///
218/// - `rename`: rename the generated JSON key.
219///
220///
221/// ## Full workflow example
222///
223/// ```rust
224/// //! Example of using proc macro to generate working client and server.
225///
226/// use std::net::SocketAddr;
227///
228/// use futures_channel::oneshot;
229/// use jsonrpsee::{ws_client::*, server::ServerBuilder};
230///
231/// // RPC is put into a separate module to clearly show names of generated entities.
232/// mod rpc_impl {
233///     use jsonrpsee::{proc_macros::rpc, Extensions};
234///     use jsonrpsee::server::{PendingSubscriptionSink, SubscriptionMessage, IntoSubscriptionCloseResponse, SubscriptionCloseResponse};
235///     use jsonrpsee::core::{async_trait, RpcResult, SubscriptionResult};
236///
237///     enum CloseResponse {
238///         None,
239///         Failed,
240///     }
241///
242///     impl IntoSubscriptionCloseResponse for CloseResponse {
243///         fn into_response(self) -> SubscriptionCloseResponse {
244///            match self {
245///                // Do not send a close response when the subscription is terminated.
246///                CloseResponse::None => SubscriptionCloseResponse::None,
247///                // Send a close response as an ordinary subscription notification
248///                // when the subscription is terminated.
249///                CloseResponse::Failed => SubscriptionCloseResponse::Notif("failed".into()),
250///            }
251///         }
252///     }
253///
254///     // Generate both server and client implementations, prepend all the methods with `foo_` prefix.
255///     #[rpc(client, server, namespace = "foo")]
256///     pub trait MyRpc {
257///         #[method(name = "foo")]
258///         async fn async_method(
259///             &self,
260///             param_a: u8,
261///             #[argument(rename = "param_c")]
262///             param_b: String
263///         ) -> RpcResult<u16>;
264///
265///         #[method(name = "bar")]
266///         fn sync_method(&self) -> RpcResult<u16>;
267///
268///         #[method(name = "baz", blocking)]
269///         fn blocking_method(&self) -> RpcResult<u16>;
270///
271///         /// Override the `foo_sub` and use `foo_subNotif` for the notifications.
272///         ///
273///         /// The item field indicates which type goes into result field below.
274///         ///
275///         /// The notification format:
276///         ///
277///         /// ```
278///         /// {
279///         ///     "jsonrpc":"2.0",
280///         ///     "method":"foo_subNotif",
281///         ///     "params":["subscription":"someID", "result":"some string"]
282///         /// }
283///         /// ```
284///         #[subscription(name = "sub" => "subNotif", unsubscribe = "unsub", item = String)]
285///         async fn sub_override_notif_method(&self) -> SubscriptionResult;
286///
287///         /// Use the same method name for both the `subscribe call` and `notifications`
288///         ///
289///         /// The unsubscribe method name is generated here `foo_unsubscribe`
290///         /// Thus the `unsubscribe attribute` is not needed unless a custom unsubscribe method name is wanted.
291///         ///
292///         /// The notification format:
293///         ///
294///         /// ```
295///         /// {
296///         ///     "jsonrpc":"2.0",
297///         ///     "method":"foo_subscribe",
298///         ///     "params":["subscription":"someID", "result":"some string"]
299///         /// }
300///         /// ```
301///         #[subscription(name = "subscribe", item = String)]
302///         async fn sub(&self) -> SubscriptionResult;
303///
304///         #[subscription(name = "sub_custom_close_msg", unsubscribe = "unsub_custom_close_msg", item = String)]
305///         async fn sub_custom_close_msg(&self) -> CloseResponse;
306///     }
307///
308///     // Structure that will implement the `MyRpcServer` trait.
309///     // It can have fields, if required, as long as it's still `Send + Sync + 'static`.
310///     pub struct RpcServerImpl;
311///
312///     // Note that the trait name we use is `MyRpcServer`, not `MyRpc`!
313///     #[async_trait]
314///     impl MyRpcServer for RpcServerImpl {
315///         async fn async_method(&self, _param_a: u8, _param_b: String) -> RpcResult<u16> {
316///             Ok(42)
317///         }
318///
319///         fn sync_method(&self) -> RpcResult<u16> {
320///             Ok(10)
321///         }
322///
323///         fn blocking_method(&self) -> RpcResult<u16> {
324///             // This will block current thread for 1 second, which is fine since we marked
325///             // this method as `blocking` above.
326///             std::thread::sleep(std::time::Duration::from_millis(1000));
327///             Ok(11)
328///         }
329///
330///         // The stream API can be used to pipe items from the underlying stream
331///         // as subscription responses.
332///         async fn sub_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult {
333///             let mut sink = pending.accept().await?;
334///             sink.send("Response_A".into()).await?;
335///             Ok(())
336///         }
337///
338///         // Send out two values on the subscription.
339///         async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult {
340///             let sink = pending.accept().await?;
341///
342///             let msg1 = SubscriptionMessage::from("Response_A");
343///             let msg2 = SubscriptionMessage::from("Response_B");
344///
345///             sink.send(msg1).await?;
346///             sink.send(msg2).await?;
347///
348///             Ok(())
349///         }
350///
351///         // If one doesn't want sent out a close message when a subscription terminates or treat
352///         // errors as subscription error notifications then it's possible to implement
353///         // `IntoSubscriptionCloseResponse` for customized behavior.
354///         async fn sub_custom_close_msg(&self, pending: PendingSubscriptionSink) -> CloseResponse {
355///             let Ok(sink) = pending.accept().await else {
356///                 return CloseResponse::None;
357///             };
358///
359///             if sink.send("Response_A".into()).await.is_ok() {
360///                 CloseResponse::Failed
361///             } else {
362///                 CloseResponse::None
363///             }
364///
365///         }
366///     }
367/// }
368///
369/// // Use the generated implementations of server and client.
370/// use rpc_impl::{MyRpcClient, MyRpcServer, RpcServerImpl};
371///
372/// pub async fn server() -> SocketAddr {
373///     let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap();
374///     let addr = server.local_addr().unwrap();
375///     let server_handle = server.start(RpcServerImpl.into_rpc());
376///
377///     // `into_rpc()` method was generated inside of the `RpcServer` trait under the hood.
378///     tokio::spawn(server_handle.stopped());
379///
380///     addr
381/// }
382///
383/// // In the main function, we start the server, create a client connected to this server,
384/// // and call the available methods.
385/// #[tokio::main]
386/// async fn main() {
387///     let server_addr = server().await;
388///     let server_url = format!("ws://{}", server_addr);
389///     // Note that we create the client as usual, but thanks to the `use rpc_impl::MyRpcClient`,
390///     // the client object will have all the methods to interact with the server.
391///     let client = WsClientBuilder::default().build(&server_url).await.unwrap();
392///
393///     // Invoke RPC methods.
394///     assert_eq!(client.async_method(10, "a".into()).await.unwrap(), 42);
395///     assert_eq!(client.sync_method().await.unwrap(), 10);
396///
397///     // Subscribe and receive messages from the subscription.
398///     let mut sub = client.sub().await.unwrap();
399///     let first_recv = sub.next().await.unwrap().unwrap();
400///     assert_eq!(first_recv, "Response_A".to_string());
401///     let second_recv = sub.next().await.unwrap().unwrap();
402///     assert_eq!(second_recv, "Response_B".to_string());
403/// }
404/// ```
405#[proc_macro_attribute]
406pub fn rpc(attr: TokenStream, item: TokenStream) -> TokenStream {
407	let rebuilt_rpc_attribute = syn::Attribute {
408		pound_token: syn::token::Pound::default(),
409		style: syn::AttrStyle::Outer,
410		bracket_token: syn::token::Bracket::default(),
411		meta: syn::Meta::List(syn::MetaList {
412			path: syn::Ident::new("rpc", proc_macro2::Span::call_site()).into(),
413			delimiter: syn::MacroDelimiter::Paren(syn::token::Paren(proc_macro2::Span::call_site())),
414			tokens: attr.into(),
415		}),
416	};
417	match rpc_impl(rebuilt_rpc_attribute, item) {
418		Ok(tokens) => tokens,
419		Err(err) => err.to_compile_error(),
420	}
421	.into()
422}
423
424/// Convenience form of `rpc` that may use `?` for error handling to avoid boilerplate.
425fn rpc_impl(attr: syn::Attribute, item: TokenStream) -> Result<proc_macro2::TokenStream, syn::Error> {
426	let trait_data: syn::ItemTrait = syn::parse(item)?;
427	let rpc = RpcDescription::from_item(attr, trait_data)?;
428	rpc.render()
429}