型を活用したシンプルな非同期 JSON-RPC 2.0 ライブラリ
jsoncallは Rust の型を最大限に活用したシンプルな非同期 JSON-RPC 2.0 ライブラリです。
特に Language Server Protocol や Model Context Protocol のような クライアントがサーバーを起動するタイプのアプリケーションを容易に作成できるようにすることを目的としています。
tokioとasync/awaitによる非同期のサポートserdeを利用し、強く型付けされたリクエストとレスポンスを利用可能typifyによって JSON Schema から Rust の型を生成すれば JSON Schema で定義された RPC も容易に実装可能
- キャンセル処理のサポート基盤あり
- Language Server Protocol や Model Context Protocol のキャンセル処理を実装可能
- エラーハンドリングの充実
anyhowやBox<dyn Error>のように任意のエラーを格納でき、それに加えて外部に送信すべき情報とそうでない情報を区別する機能を持つエラー型を用意
- 双方向通信のサポート
- 通知(Notification)のサポート
- トランスポートは
tokioのAsyncBufReadとAsyncWriteを実装した型であれば何でも可- 標準入出力を使用したトランスポートは
Session::from_stdioとSession::from_commandですぐに利用できる
- 標準入出力を使用したトランスポートは
- 小規模な理解しやすい API セット
Cargo.toml に以下を追加してください:
[dependencies]
jsoncall = "0.0.3"use serde::{Deserialize, Serialize};
use jsoncall::{Handler, Params, RequestContext, Response, Result, Session, SessionOptions};
#[derive(Debug, Serialize, Deserialize)]
struct HelloRequest {
name: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct HelloResponse {
message: String,
}
struct HelloHandler;
impl Handler for HelloHandler {
fn request(&mut self, method: &str, params: Params, cx: RequestContext) -> Result<Response> {
match method {
"hello" => cx.handle(self.hello(params.to()?)),
_ => cx.method_not_found(),
}
}
}
impl HelloHandler {
fn hello(&self, r: HelloRequest) -> Result<HelloResponse> {
Ok(HelloResponse {
message: format!("Hello, {}!", r.name),
})
}
}
#[tokio::main]
async fn main() -> Result<()> {
// 標準入出力を使用してサーバーを起動
Ok(Session::from_stdio(HelloHandler, &SessionOptions::default()).wait().await?)
}use serde::{Deserialize, Serialize};
use tokio::process::Command;
use jsoncall::{Result, Session, SessionOptions};
#[derive(Debug, Serialize, Deserialize)]
struct HelloRequest {
name: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct HelloResponse {
message: String,
}
#[tokio::main]
async fn main() -> Result<()> {
// サーバープロセスを起動してセッションを作成
let client = Session::from_command(
(),
Command::new("cargo").args(["run", "--example", "stdio_server"]),
&SessionOptions::default(),
)?;
// リクエストの送信
let response: HelloResponse = client
.request(
"hello",
Some(&HelloRequest {
name: "world".to_string(),
}),
)
.await?;
println!("{:?}", response);
Ok(())
}use jsoncall::{Handler, Params, RequestContext, Result, Response};
struct ExampleHandler;
impl Handler for ExampleHandler {
fn request(&mut self, method: &str, params: Params, cx: RequestContext) -> Result<Response> {
match method {
"add" => {
let params: (i32, i32) = params.to()?;
cx.handle_async(async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
Ok(params.0 + params.1)
})
}
_ => cx.method_not_found(),
}
}
}This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-* files for details.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.