8 个版本

0.1.3 2024 年 3 月 14 日
0.1.2 2024 年 3 月 13 日
0.0.3 2024 年 3 月 9 日

#406网络编程

Download history 46/week @ 2024-04-08 58/week @ 2024-04-15 54/week @ 2024-04-22 54/week @ 2024-04-29 41/week @ 2024-05-06 40/week @ 2024-05-13 48/week @ 2024-05-20 92/week @ 2024-05-27 75/week @ 2024-06-03 49/week @ 2024-06-10 45/week @ 2024-06-17 132/week @ 2024-06-24 154/week @ 2024-07-01 75/week @ 2024-07-08 64/week @ 2024-07-15 87/week @ 2024-07-22

每月 390 次下载

MIT/Apache

63KB
889

rpc-router - json-rpc 路由库

rpc-router 是一个 Rust 编写的 JSON-RPC 路由库,支持异步动态路由和变长参数(最多 8 个资源 + 1 个可选参数)。(以下代码片段来自:examples/c00-readme.rs

该库的目的是使具有不同参数类型和签名的应用程序函数能够如下调用

pub async fn create_task(mm: ModelManager, aim: AiManager, params: TaskForCreate) -> Result<i64, MyError> {
  // ...
}
pub async fn get_task(mm: ModelManager, params: ParamsIded) -> Result<Task, MyError> {
  // ...
}

如下从 JSON-RPC 请求中进行调用

// json-rpc request comming from Axum route payload, Tauri command params, ...
let rpc_request = json!(
   { jsonrpc: "2.0", id: 1,          // required by json-rpc
     method:  "create_task",         // method name (matches function name)
     params:  {title: "First Task"}  // optional params (last function argument)
   }).try_into()?;

// Async Execute the RPC Request 
let call_response = rpc_router.call(rpc_request).await?;

为此,我们只需要构建路由器、资源、解析 json-rpc 请求,然后从路由器执行调用,如下所示

// Build the Router with the handlers and common resources
let rpc_router = router_builder!(
    handlers: [get_task, create_task],         // will be turned into routes
    resources: [ModelManager {}, AiManager {}] // common resources for all calls
)
.build();
// Can do the same with `Router::builder().append(...)/append_resource(...)`

// Create and parse rpc request example.
let rpc_request: rpc_router::Request = json!({
    "jsonrpc": "2.0",
    "id": "some-client-req-id", // json-rpc request id. Can be null,num,string, but has to be present.
    "method": "create_task",
    "params": { "title": "First task" } // optional.
}).try_into()?;

// Async Execute the RPC Request.
let call_response = rpc_router.call(rpc_resources, rpc_request).await?;

// Or `call_with_resources` for  additional per-call Resources that override router common resources.
// e.g., rpc_router.call_with_resources(rpc_request, additional_resources)

// Display the response.
let CallResponse { id, method, value } = call_response;
println!(
	r#"RPC call response:
		
    id:  {id:?},
method:  {method},
 value:  {value:?},
"#
	);

请参阅 examples/c00-readme.rs 获取完整的代码。

要实现上述功能,以下是对各种类型的需要

  • ModelManagerAiManager 是 rpc-router 的 资源。这些类型只需要实现 rpc_router::FromResources(该特性有默认实现,并且 RpcResource derive 宏可以生成这种单行实现)。
// Make it a Resource with RpcResource derive macro
#[derive(Clone, RpcResource)]
pub struct ModelManager {}

// Make it a Resource by implementing FromResources
#[derive(Clone)]
pub struct AiManager {}
impl FromResources for AiManager {}
  • TaskForCreateParamIded 被用作 JSON-RPC 参数,必须实现 rpc_router::IntoParams 特性,该特性有默认实现,也可以通过 RpcParams derive 宏实现。
// Make it a Params with RpcParams derive macro
#[derive(Serialize, Deserialize, RpcParams)]
pub struct TaskForCreate {
	title: String,
	done: Option<bool>,
}

// Make it a Params by implementing IntoParams
#[derive(Deserialize)]
pub struct ParamsIded {
	pub id: i64,
}
impl IntoParams for ParamsIded {}
  • Task 作为返回值只需实现 serde::Serialize
#[derive(Serialize)]
pub struct Task {
	id: i64,
	title: String,
	done: bool,
}
  • MyError 必须实现 IntoHandlerError,该特性也有默认实现,也可以通过 RpcHandlerError derive 宏实现。
#[derive(Debug, thiserror::Error, RpcHandlerError)]
pub enum MyError {
	// TBC
	#[error("TitleCannotBeEmpty")]
	TitleCannotBeEmpty,
}

根据Rust类型模型,这些应用错误被设置在HandlerError中,需要通过handler_error.get::<MyError>()来检索。请参阅examples/c05-error-handling.rs

完整代码examples/c00-readme.rs

重要

对于0.1.x版本,类型或API命名可能会有一些变化。因此,版本应该锁定到使用的最新版本,例如,=0.1.0。如果有的话,我将尽量将更改保持在最低限度,并在未来的CHANGELOG中记录。

一旦发布0.2.0,我将更严格地遵循语义版本控制方法。

概念

这个库有以下主要结构

  1. Router - 路由器是包含所有处理函数的结构,可以通过router.call(resources, rpc_request)调用。构建Router对象主要有两种方式

    • RouterBuilder - 通过RouterBuilder::default()Router::build(),然后调用.append(name, function).append_dyn(name, function.into_dyn())来避免在“追加”阶段类型单态化。
    • router_builder! - 通过宏router_builder!(function1, function2, ...)。这将创建、初始化并返回一个RouterBuilder对象。
    • 在这两种情况下,调用.build()来构建不可变、可共享(通过内部Arc)的Router对象。
  2. Resources - 资源是类型映射结构,用于存储RPC处理函数可能请求的资源。

    • 它与Axum State/RequestExtractor或Tauri State模型类似。在rpc-router的情况下,有一个“域空间”用于这些称为资源的状态。
    • 它通过以下方式构建:ResourcesBuilder::default().append(my_object)...build()
    • 或者通过宏 resources_builder![my_object1, my_object2].build()
    • 资源(Resources)在一个Arc<>中持有“类型映射”,且完全不可变,可以有效地进行克隆。
    • ResourcesBuilder没有包裹在Arc<>中,克隆它将克隆整个类型映射。这可以在多个调用之间共享一个公共的基资源构建器的同时,允许每个调用添加更多的请求特定资源,非常有用。
    • 插入到资源中的所有值/对象都必须实现Clone + Send + Sync + 'static(在这个上下文中,'static仅仅意味着类型不能有任何除静态引用以外的引用)。
  3. Request - 是具有json-rpc请求的idmethodparams的对象。

    • 要使一个结构成为params,它必须实现rpc_router::IntoParams特质,它有一个默认实现。
    • 因此,实现impl rpc_router::IntoParams for ... {}#[derive(RpcParams)]
    • rpc_router::Request::from_value(serde_json::Value) -> Result<Request, RequestParsingError>将返回一个RequestParsingError,如果值不包含id: Valuemethod: String或者如果值不包含作为json-rpc空间的"jsonrpc": "2.0"
    • let request: rpc_router::Request = value.try_into()? 使用相同的 from_value 验证步骤。
    • 执行 serde_json::from_value::<rpc_router::Request>(value) 不会改变 jsonrpc
  4. Handler - RPC 处理函数可以是任何异步应用程序函数,它可以接受最多 8 个资源参数,以及一个可选的 Params 参数。

    • 例如,async fn create_task(_mm: ModelManager, aim: AiManager, params: TaskForCreate) -> MyResult<i64>
  5. HandlerError - RPC 处理函数可以返回自己的 Result,只要错误类型实现了 IntoHandlerError,这可以很容易地实现为 rpc_router::HandlerResult,它包括一个 impl IntoHandlerError for MyError {},或者使用 RpcHandlerError 声明宏。

    • 为了允许处理函数返回应用程序错误,HandlerError 实质上是一个类型持有者,然后可以使用 handler_error.get<MyError>() 提取应用程序错误。
    • 这需要应用程序代码知道要提取哪种错误类型,但提供了返回任何错误类型的灵活性。
    • 通常,应用程序将为它的处理程序拥有几个应用程序错误类型,因此这种人体工程学的权衡仍然具有净正面价值,因为它可以使用特定于应用程序的错误类型。
  6. CallResult - router.call(...) 将返回一个 CallResult,它是一个 Result<CallResponse, CallError>,其中两者都将包含 JSON-RPC 的 idmethod 名称上下文,以便进行后续处理。

    • CallError 包含 .error: rpc_router::Error,其中在处理程序错误的情况下包括 rpc_router::Error::Handler(HandlerError)
    • CallResponse 包含 .value: serde_json::Value,这是成功调用处理程序返回的序列化值。

派生宏

rpc-router 有一系列方便的派生过程宏,可以生成各种特质的实现。

这只是样式上的方便,因为特质本身有默认实现,通常是一行代码实现。

注意:这些派生过程宏以 Rpc 开头,因为我们通常只是将过程宏名称放在派生中,因此前缀增加了清晰度。其他 rpc-router 类型没有前缀,以遵循 Rust 习惯。

#[派生(rpc_router::RpcParams)]

将为类型实现 rpc_router::IntoParams

适用于简单类型。

#[derive(serde::Deserialize, rpc_router::RpcParams)]
pub strut ParamsIded {
    id: i64
}

// Will generate:
// impl rpc_router::IntoParams for ParamsIded {} 

与泛型类型(所有类型都将绑定到 DeserializeOwned + Send)一起使用

#[derive(rpc_router::RpcParams)]
pub strut ParamsForUpdate<D> {
    id: i64
    D
}
// Will generate
// impl<D> IntoParams for ParamsForCreate<D> where D: DeserializeOwned + Send {}

#[派生(rpc_router::RpcResource)]

将为特质实现 rpc_router::FromResource


#[derive(Clone, rpc_router::RpcResource)]
pub struct ModelManager;

// Will generate:
// impl FromResources for ModelManager {}

FromResources 特质有一个默认实现,用于从 rpc_router::Resources 类型映射中获取 T 类型(此处为 ModelManager)。

#[派生(rpc_router::RpcHandlerError)]

将为特质实现 rpc_router::IntoHandlerError

#[derive(Debug, Serialize, RpcHandlerError)]
pub enum MyError {
    InvalidName,
    // ...
}

// Will generate;
// impl IntoHandlerError for MyError {}

依赖关系

~2–3MB
~62K SLoC