1use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12pub(crate) const JSONRPC_VERSION: &str = "2.0";
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct JsonRpcRequest {
18 pub(crate) jsonrpc: String,
20
21 pub(crate) method: String,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub(crate) params: Option<Value>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub(crate) id: Option<JsonRpcId>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct JsonRpcResponse {
36 jsonrpc: String,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub(crate) result: Option<Value>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub(crate) error: Option<JsonRpcError>,
46
47 id: Option<JsonRpcId>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct JsonRpcError {
54 pub(crate) code: i32,
56
57 pub(crate) message: String,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 data: Option<Value>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
69#[serde(untagged)]
70pub enum JsonRpcId {
71 String(String),
73 Number(i64),
75}
76
77impl JsonRpcId {
78 fn string(s: impl Into<String>) -> Self {
80 Self::String(s.into())
81 }
82
83 fn number(n: i64) -> Self {
85 Self::Number(n)
86 }
87
88 pub fn new_uuid() -> Self {
90 Self::String(uuid::Uuid::new_v4().to_string())
91 }
92}
93
94impl std::fmt::Display for JsonRpcId {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self {
97 JsonRpcId::String(s) => write!(f, "{s}"),
98 JsonRpcId::Number(n) => write!(f, "{n}"),
99 }
100 }
101}
102
103mod error_codes {
105 pub const PARSE_ERROR: i32 = -32700;
107
108 pub const INVALID_REQUEST: i32 = -32600;
110
111 pub(crate) const METHOD_NOT_FOUND: i32 = -32601;
113
114 pub const INVALID_PARAMS: i32 = -32602;
116
117 pub const INTERNAL_ERROR: i32 = -32603;
119
120 pub const SERVER_ERROR_START: i32 = -32099;
122 pub const SERVER_ERROR_END: i32 = -32000;
123
124 pub(crate) const AUTH_REQUIRED: i32 = -32001;
128
129 pub(crate) const PERMISSION_DENIED: i32 = -32002;
131
132 pub(crate) const SESSION_NOT_FOUND: i32 = -32003;
134
135 pub(crate) const RATE_LIMITED: i32 = -32004;
137
138 pub(crate) const RESOURCE_NOT_FOUND: i32 = -32005;
140}
141
142impl JsonRpcRequest {
143 fn new(method: impl Into<String>, params: Option<Value>, id: Option<JsonRpcId>) -> Self {
145 Self {
146 jsonrpc: JSONRPC_VERSION.to_string(),
147 method: method.into(),
148 params,
149 id,
150 }
151 }
152
153 pub fn with_uuid(method: impl Into<String>, params: Option<Value>) -> Self {
155 Self::new(method, params, Some(JsonRpcId::new_uuid()))
156 }
157
158 pub(crate) fn notification(method: impl Into<String>, params: Option<Value>) -> Self {
160 Self::new(method, params, None)
161 }
162
163 fn is_notification(&self) -> bool {
165 self.id.is_none()
166 }
167}
168
169impl JsonRpcResponse {
170 fn success(result: Value, id: Option<JsonRpcId>) -> Self {
172 Self {
173 jsonrpc: JSONRPC_VERSION.to_string(),
174 result: Some(result),
175 error: None,
176 id,
177 }
178 }
179
180 fn error(error: JsonRpcError, id: Option<JsonRpcId>) -> Self {
182 Self {
183 jsonrpc: JSONRPC_VERSION.to_string(),
184 result: None,
185 error: Some(error),
186 id,
187 }
188 }
189
190 fn is_success(&self) -> bool {
192 self.error.is_none() && self.result.is_some()
193 }
194
195 fn is_error(&self) -> bool {
197 self.error.is_some()
198 }
199
200 pub fn into_result(self) -> Result<Value, JsonRpcError> {
202 if let Some(error) = self.error {
203 Err(error)
204 } else {
205 Ok(self.result.unwrap_or(Value::Null))
206 }
207 }
208}
209
210impl JsonRpcError {
211 fn new(code: i32, message: impl Into<String>) -> Self {
213 Self { code, message: message.into(), data: None }
214 }
215
216 fn with_data(code: i32, message: impl Into<String>, data: Value) -> Self {
218 Self { code, message: message.into(), data: Some(data) }
219 }
220
221 #[cold]
223 pub fn parse_error(details: impl Into<String>) -> Self {
224 Self::new(error_codes::PARSE_ERROR, details)
225 }
226
227 #[cold]
229 pub fn invalid_request(details: impl Into<String>) -> Self {
230 Self::new(error_codes::INVALID_REQUEST, details)
231 }
232
233 #[cold]
235 fn method_not_found(method: impl Into<String>) -> Self {
236 Self::new(error_codes::METHOD_NOT_FOUND, format!("Method not found: {}", method.into()))
237 }
238
239 #[cold]
241 pub fn invalid_params(details: impl Into<String>) -> Self {
242 Self::new(error_codes::INVALID_PARAMS, details)
243 }
244
245 #[cold]
247 pub fn internal_error(details: impl Into<String>) -> Self {
248 Self::new(error_codes::INTERNAL_ERROR, details)
249 }
250
251 #[cold]
256 fn auth_required(auth_methods: Vec<super::AuthMethod>) -> Self {
257 let data = serde_json::json!({
258 "authMethods": auth_methods,
259 });
260 Self::with_data(error_codes::AUTH_REQUIRED, "Authentication required", data)
261 }
262
263 fn permission_denied(details: impl Into<String>) -> Self {
265 Self::new(error_codes::PERMISSION_DENIED, details)
266 }
267
268 fn session_not_found(session_id: impl Into<String>) -> Self {
270 Self::new(error_codes::SESSION_NOT_FOUND, format!("Session not found: {}", session_id.into()))
271 }
272
273 fn rate_limited(details: impl Into<String>) -> Self {
275 Self::new(error_codes::RATE_LIMITED, details)
276 }
277
278 fn resource_not_found(resource: impl Into<String>) -> Self {
280 Self::new(error_codes::RESOURCE_NOT_FOUND, format!("Resource not found: {}", resource.into()))
281 }
282}
283
284#[cfg(test)]
285#[allow(
286 clippy::uninlined_format_args,
287 reason = "Intentional compatibility, platform, or test-only suppression."
288)]
289mod tests {
290 use super::*;
291 use serde_json::json;
292
293 #[test]
294 fn test_request_serialization() {
295 let req = JsonRpcRequest::new(
296 "initialize",
297 Some(json!({"protocolVersions": ["2025-01-01"]})),
298 Some(JsonRpcId::string("req-1")),
299 );
300
301 let json = serde_json::to_string(&req).unwrap();
302 assert!(json.contains("\"jsonrpc\":\"2.0\""));
303 assert!(json.contains("\"method\":\"initialize\""));
304 assert!(json.contains("\"id\":\"req-1\""));
305 }
306
307 #[test]
308 fn test_response_success() {
309 let resp = JsonRpcResponse::success(json!({"session_id": "sess-123"}), Some(JsonRpcId::string("req-1")));
310
311 assert!(resp.is_success());
312 assert!(!resp.is_error());
313 }
314
315 #[test]
316 fn test_response_error() {
317 let resp = JsonRpcResponse::error(JsonRpcError::method_not_found("unknown"), Some(JsonRpcId::string("req-1")));
318
319 assert!(resp.is_error());
320 assert!(!resp.is_success());
321 }
322
323 #[test]
324 fn test_notification() {
325 let notif = JsonRpcRequest::notification("session/update", Some(json!({"delta": "hello"})));
326
327 assert!(notif.is_notification());
328 assert!(notif.id.is_none());
329 }
330
331 #[test]
332 fn test_id_types() {
333 let string_id = JsonRpcId::string("abc");
334 let number_id = JsonRpcId::number(123);
335
336 assert_eq!(format!("{}", string_id), "abc");
337 assert_eq!(format!("{}", number_id), "123");
338 }
339
340 #[test]
341 fn test_auth_required_error() {
342 use super::super::AuthMethod;
343
344 let auth_methods = vec![
345 AuthMethod::Agent {
346 id: "agent_auth".to_string(),
347 name: "Agent Auth".to_string(),
348 description: None,
349 },
350 AuthMethod::EnvVar {
351 id: "openai_key".to_string(),
352 name: "OpenAI Key".to_string(),
353 description: None,
354 var_name: "OPENAI_API_KEY".to_string(),
355 link: None,
356 },
357 ];
358
359 let error = JsonRpcError::auth_required(auth_methods);
360
361 assert_eq!(error.code, error_codes::AUTH_REQUIRED);
362 assert_eq!(error.message, "Authentication required");
363 assert!(error.data.is_some());
364
365 let data = error.data.unwrap();
366 assert!(data["authMethods"].is_array());
367 assert_eq!(data["authMethods"].as_array().unwrap().len(), 2);
368 }
369
370 #[test]
371 fn test_auth_required_error_serialization() {
372 use super::super::AuthMethod;
373
374 let auth_methods = vec![AuthMethod::EnvVar {
375 id: "test".to_string(),
376 name: "Test".to_string(),
377 description: None,
378 var_name: "TEST_VAR".to_string(),
379 link: Some("https://example.com".to_string()),
380 }];
381
382 let error = JsonRpcError::auth_required(auth_methods);
383 let json = serde_json::to_value(&error).unwrap();
384
385 assert_eq!(json["code"], -32001);
386 assert_eq!(json["message"], "Authentication required");
387 assert_eq!(json["data"]["authMethods"][0]["type"], "env_var");
388 assert_eq!(json["data"]["authMethods"][0]["id"], "test");
389 }
390
391 #[test]
392 fn test_acp_error_helpers() {
393 let err_perm = JsonRpcError::permission_denied("Not allowed");
394 assert_eq!(err_perm.code, error_codes::PERMISSION_DENIED);
395
396 let err_session = JsonRpcError::session_not_found("sess-123");
397 assert_eq!(err_session.code, error_codes::SESSION_NOT_FOUND);
398 assert!(err_session.message.contains("sess-123"));
399
400 let err_rate = JsonRpcError::rate_limited("Too many requests");
401 assert_eq!(err_rate.code, error_codes::RATE_LIMITED);
402
403 let err_resource = JsonRpcError::resource_not_found("file.txt");
404 assert_eq!(err_resource.code, error_codes::RESOURCE_NOT_FOUND);
405 assert!(err_resource.message.contains("file.txt"));
406 }
407}