1use std::fmt;
13
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(into = "i32", try_from = "i32")]
24#[non_exhaustive]
25pub enum ErrorCode {
26 ParseError = -32700,
29 InvalidRequest = -32600,
31 MethodNotFound = -32601,
33 InvalidParams = -32602,
35 InternalError = -32603,
37
38 TaskNotFound = -32001,
41 TaskNotCancelable = -32002,
43 PushNotificationNotSupported = -32003,
45 UnsupportedOperation = -32004,
47 ContentTypeNotSupported = -32005,
49 InvalidAgentResponse = -32006,
51 ExtendedAgentCardNotConfigured = -32007,
53 ExtensionSupportRequired = -32008,
55 VersionNotSupported = -32009,
57}
58
59impl ErrorCode {
60 #[must_use]
62 pub const fn as_i32(self) -> i32 {
63 self as i32
64 }
65
66 #[must_use]
68 pub const fn default_message(self) -> &'static str {
69 match self {
70 Self::ParseError => "Parse error",
71 Self::InvalidRequest => "Invalid request",
72 Self::MethodNotFound => "Method not found",
73 Self::InvalidParams => "Invalid params",
74 Self::InternalError => "Internal error",
75 Self::TaskNotFound => "Task not found",
76 Self::TaskNotCancelable => "Task not cancelable",
77 Self::PushNotificationNotSupported => "Push notification not supported",
78 Self::UnsupportedOperation => "Unsupported operation",
79 Self::ContentTypeNotSupported => "Content type not supported",
80 Self::InvalidAgentResponse => "Invalid agent response",
81 Self::ExtendedAgentCardNotConfigured => "Extended agent card not configured",
82 Self::ExtensionSupportRequired => "Extension support required",
83 Self::VersionNotSupported => "Version not supported",
84 }
85 }
86}
87
88impl From<ErrorCode> for i32 {
89 fn from(code: ErrorCode) -> Self {
90 code as Self
91 }
92}
93
94impl TryFrom<i32> for ErrorCode {
95 type Error = i32;
96
97 fn try_from(v: i32) -> Result<Self, Self::Error> {
98 match v {
99 -32700 => Ok(Self::ParseError),
100 -32600 => Ok(Self::InvalidRequest),
101 -32601 => Ok(Self::MethodNotFound),
102 -32602 => Ok(Self::InvalidParams),
103 -32603 => Ok(Self::InternalError),
104 -32001 => Ok(Self::TaskNotFound),
105 -32002 => Ok(Self::TaskNotCancelable),
106 -32003 => Ok(Self::PushNotificationNotSupported),
107 -32004 => Ok(Self::UnsupportedOperation),
108 -32005 => Ok(Self::ContentTypeNotSupported),
109 -32006 => Ok(Self::InvalidAgentResponse),
110 -32007 => Ok(Self::ExtendedAgentCardNotConfigured),
111 -32008 => Ok(Self::ExtensionSupportRequired),
112 -32009 => Ok(Self::VersionNotSupported),
113 other => Err(other),
114 }
115 }
116}
117
118impl fmt::Display for ErrorCode {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(f, "{} ({})", self.default_message(), self.as_i32())
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
131#[non_exhaustive]
132pub struct A2aError {
133 pub code: ErrorCode,
135 pub message: String,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub data: Option<serde_json::Value>,
140}
141
142impl A2aError {
143 #[must_use]
145 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
146 Self {
147 code,
148 message: message.into(),
149 data: None,
150 }
151 }
152
153 #[must_use]
155 pub fn with_data(code: ErrorCode, message: impl Into<String>, data: serde_json::Value) -> Self {
156 Self {
157 code,
158 message: message.into(),
159 data: Some(data),
160 }
161 }
162
163 #[must_use]
167 pub fn task_not_found(task_id: impl fmt::Display) -> Self {
168 Self::new(
169 ErrorCode::TaskNotFound,
170 format!("Task not found: {task_id}"),
171 )
172 }
173
174 #[must_use]
176 pub fn task_not_cancelable(task_id: impl fmt::Display) -> Self {
177 Self::new(
178 ErrorCode::TaskNotCancelable,
179 format!("Task cannot be canceled: {task_id}"),
180 )
181 }
182
183 #[must_use]
185 pub fn internal(msg: impl Into<String>) -> Self {
186 Self::new(ErrorCode::InternalError, msg)
187 }
188
189 #[must_use]
191 pub fn invalid_params(msg: impl Into<String>) -> Self {
192 Self::new(ErrorCode::InvalidParams, msg)
193 }
194
195 #[must_use]
197 pub fn unsupported_operation(msg: impl Into<String>) -> Self {
198 Self::new(ErrorCode::UnsupportedOperation, msg)
199 }
200
201 #[must_use]
203 pub fn parse_error(msg: impl Into<String>) -> Self {
204 Self::new(ErrorCode::ParseError, msg)
205 }
206
207 #[must_use]
209 pub fn invalid_agent_response(msg: impl Into<String>) -> Self {
210 Self::new(ErrorCode::InvalidAgentResponse, msg)
211 }
212
213 #[must_use]
215 pub fn extended_card_not_configured(msg: impl Into<String>) -> Self {
216 Self::new(ErrorCode::ExtendedAgentCardNotConfigured, msg)
217 }
218}
219
220impl fmt::Display for A2aError {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 write!(f, "[{}] {}", self.code.as_i32(), self.message)
223 }
224}
225
226impl std::error::Error for A2aError {}
227
228pub type A2aResult<T> = Result<T, A2aError>;
232
233#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn error_code_roundtrip() {
241 let code = ErrorCode::TaskNotFound;
242 let n: i32 = code.into();
243 assert_eq!(n, -32001);
244 assert_eq!(ErrorCode::try_from(n), Ok(ErrorCode::TaskNotFound));
245 }
246
247 #[test]
248 fn error_code_unknown_value() {
249 assert!(ErrorCode::try_from(-99999).is_err());
250 }
251
252 #[test]
253 fn a2a_error_display() {
254 let err = A2aError::task_not_found("abc123");
255 let s = err.to_string();
256 assert!(s.contains("-32001"), "expected code in display: {s}");
257 assert!(s.contains("abc123"), "expected task id in display: {s}");
258 }
259
260 #[test]
261 fn a2a_error_serialization() {
262 let err = A2aError::internal("something went wrong");
263 let json = serde_json::to_string(&err).expect("serialize");
264 let back: A2aError = serde_json::from_str(&json).expect("deserialize");
265 assert_eq!(back.code, ErrorCode::InternalError);
266 assert_eq!(back.message, "something went wrong");
267 assert!(back.data.is_none());
268 }
269
270 #[test]
271 fn a2a_error_with_data() {
272 let data = serde_json::json!({"detail": "extra info"});
273 let err = A2aError::with_data(ErrorCode::InvalidParams, "bad input", data.clone());
274 let json = serde_json::to_string(&err).expect("serialize");
275 assert!(json.contains("\"data\""), "data field should be present");
276 let back: A2aError = serde_json::from_str(&json).expect("deserialize");
277 assert_eq!(back.data, Some(data));
278 }
279
280 #[test]
285 #[allow(clippy::too_many_lines)]
286 fn error_code_roundtrip_all_variants() {
287 let cases: &[(ErrorCode, i32, &str)] = &[
288 (ErrorCode::ParseError, -32700, "Parse error"),
289 (ErrorCode::InvalidRequest, -32600, "Invalid request"),
290 (ErrorCode::MethodNotFound, -32601, "Method not found"),
291 (ErrorCode::InvalidParams, -32602, "Invalid params"),
292 (ErrorCode::InternalError, -32603, "Internal error"),
293 (ErrorCode::TaskNotFound, -32001, "Task not found"),
294 (ErrorCode::TaskNotCancelable, -32002, "Task not cancelable"),
295 (
296 ErrorCode::PushNotificationNotSupported,
297 -32003,
298 "Push notification not supported",
299 ),
300 (
301 ErrorCode::UnsupportedOperation,
302 -32004,
303 "Unsupported operation",
304 ),
305 (
306 ErrorCode::ContentTypeNotSupported,
307 -32005,
308 "Content type not supported",
309 ),
310 (
311 ErrorCode::InvalidAgentResponse,
312 -32006,
313 "Invalid agent response",
314 ),
315 (
316 ErrorCode::ExtendedAgentCardNotConfigured,
317 -32007,
318 "Extended agent card not configured",
319 ),
320 (
321 ErrorCode::ExtensionSupportRequired,
322 -32008,
323 "Extension support required",
324 ),
325 (
326 ErrorCode::VersionNotSupported,
327 -32009,
328 "Version not supported",
329 ),
330 ];
331
332 for &(code, expected_i32, expected_msg) in cases {
333 assert_eq!(code.as_i32(), expected_i32, "as_i32 mismatch for {code:?}");
335
336 let n: i32 = code.into();
338 assert_eq!(n, expected_i32, "Into<i32> mismatch for {code:?}");
339
340 let back = ErrorCode::try_from(expected_i32).expect("try_from should succeed");
342 assert_eq!(back, code, "TryFrom roundtrip mismatch for {code:?}");
343
344 assert_eq!(
346 code.default_message(),
347 expected_msg,
348 "default_message mismatch for {code:?}"
349 );
350
351 let display = code.to_string();
353 assert!(
354 display.contains(expected_msg),
355 "Display missing message for {code:?}: {display}"
356 );
357 assert!(
358 display.contains(&expected_i32.to_string()),
359 "Display missing code for {code:?}: {display}"
360 );
361 }
362 }
363
364 #[test]
367 fn error_code_rejects_adjacent_values() {
368 let invalid: &[i32] = &[
369 -32701,
370 -32699, -32599,
372 -32601 + 1, -32000,
374 -32010, 0,
376 1,
377 -1,
378 i32::MIN,
379 i32::MAX,
380 ];
381 for &v in invalid {
382 if ErrorCode::try_from(v).is_ok() {
384 continue;
385 }
386 assert_eq!(
387 ErrorCode::try_from(v),
388 Err(v),
389 "value {v} should not convert to ErrorCode"
390 );
391 }
392 }
393
394 #[test]
397 fn named_constructors_use_correct_codes() {
398 assert_eq!(A2aError::task_not_found("t1").code, ErrorCode::TaskNotFound);
399 assert_eq!(
400 A2aError::task_not_cancelable("t1").code,
401 ErrorCode::TaskNotCancelable
402 );
403 assert_eq!(A2aError::internal("x").code, ErrorCode::InternalError);
404 assert_eq!(A2aError::invalid_params("x").code, ErrorCode::InvalidParams);
405 assert_eq!(
406 A2aError::unsupported_operation("x").code,
407 ErrorCode::UnsupportedOperation
408 );
409 assert_eq!(A2aError::parse_error("x").code, ErrorCode::ParseError);
410 assert_eq!(
411 A2aError::invalid_agent_response("x").code,
412 ErrorCode::InvalidAgentResponse
413 );
414 assert_eq!(
415 A2aError::extended_card_not_configured("x").code,
416 ErrorCode::ExtendedAgentCardNotConfigured
417 );
418 }
419
420 #[test]
421 fn named_constructors_include_argument_in_message() {
422 let err = A2aError::task_not_found("my-task-id");
423 assert!(
424 err.message.contains("my-task-id"),
425 "task_not_found should include task_id: {}",
426 err.message
427 );
428
429 let err = A2aError::task_not_cancelable("cancel-me");
430 assert!(
431 err.message.contains("cancel-me"),
432 "task_not_cancelable should include task_id: {}",
433 err.message
434 );
435 }
436
437 #[test]
438 fn a2a_error_new_has_no_data() {
439 let err = A2aError::new(ErrorCode::InternalError, "msg");
440 assert!(err.data.is_none());
441 }
442
443 #[test]
444 fn a2a_error_with_data_has_some_data() {
445 let err = A2aError::with_data(
446 ErrorCode::InternalError,
447 "msg",
448 serde_json::json!("details"),
449 );
450 assert!(err.data.is_some());
451 assert_eq!(err.data.unwrap(), serde_json::json!("details"));
452 }
453
454 #[test]
455 fn a2a_error_is_std_error() {
456 let err = A2aError::internal("test");
457 let _: &dyn std::error::Error = &err;
458 }
459
460 #[test]
461 fn a2a_error_display_format() {
462 let err = A2aError::new(ErrorCode::ParseError, "bad json");
463 let s = err.to_string();
464 assert_eq!(s, "[-32700] bad json");
465 }
466}