#json-rpc-server

rustic-jsonrpc

JSON-RPC 2.0 server library written in Rust

6 releases

0.1.6 Sep 19, 2024
0.1.5 Jul 5, 2024
0.1.4 May 31, 2024
0.1.1 Mar 4, 2024

#19 in #json-rpc-server

Download history 102/week @ 2024-06-30 42/week @ 2024-07-07 2/week @ 2024-07-14 10/week @ 2024-07-28 97/week @ 2024-09-15 23/week @ 2024-09-22 25/week @ 2024-09-29

55 downloads per month

MIT license

18KB
388 lines

rustic-jsonrpc

rustic-jsonrpc is a JSON-RPC 2.0 server library written in Rust. It provides a simple and efficient way to create JSON-RPC services.

Features

  • Easy to define and register RPC methods using procedural macros.
  • Support for method injection and custom argument deserialization.

Example

Here is a simple example demonstrating how to write RPC methods similar to Redis's get and set methods:

use std::collections::HashMap;
use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::sync::RwLock;

use rustic_jsonrpc::serde_json::{to_string, Value};
use rustic_jsonrpc::{method, methods, BoxError, Container, Error, FromArg, Registry, Request};

struct Auth {}

impl<'a> FromArg<&'a str> for Auth {
    type Error = Error;

    async fn from_arg(_container: &Container, password: &'a str) -> Result<Self, Self::Error> {
        if password == "123456" {
            Ok(Auth {})
        } else {
            Err(Error::new(-1, "invalid auth", None))
        }
    }
}

#[derive(Default)]
struct Storage {
    data: RwLock<HashMap<String, String>>,
}

#[method(name = "get")]
async fn get(#[inject] storage: &Storage, key: &str) -> Result<Option<String>, Infallible> {
    Ok(storage.data.read().unwrap().get(key).map(|v| v.to_string()))
}

#[method]
async fn set(
    #[inject] storage: &Storage,
    #[from(password: &str)] _auth: Auth,
    key: String,
    value: String,
) -> Result<(), Infallible> {
    storage.data.write().unwrap().insert(key, value);
    Ok(())
}

fn log_call<'a>(
    req: &'a Request<'a>,
    resp: &'a Result<Value, BoxError>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
    Box::pin(async move {
        match resp {
            Ok(_) => println!("call method `{}` ok", req.method),
            Err(err) => println!("call method `{}` error: {}", req.method, err),
        }
    })
}

#[tokio::main]
async fn main() {
    let mut registry = Registry::new();
    registry.provide(Storage::default());
    registry.register(methods!(get, set));
    registry.post_call(log_call);

    // set foo bar
    let response = registry
        .handle(
            br#"{"jsonrpc":"2.0","method":"set","params":{"password": "123456","key": "foo", "value": "bar"}, "id":1}"#,
        )
        .await
        .unwrap();
    println!("{}", to_string(&response).unwrap());

    // get foo
    let response = registry
        .handle(br#"{"jsonrpc":"2.0","method":"get","params":{"key": "foo"}, "id":2}"#)
        .await
        .unwrap();
    println!("{}", to_string(&response).unwrap());
}

Output

When the above example is run, it produces the following output:

call method `set` ok
{"jsonrpc":"2.0","result":null,"id":1}
call method `get` ok
{"jsonrpc":"2.0","result":"bar","id":2}

Dependencies

~0.7–1.6MB
~35K SLoC