8 个版本
0.1.3 | 2024 年 3 月 14 日 |
---|---|
0.1.2 | 2024 年 3 月 13 日 |
0.0.3 |
|
#406 在 网络编程
每月 390 次下载
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 获取完整的代码。
要实现上述功能,以下是对各种类型的需要
ModelManager
和AiManager
是 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 {}
TaskForCreate
和ParamIded
被用作 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。
重要
对于
0.1.x
版本,类型或API命名可能会有一些变化。因此,版本应该锁定到使用的最新版本,例如,=0.1.0
。如果有的话,我将尽量将更改保持在最低限度,并在未来的CHANGELOG中记录。一旦发布
0.2.0
,我将更严格地遵循语义版本控制方法。
概念
这个库有以下主要结构
-
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
对象。
- RouterBuilder - 通过
-
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仅仅意味着类型不能有任何除静态引用以外的引用)。
- 它与Axum State/RequestExtractor或Tauri State模型类似。在
-
Request
- 是具有json-rpc请求的id
、method
和params
的对象。- 要使一个结构成为
params
,它必须实现rpc_router::IntoParams
特质,它有一个默认实现。 - 因此,实现
impl rpc_router::IntoParams for ... {}
或#[derive(RpcParams)]
rpc_router::Request::from_value(serde_json::Value) -> Result<Request, RequestParsingError>将返回一个
RequestParsingError
,如果值不包含id: Value
、method: 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
。
- 要使一个结构成为
-
Handler
- RPC 处理函数可以是任何异步应用程序函数,它可以接受最多 8 个资源参数,以及一个可选的 Params 参数。- 例如,
async fn create_task(_mm: ModelManager, aim: AiManager, params: TaskForCreate) -> MyResult<i64>
- 例如,
-
HandlerError
- RPC 处理函数可以返回自己的Result
,只要错误类型实现了IntoHandlerError
,这可以很容易地实现为rpc_router::HandlerResult
,它包括一个impl IntoHandlerError for MyError {}
,或者使用RpcHandlerError
声明宏。- 为了允许处理函数返回应用程序错误,
HandlerError
实质上是一个类型持有者,然后可以使用handler_error.get<MyError>()
提取应用程序错误。 - 这需要应用程序代码知道要提取哪种错误类型,但提供了返回任何错误类型的灵活性。
- 通常,应用程序将为它的处理程序拥有几个应用程序错误类型,因此这种人体工程学的权衡仍然具有净正面价值,因为它可以使用特定于应用程序的错误类型。
- 为了允许处理函数返回应用程序错误,
-
CallResult
-router.call(...)
将返回一个CallResult
,它是一个Result<CallResponse, CallError>
,其中两者都将包含 JSON-RPC 的id
和method
名称上下文,以便进行后续处理。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 {}
相关链接
- GitHub 仓库
- crates.io
- Rust10x rust-web-app(使用 rpc-router 和 Axum 的 web-app 代码蓝图)
依赖关系
~2–3MB
~62K SLoC