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}