use std::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct JsonRpcVersion;
impl Default for JsonRpcVersion {
fn default() -> Self {
Self
}
}
impl fmt::Display for JsonRpcVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("2.0")
}
}
impl Serialize for JsonRpcVersion {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str("2.0")
}
}
impl<'de> Deserialize<'de> for JsonRpcVersion {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct VersionVisitor;
impl serde::de::Visitor<'_> for VersionVisitor {
type Value = JsonRpcVersion;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("the string \"2.0\"")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<JsonRpcVersion, E> {
if v == "2.0" {
Ok(JsonRpcVersion)
} else {
Err(E::custom(format!(
"expected JSON-RPC version \"2.0\", got \"{v}\""
)))
}
}
}
deserializer.deserialize_str(VersionVisitor)
}
}
pub type JsonRpcId = Option<serde_json::Value>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: JsonRpcVersion,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: JsonRpcId,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
impl JsonRpcRequest {
#[must_use]
pub fn new(id: serde_json::Value, method: impl Into<String>) -> Self {
Self {
jsonrpc: JsonRpcVersion,
id: Some(id),
method: method.into(),
params: None,
}
}
#[must_use]
pub fn with_params(
id: serde_json::Value,
method: impl Into<String>,
params: serde_json::Value,
) -> Self {
Self {
jsonrpc: JsonRpcVersion,
id: Some(id),
method: method.into(),
params: Some(params),
}
}
#[must_use]
pub fn notification(method: impl Into<String>, params: Option<serde_json::Value>) -> Self {
Self {
jsonrpc: JsonRpcVersion,
id: None,
method: method.into(),
params,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcResponse<T> {
Success(JsonRpcSuccessResponse<T>),
Error(JsonRpcErrorResponse),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcSuccessResponse<T> {
pub jsonrpc: JsonRpcVersion,
pub id: JsonRpcId,
pub result: T,
}
impl<T> JsonRpcSuccessResponse<T> {
#[must_use]
pub const fn new(id: JsonRpcId, result: T) -> Self {
Self {
jsonrpc: JsonRpcVersion,
id,
result,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcErrorResponse {
pub jsonrpc: JsonRpcVersion,
pub id: JsonRpcId,
pub error: JsonRpcError,
}
impl JsonRpcErrorResponse {
#[must_use]
pub const fn new(id: JsonRpcId, error: JsonRpcError) -> Self {
Self {
jsonrpc: JsonRpcVersion,
id,
error,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
impl JsonRpcError {
#[must_use]
pub fn new(code: i32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
#[must_use]
pub fn with_data(code: i32, message: impl Into<String>, data: serde_json::Value) -> Self {
Self {
code,
message: message.into(),
data: Some(data),
}
}
}
impl fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for JsonRpcError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_serializes_as_2_0() {
let v = JsonRpcVersion;
let s = serde_json::to_string(&v).expect("serialize");
assert_eq!(s, "\"2.0\"");
}
#[test]
fn version_rejects_wrong_version() {
let result: Result<JsonRpcVersion, _> = serde_json::from_str("\"1.0\"");
assert!(result.is_err(), "should reject non-2.0 version");
}
#[test]
fn version_accepts_2_0() {
let v: JsonRpcVersion = serde_json::from_str("\"2.0\"").expect("deserialize");
assert_eq!(v, JsonRpcVersion);
}
#[test]
fn request_roundtrip() {
let req = JsonRpcRequest::with_params(
serde_json::json!(1),
"message/send",
serde_json::json!({"message": {}}),
);
let json = serde_json::to_string(&req).expect("serialize");
assert!(json.contains("\"jsonrpc\":\"2.0\""));
assert!(json.contains("\"method\":\"message/send\""));
let back: JsonRpcRequest = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.method, "message/send");
}
#[test]
fn success_response_roundtrip() {
let resp: JsonRpcResponse<serde_json::Value> =
JsonRpcResponse::Success(JsonRpcSuccessResponse::new(
Some(serde_json::json!(42)),
serde_json::json!({"status": "ok"}),
));
let json = serde_json::to_string(&resp).expect("serialize");
assert!(json.contains("\"result\""));
assert!(!json.contains("\"error\""));
}
#[test]
fn error_response_roundtrip() {
let resp: JsonRpcResponse<serde_json::Value> =
JsonRpcResponse::Error(JsonRpcErrorResponse::new(
Some(serde_json::json!(1)),
JsonRpcError::new(-32601, "Method not found"),
));
let json = serde_json::to_string(&resp).expect("serialize");
assert!(json.contains("\"error\""));
assert!(json.contains("-32601"));
}
#[test]
fn notification_has_no_id() {
let n = JsonRpcRequest::notification("task/cancel", None);
let json = serde_json::to_string(&n).expect("serialize");
assert!(
!json.contains("\"id\""),
"notification must omit id: {json}"
);
}
#[test]
fn version_display() {
assert_eq!(JsonRpcVersion.to_string(), "2.0");
}
#[test]
#[allow(clippy::default_trait_access)]
fn version_default() {
let v: JsonRpcVersion = Default::default();
assert_eq!(v, JsonRpcVersion);
}
#[test]
fn version_rejects_non_string_types() {
assert!(serde_json::from_str::<JsonRpcVersion>("2.0").is_err());
assert!(serde_json::from_str::<JsonRpcVersion>("null").is_err());
assert!(serde_json::from_str::<JsonRpcVersion>("true").is_err());
assert!(serde_json::from_str::<JsonRpcVersion>("\"\"").is_err());
assert!(serde_json::from_str::<JsonRpcVersion>("\"2.1\"").is_err());
assert!(serde_json::from_str::<JsonRpcVersion>("\" 2.0\"").is_err());
}
#[test]
fn request_new_has_no_params() {
let req = JsonRpcRequest::new(serde_json::json!(1), "test/method");
assert_eq!(req.method, "test/method");
assert_eq!(req.id, Some(serde_json::json!(1)));
assert!(req.params.is_none());
assert_eq!(req.jsonrpc, JsonRpcVersion);
}
#[test]
fn request_with_params_has_params() {
let params = serde_json::json!({"key": "val"});
let req =
JsonRpcRequest::with_params(serde_json::json!("str-id"), "method", params.clone());
assert_eq!(req.params, Some(params));
assert_eq!(req.id, Some(serde_json::json!("str-id")));
}
#[test]
fn notification_has_method_and_params() {
let params = serde_json::json!({"task_id": "t1"});
let n = JsonRpcRequest::notification("task/cancel", Some(params.clone()));
assert!(n.id.is_none());
assert_eq!(n.method, "task/cancel");
assert_eq!(n.params, Some(params));
}
#[test]
fn jsonrpc_error_display() {
let e = JsonRpcError::new(-32600, "Invalid Request");
assert_eq!(e.to_string(), "[-32600] Invalid Request");
}
#[test]
fn jsonrpc_error_is_std_error() {
let e = JsonRpcError::new(-32600, "test");
let _: &dyn std::error::Error = &e;
}
#[test]
fn jsonrpc_error_new_has_no_data() {
let e = JsonRpcError::new(-32600, "test");
assert!(e.data.is_none());
assert_eq!(e.code, -32600);
assert_eq!(e.message, "test");
}
#[test]
fn jsonrpc_error_with_data_has_data() {
let data = serde_json::json!({"extra": true});
let e = JsonRpcError::with_data(-32601, "not found", data.clone());
assert_eq!(e.data, Some(data));
assert_eq!(e.code, -32601);
assert_eq!(e.message, "not found");
}
#[test]
fn success_response_fields() {
let resp = JsonRpcSuccessResponse::new(Some(serde_json::json!(1)), "ok");
assert_eq!(resp.id, Some(serde_json::json!(1)));
assert_eq!(resp.result, "ok");
assert_eq!(resp.jsonrpc, JsonRpcVersion);
}
#[test]
fn error_response_fields() {
let err = JsonRpcError::new(-32600, "bad");
let resp = JsonRpcErrorResponse::new(Some(serde_json::json!(2)), err);
assert_eq!(resp.id, Some(serde_json::json!(2)));
assert_eq!(resp.error.code, -32600);
assert_eq!(resp.jsonrpc, JsonRpcVersion);
}
}