use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcRequest {
pub jsonrpc: String,
pub id: u64,
pub method: String,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub params: Value,
}
impl RpcRequest {
pub fn new(id: u64, method: impl Into<String>, params: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
method: method.into(),
params,
}
}
pub fn new_no_params(id: u64, method: impl Into<String>) -> Self {
Self::new(id, method, Value::Null)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcResponse {
pub jsonrpc: String,
pub id: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<RpcError>,
}
impl RpcResponse {
pub fn success(id: u64, result: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: Some(result),
error: None,
}
}
pub fn error(id: u64, code: i32, message: impl Into<String>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: None,
error: Some(RpcError {
code,
message: message.into(),
data: None,
}),
}
}
pub fn error_with_data(id: u64, code: i32, message: impl Into<String>, data: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: None,
error: Some(RpcError {
code,
message: message.into(),
data: Some(data),
}),
}
}
pub fn is_ok(&self) -> bool {
self.error.is_none()
}
pub fn into_result<T: for<'de> Deserialize<'de>>(self) -> Result<T, RpcError> {
if let Some(error) = self.error {
Err(error)
} else if let Some(result) = self.result {
serde_json::from_value(result).map_err(|e| RpcError {
code: error_codes::INTERNAL_ERROR,
message: format!("Failed to parse result: {}", e),
data: None,
})
} else {
Err(RpcError {
code: error_codes::INTERNAL_ERROR,
message: "Response has neither result nor error".to_string(),
data: None,
})
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl std::fmt::Display for RpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for RpcError {}
pub mod error_codes {
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;
pub const SERVICE_NOT_FOUND: i32 = -32000;
pub const SERVICE_ALREADY_RUNNING: i32 = -32001;
pub const SERVICE_NOT_RUNNING: i32 = -32002;
pub const INVALID_CONFIG: i32 = -32003;
pub const CYCLE_DETECTED: i32 = -32004;
pub const UNSAFE_REMOVAL: i32 = -32005;
pub const SERVICE_EXISTS: i32 = -32010;
pub const VALIDATION_FAILED: i32 = -32011;
pub const DEPENDENCY_MISSING: i32 = -32012;
pub const EXEC_NOT_FOUND: i32 = -32013;
pub const PERSIST_FAILED: i32 = -32014;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_creation() {
let req = RpcRequest::new(1, "service.status", serde_json::json!({"name": "test"}));
assert_eq!(req.jsonrpc, "2.0");
assert_eq!(req.id, 1);
assert_eq!(req.method, "service.status");
}
#[test]
fn test_request_serialization() {
let req = RpcRequest::new(1, "system.ping", Value::Null);
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"jsonrpc\":\"2.0\""));
assert!(json.contains("\"id\":1"));
assert!(json.contains("\"method\":\"system.ping\""));
}
#[test]
fn test_success_response() {
let resp = RpcResponse::success(1, serde_json::json!({"version": "0.1.0"}));
assert!(resp.is_ok());
assert!(resp.result.is_some());
assert!(resp.error.is_none());
}
#[test]
fn test_error_response() {
let resp = RpcResponse::error(1, error_codes::SERVICE_NOT_FOUND, "Service not found");
assert!(!resp.is_ok());
assert!(resp.result.is_none());
assert!(resp.error.is_some());
let err = resp.error.unwrap();
assert_eq!(err.code, error_codes::SERVICE_NOT_FOUND);
assert_eq!(err.message, "Service not found");
}
#[test]
fn test_into_result_success() {
#[derive(Debug, Deserialize, PartialEq)]
struct Version {
version: String,
}
let resp = RpcResponse::success(1, serde_json::json!({"version": "0.1.0"}));
let result: Version = resp.into_result().unwrap();
assert_eq!(result.version, "0.1.0");
}
#[test]
fn test_into_result_error() {
let resp = RpcResponse::error(1, error_codes::SERVICE_NOT_FOUND, "not found");
let result: Result<String, _> = resp.into_result();
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, error_codes::SERVICE_NOT_FOUND);
}
#[test]
fn test_rpc_error_display() {
let err = RpcError {
code: -32000,
message: "Service not found".to_string(),
data: None,
};
assert_eq!(err.to_string(), "[-32000] Service not found");
}
}