1use thiserror::Error;
2
3use crate::domain::error_details::{ErrorDetail, FieldViolation};
4
5pub type Result<T, E = A2AError> = std::result::Result<T, E>;
10
11pub const PARSE_ERROR: i32 = -32700;
13pub const INVALID_REQUEST: i32 = -32600;
14pub const METHOD_NOT_FOUND: i32 = -32601;
15pub const INVALID_PARAMS: i32 = -32602;
16pub const INTERNAL_ERROR: i32 = -32603;
17
18pub const TASK_NOT_FOUND: i32 = -32001;
20pub const TASK_NOT_CANCELABLE: i32 = -32002;
21pub const PUSH_NOTIFICATION_NOT_SUPPORTED: i32 = -32003;
22pub const UNSUPPORTED_OPERATION: i32 = -32004;
23pub const CONTENT_TYPE_NOT_SUPPORTED: i32 = -32005;
24pub const INVALID_AGENT_RESPONSE: i32 = -32006;
25pub const AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED: i32 = -32007;
26
27pub const DATABASE_ERROR: i32 = -32100;
29pub const VERSION_CONFLICT: i32 = -32101;
31pub const CONTEXT_ACCESS_DENIED: i32 = -32102;
33
34#[derive(Error, Debug)]
36pub enum A2AError {
37 #[error("JSON-RPC error: {code} - {message}")]
38 JsonRpc {
39 code: i32,
40 message: String,
41 data: Option<serde_json::Value>,
42 },
43
44 #[error("JSON parse error: {0}")]
45 JsonParse(#[from] serde_json::Error),
46
47 #[error("Invalid request: {0}")]
48 InvalidRequest(String),
49
50 #[error("Invalid parameters: {0}")]
51 InvalidParams(String),
52
53 #[error("Method not found: {0}")]
54 MethodNotFound(String),
55
56 #[error("Task not found: {0}")]
57 TaskNotFound(String),
58
59 #[error("Task not cancelable: {0}")]
60 TaskNotCancelable(String),
61
62 #[error("Push notification not supported")]
63 PushNotificationNotSupported,
64
65 #[error("Unsupported operation: {0}")]
66 UnsupportedOperation(String),
67
68 #[error("Content type not supported: {0}")]
69 ContentTypeNotSupported(String),
70
71 #[error("Invalid agent response: {0}")]
72 InvalidAgentResponse(String),
73
74 #[error("Authenticated extended card not configured")]
75 AuthenticatedExtendedCardNotConfigured,
76
77 #[error("Internal error: {0}")]
78 Internal(String),
79
80 #[error("Validation error in {field}: {message}")]
81 ValidationError { field: String, message: String },
82
83 #[error("Version conflict for task {id}: expected {expected}, found {actual}")]
84 VersionConflict {
85 id: String,
86 expected: u64,
87 actual: u64,
88 },
89
90 #[error("Database error: {0}")]
91 DatabaseError(String),
92
93 #[error("context {context_id} belongs to another principal")]
100 ContextAccessDenied { context_id: String },
101
102 #[error("IO error: {0}")]
103 Io(#[from] std::io::Error),
104}
105
106impl A2AError {
107 pub fn to_jsonrpc_error(&self) -> serde_json::Value {
109 let (code, message) = match self {
110 A2AError::JsonParse(_) => (PARSE_ERROR, "Invalid JSON payload"),
111 A2AError::InvalidRequest(_) => (INVALID_REQUEST, "Request payload validation error"),
112 A2AError::MethodNotFound(_) => (METHOD_NOT_FOUND, "Method not found"),
113 A2AError::InvalidParams(_) => (INVALID_PARAMS, "Invalid parameters"),
114 A2AError::TaskNotFound(_) => (TASK_NOT_FOUND, "Task not found"),
115 A2AError::TaskNotCancelable(_) => (TASK_NOT_CANCELABLE, "Task cannot be canceled"),
116 A2AError::PushNotificationNotSupported => (
117 PUSH_NOTIFICATION_NOT_SUPPORTED,
118 "Push Notification is not supported",
119 ),
120 A2AError::UnsupportedOperation(_) => {
121 (UNSUPPORTED_OPERATION, "This operation is not supported")
122 }
123 A2AError::ContentTypeNotSupported(_) => {
124 (CONTENT_TYPE_NOT_SUPPORTED, "Incompatible content types")
125 }
126 A2AError::InvalidAgentResponse(_) => (INVALID_AGENT_RESPONSE, "Invalid agent response"),
127 A2AError::AuthenticatedExtendedCardNotConfigured => (
128 AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED,
129 "Authenticated Extended Card is not configured",
130 ),
131 A2AError::ValidationError { .. } => (INVALID_PARAMS, "Validation error"),
132 A2AError::VersionConflict { .. } => (VERSION_CONFLICT, "Task version conflict"),
133 A2AError::DatabaseError(_) => (DATABASE_ERROR, "Database error"),
134 A2AError::ContextAccessDenied { .. } => (
135 CONTEXT_ACCESS_DENIED,
136 "Context belongs to another principal",
137 ),
138 A2AError::Internal(_) => (INTERNAL_ERROR, "Internal error"),
139 _ => (INTERNAL_ERROR, "Internal error"),
140 };
141
142 serde_json::json!({
143 "code": code,
144 "message": message,
145 "data": null,
146 })
147 }
148
149 pub fn reason_code(&self) -> &'static str {
152 match self {
153 A2AError::JsonRpc { .. } => "JSON_RPC_ERROR",
154 A2AError::JsonParse(_) => "PARSE_ERROR",
155 A2AError::InvalidRequest(_) => "INVALID_REQUEST",
156 A2AError::InvalidParams(_) => "INVALID_PARAMS",
157 A2AError::MethodNotFound(_) => "METHOD_NOT_FOUND",
158 A2AError::TaskNotFound(_) => "TASK_NOT_FOUND",
159 A2AError::TaskNotCancelable(_) => "TASK_NOT_CANCELABLE",
160 A2AError::PushNotificationNotSupported => "PUSH_NOTIFICATION_NOT_SUPPORTED",
161 A2AError::UnsupportedOperation(_) => "UNSUPPORTED_OPERATION",
162 A2AError::ContentTypeNotSupported(_) => "CONTENT_TYPE_NOT_SUPPORTED",
163 A2AError::InvalidAgentResponse(_) => "INVALID_AGENT_RESPONSE",
164 A2AError::AuthenticatedExtendedCardNotConfigured => {
165 "AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED"
166 }
167 A2AError::Internal(_) => "INTERNAL_ERROR",
168 A2AError::ValidationError { .. } => "VALIDATION_ERROR",
169 A2AError::VersionConflict { .. } => "VERSION_CONFLICT",
170 A2AError::DatabaseError(_) => "DATABASE_ERROR",
171 A2AError::ContextAccessDenied { .. } => "CONTEXT_ACCESS_DENIED",
172 A2AError::Io(_) => "IO_ERROR",
173 }
174 }
175
176 pub fn error_details(&self) -> Vec<ErrorDetail> {
184 match self {
185 A2AError::ValidationError { field, message } => vec![
186 ErrorDetail::BadRequest {
187 field_violations: vec![FieldViolation::new(field, message)],
188 },
189 ErrorDetail::reason(self.reason_code()),
190 ],
191 A2AError::VersionConflict {
192 id,
193 expected,
194 actual,
195 } => {
196 let mut info = crate::domain::error_details::ErrorInfo::new(self.reason_code());
197 info = info
198 .with_metadata("task_id", id)
199 .with_metadata("expected", expected.to_string())
200 .with_metadata("actual", actual.to_string());
201 vec![ErrorDetail::ErrorInfo(info)]
202 }
203 _ => vec![ErrorDetail::reason(self.reason_code())],
204 }
205 }
206}