use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const JSONRPC_VERSION: &str = "2.0";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum Id {
Number(i64),
String(String),
Null,
}
impl Id {
pub fn is_notification(&self) -> bool {
matches!(self, Id::Null)
}
}
impl Default for Id {
fn default() -> Self {
Id::Null
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl JsonRpcError {
pub fn new(code: i32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
pub fn with_data(code: i32, message: impl Into<String>, data: Value) -> Self {
Self {
code,
message: message.into(),
data: Some(data),
}
}
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: Id,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
impl JsonRpcRequest {
pub fn new(id: Id, method: impl Into<String>, params: Option<Value>) -> Self {
Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id,
method: method.into(),
params,
}
}
pub fn notification(method: impl Into<String>, params: Option<Value>) -> Self {
Self::new(Id::Null, method, params)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonRpcSuccessResponse {
pub jsonrpc: String,
pub id: Id,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonRpcErrorResponse {
pub jsonrpc: String,
pub id: Id,
pub error: JsonRpcError,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcResponse {
Success(JsonRpcSuccessResponse),
Error(JsonRpcErrorResponse),
}
impl JsonRpcResponse {
pub fn success(id: Id, result: Option<Value>) -> Self {
Self::Success(JsonRpcSuccessResponse {
jsonrpc: JSONRPC_VERSION.to_string(),
id,
result,
})
}
pub fn error(id: Id, error: JsonRpcError) -> Self {
Self::Error(JsonRpcErrorResponse {
jsonrpc: JSONRPC_VERSION.to_string(),
id,
error,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonRpcNotification {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
impl JsonRpcNotification {
pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
Self {
jsonrpc: JSONRPC_VERSION.to_string(),
method: method.into(),
params,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcMessage {
Request(JsonRpcRequest),
SuccessResponse(JsonRpcSuccessResponse),
ErrorResponse(JsonRpcErrorResponse),
Notification(JsonRpcNotification),
}
impl JsonRpcMessage {
pub fn is_request(&self) -> bool {
matches!(self, JsonRpcMessage::Request(_))
}
pub fn is_response(&self) -> bool {
matches!(
self,
JsonRpcMessage::SuccessResponse(_) | JsonRpcMessage::ErrorResponse(_)
)
}
pub fn is_notification(&self) -> bool {
matches!(self, JsonRpcMessage::Notification(_))
}
pub fn id(&self) -> Option<&Id> {
match self {
JsonRpcMessage::Request(req) => Some(&req.id),
JsonRpcMessage::SuccessResponse(resp) => Some(&resp.id),
JsonRpcMessage::ErrorResponse(resp) => Some(&resp.id),
JsonRpcMessage::Notification(_) => None,
}
}
pub fn method(&self) -> Option<&str> {
match self {
JsonRpcMessage::Request(req) => Some(&req.method),
JsonRpcMessage::Notification(notif) => Some(¬if.method),
_ => None,
}
}
pub fn into_request(self) -> Option<JsonRpcRequest> {
match self {
JsonRpcMessage::Request(req) => Some(req),
_ => None,
}
}
pub fn into_notification(self) -> Option<JsonRpcNotification> {
match self {
JsonRpcMessage::Notification(notif) => Some(notif),
_ => None,
}
}
pub fn into_response(self) -> Option<JsonRpcResponse> {
match self {
JsonRpcMessage::SuccessResponse(resp) => Some(JsonRpcResponse::Success(resp)),
JsonRpcMessage::ErrorResponse(resp) => Some(JsonRpcResponse::Error(resp)),
_ => None,
}
}
}
pub mod builder {
use super::*;
pub fn request(
id: impl Into<Id>,
method: impl Into<String>,
params: Option<Value>,
) -> JsonRpcMessage {
JsonRpcMessage::Request(JsonRpcRequest::new(id.into(), method, params))
}
pub fn notification(method: impl Into<String>, params: Option<Value>) -> JsonRpcMessage {
JsonRpcMessage::Notification(JsonRpcNotification::new(method, params))
}
pub fn success_response(id: impl Into<Id>, result: Option<Value>) -> JsonRpcMessage {
JsonRpcMessage::SuccessResponse(JsonRpcSuccessResponse {
jsonrpc: JSONRPC_VERSION.to_string(),
id: id.into(),
result,
})
}
pub fn error_response(id: impl Into<Id>, error: JsonRpcError) -> JsonRpcMessage {
JsonRpcMessage::ErrorResponse(JsonRpcErrorResponse {
jsonrpc: JSONRPC_VERSION.to_string(),
id: id.into(),
error,
})
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_request_serialization() {
let req = JsonRpcRequest::new(Id::Number(1), "session/new", None);
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains(r#""jsonrpc":"2.0""#));
assert!(json.contains(r#""id":1"#));
assert!(json.contains(r#""method":"session/new""#));
}
#[test]
fn test_notification_serialization() {
let notif = JsonRpcNotification::new("session/cancel", None);
let json = serde_json::to_string(¬if).unwrap();
assert!(json.contains(r#""jsonrpc":"2.0""#));
assert!(json.contains(r#""method":"session/cancel""#));
assert!(!json.contains(r#""id""#));
}
#[test]
fn test_response_serialization() {
let resp = JsonRpcResponse::success(Id::String("abc".to_string()), Some(json!("test")));
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains(r#""jsonrpc":"2.0""#));
assert!(json.contains(r#""id":"abc""#));
}
#[test]
fn test_id_deserialization() {
let id: Id = serde_json::from_str("42").unwrap();
assert!(matches!(id, Id::Number(42)));
let id: Id = serde_json::from_str(r#""hello""#).unwrap();
assert!(matches!(id, Id::String(s) if s == "hello"));
let id: Id = serde_json::from_str("null").unwrap();
assert!(matches!(id, Id::Null));
}
#[test]
fn test_roundtrip_request() {
let req = JsonRpcRequest::new(Id::Number(1), "session/new", Some(json!({"cwd": "/test"})));
let json = serde_json::to_string(&req).unwrap();
let parsed: JsonRpcRequest = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.method, "session/new");
assert!(matches!(parsed.id, Id::Number(1)));
}
#[test]
fn test_roundtrip_notification() {
let notif = JsonRpcNotification::new("session/cancel", Some(json!({"session_id": "abc"})));
let json = serde_json::to_string(¬if).unwrap();
assert!(!json.contains(r#""id""#));
let parsed: JsonRpcNotification = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.method, "session/cancel");
}
#[test]
fn test_roundtrip_error_response() {
let resp = JsonRpcResponse::error(
Id::Number(5),
JsonRpcError::new(JsonRpcError::METHOD_NOT_FOUND, "Method not found"),
);
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains(r#""error":{"#));
let parsed: JsonRpcErrorResponse = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.error.code, JsonRpcError::METHOD_NOT_FOUND);
assert_eq!(parsed.error.message, "Method not found");
}
#[test]
fn test_message_variant_detection() {
let req_json = r#"{"jsonrpc":"2.0","id":1,"method":"test"}"#;
let msg: JsonRpcMessage = serde_json::from_str(req_json).unwrap();
assert!(msg.is_request());
assert!(msg.method() == Some("test"));
assert!(msg.id().is_some());
let notif_json = r#"{"jsonrpc":"2.0","method":"notify"}"#;
let msg: JsonRpcMessage = serde_json::from_str(notif_json).unwrap();
assert!(msg.is_notification());
assert!(msg.id().is_none());
let resp_json = r#"{"jsonrpc":"2.0","id":1,"result":"ok"}"#;
let msg: JsonRpcMessage = serde_json::from_str(resp_json).unwrap();
assert!(msg.is_response());
}
#[test]
fn test_builder_pattern() {
use super::builder;
let req = builder::request(Id::Number(1), "init", None);
assert!(req.is_request());
let notif = builder::notification("ping", None);
assert!(notif.is_notification());
let resp = builder::success_response(Id::Number(1), Some(json!("done")));
assert!(resp.is_response());
let err = builder::error_response(
Id::Number(1),
JsonRpcError::new(JsonRpcError::INTERNAL_ERROR, "Oops"),
);
assert!(err.is_response());
}
}