use std::fmt;
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub type Result<T> = std::result::Result<T, A2AError>;
#[derive(Error, Debug)]
pub enum A2AError {
#[error("parse error: {0}")]
ParseError(String),
#[error("invalid request: {0}")]
InvalidRequest(String),
#[error("method not found: {0}")]
MethodNotFound(String),
#[error("invalid params: {0}")]
InvalidParams(String),
#[error("internal error: {0}")]
InternalError(String),
#[error("server error: {0}")]
ServerError(String),
#[error("task not found: {0}")]
TaskNotFound(String),
#[error("task cannot be canceled: {0}")]
TaskNotCancelable(String),
#[error("push notification not supported")]
PushNotificationNotSupported,
#[error("unsupported operation: {0}")]
UnsupportedOperation(String),
#[error("incompatible content types: {0}")]
ContentTypeNotSupported(String),
#[error("invalid agent response: {0}")]
InvalidAgentResponse(String),
#[error("extended card not configured")]
ExtendedCardNotConfigured,
#[error("unauthenticated: {0}")]
Unauthenticated(String),
#[error("permission denied: {0}")]
Unauthorized(String),
#[error("concurrent task modification")]
ConcurrentTaskModification,
#[error("extension support required: {0}")]
ExtensionSupportRequired(String),
#[error("version not supported: {0}")]
VersionNotSupported(String),
#[error("JSON-RPC error: {0}")]
JsonRpc(#[from] JsonRpcError),
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("database error: {0}")]
Database(String),
#[error("{0}")]
Other(String),
}
impl A2AError {
#[must_use]
pub const fn is_transport_error(&self) -> bool {
matches!(self, Self::Http(_) | Self::Json(_))
}
#[must_use]
pub const fn jsonrpc_code(&self) -> Option<i32> {
if let Self::JsonRpc(e) = self {
Some(e.code)
} else {
None
}
}
#[must_use]
pub fn to_jsonrpc_error(&self) -> JsonRpcError {
let (code, default_msg) = self.jsonrpc_error_code();
let message = {
let s = self.to_string();
if s.is_empty() {
default_msg.to_owned()
} else {
s
}
};
JsonRpcError {
code: code as i32,
message,
data: None,
}
}
const fn jsonrpc_error_code(&self) -> (JsonRpcErrorCode, &'static str) {
match self {
Self::ParseError(_) => (JsonRpcErrorCode::ParseError, "Parse error"),
Self::InvalidRequest(_) => (JsonRpcErrorCode::InvalidRequest, "Invalid request"),
Self::MethodNotFound(_) => (JsonRpcErrorCode::MethodNotFound, "Method not found"),
Self::InvalidParams(_) => (JsonRpcErrorCode::InvalidParams, "Invalid params"),
Self::ServerError(_) => (JsonRpcErrorCode::ServerError, "Server error"),
Self::TaskNotFound(_) => (JsonRpcErrorCode::TaskNotFound, "Task not found"),
Self::TaskNotCancelable(_) => {
(JsonRpcErrorCode::TaskNotCancelable, "Task not cancelable")
}
Self::PushNotificationNotSupported => (
JsonRpcErrorCode::PushNotificationNotSupported,
"Push notification not supported",
),
Self::UnsupportedOperation(_) => (
JsonRpcErrorCode::UnsupportedOperation,
"Unsupported operation",
),
Self::ContentTypeNotSupported(_) => (
JsonRpcErrorCode::ContentTypeNotSupported,
"Content type not supported",
),
Self::InvalidAgentResponse(_) => (
JsonRpcErrorCode::InvalidAgentResponse,
"Invalid agent response",
),
Self::ExtendedCardNotConfigured => (
JsonRpcErrorCode::AuthenticatedExtendedCardNotConfigured,
"Extended card not configured",
),
Self::Unauthenticated(_) => (JsonRpcErrorCode::Unauthenticated, "Unauthenticated"),
Self::Unauthorized(_) => (JsonRpcErrorCode::Unauthorized, "Permission denied"),
Self::ConcurrentTaskModification => (
JsonRpcErrorCode::ConcurrentTaskModification,
"Concurrent task modification",
),
Self::ExtensionSupportRequired(_) => (
JsonRpcErrorCode::ExtensionSupportRequired,
"Extension support required",
),
Self::VersionNotSupported(_) => (
JsonRpcErrorCode::VersionNotSupported,
"Version not supported",
),
_ => (JsonRpcErrorCode::InternalError, "Internal error"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(i32)]
pub enum JsonRpcErrorCode {
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
ServerError = -32000,
TaskNotFound = -32001,
TaskNotCancelable = -32002,
PushNotificationNotSupported = -32003,
UnsupportedOperation = -32004,
ContentTypeNotSupported = -32005,
InvalidAgentResponse = -32006,
AuthenticatedExtendedCardNotConfigured = -32007,
Unauthenticated = -31401,
Unauthorized = -31403,
ConcurrentTaskModification = -32008,
ExtensionSupportRequired = -32009,
VersionNotSupported = -32010,
}
impl JsonRpcErrorCode {
#[must_use]
pub const fn default_message(self) -> &'static str {
match self {
Self::ParseError => "Invalid JSON payload",
Self::InvalidRequest => "Request payload validation error",
Self::MethodNotFound => "Method not found",
Self::InvalidParams => "Invalid parameters",
Self::InternalError => "Internal error",
Self::ServerError => "Server error",
Self::TaskNotFound => "Task not found",
Self::TaskNotCancelable => "Task cannot be canceled",
Self::PushNotificationNotSupported => "Push Notification is not supported",
Self::UnsupportedOperation => "This operation is not supported",
Self::ContentTypeNotSupported => "Incompatible content types",
Self::InvalidAgentResponse => "Invalid agent response",
Self::AuthenticatedExtendedCardNotConfigured => {
"Authenticated Extended Card is not configured"
}
Self::Unauthenticated => "Unauthenticated",
Self::Unauthorized => "Permission denied",
Self::ConcurrentTaskModification => "Concurrent task modification",
Self::ExtensionSupportRequired => "Extension support required",
Self::VersionNotSupported => "Version not supported",
}
}
}
impl From<i32> for JsonRpcErrorCode {
fn from(code: i32) -> Self {
match code {
-32700 => Self::ParseError,
-32600 => Self::InvalidRequest,
-32601 => Self::MethodNotFound,
-32602 => Self::InvalidParams,
-32603 => Self::InternalError,
-32001 => Self::TaskNotFound,
-32002 => Self::TaskNotCancelable,
-32003 => Self::PushNotificationNotSupported,
-32004 => Self::UnsupportedOperation,
-32005 => Self::ContentTypeNotSupported,
-32006 => Self::InvalidAgentResponse,
-32007 => Self::AuthenticatedExtendedCardNotConfigured,
-31401 => Self::Unauthenticated,
-31403 => Self::Unauthorized,
-32008 => Self::ConcurrentTaskModification,
-32009 => Self::ExtensionSupportRequired,
-32010 => Self::VersionNotSupported,
_ => Self::InternalError,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RestErrorInfo {
#[serde(rename = "@type")]
pub at_type: String,
pub reason: String,
pub domain: String,
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub metadata: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RestStatusError {
pub code: u16,
pub status: String,
pub message: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub details: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RestError {
#[serde(skip)]
pub http_status: u16,
pub error: RestStatusError,
}
impl A2AError {
#[must_use]
pub fn to_rest_error(&self, task_id: Option<&str>) -> RestError {
let (http_status, grpc_status, reason) = self.rest_error_details();
let mut metadata = std::collections::HashMap::new();
metadata.insert(
"timestamp".into(),
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
);
if let Some(tid) = task_id
&& !tid.is_empty()
{
metadata.insert("taskId".into(), tid.to_owned());
}
let info = RestErrorInfo {
at_type: "type.googleapis.com/google.rpc.ErrorInfo".into(),
reason: reason.into(),
domain: "a2a-protocol.org".into(),
metadata,
};
RestError {
http_status,
error: RestStatusError {
code: http_status,
status: grpc_status.into(),
message: self.to_string(),
details: vec![serde_json::to_value(info).unwrap_or_default()],
},
}
}
const fn rest_error_details(&self) -> (u16, &'static str, &'static str) {
match self {
Self::ParseError(_) | Self::InvalidRequest(_) | Self::InvalidParams(_) => {
(400, "INVALID_ARGUMENT", "INVALID_REQUEST")
}
Self::MethodNotFound(_) => (404, "NOT_FOUND", "METHOD_NOT_FOUND"),
Self::TaskNotFound(_) => (404, "NOT_FOUND", "TASK_NOT_FOUND"),
Self::TaskNotCancelable(_) => (400, "FAILED_PRECONDITION", "TASK_NOT_CANCELABLE"),
Self::PushNotificationNotSupported => {
(501, "UNIMPLEMENTED", "PUSH_NOTIFICATION_NOT_SUPPORTED")
}
Self::UnsupportedOperation(_) => (501, "UNIMPLEMENTED", "UNSUPPORTED_OPERATION"),
Self::ContentTypeNotSupported(_) => {
(400, "INVALID_ARGUMENT", "UNSUPPORTED_CONTENT_TYPE")
}
Self::InvalidAgentResponse(_) => (500, "INTERNAL", "INVALID_AGENT_RESPONSE"),
Self::ExtendedCardNotConfigured => (
400,
"FAILED_PRECONDITION",
"EXTENDED_AGENT_CARD_NOT_CONFIGURED",
),
Self::Unauthenticated(_) => (401, "UNAUTHENTICATED", "UNAUTHENTICATED"),
Self::Unauthorized(_) => (403, "PERMISSION_DENIED", "UNAUTHORIZED"),
Self::ConcurrentTaskModification => (409, "ABORTED", "CONCURRENT_TASK_MODIFICATION"),
Self::ExtensionSupportRequired(_) => {
(400, "FAILED_PRECONDITION", "EXTENSION_SUPPORT_REQUIRED")
}
Self::VersionNotSupported(_) => (501, "UNIMPLEMENTED", "VERSION_NOT_SUPPORTED"),
_ => (500, "INTERNAL", "INTERNAL_ERROR"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Error)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
impl fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl JsonRpcError {
pub fn new(code: JsonRpcErrorCode, message: impl Into<String>) -> Self {
Self {
code: code as i32,
message: message.into(),
data: None,
}
}
#[must_use]
pub fn parse_error() -> Self {
Self::new(
JsonRpcErrorCode::ParseError,
JsonRpcErrorCode::ParseError.default_message(),
)
}
pub fn invalid_request(message: impl Into<String>) -> Self {
Self::new(JsonRpcErrorCode::InvalidRequest, message)
}
#[must_use]
pub fn method_not_found(method: &str) -> Self {
Self::new(
JsonRpcErrorCode::MethodNotFound,
format!("Method '{method}' not found"),
)
}
pub fn invalid_params(message: impl Into<String>) -> Self {
Self::new(JsonRpcErrorCode::InvalidParams, message)
}
#[must_use]
pub fn error_code(&self) -> JsonRpcErrorCode {
JsonRpcErrorCode::from(self.code)
}
}