use serde::{Deserialize, Serialize};
use serde_json::Value;
#[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>,
pub id: Value,
}
impl JsonRpcRequest {
pub fn new(method: &str, params: Option<Value>, id: u64) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: method.to_string(),
params,
id: Value::Number(id.into()),
}
}
}
pub fn encode_json_rpc(method: &str, params: &Value, id: u64) -> String {
let request = JsonRpcRequest {
jsonrpc: "2.0".to_string(),
method: method.to_string(),
params: Some(params.clone()),
id: Value::Number(id.into()),
};
serde_json::to_string(&request).unwrap_or_default()
}
pub fn measure_json_rpc_size(method: &str, params: &Value, id: u64) -> usize {
encode_json_rpc(method, params, id).len()
}
pub const DCP_HEADER_SIZE: usize = 8;
pub const DCP_INVOCATION_SIZE: usize = 20;
pub fn measure_dcp_size(args_size: usize) -> usize {
DCP_HEADER_SIZE + DCP_INVOCATION_SIZE + args_size
}
#[derive(Debug, Clone)]
pub struct SizeComparison {
pub json_rpc_size: usize,
pub dcp_size: usize,
pub ratio: f64,
}
impl SizeComparison {
pub fn new(json_rpc_size: usize, dcp_size: usize) -> Self {
let ratio = if dcp_size > 0 {
json_rpc_size as f64 / dcp_size as f64
} else {
0.0
};
Self {
json_rpc_size,
dcp_size,
ratio,
}
}
pub fn dcp_is_smaller_by(&self, factor: f64) -> bool {
self.ratio >= factor
}
}
pub fn compare_sizes(method: &str, params: &Value, args_binary_size: usize) -> SizeComparison {
let json_size = measure_json_rpc_size(method, params, 1);
let dcp_size = measure_dcp_size(args_binary_size);
SizeComparison::new(json_size, dcp_size)
}
pub fn estimate_binary_size(value: &Value) -> usize {
match value {
Value::Null => 0,
Value::Bool(_) => 1,
Value::Number(n) => {
if n.is_i64() || n.is_f64() {
8
} else {
4
}
}
Value::String(s) => 4 + s.len(), Value::Array(arr) => {
4 + arr.iter().map(estimate_binary_size).sum::<usize>() }
Value::Object(obj) => {
4 + obj
.iter()
.map(|(k, v)| 1 + k.len() + estimate_binary_size(v))
.sum::<usize>()
}
}
}
pub fn compare_sizes_auto(method: &str, params: &Value) -> SizeComparison {
let binary_size = estimate_binary_size(params);
compare_sizes(method, params, binary_size)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_json_rpc_encoding() {
let params = json!({"name": "test", "value": 42});
let encoded = encode_json_rpc("tools/call", ¶ms, 1);
assert!(encoded.contains("\"jsonrpc\":\"2.0\""));
assert!(encoded.contains("\"method\":\"tools/call\""));
assert!(encoded.contains("\"params\""));
assert!(encoded.contains("\"id\":1"));
}
#[test]
fn test_size_comparison() {
let params = json!({"name": "test"});
let comparison = compare_sizes("test", ¶ms, 8);
assert!(comparison.json_rpc_size > 0);
assert!(comparison.dcp_size > 0);
assert!(comparison.ratio > 0.0);
}
#[test]
fn test_dcp_smaller_for_simple_call() {
let params = json!({"path": "/tmp/test.txt"});
let comparison = compare_sizes_auto("read_file", ¶ms);
println!(
"JSON-RPC: {} bytes, DCP: {} bytes, ratio: {:.2}x",
comparison.json_rpc_size, comparison.dcp_size, comparison.ratio
);
assert!(comparison.ratio >= 1.4);
}
#[test]
fn test_estimate_binary_size() {
assert_eq!(estimate_binary_size(&json!(null)), 0);
assert_eq!(estimate_binary_size(&json!(true)), 1);
assert_eq!(estimate_binary_size(&json!(42)), 8);
assert_eq!(estimate_binary_size(&json!(3.14)), 8);
assert_eq!(estimate_binary_size(&json!("hello")), 4 + 5);
}
#[test]
fn test_size_comparison_ratio() {
let comparison = SizeComparison::new(100, 20);
assert_eq!(comparison.ratio, 5.0);
assert!(comparison.dcp_is_smaller_by(5.0));
assert!(!comparison.dcp_is_smaller_by(6.0));
}
}