use serde_json::Value;
use super::message::RpcError;
pub trait HostHandler: Send + Sync {
fn handle(&self, method: &str, params: Option<Value>) -> Result<Value, RpcError>;
}
pub struct RejectAllHandler;
impl HostHandler for RejectAllHandler {
fn handle(&self, method: &str, _params: Option<Value>) -> Result<Value, RpcError> {
Err(RpcError::method_not_found(method))
}
}
pub struct MethodRouter {
routes: Vec<(String, Box<dyn Fn(Option<Value>) -> Result<Value, RpcError> + Send + Sync>)>,
fallback: Box<dyn HostHandler>,
}
impl MethodRouter {
pub fn new() -> Self {
Self {
routes: Vec::new(),
fallback: Box::new(RejectAllHandler),
}
}
pub fn on(
mut self,
method: impl Into<String>,
f: impl Fn(Option<Value>) -> Result<Value, RpcError> + Send + Sync + 'static,
) -> Self {
self.routes.push((method.into(), Box::new(f)));
self
}
pub fn fallback(mut self, handler: impl HostHandler + 'static) -> Self {
self.fallback = Box::new(handler);
self
}
}
impl Default for MethodRouter {
fn default() -> Self {
Self::new()
}
}
impl HostHandler for MethodRouter {
fn handle(&self, method: &str, params: Option<Value>) -> Result<Value, RpcError> {
for (route_method, handler) in &self.routes {
if route_method == method {
return handler(params);
}
}
self.fallback.handle(method, params)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn reject_all_returns_method_not_found() {
let h = RejectAllHandler;
let err = h.handle("fs/read_text_file", None).unwrap_err();
assert_eq!(err.code, RpcError::METHOD_NOT_FOUND);
assert!(err.message.contains("fs/read_text_file"));
}
#[test]
fn method_router_dispatches_registered() {
let router = MethodRouter::new().on("ping", |_params| Ok(json!({"pong": true})));
let result = router.handle("ping", None).unwrap();
assert_eq!(result["pong"], true);
}
#[test]
fn method_router_falls_back_on_unknown() {
let router = MethodRouter::new().on("ping", |_| Ok(json!(null)));
let err = router.handle("unknown/method", None).unwrap_err();
assert_eq!(err.code, RpcError::METHOD_NOT_FOUND);
}
#[test]
fn method_router_custom_fallback() {
struct AllowAll;
impl HostHandler for AllowAll {
fn handle(&self, _m: &str, _p: Option<Value>) -> Result<Value, RpcError> {
Ok(json!({"allowed": true}))
}
}
let router = MethodRouter::new()
.on("known", |_| Ok(json!({"known": true})))
.fallback(AllowAll);
let known = router.handle("known", None).unwrap();
assert_eq!(known["known"], true);
let unknown = router.handle("anything/else", None).unwrap();
assert_eq!(unknown["allowed"], true);
}
#[test]
fn method_router_passes_params_to_handler() {
let router =
MethodRouter::new().on("echo", |params| Ok(params.unwrap_or(json!(null))));
let params = json!({"key": "value"});
let result = router.handle("echo", Some(params.clone())).unwrap();
assert_eq!(result, params);
}
}