use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Deserialize)]
pub struct JsonRpcRequest {
pub method: String,
#[serde(default)]
pub params: Value,
pub id: Value,
}
#[derive(Debug, Serialize)]
pub struct JsonRpcResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
pub id: Value,
}
#[derive(Debug, Serialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
}
#[derive(Debug, Deserialize)]
pub struct RunParams {
pub capability: String,
#[serde(default)]
pub args: Value,
#[serde(default)]
pub dry_run: bool,
#[serde(default)]
pub working_dir: Option<String>,
#[serde(default)]
pub timeout_secs: Option<u64>,
}
#[derive(Debug, Deserialize)]
pub struct LogsParams {
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default)]
pub job_id: Option<String>,
}
fn default_limit() -> usize {
10
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_json_rpc_request_deserialization() {
let json = serde_json::json!({
"method": "run",
"params": {"capability": "FileRead"},
"id": 1
});
let req: JsonRpcRequest = serde_json::from_value(json).unwrap();
assert_eq!(req.method, "run");
assert_eq!(req.id, serde_json::Value::from(1));
}
#[test]
fn test_json_rpc_response_result_serialization() {
let resp = JsonRpcResponse {
result: Some(serde_json::json!({"success": true})),
error: None,
id: serde_json::Value::from(1),
};
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("success"));
assert!(!json.contains("error"));
}
#[test]
fn test_json_rpc_response_error_serialization() {
let resp = JsonRpcResponse {
result: None,
error: Some(JsonRpcError {
code: -32601,
message: "Method not found".into(),
}),
id: serde_json::Value::from(1),
};
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("Method not found"));
assert!(json.contains("-32601"));
}
#[test]
fn test_run_params_all_fields_valid() {
let json = serde_json::json!({
"capability": "FileRead",
"args": {"path": "/tmp/test.txt"},
"dry_run": true,
"working_dir": "/tmp",
"timeout_secs": 60
});
let params: RunParams = serde_json::from_value(json).unwrap();
assert_eq!(params.capability, "FileRead");
assert_eq!(params.args, serde_json::json!({"path": "/tmp/test.txt"}));
assert!(params.dry_run);
assert_eq!(params.working_dir.as_deref(), Some("/tmp"));
assert_eq!(params.timeout_secs, Some(60));
}
#[test]
fn test_run_params_minimal_valid() {
let json = serde_json::json!({"capability": "ShellExec"});
let params: RunParams = serde_json::from_value(json).unwrap();
assert_eq!(params.capability, "ShellExec");
assert_eq!(params.args, serde_json::Value::Null);
assert!(!params.dry_run);
assert!(params.working_dir.is_none());
assert!(params.timeout_secs.is_none());
}
#[test]
fn test_run_params_missing_capability() {
let json = serde_json::json!({"args": {"path": "/tmp/test.txt"}});
let result = serde_json::from_value::<RunParams>(json);
assert!(result.is_err());
}
#[test]
fn test_logs_params_default_limit() {
let params: LogsParams = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(params.limit, 10);
}
#[test]
fn test_logs_params_custom_limit() {
let params: LogsParams = serde_json::from_value(serde_json::json!({"limit": 5})).unwrap();
assert_eq!(params.limit, 5);
}
}