use std::sync::Arc;
use dig_rpc_types::envelope::{
JsonRpcError, JsonRpcRequest, JsonRpcResponse, JsonRpcResponseBody, Version,
};
use dig_rpc_types::errors::ErrorCode;
use dig_service::RpcApi;
use crate::method::MethodRegistry;
pub async fn dispatch_envelope<R: RpcApi + ?Sized>(
req: JsonRpcRequest<serde_json::Value>,
api: &R,
registry: &MethodRegistry,
) -> JsonRpcResponse<serde_json::Value> {
if registry.get(&req.method).is_none() {
return JsonRpcResponse {
jsonrpc: Version,
id: req.id,
body: JsonRpcResponseBody::Error {
error: JsonRpcError {
code: ErrorCode::MethodNotFound,
message: format!("method {:?} not registered", req.method),
data: None,
},
},
};
}
let method = req.method.clone();
let params = req.params.unwrap_or(serde_json::Value::Null);
let result = api.dispatch(&method, params).await;
match result {
Ok(value) => JsonRpcResponse {
jsonrpc: Version,
id: req.id,
body: JsonRpcResponseBody::Success { result: value },
},
Err(err) => JsonRpcResponse {
jsonrpc: Version,
id: req.id,
body: JsonRpcResponseBody::Error { error: err },
},
}
}
pub fn error_envelope(
id: dig_rpc_types::envelope::RequestId,
code: ErrorCode,
message: impl Into<String>,
) -> JsonRpcResponse<serde_json::Value> {
JsonRpcResponse {
jsonrpc: Version,
id,
body: JsonRpcResponseBody::Error {
error: JsonRpcError {
code,
message: message.into(),
data: None,
},
},
}
}
#[cfg(test)]
pub(crate) struct StubApi;
#[cfg(test)]
#[async_trait::async_trait]
impl RpcApi for StubApi {
async fn dispatch(
&self,
method: &str,
_params: serde_json::Value,
) -> Result<serde_json::Value, JsonRpcError> {
Err(JsonRpcError {
code: ErrorCode::InternalError,
message: format!("stub api does not implement {method:?}"),
data: None,
})
}
}
#[allow(dead_code)]
#[doc(hidden)]
pub(crate) fn _keep_arc_usage_if_any(_: Arc<()>) {}
#[cfg(test)]
mod tests {
use super::*;
use crate::method::{MethodMeta, RateBucket};
use crate::role::Role;
use dig_rpc_types::envelope::RequestId;
#[tokio::test]
async fn unknown_method_returns_method_not_found() {
let api = StubApi;
let reg = MethodRegistry::new();
let req = JsonRpcRequest {
jsonrpc: Version,
id: RequestId::Num(7),
method: "nope".to_string(),
params: None,
};
let resp = dispatch_envelope(req, &api, ®).await;
assert!(matches!(resp.id, RequestId::Num(7)));
match resp.body {
JsonRpcResponseBody::Error { error } => {
assert_eq!(error.code, ErrorCode::MethodNotFound);
}
_ => panic!("expected error response"),
}
}
#[tokio::test]
async fn api_error_propagates() {
let reg = MethodRegistry::new();
reg.register(MethodMeta::read(
"stub",
Role::Explorer,
RateBucket::ReadLight,
));
let api = StubApi;
let req = JsonRpcRequest {
jsonrpc: Version,
id: RequestId::Num(1),
method: "stub".to_string(),
params: None,
};
let resp = dispatch_envelope(req, &api, ®).await;
match resp.body {
JsonRpcResponseBody::Error { error } => {
assert_eq!(error.code, ErrorCode::InternalError);
assert!(error.message.contains("stub"));
}
_ => panic!("expected error response"),
}
}
#[test]
fn error_envelope_shape() {
let resp = error_envelope(RequestId::Num(5), ErrorCode::RateLimited, "slow down");
assert!(matches!(resp.id, RequestId::Num(5)));
match resp.body {
JsonRpcResponseBody::Error { error } => {
assert_eq!(error.code, ErrorCode::RateLimited);
assert_eq!(error.message, "slow down");
}
_ => panic!("expected error response"),
}
}
}