use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(into = "i32", try_from = "i32")]
#[non_exhaustive]
pub enum ErrorCode {
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
TaskNotFound = -32001,
TaskNotCancelable = -32002,
PushNotificationNotSupported = -32003,
UnsupportedOperation = -32004,
ContentTypeNotSupported = -32005,
InvalidAgentResponse = -32006,
ExtendedAgentCardNotConfigured = -32007,
ExtensionSupportRequired = -32008,
VersionNotSupported = -32009,
}
impl ErrorCode {
#[must_use]
pub const fn as_i32(self) -> i32 {
self as i32
}
#[must_use]
pub const fn default_message(self) -> &'static str {
match self {
Self::ParseError => "Parse error",
Self::InvalidRequest => "Invalid request",
Self::MethodNotFound => "Method not found",
Self::InvalidParams => "Invalid params",
Self::InternalError => "Internal error",
Self::TaskNotFound => "Task not found",
Self::TaskNotCancelable => "Task not cancelable",
Self::PushNotificationNotSupported => "Push notification not supported",
Self::UnsupportedOperation => "Unsupported operation",
Self::ContentTypeNotSupported => "Content type not supported",
Self::InvalidAgentResponse => "Invalid agent response",
Self::ExtendedAgentCardNotConfigured => "Extended agent card not configured",
Self::ExtensionSupportRequired => "Extension support required",
Self::VersionNotSupported => "Version not supported",
}
}
#[must_use]
pub const fn a2a_reason(self) -> Option<&'static str> {
match self {
Self::TaskNotFound => Some("TASK_NOT_FOUND"),
Self::TaskNotCancelable => Some("TASK_NOT_CANCELABLE"),
Self::PushNotificationNotSupported => Some("PUSH_NOTIFICATION_NOT_SUPPORTED"),
Self::UnsupportedOperation => Some("UNSUPPORTED_OPERATION"),
Self::ContentTypeNotSupported => Some("CONTENT_TYPE_NOT_SUPPORTED"),
Self::InvalidAgentResponse => Some("INVALID_AGENT_RESPONSE"),
Self::ExtendedAgentCardNotConfigured => Some("EXTENDED_AGENT_CARD_NOT_CONFIGURED"),
Self::ExtensionSupportRequired => Some("EXTENSION_SUPPORT_REQUIRED"),
Self::VersionNotSupported => Some("VERSION_NOT_SUPPORTED"),
_ => None,
}
}
#[must_use]
pub const fn http_status(self) -> u16 {
match self {
Self::TaskNotFound | Self::MethodNotFound => 404,
Self::TaskNotCancelable => 409,
Self::ContentTypeNotSupported => 415,
Self::InvalidAgentResponse => 502,
Self::PushNotificationNotSupported
| Self::UnsupportedOperation
| Self::ExtendedAgentCardNotConfigured
| Self::ExtensionSupportRequired
| Self::VersionNotSupported
| Self::ParseError
| Self::InvalidRequest
| Self::InvalidParams => 400,
Self::InternalError => 500,
}
}
#[must_use]
pub const fn grpc_status(self) -> &'static str {
match self {
Self::TaskNotFound => "NOT_FOUND",
Self::TaskNotCancelable
| Self::ExtendedAgentCardNotConfigured
| Self::ExtensionSupportRequired => "FAILED_PRECONDITION",
Self::PushNotificationNotSupported
| Self::UnsupportedOperation
| Self::VersionNotSupported
| Self::MethodNotFound => "UNIMPLEMENTED",
Self::ContentTypeNotSupported
| Self::InvalidParams
| Self::InvalidRequest
| Self::ParseError => "INVALID_ARGUMENT",
Self::InvalidAgentResponse | Self::InternalError => "INTERNAL",
}
}
}
impl From<ErrorCode> for i32 {
fn from(code: ErrorCode) -> Self {
code as Self
}
}
impl TryFrom<i32> for ErrorCode {
type Error = i32;
fn try_from(v: i32) -> Result<Self, Self::Error> {
match v {
-32700 => Ok(Self::ParseError),
-32600 => Ok(Self::InvalidRequest),
-32601 => Ok(Self::MethodNotFound),
-32602 => Ok(Self::InvalidParams),
-32603 => Ok(Self::InternalError),
-32001 => Ok(Self::TaskNotFound),
-32002 => Ok(Self::TaskNotCancelable),
-32003 => Ok(Self::PushNotificationNotSupported),
-32004 => Ok(Self::UnsupportedOperation),
-32005 => Ok(Self::ContentTypeNotSupported),
-32006 => Ok(Self::InvalidAgentResponse),
-32007 => Ok(Self::ExtendedAgentCardNotConfigured),
-32008 => Ok(Self::ExtensionSupportRequired),
-32009 => Ok(Self::VersionNotSupported),
other => Err(other),
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({})", self.default_message(), self.as_i32())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct A2aError {
pub code: ErrorCode,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
impl A2aError {
#[must_use]
pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
#[must_use]
pub fn with_data(code: ErrorCode, message: impl Into<String>, data: serde_json::Value) -> Self {
Self {
code,
message: message.into(),
data: Some(data),
}
}
#[must_use]
pub fn task_not_found(task_id: impl fmt::Display) -> Self {
Self::new(
ErrorCode::TaskNotFound,
format!("Task not found: {task_id}"),
)
}
#[must_use]
pub fn task_not_cancelable(task_id: impl fmt::Display) -> Self {
Self::new(
ErrorCode::TaskNotCancelable,
format!("Task cannot be canceled: {task_id}"),
)
}
#[must_use]
pub fn internal(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::InternalError, msg)
}
#[must_use]
pub fn invalid_params(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::InvalidParams, msg)
}
#[must_use]
pub fn unsupported_operation(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::UnsupportedOperation, msg)
}
#[must_use]
pub fn parse_error(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::ParseError, msg)
}
#[must_use]
pub fn invalid_agent_response(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::InvalidAgentResponse, msg)
}
#[must_use]
pub fn extended_card_not_configured(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::ExtendedAgentCardNotConfigured, msg)
}
#[must_use]
pub fn push_not_supported(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::PushNotificationNotSupported, msg)
}
#[must_use]
pub fn content_type_not_supported(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::ContentTypeNotSupported, msg)
}
#[must_use]
pub fn extension_support_required(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::ExtensionSupportRequired, msg)
}
#[must_use]
pub fn version_not_supported(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::VersionNotSupported, msg)
}
#[must_use]
pub fn error_info_data(&self, metadata: Option<serde_json::Value>) -> serde_json::Value {
self.code
.a2a_reason()
.map_or(serde_json::Value::Null, |reason| {
let mut info = serde_json::json!({
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": reason,
"domain": "a2a-protocol.org"
});
if let Some(meta) = metadata {
info["metadata"] = meta;
}
serde_json::json!([info])
})
}
}
impl fmt::Display for A2aError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.code.as_i32(), self.message)
}
}
impl std::error::Error for A2aError {}
pub type A2aResult<T> = Result<T, A2aError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_code_roundtrip() {
let code = ErrorCode::TaskNotFound;
let n: i32 = code.into();
assert_eq!(n, -32001);
assert_eq!(ErrorCode::try_from(n), Ok(ErrorCode::TaskNotFound));
}
#[test]
fn error_code_unknown_value() {
assert!(ErrorCode::try_from(-99999).is_err());
}
#[test]
fn a2a_error_display() {
let err = A2aError::task_not_found("abc123");
let s = err.to_string();
assert!(s.contains("-32001"), "expected code in display: {s}");
assert!(s.contains("abc123"), "expected task id in display: {s}");
}
#[test]
fn a2a_error_serialization() {
let err = A2aError::internal("something went wrong");
let json = serde_json::to_string(&err).expect("serialize");
let back: A2aError = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.code, ErrorCode::InternalError);
assert_eq!(back.message, "something went wrong");
assert!(back.data.is_none());
}
#[test]
fn a2a_error_with_data() {
let data = serde_json::json!({"detail": "extra info"});
let err = A2aError::with_data(ErrorCode::InvalidParams, "bad input", data.clone());
let json = serde_json::to_string(&err).expect("serialize");
assert!(json.contains("\"data\""), "data field should be present");
let back: A2aError = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.data, Some(data));
}
#[test]
#[allow(clippy::too_many_lines)]
fn error_code_roundtrip_all_variants() {
let cases: &[(ErrorCode, i32, &str)] = &[
(ErrorCode::ParseError, -32700, "Parse error"),
(ErrorCode::InvalidRequest, -32600, "Invalid request"),
(ErrorCode::MethodNotFound, -32601, "Method not found"),
(ErrorCode::InvalidParams, -32602, "Invalid params"),
(ErrorCode::InternalError, -32603, "Internal error"),
(ErrorCode::TaskNotFound, -32001, "Task not found"),
(ErrorCode::TaskNotCancelable, -32002, "Task not cancelable"),
(
ErrorCode::PushNotificationNotSupported,
-32003,
"Push notification not supported",
),
(
ErrorCode::UnsupportedOperation,
-32004,
"Unsupported operation",
),
(
ErrorCode::ContentTypeNotSupported,
-32005,
"Content type not supported",
),
(
ErrorCode::InvalidAgentResponse,
-32006,
"Invalid agent response",
),
(
ErrorCode::ExtendedAgentCardNotConfigured,
-32007,
"Extended agent card not configured",
),
(
ErrorCode::ExtensionSupportRequired,
-32008,
"Extension support required",
),
(
ErrorCode::VersionNotSupported,
-32009,
"Version not supported",
),
];
for &(code, expected_i32, expected_msg) in cases {
assert_eq!(code.as_i32(), expected_i32, "as_i32 mismatch for {code:?}");
let n: i32 = code.into();
assert_eq!(n, expected_i32, "Into<i32> mismatch for {code:?}");
let back = ErrorCode::try_from(expected_i32).expect("try_from should succeed");
assert_eq!(back, code, "TryFrom roundtrip mismatch for {code:?}");
assert_eq!(
code.default_message(),
expected_msg,
"default_message mismatch for {code:?}"
);
let display = code.to_string();
assert!(
display.contains(expected_msg),
"Display missing message for {code:?}: {display}"
);
assert!(
display.contains(&expected_i32.to_string()),
"Display missing code for {code:?}: {display}"
);
}
}
#[test]
fn error_code_rejects_adjacent_values() {
let invalid: &[i32] = &[
-32701,
-32699, -32599,
-32601 + 1, -32000,
-32010, 0,
1,
-1,
i32::MIN,
i32::MAX,
];
for &v in invalid {
if ErrorCode::try_from(v).is_ok() {
continue;
}
assert_eq!(
ErrorCode::try_from(v),
Err(v),
"value {v} should not convert to ErrorCode"
);
}
}
#[test]
fn named_constructors_use_correct_codes() {
assert_eq!(A2aError::task_not_found("t1").code, ErrorCode::TaskNotFound);
assert_eq!(
A2aError::task_not_cancelable("t1").code,
ErrorCode::TaskNotCancelable
);
assert_eq!(A2aError::internal("x").code, ErrorCode::InternalError);
assert_eq!(A2aError::invalid_params("x").code, ErrorCode::InvalidParams);
assert_eq!(
A2aError::unsupported_operation("x").code,
ErrorCode::UnsupportedOperation
);
assert_eq!(A2aError::parse_error("x").code, ErrorCode::ParseError);
assert_eq!(
A2aError::invalid_agent_response("x").code,
ErrorCode::InvalidAgentResponse
);
assert_eq!(
A2aError::extended_card_not_configured("x").code,
ErrorCode::ExtendedAgentCardNotConfigured
);
}
#[test]
fn named_constructors_include_argument_in_message() {
let err = A2aError::task_not_found("my-task-id");
assert!(
err.message.contains("my-task-id"),
"task_not_found should include task_id: {}",
err.message
);
let err = A2aError::task_not_cancelable("cancel-me");
assert!(
err.message.contains("cancel-me"),
"task_not_cancelable should include task_id: {}",
err.message
);
}
#[test]
fn a2a_error_new_has_no_data() {
let err = A2aError::new(ErrorCode::InternalError, "msg");
assert!(err.data.is_none());
}
#[test]
fn a2a_error_with_data_has_some_data() {
let err = A2aError::with_data(
ErrorCode::InternalError,
"msg",
serde_json::json!("details"),
);
assert!(err.data.is_some());
assert_eq!(err.data.unwrap(), serde_json::json!("details"));
}
#[test]
fn a2a_error_is_std_error() {
let err = A2aError::internal("test");
let _: &dyn std::error::Error = &err;
}
#[test]
fn a2a_error_display_format() {
let err = A2aError::new(ErrorCode::ParseError, "bad json");
let s = err.to_string();
assert_eq!(s, "[-32700] bad json");
}
#[test]
fn a2a_reason_task_not_found() {
assert_eq!(ErrorCode::TaskNotFound.a2a_reason(), Some("TASK_NOT_FOUND"));
}
#[test]
fn a2a_reason_task_not_cancelable() {
assert_eq!(
ErrorCode::TaskNotCancelable.a2a_reason(),
Some("TASK_NOT_CANCELABLE")
);
}
#[test]
fn a2a_reason_push_notification_not_supported() {
assert_eq!(
ErrorCode::PushNotificationNotSupported.a2a_reason(),
Some("PUSH_NOTIFICATION_NOT_SUPPORTED")
);
}
#[test]
fn a2a_reason_unsupported_operation() {
assert_eq!(
ErrorCode::UnsupportedOperation.a2a_reason(),
Some("UNSUPPORTED_OPERATION")
);
}
#[test]
fn a2a_reason_content_type_not_supported() {
assert_eq!(
ErrorCode::ContentTypeNotSupported.a2a_reason(),
Some("CONTENT_TYPE_NOT_SUPPORTED")
);
}
#[test]
fn a2a_reason_invalid_agent_response() {
assert_eq!(
ErrorCode::InvalidAgentResponse.a2a_reason(),
Some("INVALID_AGENT_RESPONSE")
);
}
#[test]
fn a2a_reason_extended_agent_card_not_configured() {
assert_eq!(
ErrorCode::ExtendedAgentCardNotConfigured.a2a_reason(),
Some("EXTENDED_AGENT_CARD_NOT_CONFIGURED")
);
}
#[test]
fn a2a_reason_extension_support_required() {
assert_eq!(
ErrorCode::ExtensionSupportRequired.a2a_reason(),
Some("EXTENSION_SUPPORT_REQUIRED")
);
}
#[test]
fn a2a_reason_version_not_supported() {
assert_eq!(
ErrorCode::VersionNotSupported.a2a_reason(),
Some("VERSION_NOT_SUPPORTED")
);
}
#[test]
fn a2a_reason_none_for_standard_jsonrpc_errors() {
assert_eq!(ErrorCode::ParseError.a2a_reason(), None);
assert_eq!(ErrorCode::InvalidRequest.a2a_reason(), None);
assert_eq!(ErrorCode::MethodNotFound.a2a_reason(), None);
assert_eq!(ErrorCode::InvalidParams.a2a_reason(), None);
assert_eq!(ErrorCode::InternalError.a2a_reason(), None);
}
#[test]
fn a2a_reason_all_a2a_variants_distinct_and_non_empty() {
let reasons: &[&str] = &[
ErrorCode::TaskNotFound.a2a_reason().unwrap(),
ErrorCode::TaskNotCancelable.a2a_reason().unwrap(),
ErrorCode::PushNotificationNotSupported
.a2a_reason()
.unwrap(),
ErrorCode::UnsupportedOperation.a2a_reason().unwrap(),
ErrorCode::ContentTypeNotSupported.a2a_reason().unwrap(),
ErrorCode::InvalidAgentResponse.a2a_reason().unwrap(),
ErrorCode::ExtendedAgentCardNotConfigured
.a2a_reason()
.unwrap(),
ErrorCode::ExtensionSupportRequired.a2a_reason().unwrap(),
ErrorCode::VersionNotSupported.a2a_reason().unwrap(),
];
for r in reasons {
assert!(!r.is_empty(), "a2a_reason must not be empty: {r}");
}
let mut sorted: Vec<&str> = reasons.to_vec();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(
sorted.len(),
reasons.len(),
"a2a_reason values must be distinct across variants"
);
}
#[test]
fn http_status_task_not_found_is_404() {
assert_eq!(ErrorCode::TaskNotFound.http_status(), 404);
}
#[test]
fn http_status_method_not_found_is_404() {
assert_eq!(ErrorCode::MethodNotFound.http_status(), 404);
}
#[test]
fn http_status_task_not_cancelable_is_409() {
assert_eq!(ErrorCode::TaskNotCancelable.http_status(), 409);
}
#[test]
fn http_status_content_type_not_supported_is_415() {
assert_eq!(ErrorCode::ContentTypeNotSupported.http_status(), 415);
}
#[test]
fn http_status_invalid_agent_response_is_502() {
assert_eq!(ErrorCode::InvalidAgentResponse.http_status(), 502);
}
#[test]
fn http_status_push_not_supported_is_400() {
assert_eq!(ErrorCode::PushNotificationNotSupported.http_status(), 400);
}
#[test]
fn http_status_unsupported_operation_is_400() {
assert_eq!(ErrorCode::UnsupportedOperation.http_status(), 400);
}
#[test]
fn http_status_extended_card_not_configured_is_400() {
assert_eq!(ErrorCode::ExtendedAgentCardNotConfigured.http_status(), 400);
}
#[test]
fn http_status_extension_support_required_is_400() {
assert_eq!(ErrorCode::ExtensionSupportRequired.http_status(), 400);
}
#[test]
fn http_status_version_not_supported_is_400() {
assert_eq!(ErrorCode::VersionNotSupported.http_status(), 400);
}
#[test]
fn http_status_parse_error_is_400() {
assert_eq!(ErrorCode::ParseError.http_status(), 400);
}
#[test]
fn http_status_invalid_request_is_400() {
assert_eq!(ErrorCode::InvalidRequest.http_status(), 400);
}
#[test]
fn http_status_invalid_params_is_400() {
assert_eq!(ErrorCode::InvalidParams.http_status(), 400);
}
#[test]
fn http_status_internal_error_is_500() {
assert_eq!(ErrorCode::InternalError.http_status(), 500);
}
#[test]
fn grpc_status_task_not_found_is_not_found() {
assert_eq!(ErrorCode::TaskNotFound.grpc_status(), "NOT_FOUND");
}
#[test]
fn grpc_status_task_not_cancelable_is_failed_precondition() {
assert_eq!(
ErrorCode::TaskNotCancelable.grpc_status(),
"FAILED_PRECONDITION"
);
}
#[test]
fn grpc_status_extended_card_not_configured_is_failed_precondition() {
assert_eq!(
ErrorCode::ExtendedAgentCardNotConfigured.grpc_status(),
"FAILED_PRECONDITION"
);
}
#[test]
fn grpc_status_extension_support_required_is_failed_precondition() {
assert_eq!(
ErrorCode::ExtensionSupportRequired.grpc_status(),
"FAILED_PRECONDITION"
);
}
#[test]
fn grpc_status_push_not_supported_is_unimplemented() {
assert_eq!(
ErrorCode::PushNotificationNotSupported.grpc_status(),
"UNIMPLEMENTED"
);
}
#[test]
fn grpc_status_unsupported_operation_is_unimplemented() {
assert_eq!(
ErrorCode::UnsupportedOperation.grpc_status(),
"UNIMPLEMENTED"
);
}
#[test]
fn grpc_status_version_not_supported_is_unimplemented() {
assert_eq!(
ErrorCode::VersionNotSupported.grpc_status(),
"UNIMPLEMENTED"
);
}
#[test]
fn grpc_status_method_not_found_is_unimplemented() {
assert_eq!(ErrorCode::MethodNotFound.grpc_status(), "UNIMPLEMENTED");
}
#[test]
fn grpc_status_content_type_not_supported_is_invalid_argument() {
assert_eq!(
ErrorCode::ContentTypeNotSupported.grpc_status(),
"INVALID_ARGUMENT"
);
}
#[test]
fn grpc_status_invalid_params_is_invalid_argument() {
assert_eq!(ErrorCode::InvalidParams.grpc_status(), "INVALID_ARGUMENT");
}
#[test]
fn grpc_status_invalid_request_is_invalid_argument() {
assert_eq!(ErrorCode::InvalidRequest.grpc_status(), "INVALID_ARGUMENT");
}
#[test]
fn grpc_status_parse_error_is_invalid_argument() {
assert_eq!(ErrorCode::ParseError.grpc_status(), "INVALID_ARGUMENT");
}
#[test]
fn grpc_status_invalid_agent_response_is_internal() {
assert_eq!(ErrorCode::InvalidAgentResponse.grpc_status(), "INTERNAL");
}
#[test]
fn grpc_status_internal_error_is_internal() {
assert_eq!(ErrorCode::InternalError.grpc_status(), "INTERNAL");
}
#[test]
fn grpc_status_never_empty() {
let all = [
ErrorCode::ParseError,
ErrorCode::InvalidRequest,
ErrorCode::MethodNotFound,
ErrorCode::InvalidParams,
ErrorCode::InternalError,
ErrorCode::TaskNotFound,
ErrorCode::TaskNotCancelable,
ErrorCode::PushNotificationNotSupported,
ErrorCode::UnsupportedOperation,
ErrorCode::ContentTypeNotSupported,
ErrorCode::InvalidAgentResponse,
ErrorCode::ExtendedAgentCardNotConfigured,
ErrorCode::ExtensionSupportRequired,
ErrorCode::VersionNotSupported,
];
for code in all {
assert!(
!code.grpc_status().is_empty(),
"grpc_status must never be empty for {code:?}"
);
}
}
#[test]
fn error_info_data_for_a2a_error_has_expected_shape() {
let err = A2aError::task_not_found("t1");
let data = err.error_info_data(None);
let arr = data.as_array().expect("error_info_data must be an array");
assert_eq!(arr.len(), 1);
let info = &arr[0];
assert_eq!(info["@type"], "type.googleapis.com/google.rpc.ErrorInfo");
assert_eq!(info["reason"], "TASK_NOT_FOUND");
assert_eq!(info["domain"], "a2a-protocol.org");
assert!(
info.get("metadata").is_none(),
"metadata must be absent when None is passed"
);
}
#[test]
fn error_info_data_with_metadata_includes_metadata() {
let err = A2aError::task_not_cancelable("t2");
let meta = serde_json::json!({"task_id": "t2", "current_state": "completed"});
let data = err.error_info_data(Some(meta.clone()));
let arr = data.as_array().expect("error_info_data must be an array");
let info = &arr[0];
assert_eq!(info["reason"], "TASK_NOT_CANCELABLE");
assert_eq!(info["metadata"], meta);
}
#[test]
fn error_info_data_for_standard_jsonrpc_error_is_null() {
let err = A2aError::parse_error("bad json");
let data = err.error_info_data(None);
assert!(data.is_null(), "expected null, got {data}");
}
#[test]
fn error_info_data_mutation_not_default_value() {
let err = A2aError::unsupported_operation("nope");
let data = err.error_info_data(None);
assert_ne!(
data,
serde_json::Value::default(),
"A2A error must not produce default serde_json::Value"
);
assert!(data.is_array(), "A2A error must produce an array");
}
#[test]
fn error_info_data_metadata_not_added_when_none() {
let err = A2aError::version_not_supported("old");
let data = err.error_info_data(None);
let info = &data[0];
assert!(info.get("metadata").is_none());
let with = err.error_info_data(Some(serde_json::json!({"x": 1})));
assert_eq!(with[0]["metadata"], serde_json::json!({"x": 1}));
}
}