#[rpc]Expand description
Main RPC macro.
§Description
This macro generates the server implementation of an RPC trait, a trait mostly equivalent to the input, with the following differences:
- The trait will have one additional (already implemented) method,
into_rpc, which turns any object that implements the server trait into anRpcModule. - For subscription methods:
- There will be one additional argument inserted right after
&self:subscription_sink: SubscriptionSink. It should be used to accept or reject a subscription and send data back to the subscribers. - The return type of the subscription method must implement
IntoSubscriptionCloseResponse.
- There will be one additional argument inserted right after
For the Foo trait, the server trait will be named FooServer, which must be implemented on
your type.
Note: you need to import the jsonrpsee façade crate in your code for the macro to work
properly.
Parameters are positional: they must be sent as a JSON array, like geth expects. Requests with object (named) parameters are answered with an invalid params error.
§Prerequisites
- Implementors of the server trait must be
Sync,Send,Sizedand'static. If you want to implement this trait on some type that is not thread-safe, consider usingArc<RwLock<..>>.
§Examples
Below you can find examples of the macro usage along with the code that generated for it by the macro.
#[rpc(server, namespace = "foo", namespace_separator = ".")]
pub trait Rpc {
#[method(name = "foo")]
async fn async_method(&self, param_a: u8, param_b: String) -> u16;
#[method(name = "bar")]
fn sync_method(&self) -> String;
#[subscription(name = "subscribe", item = String)]
async fn sub(&self) -> SubscriptionResult;
}Server code that will be generated:
pub trait RpcServer {
// RPC methods are normal methods and can be either sync or async. Async methods return a
// `Send` future, implementations can use `async fn`.
fn async_method(&self, param_a: u8, param_b: String) -> impl Future<Output = u16> + Send;
fn sync_method(&self) -> String;
// Note that `pending_subscription_sink` was added automatically.
fn sub(&self, pending: PendingSubscriptionSink) -> impl Future<Output = SubscriptionResult> + Send;
fn into_rpc(self) -> Result<Self, jsonrpsee::core::Error> {
// Actual implementation stripped, but inside we will create
// a module with one method and one subscription
}
}§Attributes
§rpc attribute
rpc attribute is applied to a trait in order to turn it into an RPC implementation.
Arguments:
server(mandatory): generate<Trait>Servertrait for the server implementation.namespace: add a prefix to all the methods and subscriptions in this RPC. For example, with namespacefooand methodspam, the resulting method name will befoo_spam.namespace_separator: customize the separator used between namespace and method name. Defaults to_. For example,namespace = "foo", namespace_separator = "."results in method names likefoo.barinstead offoo_bar.server_bounds: replace all auto-generated trait bounds with the user-defined ones for the server implementation.
Trait requirements:
A trait wrapped with the rpc attribute must not:
- have associated types or constants;
- have Rust methods not marked with either the
methodorsubscriptionattribute; - be empty.
§method attribute
method attribute is used to define an RPC method.
Arguments:
name(mandatory): name of the RPC method. Does not have to be the same as the Rust method name.aliases: list of name aliases for the RPC method as a comma separated string. Aliases are processed ignoring the namespace, so add the complete name, including the namespace.blocking: when set method execution will always spawn on a dedicated thread. Only usable with non-asyncmethods.
Method requirements:
A Rust method marked with the method attribute, may:
- be either
asyncor not; - have input parameters or not;
- have a return value or not (in the latter case, it will be considered a notification method).
§subscription attribute
subscription attribute is used to define a publish/subscribe interface according to the ethereum pubsub specification
Arguments:
name(mandatory): name of the RPC method. Does not have to be the same as the Rust method name.unsubscribe(optional): name of the RPC method to unsubscribe from the subscription. Must not be the same asname. This is generated for you if the subscription name starts withsubscribe.aliases(optional): aliases forname. Aliases are processed ignoring the namespace, so add the complete name, including the namespace.unsubscribe_aliases(optional): Similar toaliasesbut forunsubscribe.item(mandatory): type of items yielded by the subscription. Note that it must be the type, not string.
Method requirements:
Rust method marked with the subscription attribute must:
- be asynchronous;
- return a type that implements
jsonrpsee::server::IntoSubscriptionCloseResponse.
Rust method marked with subscription attribute may:
- have input parameters or not.
§Full workflow example
//! Example of using proc macro to generate a working server.
use std::net::SocketAddr;
use jsonrpsee::server::ServerBuilder;
// RPC is put into a separate module to clearly show names of generated entities.
mod rpc_impl {
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::{PendingSubscriptionSink, SubscriptionMessage, IntoSubscriptionCloseResponse, SubscriptionCloseResponse};
use jsonrpsee::core::{RpcResult, SubscriptionResult, to_json_raw_value};
pub enum CloseResponse {
None,
Failed,
}
impl IntoSubscriptionCloseResponse for CloseResponse {
fn into_response(self) -> SubscriptionCloseResponse {
match self {
// Do not send a close response when the subscription is terminated.
CloseResponse::None => SubscriptionCloseResponse::None,
// Send a close response as an ordinary subscription notification
// when the subscription is terminated.
CloseResponse::Failed => {
let err = to_json_raw_value(&"Failed").unwrap();
SubscriptionCloseResponse::Notif(err.into())
}
}
}
}
// Generate the server implementation, prepend all the methods with `foo_` prefix.
#[rpc(server, namespace = "foo")]
pub trait MyRpc {
#[method(name = "foo")]
async fn async_method(
&self,
param_a: u8,
param_b: String
) -> RpcResult<u16>;
#[method(name = "bar")]
fn sync_method(&self) -> RpcResult<u16>;
/// Methods can have default implementations, which implementors may leave out.
#[method(name = "baz")]
async fn default_method(&self, param: u16) -> RpcResult<u16> {
Ok(param + self.sync_method()?)
}
/// Override the `foo_sub` and use `foo_subNotif` for the notifications.
///
/// The item field indicates which type goes into result field below.
///
/// The notification format:
///
/// ```
/// {
/// "jsonrpc":"2.0",
/// "method":"foo_subNotif",
/// "params":["subscription":"someID", "result":"some string"]
/// }
/// ```
#[subscription(name = "sub" => "subNotif", unsubscribe = "unsub", item = String)]
async fn sub_override_notif_method(&self) -> SubscriptionResult;
/// Use the same method name for both the `subscribe call` and `notifications`
///
/// The unsubscribe method name is generated here `foo_unsubscribe`
/// Thus the `unsubscribe attribute` is not needed unless a custom unsubscribe method name is wanted.
///
/// The notification format:
///
/// ```
/// {
/// "jsonrpc":"2.0",
/// "method":"foo_subscribe",
/// "params":["subscription":"someID", "result":"some string"]
/// }
/// ```
#[subscription(name = "subscribe", item = String)]
async fn sub(&self) -> SubscriptionResult;
#[subscription(name = "sub_custom_close_msg", unsubscribe = "unsub_custom_close_msg", item = String)]
async fn sub_custom_close_msg(&self) -> CloseResponse;
}
// Structure that will implement the `MyRpcServer` trait.
// It can have fields, if required, as long as it's still `Send + Sync + 'static`.
pub struct RpcServerImpl;
// Note that the trait name we use is `MyRpcServer`, not `MyRpc`!
impl MyRpcServer for RpcServerImpl {
async fn async_method(&self, _param_a: u8, _param_b: String) -> RpcResult<u16> {
Ok(42)
}
fn sync_method(&self) -> RpcResult<u16> {
Ok(10)
}
// The stream API can be used to pipe items from the underlying stream
// as subscription responses.
async fn sub_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult {
let mut sink = pending.accept().await?;
let msg = to_json_raw_value(&"Response_A").unwrap();
sink.send(msg).await?;
Ok(())
}
// Send out two values on the subscription.
async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult {
let sink = pending.accept().await?;
let msg1 = to_json_raw_value(&"Response_A").unwrap();
let msg2 = to_json_raw_value(&"Response_B").unwrap();
sink.send(msg1).await?;
sink.send(msg2).await?;
Ok(())
}
// If one doesn't want sent out a close message when a subscription terminates or treat
// errors as subscription error notifications then it's possible to implement
// `IntoSubscriptionCloseResponse` for customized behavior.
async fn sub_custom_close_msg(&self, pending: PendingSubscriptionSink) -> CloseResponse {
let Ok(sink) = pending.accept().await else {
return CloseResponse::None;
};
let msg = to_json_raw_value(&"Response_A").unwrap();
if sink.send(msg).await.is_ok() {
CloseResponse::Failed
} else {
CloseResponse::None
}
}
}
}
// Use the generated server implementation.
use rpc_impl::{MyRpcServer, RpcServerImpl};
pub async fn server() -> SocketAddr {
let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap();
let addr = server.local_addr().unwrap();
let server_handle = server.start(RpcServerImpl.into_rpc());
// `into_rpc()` method was generated inside of the `RpcServer` trait under the hood.
tokio::spawn(server_handle.stopped());
addr
}
#[tokio::main]
async fn main() {
// The generated `RpcModule` can also be called directly without a transport.
let module = RpcServerImpl.into_rpc();
let (res, _) =
module.raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_bar","id":1}"#, 1).await.unwrap();
assert_eq!(res, r#"{"jsonrpc":"2.0","id":1,"result":10}"#);
server().await;
}