use serde::{Deserialize, Serialize};
use serde_json::Value;
const MAX_METHOD_NAME_LENGTH: usize = 255;
pub fn validate_method_name(method_name: &str) -> Result<(), String> {
if method_name.is_empty() {
return Err("Method name cannot be empty".to_string());
}
if method_name.starts_with(char::is_whitespace) || method_name.ends_with(char::is_whitespace) {
return Err("Method name cannot have leading or trailing whitespace".to_string());
}
if method_name.len() > MAX_METHOD_NAME_LENGTH {
return Err(format!(
"Method name exceeds maximum length of {} characters (got {})",
MAX_METHOD_NAME_LENGTH,
method_name.len()
));
}
for ch in method_name.chars() {
match ch {
'a'..='z' | 'A'..='Z' | '0'..='9' => {}
'.' => {}
'_' => {}
'-' => {}
c if (c as u32) < 0x20 || (c as u32) == 0x7F => {
return Err(format!(
"Method name contains invalid control character: 0x{:02X}",
c as u32
));
}
c => {
return Err(format!(
"Method name contains invalid character: '{}' (0x{:02X}). \
Only alphanumeric, dot (.), underscore (_), and hyphen (-) are allowed",
c, c as u32
));
}
}
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Value>,
}
impl JsonRpcRequest {
pub fn new(method: impl Into<String>, params: Option<Value>, id: Option<Value>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: method.into(),
params,
id,
}
}
pub fn is_notification(&self) -> bool {
self.id.is_none()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub result: Value,
pub id: Value,
}
impl JsonRpcResponse {
pub fn success(result: Value, id: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
result,
id,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcErrorResponse {
pub jsonrpc: String,
pub error: JsonRpcError,
pub id: Value,
}
impl JsonRpcErrorResponse {
pub fn error(code: i32, message: impl Into<String>, id: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
error: JsonRpcError {
code,
message: message.into(),
data: None,
},
id,
}
}
pub fn error_with_data(code: i32, message: impl Into<String>, data: Value, id: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
error: JsonRpcError {
code,
message: message.into(),
data: Some(data),
},
id,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcResponseType {
Success(JsonRpcResponse),
Error(JsonRpcErrorResponse),
}
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 SERVER_ERROR_BASE: i32 = -32000;
pub const SERVER_ERROR_END: i32 = -32099;
pub fn is_server_error(code: i32) -> bool {
(SERVER_ERROR_END..=SERVER_ERROR_BASE).contains(&code)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_jsonrpc_request_creation() {
let req = JsonRpcRequest::new("method", Some(json!({"key": "value"})), Some(json!(1)));
assert_eq!(req.jsonrpc, "2.0");
assert_eq!(req.method, "method");
assert!(!req.is_notification());
}
#[test]
fn test_jsonrpc_notification() {
let notif = JsonRpcRequest::new("notify", None, None);
assert!(notif.is_notification());
}
#[test]
fn test_jsonrpc_response_success() {
let response = JsonRpcResponse::success(json!(42), json!(1));
assert_eq!(response.jsonrpc, "2.0");
assert_eq!(response.result, json!(42));
assert_eq!(response.id, json!(1));
}
#[test]
fn test_jsonrpc_error_response() {
let err = JsonRpcErrorResponse::error(error_codes::METHOD_NOT_FOUND, "Method not found", json!(1));
assert_eq!(err.jsonrpc, "2.0");
assert_eq!(err.error.code, error_codes::METHOD_NOT_FOUND);
assert_eq!(err.error.message, "Method not found");
assert!(err.error.data.is_none());
}
#[test]
fn test_jsonrpc_error_response_with_data() {
let data = json!({"reason": "Missing parameter"});
let err = JsonRpcErrorResponse::error_with_data(
error_codes::INVALID_PARAMS,
"Invalid parameters",
data.clone(),
json!(null),
);
assert_eq!(err.error.code, error_codes::INVALID_PARAMS);
assert_eq!(err.error.data, Some(data));
}
#[test]
fn test_error_codes_constants() {
assert_eq!(error_codes::PARSE_ERROR, -32700);
assert_eq!(error_codes::INVALID_REQUEST, -32600);
assert_eq!(error_codes::METHOD_NOT_FOUND, -32601);
assert_eq!(error_codes::INVALID_PARAMS, -32602);
assert_eq!(error_codes::INTERNAL_ERROR, -32603);
}
#[test]
fn test_is_server_error() {
assert!(error_codes::is_server_error(-32000));
assert!(error_codes::is_server_error(-32050));
assert!(error_codes::is_server_error(-32099));
assert!(!error_codes::is_server_error(-32700));
assert!(!error_codes::is_server_error(0));
}
#[test]
fn test_request_serialization() {
let req = JsonRpcRequest::new("test", Some(json!([1, 2])), Some(json!(1)));
let json = serde_json::to_value(&req).unwrap();
assert_eq!(json["jsonrpc"], "2.0");
assert_eq!(json["method"], "test");
assert!(json["params"].is_array());
assert_eq!(json["id"], 1);
}
#[test]
fn test_notification_serialization() {
let notif = JsonRpcRequest::new("notify", Some(json!({})), None);
let json = serde_json::to_value(¬if).unwrap();
assert!(!json.get("id").is_some() || json["id"].is_null());
}
#[test]
fn test_response_serialization() {
let resp = JsonRpcResponse::success(json!({"result": 100}), json!("string-id"));
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["jsonrpc"], "2.0");
assert_eq!(json["id"], "string-id");
}
#[test]
fn test_error_response_serialization() {
let err = JsonRpcErrorResponse::error(error_codes::PARSE_ERROR, "Parse error", json!(null));
let json = serde_json::to_value(&err).unwrap();
assert_eq!(json["jsonrpc"], "2.0");
assert_eq!(json["error"]["code"], -32700);
assert_eq!(json["error"]["message"], "Parse error");
}
#[test]
fn test_response_type_enum() {
let success_resp = JsonRpcResponseType::Success(JsonRpcResponse::success(json!(1), json!(1)));
let error_resp = JsonRpcResponseType::Error(JsonRpcErrorResponse::error(
error_codes::INVALID_REQUEST,
"Invalid",
json!(1),
));
let _success_json = serde_json::to_value(&success_resp).unwrap();
let _error_json = serde_json::to_value(&error_resp).unwrap();
}
#[test]
fn test_validate_method_name_valid_simple() {
assert!(validate_method_name("test").is_ok());
assert!(validate_method_name("method").is_ok());
assert!(validate_method_name("rpc").is_ok());
}
#[test]
fn test_validate_method_name_valid_with_dot() {
assert!(validate_method_name("user.get").is_ok());
assert!(validate_method_name("api.v1.endpoint").is_ok());
assert!(validate_method_name("service.method.action").is_ok());
}
#[test]
fn test_validate_method_name_valid_with_underscore() {
assert!(validate_method_name("get_user").is_ok());
assert!(validate_method_name("_private_method").is_ok());
assert!(validate_method_name("method_v1").is_ok());
}
#[test]
fn test_validate_method_name_valid_with_hyphen() {
assert!(validate_method_name("get-user").is_ok());
assert!(validate_method_name("api-v1").is_ok());
assert!(validate_method_name("my-method-name").is_ok());
}
#[test]
fn test_validate_method_name_valid_with_numbers() {
assert!(validate_method_name("method1").is_ok());
assert!(validate_method_name("v2.endpoint").is_ok());
assert!(validate_method_name("rpc123abc").is_ok());
}
#[test]
fn test_validate_method_name_valid_mixed() {
assert!(validate_method_name("user.get_by_id").is_ok());
assert!(validate_method_name("api-v1.service_name").is_ok());
assert!(validate_method_name("Service_v1_2_3").is_ok());
}
#[test]
fn test_validate_method_name_valid_max_length() {
let max_name = "a".repeat(255);
assert!(validate_method_name(&max_name).is_ok());
}
#[test]
fn test_validate_method_name_empty() {
let result = validate_method_name("");
assert!(result.is_err());
assert!(result.unwrap_err().contains("cannot be empty"));
}
#[test]
fn test_validate_method_name_leading_space() {
let result = validate_method_name(" method");
assert!(result.is_err());
assert!(result.unwrap_err().contains("leading or trailing whitespace"));
}
#[test]
fn test_validate_method_name_trailing_space() {
let result = validate_method_name("method ");
assert!(result.is_err());
assert!(result.unwrap_err().contains("leading or trailing whitespace"));
}
#[test]
fn test_validate_method_name_leading_and_trailing_space() {
let result = validate_method_name(" method ");
assert!(result.is_err());
assert!(result.unwrap_err().contains("leading or trailing whitespace"));
}
#[test]
fn test_validate_method_name_internal_space() {
let result = validate_method_name("method name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid character"));
}
#[test]
fn test_validate_method_name_too_long() {
let too_long_name = "a".repeat(256);
let result = validate_method_name(&too_long_name);
assert!(result.is_err());
assert!(result.unwrap_err().contains("exceeds maximum length"));
}
#[test]
fn test_validate_method_name_null_byte() {
let result = validate_method_name("method\x00name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("control character"));
}
#[test]
fn test_validate_method_name_newline() {
let result = validate_method_name("method\nname");
assert!(result.is_err());
assert!(result.unwrap_err().contains("control character"));
}
#[test]
fn test_validate_method_name_carriage_return() {
let result = validate_method_name("method\rname");
assert!(result.is_err());
assert!(result.unwrap_err().contains("control character"));
}
#[test]
fn test_validate_method_name_tab() {
let result = validate_method_name("method\tname");
assert!(result.is_err());
assert!(result.unwrap_err().contains("control character"));
}
#[test]
fn test_validate_method_name_delete_char() {
let result = validate_method_name("method\x7fname");
assert!(result.is_err());
assert!(result.unwrap_err().contains("control character"));
}
#[test]
fn test_validate_method_name_special_char_at_sign() {
let result = validate_method_name("method@name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid character"));
}
#[test]
fn test_validate_method_name_special_char_hash() {
let result = validate_method_name("method#name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid character"));
}
#[test]
fn test_validate_method_name_special_char_percent() {
let result = validate_method_name("method%name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid character"));
}
#[test]
fn test_validate_method_name_special_char_slash() {
let result = validate_method_name("method/name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid character"));
}
#[test]
fn test_validate_method_name_special_char_backslash() {
let result = validate_method_name("method\\name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid character"));
}
#[test]
fn test_validate_method_name_special_char_quote() {
let result = validate_method_name("method\"name");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid character"));
}
#[test]
fn test_validate_method_name_dos_attack_very_long() {
let very_long = "a".repeat(10000);
let result = validate_method_name(&very_long);
assert!(result.is_err());
assert!(result.unwrap_err().contains("exceeds maximum length"));
}
#[test]
fn test_validate_method_name_dos_attack_control_chars() {
let result = validate_method_name("method\x00\x00\x00\x00");
assert!(result.is_err());
assert!(result.unwrap_err().contains("control character"));
}
#[test]
fn test_validate_method_name_edge_case_single_char() {
assert!(validate_method_name("a").is_ok());
assert!(validate_method_name("_").is_ok());
assert!(validate_method_name("-").is_ok());
assert!(validate_method_name(".").is_ok());
}
#[test]
fn test_request_with_null_id_is_notification() {
let json = json!({
"jsonrpc": "2.0",
"method": "notify",
"params": []
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(request.is_notification());
assert_eq!(request.method, "notify");
}
#[test]
fn test_request_with_string_id_preserved() {
let json = json!({
"jsonrpc": "2.0",
"method": "add",
"params": [1, 2],
"id": "abc-123"
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(!request.is_notification());
assert_eq!(request.id, Some(json!("abc-123")));
}
#[test]
fn test_request_with_zero_id_valid() {
let json = json!({
"jsonrpc": "2.0",
"method": "test",
"id": 0
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(!request.is_notification());
assert_eq!(request.id, Some(json!(0)));
}
#[test]
fn test_request_with_negative_id_valid() {
let json = json!({
"jsonrpc": "2.0",
"method": "test",
"id": -999
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(!request.is_notification());
assert_eq!(request.id, Some(json!(-999)));
}
#[test]
fn test_request_without_jsonrpc_field_invalid() {
let json = json!({
"method": "test",
"id": 1
});
let result: serde_json::Result<JsonRpcRequest> = serde_json::from_value(json);
assert!(result.is_err());
}
#[test]
fn test_response_preserves_id_type_numeric() {
let response = JsonRpcResponse::success(json!(42), json!(999));
let serialized = serde_json::to_value(&response).unwrap();
assert_eq!(serialized["id"], 999);
assert!(serialized["id"].is_number());
}
#[test]
fn test_error_response_never_has_result_field() {
let err = JsonRpcErrorResponse::error(error_codes::INVALID_PARAMS, "Bad params", json!(1));
let serialized = serde_json::to_value(&err).unwrap();
assert!(serialized.get("result").is_none());
assert!(serialized.get("error").is_some());
}
#[test]
fn test_success_response_never_has_error_field() {
let success = JsonRpcResponse::success(json!({"data": 123}), json!(1));
let serialized = serde_json::to_value(&success).unwrap();
assert!(serialized.get("error").is_none());
assert!(serialized.get("result").is_some());
}
#[test]
fn test_params_array_type() {
let json = json!({
"jsonrpc": "2.0",
"method": "sum",
"params": [1, 2, 3, 4, 5],
"id": 1
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(request.params.is_some());
let params = request.params.unwrap();
assert!(params.is_array());
let arr = params.as_array().unwrap();
assert_eq!(arr.len(), 5);
assert_eq!(arr[0], json!(1));
}
#[test]
fn test_params_object_type() {
let json = json!({
"jsonrpc": "2.0",
"method": "subtract",
"params": {"a": 5, "b": 3},
"id": 2
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(request.params.is_some());
let params = request.params.unwrap();
assert!(params.is_object());
let obj = params.as_object().unwrap();
assert_eq!(obj.get("a"), Some(&json!(5)));
assert_eq!(obj.get("b"), Some(&json!(3)));
}
#[test]
fn test_params_null_type() {
let json_no_params = json!({
"jsonrpc": "2.0",
"method": "test",
"id": 3
});
let request: JsonRpcRequest = serde_json::from_value(json_no_params).unwrap();
assert!(request.params.is_none());
}
#[test]
fn test_params_primitive_string() {
let json = json!({
"jsonrpc": "2.0",
"method": "echo",
"params": "hello world",
"id": 4
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(request.params.is_some());
assert_eq!(request.params.unwrap(), json!("hello world"));
}
#[test]
fn test_params_primitive_number() {
let json = json!({
"jsonrpc": "2.0",
"method": "increment",
"params": 42,
"id": 5
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(request.params.is_some());
assert_eq!(request.params.unwrap(), json!(42));
}
#[test]
fn test_params_deeply_nested() {
let json = json!({
"jsonrpc": "2.0",
"method": "process",
"params": {
"level1": {
"level2": {
"level3": {
"level4": {
"level5": {
"level6": {
"level7": {
"level8": {
"level9": {
"level10": "deep value"
}
}
}
}
}
}
}
}
}
},
"id": 6
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert!(request.params.is_some());
let deep = request.params.unwrap();
assert!(
deep["level1"]["level2"]["level3"]["level4"]["level5"]["level6"]["level7"]["level8"]["level9"]["level10"]
.is_string()
);
assert_eq!(
deep["level1"]["level2"]["level3"]["level4"]["level5"]["level6"]["level7"]["level8"]["level9"]["level10"],
"deep value"
);
}
#[test]
fn test_params_unicode_strings() {
let json = json!({
"jsonrpc": "2.0",
"method": "translate",
"params": {
"emoji": "Hello 👋 World 🌍",
"rtl": "שלום עולם",
"cjk": "你好世界",
"special": "café ñ ü"
},
"id": 7
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
let params = request.params.unwrap();
assert_eq!(params["emoji"], "Hello 👋 World 🌍");
assert_eq!(params["rtl"], "שלום עולם");
assert_eq!(params["cjk"], "你好世界");
assert_eq!(params["special"], "café ñ ü");
}
#[test]
fn test_response_result_with_null_valid() {
let response = JsonRpcResponse::success(json!(null), json!(1));
let serialized = serde_json::to_value(&response).unwrap();
assert_eq!(serialized["jsonrpc"], "2.0");
assert_eq!(serialized["id"], 1);
assert_eq!(serialized["result"], json!(null));
assert!(serialized.get("error").is_none());
}
#[test]
fn test_response_result_with_false_valid() {
let response = JsonRpcResponse::success(json!(false), json!(2));
let serialized = serde_json::to_value(&response).unwrap();
assert_eq!(serialized["jsonrpc"], "2.0");
assert_eq!(serialized["id"], 2);
assert_eq!(serialized["result"], json!(false));
assert!(serialized.get("error").is_none());
}
#[test]
fn test_response_result_with_zero_valid() {
let response = JsonRpcResponse::success(json!(0), json!(3));
let serialized = serde_json::to_value(&response).unwrap();
assert_eq!(serialized["jsonrpc"], "2.0");
assert_eq!(serialized["id"], 3);
assert_eq!(serialized["result"], json!(0));
assert!(serialized.get("error").is_none());
}
#[test]
fn test_response_result_with_empty_object_valid() {
let response = JsonRpcResponse::success(json!({}), json!(4));
let serialized = serde_json::to_value(&response).unwrap();
assert_eq!(serialized["jsonrpc"], "2.0");
assert_eq!(serialized["id"], 4);
assert_eq!(serialized["result"], json!({}));
assert!(serialized.get("error").is_none());
}
#[test]
fn test_response_result_with_empty_array_valid() {
let response = JsonRpcResponse::success(json!([]), json!(5));
let serialized = serde_json::to_value(&response).unwrap();
assert_eq!(serialized["jsonrpc"], "2.0");
assert_eq!(serialized["id"], 5);
assert_eq!(serialized["result"], json!([]));
assert!(serialized.get("error").is_none());
}
#[test]
fn test_error_code_parse_error() {
let err = JsonRpcErrorResponse::error(error_codes::PARSE_ERROR, "Parse error", json!(null));
let serialized = serde_json::to_value(&err).unwrap();
assert_eq!(serialized["error"]["code"], -32700);
assert!(serialized.get("result").is_none());
}
#[test]
fn test_error_code_roundtrip() {
let codes = vec![
error_codes::PARSE_ERROR,
error_codes::INVALID_REQUEST,
error_codes::METHOD_NOT_FOUND,
error_codes::INVALID_PARAMS,
error_codes::INTERNAL_ERROR,
];
for code in codes {
let err = JsonRpcErrorResponse::error(code, "Test error", json!(1));
let serialized = serde_json::to_value(&err).unwrap();
let deserialized: JsonRpcErrorResponse = serde_json::from_value(serialized).unwrap();
assert_eq!(deserialized.error.code, code);
}
}
#[test]
fn test_notification_has_no_id_field() {
let notif = JsonRpcRequest::new("notify", None, None);
let serialized = serde_json::to_value(¬if).unwrap();
assert!(serialized.get("id").is_none());
}
#[test]
fn test_id_preservation_in_batch() {
let json_batch = json!([
{
"jsonrpc": "2.0",
"method": "method1",
"id": "string-id"
},
{
"jsonrpc": "2.0",
"method": "method2",
"id": 42
},
{
"jsonrpc": "2.0",
"method": "method3"
}
]);
let batch: Vec<JsonRpcRequest> = serde_json::from_value(json_batch).unwrap();
assert_eq!(batch.len(), 3);
assert_eq!(batch[0].id, Some(json!("string-id")));
assert_eq!(batch[1].id, Some(json!(42)));
assert_eq!(batch[2].id, None);
}
#[test]
fn test_mixed_id_types_in_batch() {
let responses = vec![
JsonRpcResponse::success(json!(100), json!("id1")),
JsonRpcResponse::success(json!(200), json!(2)),
JsonRpcResponse::success(json!(300), json!(null)),
];
for resp in responses {
let serialized = serde_json::to_value(&resp).unwrap();
let deserialized: JsonRpcResponse = serde_json::from_value(serialized).unwrap();
assert_eq!(deserialized.jsonrpc, "2.0");
}
}
#[test]
fn test_large_numeric_id() {
let large_id = i64::MAX;
let json = json!({
"jsonrpc": "2.0",
"method": "test",
"id": large_id
});
let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert_eq!(request.id, Some(json!(large_id)));
}
#[test]
fn test_error_always_has_code() {
let err = JsonRpcErrorResponse::error(error_codes::METHOD_NOT_FOUND, "Not found", json!(1));
let serialized = serde_json::to_value(&err).unwrap();
assert!(serialized["error"].get("code").is_some());
assert_eq!(serialized["error"]["code"], -32601);
}
#[test]
fn test_error_always_has_message() {
let err = JsonRpcErrorResponse::error(error_codes::INVALID_PARAMS, "Invalid parameters", json!(2));
let serialized = serde_json::to_value(&err).unwrap();
assert!(serialized["error"].get("message").is_some());
assert_eq!(serialized["error"]["message"], "Invalid parameters");
}
#[test]
fn test_error_data_optional() {
let err_without_data = JsonRpcErrorResponse::error(error_codes::INTERNAL_ERROR, "Internal error", json!(3));
let serialized_without = serde_json::to_value(&err_without_data).unwrap();
assert!(serialized_without["error"].get("data").is_none());
let err_with_data = JsonRpcErrorResponse::error_with_data(
error_codes::INTERNAL_ERROR,
"Internal error",
json!({"details": "something went wrong"}),
json!(4),
);
let serialized_with = serde_json::to_value(&err_with_data).unwrap();
assert!(serialized_with["error"].get("data").is_some());
}
}