neptune-rpc-api 0.14.0

Neptune JSON-RPC API contract: the RpcApi trait and its data-transfer model types
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use crate::api::rpc::RpcApi;
use crate::model::json::JsonError;
use crate::model::json::JsonResult;

type HandlerFn = Box<
    dyn Fn(serde_json::Value) -> Pin<Box<dyn Future<Output = JsonResult<serde_json::Value>> + Send>>
        + Send
        + Sync,
>;

#[allow(missing_debug_implementations)]
pub struct Router<A: ?Sized> {
    routes: HashMap<&'static str, HandlerFn>,
    api: Arc<A>,
}

impl<A> Router<A>
where
    A: Send + Sync + 'static + ?Sized,
{
    pub fn new(api: Arc<A>) -> Self {
        Self {
            routes: HashMap::new(),
            api,
        }
    }

    pub fn insert<F, Fut>(&mut self, name: &'static str, f: F)
    where
        F: Fn(Arc<A>, serde_json::Value) -> Fut + Send + Sync + 'static,
        Fut: Future<Output = JsonResult<serde_json::Value>> + Send + 'static,
    {
        let api = self.api.clone();
        self.routes.insert(
            name,
            Box::new(move |params| Box::pin(f(api.clone(), params))),
        );
    }

    pub async fn dispatch(
        &self,
        method: &str,
        params: serde_json::Value,
    ) -> JsonResult<serde_json::Value> {
        if let Some(handler) = self.routes.get(method) {
            handler(params).await
        } else {
            Err(JsonError::MethodNotFound)
        }
    }
}

pub type RpcRouter = Router<dyn RpcApi>;

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
    use std::sync::Arc;

    use macro_rules_attr::apply;
    use serde_json::json;

    use crate::api::server::router::Router;
    use crate::model::json::JsonError;
    use crate::test_utils::shared_tokio_runtime;

    struct DummyApi;

    #[apply(shared_tokio_runtime)]
    async fn dispatch_known_method() {
        let api = Arc::new(DummyApi);
        let mut router = Router::new(api.clone());

        router.insert("echo", |_api, params| async move {
            Ok(json!({ "echo": params }))
        });

        let params = json!({ "message": "hello" });
        let result = router.dispatch("echo", params.clone()).await.unwrap();

        assert_eq!(result, json!({ "echo": params }));
    }

    #[apply(shared_tokio_runtime)]
    async fn dispatch_unknown_method() {
        let api = Arc::new(DummyApi);
        let router = Router::new(api);

        let err = router.dispatch("nonexistent", json!({})).await.unwrap_err();
        assert!(matches!(err, JsonError::MethodNotFound));
    }
}