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}