3 releases

0.1.5 Nov 27, 2022
0.1.4 Nov 8, 2022
0.1.3 Oct 30, 2022
0.1.0 Aug 19, 2022

#1464 in Procedural macros

Download history 305/week @ 2023-08-08 268/week @ 2023-08-15 255/week @ 2023-08-22 310/week @ 2023-08-29 541/week @ 2023-09-05 271/week @ 2023-09-12 297/week @ 2023-09-19 309/week @ 2023-09-26 268/week @ 2023-10-03 473/week @ 2023-10-10 476/week @ 2023-10-17 393/week @ 2023-10-24 433/week @ 2023-10-31 350/week @ 2023-11-07 257/week @ 2023-11-14 298/week @ 2023-11-21

1,390 downloads per month
Used in 17 crates (via nanorpc)

ISC license

213 lines

nanorpc: magic library for a JSON-RPC 2.0 subset (WIP)


Typically, writing a client-server networked API (say, a REST API) involves the following three steps:

  • Specify the protocol, in English
  • Implement the server side of the protocol
  • Separately implement the client side of the protocol

This is annoying and error-prone. The protocol is essentially specified three times in three different places and ways, and keeping them in sync is a huge chore.

Instead, we want to specify the protocol once, then automatically have:

  • A server implementation, generic over
    • The business logic of every endpoint
    • The low-level network details (e.g. "listen at this HTTP endpoint")
  • A client implementation, generic over the low-level network details (e.g. "call this HTTP endpoint)
  • Rust's type system fully utilized to help avoid bugs from things like serialization and deserialization mismatch, typos, etc.

About nanorpc

nanorpc does exactly. It is a JSON-RPC subset implementation with a macro, #[nanorpc_derive], that given a trait representing the API interface, abstracts away all the duplicate parts of implementing an API.

In particular:

  • nanorpc defines dynamically typed JSON-RPC server and client traits:
    • a trait RpcService that describes a JSON-RPC server-side responder (given a JSON request, produce a JSON response)
    • a trait RpcTransport that describes a JSON-RPC client-side requester (given a JSON request, talk to somebody else to produce a JSON response)
  • [nanorpc_derive], given a trait FooProtocol that describes the RPC methods, their argument types, and their return types, derives:
    • a struct FooService that, given any "business logic" struct that implements FooProtocol, wraps it into something implementing RpcService
    • a struct FooClient that, given any JSON transport implemetning RpcTransport, wraps it into a struct with methods corresponding to the RPC methods.

For example:

pub trait MathProtocol {
    /// Adds two numbers. Arguments and return type must be JSON-serializable through `serde_json`
    async fn add(&self, x: f64, y: f64) -> f64;
    /// Multiplies two numbers
    async fn mult(&self, x: f64, y: f64) -> f64;

// Autogenerates a server struct:
pub struct MathService<T: MathProtocol>(pub T);

impl <T: MathService> RpcService for MathService<T> {

// Autogenerates a client struct like:
pub struct MathClient<T: RpcTransport>(pub T);

impl <T: RpcTransport> MathClient {
    /// Adds two numbers
    pub async fn add(&self, x: f64, y: f64) -> Result<f64, T::Error>;


At the JSON level, the above protocol will respond to a JSON-RPC 2.0 request like:

{"jsonrpc": "2.0", "method": "mult", "params": [42, 23], "id": 1}


{"jsonrpc": "2.0", "result": 966, "id": 1}


~31K SLoC