use rpc_router::{router_builder, FromResources, IntoParams, Request, Router, RpcHandlerError, RpcParams, RpcResource}; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::task::JoinSet; // -- Application Error // Must implement IntoHandlerError, here with RpcHandlerError derive macro #[derive(Debug, thiserror::Error, RpcHandlerError)] pub enum MyError { // TBC #[error("TitleCannotBeEmpty")] TitleCannotBeEmpty, } // -- Application Resources // Must implement FromResources, here with RpcResource derive macro #[derive(Clone, RpcResource)] pub struct ModelManager {} // Or can implement FromResources manual (default implementation provided) #[derive(Clone)] pub struct AiManager {} impl FromResources for AiManager {} // -- Params // Must implement IntoParams, here with RpcParams derive macro #[derive(Serialize, Deserialize, RpcParams)] pub struct TaskForCreate { title: String, done: Option, } // Or can implement FromResources manual (default implementation provided) // Another type that can be used as handler params argument. // Typicaly, all apis that just get data by `id` #[derive(Deserialize)] pub struct ParamsIded { pub id: i64, } impl IntoParams for ParamsIded {} // -- Application Return Values // Handler returned value must implement Serialize #[derive(Serialize)] pub struct Task { id: i64, title: String, done: bool, } // -- Application handler functions // An application rpc handler. Can take up to 8 arguments that have been marked RpcResource. // And have optionally have a last argument of a RpcParams pub async fn create_task(_mm: ModelManager, _aim: AiManager, params: TaskForCreate) -> Result { if params.title.is_empty() { return Err(MyError::TitleCannotBeEmpty); } Ok(123) // return fake id } // Another handler, does not have to have the same resource arguments. pub async fn get_task(_mm: ModelManager, params: ParamsIded) -> Result { Ok(Task { id: params.id, title: "fake task".to_string(), done: false, }) } // -- Main example implementation #[tokio::main] async fn main() -> Result<(), Box> { // -- Build router (can be built with Router::builder().add(..)) let rpc_router: Router = router_builder!(create_task, get_task) .append_resource(ModelManager {}) .append_resource(AiManager {}) .build(); // -- Simulate RPC request intakes (typically manage by the Web/IPC layer) let rpc_requests: Vec = vec![ // create_task request json!({ "jsonrpc": "2.0", "id": null, // json-rpc request id, generated by the client. Can be null, but has to be present. "method": "create_task", "params": { "title": "First task" } }) .try_into()?, // get_task request json!({ "jsonrpc": "2.0", "id": "some-string", // Can be string. Does not have to be unique from a server side. "method": "get_task", "params": { "id": 234 } }) .try_into()?, // create_task with invalid name request json!({ "jsonrpc": "2.0", "id": 123, // Can be number as well. "method": "create_task", "params": { "title": "" } }) .try_into()?, ]; // -- Simulate async processing (typically managed by the Web/IPC layer) let mut joinset = JoinSet::new(); for (idx, rpc_request) in rpc_requests.into_iter().enumerate() { let rpc_router = rpc_router.clone(); // Just Arc clone. joinset.spawn(async move { // Cheap way to "ensure" start spawns matches join_next order. (not for prod) tokio::time::sleep(std::time::Duration::from_millis(idx as u64 * 10)).await; // Do the router call rpc_router.call(rpc_request).await }); } // -- Print the result while let Some(Ok(call_result)) = joinset.join_next().await { match call_result { Ok(call_response) => println!("success: {call_response:?}"), Err(call_error) => { println!("Error for request id: {}, method: {}", call_error.id, call_error.method); match call_error.error { // It's a application handler type wrapped in a rpc_router CallError // we need to know it's type to extract it. rpc_router::Error::Handler(mut handler_error) => { // We can remove it not needed anymore if let Some(my_error) = handler_error.remove::() { println!("MyError: {my_error:?}") } else { println!("Unhandled App Error: {handler_error:?}"); } } // if it is other rpc_router error, can be handled normally other => println!("{other:?}"), } } } println!(); } Ok(()) }