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 #[must_use]
92 pub const fn a2a_reason(self) -> Option<&'static str> {
93 match self {
94 Self::TaskNotFound => Some("TASK_NOT_FOUND"),
95 Self::TaskNotCancelable => Some("TASK_NOT_CANCELABLE"),
96 Self::PushNotificationNotSupported => Some("PUSH_NOTIFICATION_NOT_SUPPORTED"),
97 Self::UnsupportedOperation => Some("UNSUPPORTED_OPERATION"),
98 Self::ContentTypeNotSupported => Some("CONTENT_TYPE_NOT_SUPPORTED"),
99 Self::InvalidAgentResponse => Some("INVALID_AGENT_RESPONSE"),
100 Self::ExtendedAgentCardNotConfigured => Some("EXTENDED_AGENT_CARD_NOT_CONFIGURED"),
101 Self::ExtensionSupportRequired => Some("EXTENSION_SUPPORT_REQUIRED"),
102 Self::VersionNotSupported => Some("VERSION_NOT_SUPPORTED"),
103 _ => None,
104 }
105 }
106
107 #[must_use]
109 pub const fn http_status(self) -> u16 {
110 match self {
111 Self::TaskNotFound | Self::MethodNotFound => 404,
112 Self::TaskNotCancelable => 409,
113 Self::ContentTypeNotSupported => 415,
114 Self::InvalidAgentResponse => 502,
115 Self::PushNotificationNotSupported
116 | Self::UnsupportedOperation
117 | Self::ExtendedAgentCardNotConfigured
118 | Self::ExtensionSupportRequired
119 | Self::VersionNotSupported
120 | Self::ParseError
121 | Self::InvalidRequest
122 | Self::InvalidParams => 400,
123 Self::InternalError => 500,
124 }
125 }
126
127 #[must_use]
129 pub const fn grpc_status(self) -> &'static str {
130 match self {
131 Self::TaskNotFound => "NOT_FOUND",
132 Self::TaskNotCancelable
133 | Self::ExtendedAgentCardNotConfigured
134 | Self::ExtensionSupportRequired => "FAILED_PRECONDITION",
135 Self::PushNotificationNotSupported
136 | Self::UnsupportedOperation
137 | Self::VersionNotSupported
138 | Self::MethodNotFound => "UNIMPLEMENTED",
139 Self::ContentTypeNotSupported
140 | Self::InvalidParams
141 | Self::InvalidRequest
142 | Self::ParseError => "INVALID_ARGUMENT",
143 Self::InvalidAgentResponse | Self::InternalError => "INTERNAL",
144 }
145 }
146}
147
148impl From<ErrorCode> for i32 {
149 fn from(code: ErrorCode) -> Self {
150 code as Self
151 }
152}
153
154impl TryFrom<i32> for ErrorCode {
155 type Error = i32;
156
157 fn try_from(v: i32) -> Result<Self, Self::Error> {
158 match v {
159 -32700 => Ok(Self::ParseError),
160 -32600 => Ok(Self::InvalidRequest),
161 -32601 => Ok(Self::MethodNotFound),
162 -32602 => Ok(Self::InvalidParams),
163 -32603 => Ok(Self::InternalError),
164 -32001 => Ok(Self::TaskNotFound),
165 -32002 => Ok(Self::TaskNotCancelable),
166 -32003 => Ok(Self::PushNotificationNotSupported),
167 -32004 => Ok(Self::UnsupportedOperation),
168 -32005 => Ok(Self::ContentTypeNotSupported),
169 -32006 => Ok(Self::InvalidAgentResponse),
170 -32007 => Ok(Self::ExtendedAgentCardNotConfigured),
171 -32008 => Ok(Self::ExtensionSupportRequired),
172 -32009 => Ok(Self::VersionNotSupported),
173 other => Err(other),
174 }
175 }
176}
177
178impl fmt::Display for ErrorCode {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 write!(f, "{} ({})", self.default_message(), self.as_i32())
181 }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
191#[non_exhaustive]
192pub struct A2aError {
193 pub code: ErrorCode,
195 pub message: String,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub data: Option<serde_json::Value>,
200}
201
202impl A2aError {
203 #[must_use]
205 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
206 Self {
207 code,
208 message: message.into(),
209 data: None,
210 }
211 }
212
213 #[must_use]
215 pub fn with_data(code: ErrorCode, message: impl Into<String>, data: serde_json::Value) -> Self {
216 Self {
217 code,
218 message: message.into(),
219 data: Some(data),
220 }
221 }
222
223 #[must_use]
227 pub fn task_not_found(task_id: impl fmt::Display) -> Self {
228 Self::new(
229 ErrorCode::TaskNotFound,
230 format!("Task not found: {task_id}"),
231 )
232 }
233
234 #[must_use]
236 pub fn task_not_cancelable(task_id: impl fmt::Display) -> Self {
237 Self::new(
238 ErrorCode::TaskNotCancelable,
239 format!("Task cannot be canceled: {task_id}"),
240 )
241 }
242
243 #[must_use]
245 pub fn internal(msg: impl Into<String>) -> Self {
246 Self::new(ErrorCode::InternalError, msg)
247 }
248
249 #[must_use]
251 pub fn invalid_params(msg: impl Into<String>) -> Self {
252 Self::new(ErrorCode::InvalidParams, msg)
253 }
254
255 #[must_use]
257 pub fn unsupported_operation(msg: impl Into<String>) -> Self {
258 Self::new(ErrorCode::UnsupportedOperation, msg)
259 }
260
261 #[must_use]
263 pub fn parse_error(msg: impl Into<String>) -> Self {
264 Self::new(ErrorCode::ParseError, msg)
265 }
266
267 #[must_use]
269 pub fn invalid_agent_response(msg: impl Into<String>) -> Self {
270 Self::new(ErrorCode::InvalidAgentResponse, msg)
271 }
272
273 #[must_use]
275 pub fn extended_card_not_configured(msg: impl Into<String>) -> Self {
276 Self::new(ErrorCode::ExtendedAgentCardNotConfigured, msg)
277 }
278
279 #[must_use]
281 pub fn push_not_supported(msg: impl Into<String>) -> Self {
282 Self::new(ErrorCode::PushNotificationNotSupported, msg)
283 }
284
285 #[must_use]
287 pub fn content_type_not_supported(msg: impl Into<String>) -> Self {
288 Self::new(ErrorCode::ContentTypeNotSupported, msg)
289 }
290
291 #[must_use]
293 pub fn extension_support_required(msg: impl Into<String>) -> Self {
294 Self::new(ErrorCode::ExtensionSupportRequired, msg)
295 }
296
297 #[must_use]
299 pub fn version_not_supported(msg: impl Into<String>) -> Self {
300 Self::new(ErrorCode::VersionNotSupported, msg)
301 }
302
303 #[must_use]
308 pub fn error_info_data(&self, metadata: Option<serde_json::Value>) -> serde_json::Value {
309 self.code
310 .a2a_reason()
311 .map_or(serde_json::Value::Null, |reason| {
312 let mut info = serde_json::json!({
313 "@type": "type.googleapis.com/google.rpc.ErrorInfo",
314 "reason": reason,
315 "domain": "a2a-protocol.org"
316 });
317 if let Some(meta) = metadata {
318 info["metadata"] = meta;
319 }
320 serde_json::json!([info])
321 })
322 }
323}
324
325impl fmt::Display for A2aError {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 write!(f, "[{}] {}", self.code.as_i32(), self.message)
328 }
329}
330
331impl std::error::Error for A2aError {}
332
333pub type A2aResult<T> = Result<T, A2aError>;
337
338#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn error_code_roundtrip() {
346 let code = ErrorCode::TaskNotFound;
347 let n: i32 = code.into();
348 assert_eq!(n, -32001);
349 assert_eq!(ErrorCode::try_from(n), Ok(ErrorCode::TaskNotFound));
350 }
351
352 #[test]
353 fn error_code_unknown_value() {
354 assert!(ErrorCode::try_from(-99999).is_err());
355 }
356
357 #[test]
358 fn a2a_error_display() {
359 let err = A2aError::task_not_found("abc123");
360 let s = err.to_string();
361 assert!(s.contains("-32001"), "expected code in display: {s}");
362 assert!(s.contains("abc123"), "expected task id in display: {s}");
363 }
364
365 #[test]
366 fn a2a_error_serialization() {
367 let err = A2aError::internal("something went wrong");
368 let json = serde_json::to_string(&err).expect("serialize");
369 let back: A2aError = serde_json::from_str(&json).expect("deserialize");
370 assert_eq!(back.code, ErrorCode::InternalError);
371 assert_eq!(back.message, "something went wrong");
372 assert!(back.data.is_none());
373 }
374
375 #[test]
376 fn a2a_error_with_data() {
377 let data = serde_json::json!({"detail": "extra info"});
378 let err = A2aError::with_data(ErrorCode::InvalidParams, "bad input", data.clone());
379 let json = serde_json::to_string(&err).expect("serialize");
380 assert!(json.contains("\"data\""), "data field should be present");
381 let back: A2aError = serde_json::from_str(&json).expect("deserialize");
382 assert_eq!(back.data, Some(data));
383 }
384
385 #[test]
390 #[allow(clippy::too_many_lines)]
391 fn error_code_roundtrip_all_variants() {
392 let cases: &[(ErrorCode, i32, &str)] = &[
393 (ErrorCode::ParseError, -32700, "Parse error"),
394 (ErrorCode::InvalidRequest, -32600, "Invalid request"),
395 (ErrorCode::MethodNotFound, -32601, "Method not found"),
396 (ErrorCode::InvalidParams, -32602, "Invalid params"),
397 (ErrorCode::InternalError, -32603, "Internal error"),
398 (ErrorCode::TaskNotFound, -32001, "Task not found"),
399 (ErrorCode::TaskNotCancelable, -32002, "Task not cancelable"),
400 (
401 ErrorCode::PushNotificationNotSupported,
402 -32003,
403 "Push notification not supported",
404 ),
405 (
406 ErrorCode::UnsupportedOperation,
407 -32004,
408 "Unsupported operation",
409 ),
410 (
411 ErrorCode::ContentTypeNotSupported,
412 -32005,
413 "Content type not supported",
414 ),
415 (
416 ErrorCode::InvalidAgentResponse,
417 -32006,
418 "Invalid agent response",
419 ),
420 (
421 ErrorCode::ExtendedAgentCardNotConfigured,
422 -32007,
423 "Extended agent card not configured",
424 ),
425 (
426 ErrorCode::ExtensionSupportRequired,
427 -32008,
428 "Extension support required",
429 ),
430 (
431 ErrorCode::VersionNotSupported,
432 -32009,
433 "Version not supported",
434 ),
435 ];
436
437 for &(code, expected_i32, expected_msg) in cases {
438 assert_eq!(code.as_i32(), expected_i32, "as_i32 mismatch for {code:?}");
440
441 let n: i32 = code.into();
443 assert_eq!(n, expected_i32, "Into<i32> mismatch for {code:?}");
444
445 let back = ErrorCode::try_from(expected_i32).expect("try_from should succeed");
447 assert_eq!(back, code, "TryFrom roundtrip mismatch for {code:?}");
448
449 assert_eq!(
451 code.default_message(),
452 expected_msg,
453 "default_message mismatch for {code:?}"
454 );
455
456 let display = code.to_string();
458 assert!(
459 display.contains(expected_msg),
460 "Display missing message for {code:?}: {display}"
461 );
462 assert!(
463 display.contains(&expected_i32.to_string()),
464 "Display missing code for {code:?}: {display}"
465 );
466 }
467 }
468
469 #[test]
472 fn error_code_rejects_adjacent_values() {
473 let invalid: &[i32] = &[
474 -32701,
475 -32699, -32599,
477 -32601 + 1, -32000,
479 -32010, 0,
481 1,
482 -1,
483 i32::MIN,
484 i32::MAX,
485 ];
486 for &v in invalid {
487 if ErrorCode::try_from(v).is_ok() {
489 continue;
490 }
491 assert_eq!(
492 ErrorCode::try_from(v),
493 Err(v),
494 "value {v} should not convert to ErrorCode"
495 );
496 }
497 }
498
499 #[test]
502 fn named_constructors_use_correct_codes() {
503 assert_eq!(A2aError::task_not_found("t1").code, ErrorCode::TaskNotFound);
504 assert_eq!(
505 A2aError::task_not_cancelable("t1").code,
506 ErrorCode::TaskNotCancelable
507 );
508 assert_eq!(A2aError::internal("x").code, ErrorCode::InternalError);
509 assert_eq!(A2aError::invalid_params("x").code, ErrorCode::InvalidParams);
510 assert_eq!(
511 A2aError::unsupported_operation("x").code,
512 ErrorCode::UnsupportedOperation
513 );
514 assert_eq!(A2aError::parse_error("x").code, ErrorCode::ParseError);
515 assert_eq!(
516 A2aError::invalid_agent_response("x").code,
517 ErrorCode::InvalidAgentResponse
518 );
519 assert_eq!(
520 A2aError::extended_card_not_configured("x").code,
521 ErrorCode::ExtendedAgentCardNotConfigured
522 );
523 }
524
525 #[test]
526 fn named_constructors_include_argument_in_message() {
527 let err = A2aError::task_not_found("my-task-id");
528 assert!(
529 err.message.contains("my-task-id"),
530 "task_not_found should include task_id: {}",
531 err.message
532 );
533
534 let err = A2aError::task_not_cancelable("cancel-me");
535 assert!(
536 err.message.contains("cancel-me"),
537 "task_not_cancelable should include task_id: {}",
538 err.message
539 );
540 }
541
542 #[test]
543 fn a2a_error_new_has_no_data() {
544 let err = A2aError::new(ErrorCode::InternalError, "msg");
545 assert!(err.data.is_none());
546 }
547
548 #[test]
549 fn a2a_error_with_data_has_some_data() {
550 let err = A2aError::with_data(
551 ErrorCode::InternalError,
552 "msg",
553 serde_json::json!("details"),
554 );
555 assert!(err.data.is_some());
556 assert_eq!(err.data.unwrap(), serde_json::json!("details"));
557 }
558
559 #[test]
560 fn a2a_error_is_std_error() {
561 let err = A2aError::internal("test");
562 let _: &dyn std::error::Error = &err;
563 }
564
565 #[test]
566 fn a2a_error_display_format() {
567 let err = A2aError::new(ErrorCode::ParseError, "bad json");
568 let s = err.to_string();
569 assert_eq!(s, "[-32700] bad json");
570 }
571
572 #[test]
580 fn a2a_reason_task_not_found() {
581 assert_eq!(ErrorCode::TaskNotFound.a2a_reason(), Some("TASK_NOT_FOUND"));
582 }
583
584 #[test]
585 fn a2a_reason_task_not_cancelable() {
586 assert_eq!(
587 ErrorCode::TaskNotCancelable.a2a_reason(),
588 Some("TASK_NOT_CANCELABLE")
589 );
590 }
591
592 #[test]
593 fn a2a_reason_push_notification_not_supported() {
594 assert_eq!(
595 ErrorCode::PushNotificationNotSupported.a2a_reason(),
596 Some("PUSH_NOTIFICATION_NOT_SUPPORTED")
597 );
598 }
599
600 #[test]
601 fn a2a_reason_unsupported_operation() {
602 assert_eq!(
603 ErrorCode::UnsupportedOperation.a2a_reason(),
604 Some("UNSUPPORTED_OPERATION")
605 );
606 }
607
608 #[test]
609 fn a2a_reason_content_type_not_supported() {
610 assert_eq!(
611 ErrorCode::ContentTypeNotSupported.a2a_reason(),
612 Some("CONTENT_TYPE_NOT_SUPPORTED")
613 );
614 }
615
616 #[test]
617 fn a2a_reason_invalid_agent_response() {
618 assert_eq!(
619 ErrorCode::InvalidAgentResponse.a2a_reason(),
620 Some("INVALID_AGENT_RESPONSE")
621 );
622 }
623
624 #[test]
625 fn a2a_reason_extended_agent_card_not_configured() {
626 assert_eq!(
627 ErrorCode::ExtendedAgentCardNotConfigured.a2a_reason(),
628 Some("EXTENDED_AGENT_CARD_NOT_CONFIGURED")
629 );
630 }
631
632 #[test]
633 fn a2a_reason_extension_support_required() {
634 assert_eq!(
635 ErrorCode::ExtensionSupportRequired.a2a_reason(),
636 Some("EXTENSION_SUPPORT_REQUIRED")
637 );
638 }
639
640 #[test]
641 fn a2a_reason_version_not_supported() {
642 assert_eq!(
643 ErrorCode::VersionNotSupported.a2a_reason(),
644 Some("VERSION_NOT_SUPPORTED")
645 );
646 }
647
648 #[test]
649 fn a2a_reason_none_for_standard_jsonrpc_errors() {
650 assert_eq!(ErrorCode::ParseError.a2a_reason(), None);
651 assert_eq!(ErrorCode::InvalidRequest.a2a_reason(), None);
652 assert_eq!(ErrorCode::MethodNotFound.a2a_reason(), None);
653 assert_eq!(ErrorCode::InvalidParams.a2a_reason(), None);
654 assert_eq!(ErrorCode::InternalError.a2a_reason(), None);
655 }
656
657 #[test]
658 fn a2a_reason_all_a2a_variants_distinct_and_non_empty() {
659 let reasons: &[&str] = &[
660 ErrorCode::TaskNotFound.a2a_reason().unwrap(),
661 ErrorCode::TaskNotCancelable.a2a_reason().unwrap(),
662 ErrorCode::PushNotificationNotSupported
663 .a2a_reason()
664 .unwrap(),
665 ErrorCode::UnsupportedOperation.a2a_reason().unwrap(),
666 ErrorCode::ContentTypeNotSupported.a2a_reason().unwrap(),
667 ErrorCode::InvalidAgentResponse.a2a_reason().unwrap(),
668 ErrorCode::ExtendedAgentCardNotConfigured
669 .a2a_reason()
670 .unwrap(),
671 ErrorCode::ExtensionSupportRequired.a2a_reason().unwrap(),
672 ErrorCode::VersionNotSupported.a2a_reason().unwrap(),
673 ];
674 for r in reasons {
676 assert!(!r.is_empty(), "a2a_reason must not be empty: {r}");
677 }
678 let mut sorted: Vec<&str> = reasons.to_vec();
680 sorted.sort_unstable();
681 sorted.dedup();
682 assert_eq!(
683 sorted.len(),
684 reasons.len(),
685 "a2a_reason values must be distinct across variants"
686 );
687 }
688
689 #[test]
692 fn http_status_task_not_found_is_404() {
693 assert_eq!(ErrorCode::TaskNotFound.http_status(), 404);
694 }
695
696 #[test]
697 fn http_status_method_not_found_is_404() {
698 assert_eq!(ErrorCode::MethodNotFound.http_status(), 404);
699 }
700
701 #[test]
702 fn http_status_task_not_cancelable_is_409() {
703 assert_eq!(ErrorCode::TaskNotCancelable.http_status(), 409);
704 }
705
706 #[test]
707 fn http_status_content_type_not_supported_is_415() {
708 assert_eq!(ErrorCode::ContentTypeNotSupported.http_status(), 415);
709 }
710
711 #[test]
712 fn http_status_invalid_agent_response_is_502() {
713 assert_eq!(ErrorCode::InvalidAgentResponse.http_status(), 502);
714 }
715
716 #[test]
717 fn http_status_push_not_supported_is_400() {
718 assert_eq!(ErrorCode::PushNotificationNotSupported.http_status(), 400);
719 }
720
721 #[test]
722 fn http_status_unsupported_operation_is_400() {
723 assert_eq!(ErrorCode::UnsupportedOperation.http_status(), 400);
724 }
725
726 #[test]
727 fn http_status_extended_card_not_configured_is_400() {
728 assert_eq!(ErrorCode::ExtendedAgentCardNotConfigured.http_status(), 400);
729 }
730
731 #[test]
732 fn http_status_extension_support_required_is_400() {
733 assert_eq!(ErrorCode::ExtensionSupportRequired.http_status(), 400);
734 }
735
736 #[test]
737 fn http_status_version_not_supported_is_400() {
738 assert_eq!(ErrorCode::VersionNotSupported.http_status(), 400);
739 }
740
741 #[test]
742 fn http_status_parse_error_is_400() {
743 assert_eq!(ErrorCode::ParseError.http_status(), 400);
744 }
745
746 #[test]
747 fn http_status_invalid_request_is_400() {
748 assert_eq!(ErrorCode::InvalidRequest.http_status(), 400);
749 }
750
751 #[test]
752 fn http_status_invalid_params_is_400() {
753 assert_eq!(ErrorCode::InvalidParams.http_status(), 400);
754 }
755
756 #[test]
757 fn http_status_internal_error_is_500() {
758 assert_eq!(ErrorCode::InternalError.http_status(), 500);
759 }
760
761 #[test]
764 fn grpc_status_task_not_found_is_not_found() {
765 assert_eq!(ErrorCode::TaskNotFound.grpc_status(), "NOT_FOUND");
766 }
767
768 #[test]
769 fn grpc_status_task_not_cancelable_is_failed_precondition() {
770 assert_eq!(
771 ErrorCode::TaskNotCancelable.grpc_status(),
772 "FAILED_PRECONDITION"
773 );
774 }
775
776 #[test]
777 fn grpc_status_extended_card_not_configured_is_failed_precondition() {
778 assert_eq!(
779 ErrorCode::ExtendedAgentCardNotConfigured.grpc_status(),
780 "FAILED_PRECONDITION"
781 );
782 }
783
784 #[test]
785 fn grpc_status_extension_support_required_is_failed_precondition() {
786 assert_eq!(
787 ErrorCode::ExtensionSupportRequired.grpc_status(),
788 "FAILED_PRECONDITION"
789 );
790 }
791
792 #[test]
793 fn grpc_status_push_not_supported_is_unimplemented() {
794 assert_eq!(
795 ErrorCode::PushNotificationNotSupported.grpc_status(),
796 "UNIMPLEMENTED"
797 );
798 }
799
800 #[test]
801 fn grpc_status_unsupported_operation_is_unimplemented() {
802 assert_eq!(
803 ErrorCode::UnsupportedOperation.grpc_status(),
804 "UNIMPLEMENTED"
805 );
806 }
807
808 #[test]
809 fn grpc_status_version_not_supported_is_unimplemented() {
810 assert_eq!(
811 ErrorCode::VersionNotSupported.grpc_status(),
812 "UNIMPLEMENTED"
813 );
814 }
815
816 #[test]
817 fn grpc_status_method_not_found_is_unimplemented() {
818 assert_eq!(ErrorCode::MethodNotFound.grpc_status(), "UNIMPLEMENTED");
819 }
820
821 #[test]
822 fn grpc_status_content_type_not_supported_is_invalid_argument() {
823 assert_eq!(
824 ErrorCode::ContentTypeNotSupported.grpc_status(),
825 "INVALID_ARGUMENT"
826 );
827 }
828
829 #[test]
830 fn grpc_status_invalid_params_is_invalid_argument() {
831 assert_eq!(ErrorCode::InvalidParams.grpc_status(), "INVALID_ARGUMENT");
832 }
833
834 #[test]
835 fn grpc_status_invalid_request_is_invalid_argument() {
836 assert_eq!(ErrorCode::InvalidRequest.grpc_status(), "INVALID_ARGUMENT");
837 }
838
839 #[test]
840 fn grpc_status_parse_error_is_invalid_argument() {
841 assert_eq!(ErrorCode::ParseError.grpc_status(), "INVALID_ARGUMENT");
842 }
843
844 #[test]
845 fn grpc_status_invalid_agent_response_is_internal() {
846 assert_eq!(ErrorCode::InvalidAgentResponse.grpc_status(), "INTERNAL");
847 }
848
849 #[test]
850 fn grpc_status_internal_error_is_internal() {
851 assert_eq!(ErrorCode::InternalError.grpc_status(), "INTERNAL");
852 }
853
854 #[test]
855 fn grpc_status_never_empty() {
856 let all = [
857 ErrorCode::ParseError,
858 ErrorCode::InvalidRequest,
859 ErrorCode::MethodNotFound,
860 ErrorCode::InvalidParams,
861 ErrorCode::InternalError,
862 ErrorCode::TaskNotFound,
863 ErrorCode::TaskNotCancelable,
864 ErrorCode::PushNotificationNotSupported,
865 ErrorCode::UnsupportedOperation,
866 ErrorCode::ContentTypeNotSupported,
867 ErrorCode::InvalidAgentResponse,
868 ErrorCode::ExtendedAgentCardNotConfigured,
869 ErrorCode::ExtensionSupportRequired,
870 ErrorCode::VersionNotSupported,
871 ];
872 for code in all {
873 assert!(
874 !code.grpc_status().is_empty(),
875 "grpc_status must never be empty for {code:?}"
876 );
877 }
878 }
879
880 #[test]
883 fn error_info_data_for_a2a_error_has_expected_shape() {
884 let err = A2aError::task_not_found("t1");
885 let data = err.error_info_data(None);
886
887 let arr = data.as_array().expect("error_info_data must be an array");
889 assert_eq!(arr.len(), 1);
890 let info = &arr[0];
891 assert_eq!(info["@type"], "type.googleapis.com/google.rpc.ErrorInfo");
892 assert_eq!(info["reason"], "TASK_NOT_FOUND");
893 assert_eq!(info["domain"], "a2a-protocol.org");
894 assert!(
895 info.get("metadata").is_none(),
896 "metadata must be absent when None is passed"
897 );
898 }
899
900 #[test]
901 fn error_info_data_with_metadata_includes_metadata() {
902 let err = A2aError::task_not_cancelable("t2");
903 let meta = serde_json::json!({"task_id": "t2", "current_state": "completed"});
904 let data = err.error_info_data(Some(meta.clone()));
905
906 let arr = data.as_array().expect("error_info_data must be an array");
907 let info = &arr[0];
908 assert_eq!(info["reason"], "TASK_NOT_CANCELABLE");
909 assert_eq!(info["metadata"], meta);
910 }
911
912 #[test]
913 fn error_info_data_for_standard_jsonrpc_error_is_null() {
914 let err = A2aError::parse_error("bad json");
920 let data = err.error_info_data(None);
921 assert!(data.is_null(), "expected null, got {data}");
922 }
923
924 #[test]
925 fn error_info_data_mutation_not_default_value() {
926 let err = A2aError::unsupported_operation("nope");
930 let data = err.error_info_data(None);
931 assert_ne!(
932 data,
933 serde_json::Value::default(),
934 "A2A error must not produce default serde_json::Value"
935 );
936 assert!(data.is_array(), "A2A error must produce an array");
937 }
938
939 #[test]
940 fn error_info_data_metadata_not_added_when_none() {
941 let err = A2aError::version_not_supported("old");
944 let data = err.error_info_data(None);
945 let info = &data[0];
946 assert!(info.get("metadata").is_none());
947 let with = err.error_info_data(Some(serde_json::json!({"x": 1})));
949 assert_eq!(with[0]["metadata"], serde_json::json!({"x": 1}));
950 }
951}