use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: RequestId,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
impl JsonRpcRequest {
pub fn new(id: impl Into<RequestId>, method: impl Into<String>, params: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: id.into(),
method: method.into(),
params: Some(params),
}
}
pub fn without_params(id: impl Into<RequestId>, method: impl Into<String>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: id.into(),
method: method.into(),
params: None,
}
}
pub fn with_random_id(method: impl Into<String>, params: Value) -> Self {
Self::new(Uuid::new_v4().to_string(), method, params)
}
pub fn has_params(&self) -> bool {
self.params.is_some()
}
pub fn params_as<T>(&self) -> Result<T, serde_json::Error>
where
T: for<'de> Deserialize<'de>,
{
match &self.params {
Some(params) => serde_json::from_value(params.clone()),
None => serde_json::from_value(Value::Null),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub id: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
}
impl JsonRpcResponse {
pub fn success(id: impl Into<RequestId>, result: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: id.into(),
result: Some(result),
error: None,
}
}
pub fn error(id: impl Into<RequestId>, error: JsonRpcError) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: id.into(),
result: None,
error: Some(error),
}
}
pub fn is_success(&self) -> bool {
self.result.is_some() && self.error.is_none()
}
pub fn is_error(&self) -> bool {
self.error.is_some()
}
pub fn result_as<T>(&self) -> Result<T, Box<dyn std::error::Error + Send + Sync>>
where
T: for<'de> Deserialize<'de>,
{
match (&self.result, &self.error) {
(Some(result), None) => Ok(serde_json::from_value(result.clone())?),
(None, Some(error)) => Err(format!("JSON-RPC error: {error}").into()),
_ => Err("Invalid response: both result and error are present or missing".into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
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: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: method.into(),
params: Some(params),
}
}
pub fn without_params(method: impl Into<String>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: method.into(),
params: None,
}
}
pub fn has_params(&self) -> bool {
self.params.is_some()
}
pub fn params_as<T>(&self) -> Result<T, serde_json::Error>
where
T: for<'de> Deserialize<'de>,
{
match &self.params {
Some(params) => serde_json::from_value(params.clone()),
None => serde_json::from_value(Value::Null),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
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>, data: Option<Value>) -> Self {
Self {
code,
message: message.into(),
data,
}
}
pub fn parse_error() -> Self {
Self::new(-32700, "Parse error", None)
}
pub fn invalid_request(details: impl Into<String>) -> Self {
Self::new(
-32600,
"Invalid Request",
Some(Value::String(details.into())),
)
}
pub fn method_not_found(method: impl Into<String>) -> Self {
Self::new(
-32601,
"Method not found",
Some(Value::String(format!(
"Method '{}' not found",
method.into()
))),
)
}
pub fn invalid_params(details: impl Into<String>) -> Self {
Self::new(
-32602,
"Invalid params",
Some(Value::String(details.into())),
)
}
pub fn internal_error(details: impl Into<String>) -> Self {
Self::new(
-32603,
"Internal error",
Some(Value::String(details.into())),
)
}
pub fn application_error(
code: i32,
message: impl Into<String>,
details: impl Into<String>,
) -> Self {
Self::new(code, message, Some(Value::String(details.into())))
}
pub fn is_standard_error(&self) -> bool {
matches!(self.code, -32700..=-32600)
}
pub fn is_application_error(&self) -> bool {
matches!(self.code, -32099..=-32000)
}
}
impl std::fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "JSON-RPC Error {}: {}", self.code, self.message)?;
if let Some(data) = &self.data {
write!(f, " ({data})")?;
}
Ok(())
}
}
impl std::error::Error for JsonRpcError {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RequestId {
String(String),
Number(i64),
Null,
}
impl From<String> for RequestId {
fn from(s: String) -> Self {
Self::String(s)
}
}
impl From<&str> for RequestId {
fn from(s: &str) -> Self {
Self::String(s.to_string())
}
}
impl From<i64> for RequestId {
fn from(n: i64) -> Self {
Self::Number(n)
}
}
impl From<i32> for RequestId {
fn from(n: i32) -> Self {
Self::Number(n as i64)
}
}
impl std::fmt::Display for RequestId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(s) => write!(f, "{s}"),
Self::Number(n) => write!(f, "{n}"),
Self::Null => write!(f, "null"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcMessage {
Request(JsonRpcRequest),
Response(JsonRpcResponse),
Notification(JsonRpcNotification),
}
impl JsonRpcMessage {
pub fn method(&self) -> Option<&str> {
match self {
Self::Request(req) => Some(&req.method),
Self::Notification(notif) => Some(¬if.method),
Self::Response(_) => None,
}
}
pub fn id(&self) -> Option<&RequestId> {
match self {
Self::Request(req) => Some(&req.id),
Self::Response(resp) => Some(&resp.id),
Self::Notification(_) => None,
}
}
pub fn expects_response(&self) -> bool {
matches!(self, Self::Request(_))
}
}
impl From<JsonRpcRequest> for JsonRpcMessage {
fn from(req: JsonRpcRequest) -> Self {
Self::Request(req)
}
}
impl From<JsonRpcResponse> for JsonRpcMessage {
fn from(resp: JsonRpcResponse) -> Self {
Self::Response(resp)
}
}
impl From<JsonRpcNotification> for JsonRpcMessage {
fn from(notif: JsonRpcNotification) -> Self {
Self::Notification(notif)
}
}
pub type JsonRpcId = RequestId;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_request_creation() {
let request = JsonRpcRequest::new("1", "test_method", json!({"param": "value"}));
assert_eq!(request.jsonrpc, "2.0");
assert_eq!(request.id, RequestId::String("1".to_string()));
assert_eq!(request.method, "test_method");
assert!(request.has_params());
}
#[test]
fn test_request_without_params() {
let request = JsonRpcRequest::without_params("1", "test_method");
assert!(!request.has_params());
assert_eq!(request.params, None);
}
#[test]
fn test_success_response() {
let response = JsonRpcResponse::success("1", json!({"result": "ok"}));
assert!(response.is_success());
assert!(!response.is_error());
assert_eq!(response.id, RequestId::String("1".to_string()));
}
#[test]
fn test_error_response() {
let error = JsonRpcError::method_not_found("unknown");
let response = JsonRpcResponse::error("1", error);
assert!(!response.is_success());
assert!(response.is_error());
assert_eq!(response.error.as_ref().unwrap().code, -32601);
}
#[test]
fn test_notification_creation() {
let notification = JsonRpcNotification::new("event", json!({"data": "value"}));
assert_eq!(notification.method, "event");
assert!(notification.has_params());
}
#[test]
fn test_json_rpc_error_types() {
let parse_error = JsonRpcError::parse_error();
assert_eq!(parse_error.code, -32700);
assert!(parse_error.is_standard_error());
let app_error = JsonRpcError::application_error(-32000, "App error", "Details");
assert_eq!(app_error.code, -32000);
assert!(app_error.is_application_error());
}
#[test]
fn test_request_id_variants() {
let string_id = RequestId::from("test");
let number_id = RequestId::from(42i64);
let null_id = RequestId::Null;
assert_eq!(string_id.to_string(), "test");
assert_eq!(number_id.to_string(), "42");
assert_eq!(null_id.to_string(), "null");
}
#[test]
fn test_message_serialization() {
let request = JsonRpcRequest::new("1", "test", json!({}));
let json = serde_json::to_string(&request).unwrap();
let deserialized: JsonRpcRequest = serde_json::from_str(&json).unwrap();
assert_eq!(request, deserialized);
}
#[test]
fn test_generic_message_handling() {
let request = JsonRpcMessage::Request(JsonRpcRequest::new("1", "test", json!({})));
let notification =
JsonRpcMessage::Notification(JsonRpcNotification::new("event", json!({})));
assert_eq!(request.method(), Some("test"));
assert_eq!(notification.method(), Some("event"));
assert!(request.expects_response());
assert!(!notification.expects_response());
}
}