Skip to main content

dcp/bench/
json_rpc.rs

1//! JSON-RPC encoding for size comparison with DCP.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// JSON-RPC 2.0 request for size comparison
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct JsonRpcRequest {
9    pub jsonrpc: String,
10    pub method: String,
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub params: Option<Value>,
13    pub id: Value,
14}
15
16impl JsonRpcRequest {
17    /// Create a new JSON-RPC request
18    pub fn new(method: &str, params: Option<Value>, id: u64) -> Self {
19        Self {
20            jsonrpc: "2.0".to_string(),
21            method: method.to_string(),
22            params,
23            id: Value::Number(id.into()),
24        }
25    }
26}
27
28/// Encode a tool invocation as JSON-RPC
29pub fn encode_json_rpc(method: &str, params: &Value, id: u64) -> String {
30    let request = JsonRpcRequest {
31        jsonrpc: "2.0".to_string(),
32        method: method.to_string(),
33        params: Some(params.clone()),
34        id: Value::Number(id.into()),
35    };
36    serde_json::to_string(&request).unwrap_or_default()
37}
38
39/// Measure the size of a JSON-RPC encoded message
40pub fn measure_json_rpc_size(method: &str, params: &Value, id: u64) -> usize {
41    encode_json_rpc(method, params, id).len()
42}
43
44/// DCP binary message header size
45pub const DCP_HEADER_SIZE: usize = 8;
46
47/// DCP tool invocation header size
48pub const DCP_INVOCATION_SIZE: usize = 20;
49
50/// Measure the size of a DCP encoded message
51pub fn measure_dcp_size(args_size: usize) -> usize {
52    // DCP message = envelope (8) + invocation header (20) + args
53    DCP_HEADER_SIZE + DCP_INVOCATION_SIZE + args_size
54}
55
56/// Size comparison result
57#[derive(Debug, Clone)]
58pub struct SizeComparison {
59    /// JSON-RPC message size in bytes
60    pub json_rpc_size: usize,
61    /// DCP message size in bytes
62    pub dcp_size: usize,
63    /// Size ratio (JSON-RPC / DCP)
64    pub ratio: f64,
65}
66
67impl SizeComparison {
68    /// Create a new size comparison
69    pub fn new(json_rpc_size: usize, dcp_size: usize) -> Self {
70        let ratio = if dcp_size > 0 {
71            json_rpc_size as f64 / dcp_size as f64
72        } else {
73            0.0
74        };
75        Self {
76            json_rpc_size,
77            dcp_size,
78            ratio,
79        }
80    }
81
82    /// Check if DCP is at least N times smaller
83    pub fn dcp_is_smaller_by(&self, factor: f64) -> bool {
84        self.ratio >= factor
85    }
86}
87
88/// Compare sizes for a tool invocation
89pub fn compare_sizes(method: &str, params: &Value, args_binary_size: usize) -> SizeComparison {
90    let json_size = measure_json_rpc_size(method, params, 1);
91    let dcp_size = measure_dcp_size(args_binary_size);
92    SizeComparison::new(json_size, dcp_size)
93}
94
95/// Estimate binary size for JSON value
96pub fn estimate_binary_size(value: &Value) -> usize {
97    match value {
98        Value::Null => 0,
99        Value::Bool(_) => 1,
100        Value::Number(n) => {
101            if n.is_i64() || n.is_f64() {
102                8
103            } else {
104                4
105            }
106        }
107        Value::String(s) => 4 + s.len(), // length prefix + data
108        Value::Array(arr) => {
109            4 + arr.iter().map(estimate_binary_size).sum::<usize>() // length + elements
110        }
111        Value::Object(obj) => {
112            4 + obj
113                .iter()
114                .map(|(k, v)| 1 + k.len() + estimate_binary_size(v))
115                .sum::<usize>()
116        }
117    }
118}
119
120/// Compare sizes using automatic binary size estimation
121pub fn compare_sizes_auto(method: &str, params: &Value) -> SizeComparison {
122    let binary_size = estimate_binary_size(params);
123    compare_sizes(method, params, binary_size)
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use serde_json::json;
130
131    #[test]
132    fn test_json_rpc_encoding() {
133        let params = json!({"name": "test", "value": 42});
134        let encoded = encode_json_rpc("tools/call", &params, 1);
135
136        assert!(encoded.contains("\"jsonrpc\":\"2.0\""));
137        assert!(encoded.contains("\"method\":\"tools/call\""));
138        assert!(encoded.contains("\"params\""));
139        assert!(encoded.contains("\"id\":1"));
140    }
141
142    #[test]
143    fn test_size_comparison() {
144        let params = json!({"name": "test"});
145        let comparison = compare_sizes("test", &params, 8);
146
147        assert!(comparison.json_rpc_size > 0);
148        assert!(comparison.dcp_size > 0);
149        assert!(comparison.ratio > 0.0);
150    }
151
152    #[test]
153    fn test_dcp_smaller_for_simple_call() {
154        // Simple tool call with minimal params
155        let params = json!({"path": "/tmp/test.txt"});
156        let comparison = compare_sizes_auto("read_file", &params);
157
158        // DCP should be significantly smaller
159        println!(
160            "JSON-RPC: {} bytes, DCP: {} bytes, ratio: {:.2}x",
161            comparison.json_rpc_size, comparison.dcp_size, comparison.ratio
162        );
163
164        // For typical tool calls, DCP should be at least 1.4x smaller
165        assert!(comparison.ratio >= 1.4);
166    }
167
168    #[test]
169    fn test_estimate_binary_size() {
170        assert_eq!(estimate_binary_size(&json!(null)), 0);
171        assert_eq!(estimate_binary_size(&json!(true)), 1);
172        assert_eq!(estimate_binary_size(&json!(42)), 8);
173        assert_eq!(estimate_binary_size(&json!(3.14)), 8);
174        assert_eq!(estimate_binary_size(&json!("hello")), 4 + 5);
175    }
176
177    #[test]
178    fn test_size_comparison_ratio() {
179        let comparison = SizeComparison::new(100, 20);
180        assert_eq!(comparison.ratio, 5.0);
181        assert!(comparison.dcp_is_smaller_by(5.0));
182        assert!(!comparison.dcp_is_smaller_by(6.0));
183    }
184}