use serde::{Deserialize, Serialize};
use crate::error::RpcError;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct JsonRpcRequest<P = serde_json::Value> {
pub jsonrpc: Version,
pub id: RequestId,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub params: Option<P>,
}
impl<P> JsonRpcRequest<P> {
pub fn new(id: u64, method: impl Into<String>, params: P) -> Self {
Self {
jsonrpc: Version,
id: RequestId::Num(id),
method: method.into(),
params: Some(params),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct JsonRpcResponse<R = serde_json::Value> {
pub jsonrpc: Version,
pub id: RequestId,
#[serde(flatten)]
pub body: JsonRpcResponseBody<R>,
}
impl<R> JsonRpcResponse<R> {
pub fn success(id: RequestId, result: R) -> Self {
Self {
jsonrpc: Version,
id,
body: JsonRpcResponseBody::Success { result },
}
}
pub fn error(id: RequestId, error: RpcError) -> Self {
Self {
jsonrpc: Version,
id,
body: JsonRpcResponseBody::Error { error },
}
}
pub fn as_error(&self) -> Option<&RpcError> {
match &self.body {
JsonRpcResponseBody::Error { error } => Some(error),
JsonRpcResponseBody::Success { .. } => None,
}
}
pub fn as_result(&self) -> Option<&R> {
match &self.body {
JsonRpcResponseBody::Success { result } => Some(result),
JsonRpcResponseBody::Error { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcResponseBody<R> {
Success {
result: R,
},
Error {
error: RpcError,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum RequestId {
Num(u64),
Str(String),
Null,
}
impl Default for RequestId {
fn default() -> Self {
RequestId::Num(1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Version;
impl Serialize for Version {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str("2.0")
}
}
impl<'de> Deserialize<'de> for Version {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
if s == "2.0" {
Ok(Version)
} else {
Err(serde::de::Error::custom(format!(
"expected jsonrpc version \"2.0\", got {s:?}"
)))
}
}
}
impl Default for Version {
fn default() -> Self {
Self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::{ErrorCode, ErrorOrigin};
#[test]
fn version_serialises_as_two_point_zero() {
assert_eq!(serde_json::to_string(&Version).unwrap(), "\"2.0\"");
}
#[test]
fn version_rejects_non_2_0() {
assert!(serde_json::from_str::<Version>(r#""1.0""#).is_err());
}
#[test]
fn request_id_roundtrip() {
for (rid, expected) in [
(RequestId::Num(42), "42"),
(RequestId::Str("abc".into()), "\"abc\""),
(RequestId::Null, "null"),
] {
let s = serde_json::to_string(&rid).unwrap();
assert_eq!(s, expected);
assert_eq!(serde_json::from_str::<RequestId>(&s).unwrap(), rid);
}
}
#[test]
fn response_body_success_and_error_shape() {
let ok = JsonRpcResponse::success(RequestId::Num(1), serde_json::json!({"ok": true}));
let s = serde_json::to_string(&ok).unwrap();
assert!(s.contains("\"result\""), "{s}");
assert!(!s.contains("\"error\""), "{s}");
let err = JsonRpcResponse::<serde_json::Value>::error(
RequestId::Num(2),
RpcError::new(
ErrorCode::MethodNotFound,
"no such method",
ErrorOrigin::Node,
),
);
let s = serde_json::to_string(&err).unwrap();
assert!(s.contains("\"error\""), "{s}");
assert!(!s.contains("\"result\""), "{s}");
}
#[test]
fn request_without_params_deserialises() {
let raw = r#"{"jsonrpc":"2.0","id":1,"method":"dig.health"}"#;
let req: JsonRpcRequest = serde_json::from_str(raw).unwrap();
assert_eq!(req.method, "dig.health");
assert!(req.params.is_none());
}
#[test]
fn accessors_branch() {
let ok = JsonRpcResponse::success(RequestId::Num(1), 7u32);
assert_eq!(ok.as_result(), Some(&7));
assert!(ok.as_error().is_none());
}
}