| Crates.io | nanorpc |
| lib.rs | nanorpc |
| version | 0.2.0-beta.1 |
| created_at | 2022-08-19 00:32:11.888763+00 |
| updated_at | 2025-12-31 22:31:04.239381+00 |
| description | a subset of JSON-RPC 2.0, with magical autogeneration of servers and clients |
| homepage | |
| repository | https://github.com/themeliolabs/nanorpc |
| max_upload_size | |
| id | 648441 |
| size | 68,271 |
nanorpc lets you define an RPC protocol once (as a Rust trait) and get:
The crate focuses on a small, practical subset of JSON-RPC 2.0: positional
arguments (params as an array) and request/response (not notifications).
Define a protocol with #[nanorpc_derive] and implement it as normal Rust:
use nanorpc::nanorpc_derive;
#[nanorpc_derive]
pub trait MathProtocol {
async fn add(&self, x: f64, y: f64) -> f64;
async fn mult(&self, x: f64, y: f64) -> f64;
async fn maybe_fail(&self) -> Result<f64, String>;
}
The macro generates:
MathService<T: MathProtocol> which implements nanorpc::RpcService.MathClient<T: RpcTransport> with methods add, mult, maybe_fail.MathError<T> which is the client-side error type.This example uses an in-process loopback transport to show the entire flow without any network code. A real transport would send JSON over HTTP, TCP, etc.
use nanorpc::{
nanorpc_derive, JrpcRequest, JrpcResponse, RpcService, RpcTransport,
};
#[nanorpc_derive]
pub trait MathProtocol {
async fn add(&self, x: f64, y: f64) -> f64;
async fn maybe_fail(&self) -> Result<f64, String>;
}
struct MathImpl;
impl MathProtocol for MathImpl {
async fn add(&self, x: f64, y: f64) -> f64 {
x + y
}
async fn maybe_fail(&self) -> Result<f64, String> {
Err("nope".into())
}
}
struct Loopback<T>(T);
impl<T: RpcService> RpcTransport for Loopback<T> {
type Error = std::convert::Infallible;
async fn call_raw(&self, req: JrpcRequest) -> Result<JrpcResponse, Self::Error> {
Ok(self.0.respond_raw(req).await)
}
}
async fn demo() {
let service = MathService(MathImpl);
let client = MathClient(Loopback(service));
let sum = client.add(2.0, 3.0).await.unwrap();
assert_eq!(sum, 5.0);
let err = client.maybe_fail().await.unwrap_err();
assert!(format!("{err}").contains("nope"));
}
There are three layers of errors:
Result.Generated client methods return Result<T, ProtocolError<TransportErr>>:
ProtocolError::Transport(e) for transport failures.ProtocolError::NotFound if the server reports "method not found".ProtocolError::FailedDecode if a JSON decode fails.ProtocolError::ServerFail when an infallible method returns an error.You can also build servers and clients directly on the JSON layer:
RpcService::respond_raw(JrpcRequest) -> JrpcResponseRpcTransport::call_raw(JrpcRequest) -> JrpcResponseThe higher-level respond/call methods translate between JSON-RPC types and
serde_json::Value.
Example JSON-RPC request/response:
{"jsonrpc": "2.0", "method": "mult", "params": [42, 23], "id": 1}
{"jsonrpc": "2.0", "result": 966, "id": 1}
nanorpc ships a few helpers:
DynRpcTransport: type-erased transport using anyhow::Error.OrService: chain two services; fall back to the second if the first
does not recognize a method.FnService: wrap an async function/closure as a RpcService.Vec<serde_json::Value>), not named.id.