rpc-router - json-rpc routing library
rpc-router is a JSON-RPC routing library in Rust for asynchronous dynamic dispatch with support for variadic arguments (up to 8 resources + 1 optional parameter). (code snippets below from: examples/c00-readme.rs)
The goal of this library is to enable application functions with different argument types and signatures as follows:
pub async
pub async
To be callable from a JSON-RPC request as follows:
// json-rpc request comming from Axum route payload, Tauri command params, ...
let rpc_request = json!.try_into?;
// Async Execute the RPC Request
let call_response = rpc_router.call.await?;
For this, we just need to build the router, the resources, parse the json-rpc request, and execute the call from the router as follows:
// Build the Router with the handlers and common resources
let rpc_router = router_builder!
.build;
// Can do the same with `Router::builder().append(...)/append_resource(...)`
// Create and parse rpc request example.
let rpc_request: Request = json!.try_into?;
// Async Execute the RPC Request.
let call_response = rpc_router.call.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 = call_response;
println!;
See examples/c00-readme.rs for the complete functioning code.
For the above to work, here are the requirements for the various types:
ModelManagerandAiManagerare rpc-router Resources. These types just need to implementrpc_router::FromResources(the trait has a default implementation, andRpcResourcederive macros can generate this one-liner implementation).
// Make it a Resource with RpcResource derive macro
// Make it a Resource by implementing FromResources
TaskForCreateandParamIdedare use as JSON-RPC Params and must implement therpc_router::IntoParamstrait, which has a default implementation, and can also be implemented byRpcParamsderive macros.
// Make it a Params with RpcParams derive macro
// Make it a Params by implementing IntoParams
Task, as a returned value just needs implementsserde::Serialize
MyErrormust implement theIntoHandlerError, which also has a default implementation, and can also be implemented byRpcHandlerErrorderive macros.
By the Rust type model, these application errors are set in the HandlerError and need to be retrieved by handler_error.get::<MyError>(). See examples/c05-error-handling.rs.
Full code examples/c00-readme.rs
IMPORTANT
For the
0.1.xreleases, there may be some changes to types or API naming. Therefore, the version should be locked to the latest version used, for example,=0.1.0. I will try to keep changes to a minimum, if any, and document them in the future CHANGELOG.Once
0.2.0is released, I will adhere more strictly to the semantic versioning methodology.
Concepts
This library has the following main constructs:
-
Router- Router is the construct that holds all of the Handler Functions, and can be invoked withrouter.call(resources, rpc_request). Here are the two main ways to build aRouterobject:- RouterBuilder - via
RouterBuilder::default()orRouter::build(), then call.append(name, function)or.append_dyn(name, function.into_dyn())to avoid type monomorphization at the "append" stage. - router_builder! - via the macro
router_builder!(function1, function2, ...). This will create, initialize, and return aRouterBuilderobject. - In both cases, call
.build()to construct the immutable, shareable (via inner Arc)Routerobject.
- RouterBuilder - via
-
Resources- Resources is the type map contstruct that hold the resources that a rpc handler function might request.- It's similar to Axum State/RequestExtractor, or Tauri State model. In the case of
rpc-routerthere in one "domain space" for those states, that are called resources. - It's built via
ResourcesBuilder::default().append(my_object)...build() - Or viat the macro
resources_builder![my_object1, my_object2].build() - The
Resourceshold the "type map" in aArc<>and is completely immutable and can be cloned effectively. ResourcesBuilderis not wrapped inArc<>, and cloning it will clone the full type map. This can be very useful for sharing a common base resources builder across various calls while allowing each call to add more per-request resources.- All the value/object inserted in the Resources must implement
Clone + Send + Sync + 'static(in this context the'staticjust means that the type cannot have any reference other than static one )
- It's similar to Axum State/RequestExtractor, or Tauri State model. In the case of
-
Request- Is the object that have the json-rpc Requestid,method, andparams.- To make a struct a
paramsit has to implement therpc_router::IntoParamstrait, which has the default implementation. - So, implement
impl rpc_router::IntoParams for ... {}or#[derive(RpcParams)] rpc_router::Request::from_value(serde_json::Value) -> Result<Request, RequestParsingError>will return andRequestParsingErrorif the Value does not haveid: Value,method: Stringor if the Value does not contain"jsonrpc": "2.0"as per the json-rpc space.let request: rpc_router::Request = value.try_into()?use the samefrom_valuevalidation steps.- Doing
serde_json::from_value::<rpc_router::Request>(value)will not chane thejsonrpc.
- To make a struct a
-
Handler- RPC handler functions can be any async application function that takes up to 8 resource arguments, plus an optional Params argument.- For example,
async fn create_task(_mm: ModelManager, aim: AiManager, params: TaskForCreate) -> MyResult<i64>
- For example,
-
HandlerError- RPC handler functions can return their ownResultas long as the error type implementsIntoHandlerError, which can be easily implemented asrpc_router::HandlerResultwhich includes animpl IntoHandlerError for MyError {}, or with theRpcHandlerErrorderive macro.- To allow handler functions to return their application error,
HandlerErroris essentially a type holder, which then allows the extraction of the application error withhandler_error.get<MyError>(). - This requires the application code to know which error type to extract but provides flexibility to return any Error type.
- Typically, an application will have a few application error types for its handlers, so this ergonomic trade-off still has net positive value as it enables the use of application-specific error types.
- To allow handler functions to return their application error,
-
CallResult-router.call(...)will return aCallResult, which is aResult<CallResponse, CallError>where both will include the JSON-RPCidandmethodname context for future processing.CallErrorcontains.error: rpc_router::Error, which includesrpc_router::Error::Handler(HandlerError)in the event of a handler error.CallResponsecontains.value: serde_json::Value, which is the serialized value returned by a successful handler call.
Derive Macros
rpc-router has some convenient derive proc macros that generate the implementation of various traits.
This is just a stylistic convenience, as the traits themselves have default implementations and are typically one-liner implementations.
Note: Those derive proc macros are prefixed with
Rpcas we often tend to just put the proc macro name in the derive, and therefore the prefix adds some clarity. Otherrpc-routertypes, are without the prefix to follow Rust customs.
#[derive(rpc_router::RpcParams)]
Will implement rpc_router::IntoParams for the type.
Works on simple type.
pub strut ParamsIded
// Will generate:
// impl rpc_router::IntoParams for ParamsIded {}
Works with typed with generic (all will be bound to DeserializeOwned + Send)
pub strut
// Will generate
// impl<D> IntoParams for ParamsForCreate<D> where D: DeserializeOwned + Send {}
#[derive(rpc_router::RpcResource)]
Will implement the rpc_router::FromResource trait.
;
// Will generate:
// impl FromResources for ModelManager {}
The FromResources trait has a default implementation to get the T type (here ModelManager) from the rpc_router::Resources type map.
#[derive(rpc_router::RpcHandlerError)]
Will implment the rpc_router::IntoHandlerError trait.
// Will generate;
// impl IntoHandlerError for MyError {}
Related links
- GitHub Repo
- crates.io
- Rust10x rust-web-app (web-app code blueprint using rpc-router with Axum)