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}