1use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12pub const JSONRPC_VERSION: &str = "2.0";
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct JsonRpcRequest {
18 pub jsonrpc: String,
20
21 pub method: String,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub params: Option<Value>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub id: Option<JsonRpcId>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct JsonRpcResponse {
36 pub jsonrpc: String,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub result: Option<Value>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub error: Option<JsonRpcError>,
46
47 pub id: Option<JsonRpcId>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct JsonRpcError {
54 pub code: i32,
56
57 pub message: String,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub 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 pub fn string(s: impl Into<String>) -> Self {
80 Self::String(s.into())
81 }
82
83 pub 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
103pub mod error_codes {
105 pub const PARSE_ERROR: i32 = -32700;
107
108 pub const INVALID_REQUEST: i32 = -32600;
110
111 pub 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 const AUTH_REQUIRED: i32 = -32001;
128
129 pub const PERMISSION_DENIED: i32 = -32002;
131
132 pub const SESSION_NOT_FOUND: i32 = -32003;
134
135 pub const RATE_LIMITED: i32 = -32004;
137
138 pub const RESOURCE_NOT_FOUND: i32 = -32005;
140}
141
142impl JsonRpcRequest {
143 pub 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 fn notification(method: impl Into<String>, params: Option<Value>) -> Self {
160 Self::new(method, params, None)
161 }
162
163 pub fn is_notification(&self) -> bool {
165 self.id.is_none()
166 }
167}
168
169impl JsonRpcResponse {
170 pub 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 pub 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 pub fn is_success(&self) -> bool {
192 self.error.is_none() && self.result.is_some()
193 }
194
195 pub 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 pub fn new(code: i32, message: impl Into<String>) -> Self {
213 Self {
214 code,
215 message: message.into(),
216 data: None,
217 }
218 }
219
220 pub fn with_data(code: i32, message: impl Into<String>, data: Value) -> Self {
222 Self {
223 code,
224 message: message.into(),
225 data: Some(data),
226 }
227 }
228
229 #[cold]
231 pub fn parse_error(details: impl Into<String>) -> Self {
232 Self::new(error_codes::PARSE_ERROR, details)
233 }
234
235 #[cold]
237 pub fn invalid_request(details: impl Into<String>) -> Self {
238 Self::new(error_codes::INVALID_REQUEST, details)
239 }
240
241 #[cold]
243 pub fn method_not_found(method: impl Into<String>) -> Self {
244 Self::new(
245 error_codes::METHOD_NOT_FOUND,
246 format!("Method not found: {}", method.into()),
247 )
248 }
249
250 #[cold]
252 pub fn invalid_params(details: impl Into<String>) -> Self {
253 Self::new(error_codes::INVALID_PARAMS, details)
254 }
255
256 #[cold]
258 pub fn internal_error(details: impl Into<String>) -> Self {
259 Self::new(error_codes::INTERNAL_ERROR, details)
260 }
261
262 #[cold]
267 pub fn auth_required(auth_methods: Vec<super::AuthMethod>) -> Self {
268 let data = serde_json::json!({
269 "authMethods": auth_methods,
270 });
271 Self::with_data(error_codes::AUTH_REQUIRED, "Authentication required", data)
272 }
273
274 pub fn permission_denied(details: impl Into<String>) -> Self {
276 Self::new(error_codes::PERMISSION_DENIED, details)
277 }
278
279 pub fn session_not_found(session_id: impl Into<String>) -> Self {
281 Self::new(
282 error_codes::SESSION_NOT_FOUND,
283 format!("Session not found: {}", session_id.into()),
284 )
285 }
286
287 pub fn rate_limited(details: impl Into<String>) -> Self {
289 Self::new(error_codes::RATE_LIMITED, details)
290 }
291
292 pub fn resource_not_found(resource: impl Into<String>) -> Self {
294 Self::new(
295 error_codes::RESOURCE_NOT_FOUND,
296 format!("Resource not found: {}", resource.into()),
297 )
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use serde_json::json;
305
306 #[test]
307 fn test_request_serialization() {
308 let req = JsonRpcRequest::new(
309 "initialize",
310 Some(json!({"protocolVersions": ["2025-01-01"]})),
311 Some(JsonRpcId::string("req-1")),
312 );
313
314 let json = serde_json::to_string(&req).unwrap();
315 assert!(json.contains("\"jsonrpc\":\"2.0\""));
316 assert!(json.contains("\"method\":\"initialize\""));
317 assert!(json.contains("\"id\":\"req-1\""));
318 }
319
320 #[test]
321 fn test_response_success() {
322 let resp = JsonRpcResponse::success(
323 json!({"session_id": "sess-123"}),
324 Some(JsonRpcId::string("req-1")),
325 );
326
327 assert!(resp.is_success());
328 assert!(!resp.is_error());
329 }
330
331 #[test]
332 fn test_response_error() {
333 let resp = JsonRpcResponse::error(
334 JsonRpcError::method_not_found("unknown"),
335 Some(JsonRpcId::string("req-1")),
336 );
337
338 assert!(resp.is_error());
339 assert!(!resp.is_success());
340 }
341
342 #[test]
343 fn test_notification() {
344 let notif = JsonRpcRequest::notification("session/update", Some(json!({"delta": "hello"})));
345
346 assert!(notif.is_notification());
347 assert!(notif.id.is_none());
348 }
349
350 #[test]
351 fn test_id_types() {
352 let string_id = JsonRpcId::string("abc");
353 let number_id = JsonRpcId::number(123);
354
355 assert_eq!(format!("{}", string_id), "abc");
356 assert_eq!(format!("{}", number_id), "123");
357 }
358
359 #[test]
360 fn test_auth_required_error() {
361 use super::super::AuthMethod;
362
363 let auth_methods = vec![
364 AuthMethod::Agent {
365 id: "agent_auth".to_string(),
366 name: "Agent Auth".to_string(),
367 description: None,
368 },
369 AuthMethod::EnvVar {
370 id: "openai_key".to_string(),
371 name: "OpenAI Key".to_string(),
372 description: None,
373 var_name: "OPENAI_API_KEY".to_string(),
374 link: None,
375 },
376 ];
377
378 let error = JsonRpcError::auth_required(auth_methods);
379
380 assert_eq!(error.code, error_codes::AUTH_REQUIRED);
381 assert_eq!(error.message, "Authentication required");
382 assert!(error.data.is_some());
383
384 let data = error.data.unwrap();
385 assert!(data["authMethods"].is_array());
386 assert_eq!(data["authMethods"].as_array().unwrap().len(), 2);
387 }
388
389 #[test]
390 fn test_auth_required_error_serialization() {
391 use super::super::AuthMethod;
392
393 let auth_methods = vec![AuthMethod::EnvVar {
394 id: "test".to_string(),
395 name: "Test".to_string(),
396 description: None,
397 var_name: "TEST_VAR".to_string(),
398 link: Some("https://example.com".to_string()),
399 }];
400
401 let error = JsonRpcError::auth_required(auth_methods);
402 let json = serde_json::to_value(&error).unwrap();
403
404 assert_eq!(json["code"], -32001);
405 assert_eq!(json["message"], "Authentication required");
406 assert_eq!(json["data"]["authMethods"][0]["type"], "env_var");
407 assert_eq!(json["data"]["authMethods"][0]["id"], "test");
408 }
409
410 #[test]
411 fn test_acp_error_helpers() {
412 let err_perm = JsonRpcError::permission_denied("Not allowed");
413 assert_eq!(err_perm.code, error_codes::PERMISSION_DENIED);
414
415 let err_session = JsonRpcError::session_not_found("sess-123");
416 assert_eq!(err_session.code, error_codes::SESSION_NOT_FOUND);
417 assert!(err_session.message.contains("sess-123"));
418
419 let err_rate = JsonRpcError::rate_limited("Too many requests");
420 assert_eq!(err_rate.code, error_codes::RATE_LIMITED);
421
422 let err_resource = JsonRpcError::resource_not_found("file.txt");
423 assert_eq!(err_resource.code, error_codes::RESOURCE_NOT_FOUND);
424 assert!(err_resource.message.contains("file.txt"));
425 }
426}