use serde::{Deserialize, Serialize};
use crate::errors::ErrorCode;
#[derive(Debug, Clone, 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")]
pub params: Option<P>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse<R = serde_json::Value> {
pub jsonrpc: Version,
pub id: RequestId,
#[serde(flatten)]
pub body: JsonRpcResponseBody<R>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcResponseBody<R> {
Success {
result: R,
},
Error {
error: JsonRpcError,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum RequestId {
Num(u64),
Str(String),
Null,
}
#[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
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: ErrorCode,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_serialises_as_two_point_zero() {
let s = serde_json::to_string(&Version).unwrap();
assert_eq!(s, "\"2.0\"");
}
#[test]
fn version_rejects_non_2_0() {
let r: Result<Version, _> = serde_json::from_str(r#""1.0""#);
assert!(r.is_err());
let r: Result<Version, _> = serde_json::from_str(r#""3"#);
assert!(r.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);
let back: RequestId = serde_json::from_str(&s).unwrap();
assert_eq!(back, rid);
}
}
#[test]
fn response_body_success_and_error_round_trip() {
let ok: JsonRpcResponse<u32> = JsonRpcResponse {
jsonrpc: Version,
id: RequestId::Num(1),
body: JsonRpcResponseBody::Success { result: 7 },
};
let s = serde_json::to_string(&ok).unwrap();
assert!(s.contains("\"result\":7"), "actual: {s}");
assert!(!s.contains("\"error\""), "must not contain error: {s}");
let err: JsonRpcResponse<u32> = JsonRpcResponse {
jsonrpc: Version,
id: RequestId::Num(2),
body: JsonRpcResponseBody::Error {
error: JsonRpcError {
code: ErrorCode::MethodNotFound,
message: "no such method".into(),
data: None,
},
},
};
let s = serde_json::to_string(&err).unwrap();
assert!(s.contains("\"error\""), "actual: {s}");
assert!(!s.contains("\"result\""), "must not contain result: {s}");
}
#[test]
fn request_without_params_deserialises() {
let raw = r#"{"jsonrpc":"2.0","id":1,"method":"get_blockchain_state"}"#;
let req: JsonRpcRequest<serde_json::Value> = serde_json::from_str(raw).unwrap();
assert_eq!(req.method, "get_blockchain_state");
assert!(req.params.is_none());
}
}