1use std::fmt;
7use thiserror::Error;
8
9pub const JSON_PARSE_ERROR: i32 = -32700;
15
16pub const INVALID_REQUEST_ERROR: i32 = -32600;
18
19pub const METHOD_NOT_FOUND_ERROR: i32 = -32601;
21
22pub const INVALID_PARAMS_ERROR: i32 = -32602;
24
25pub const INTERNAL_ERROR: i32 = -32603;
27
28pub const TASK_NOT_FOUND_ERROR: i32 = -32001;
34
35pub const TASK_NOT_CANCELABLE_ERROR: i32 = -32002;
37
38pub const PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR: i32 = -32003;
40
41pub const UNSUPPORTED_OPERATION_ERROR: i32 = -32004;
43
44pub const CONTENT_TYPE_NOT_SUPPORTED_ERROR: i32 = -32005;
46
47pub const INVALID_AGENT_RESPONSE_ERROR: i32 = -32006;
49
50pub const AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED_ERROR: i32 = -32007;
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum A2aErrorCode {
60 JsonParseError,
62 InvalidRequest,
63 MethodNotFound,
64 InvalidParams,
65 InternalError,
66 TaskNotFound,
68 TaskNotCancelable,
69 PushNotificationNotSupported,
70 UnsupportedOperation,
71 ContentTypeNotSupported,
72 InvalidAgentResponse,
73 AuthenticatedExtendedCardNotConfigured,
74 Custom(i32),
76}
77
78impl From<A2aErrorCode> for i32 {
79 fn from(code: A2aErrorCode) -> Self {
80 match code {
81 A2aErrorCode::JsonParseError => JSON_PARSE_ERROR,
82 A2aErrorCode::InvalidRequest => INVALID_REQUEST_ERROR,
83 A2aErrorCode::MethodNotFound => METHOD_NOT_FOUND_ERROR,
84 A2aErrorCode::InvalidParams => INVALID_PARAMS_ERROR,
85 A2aErrorCode::InternalError => INTERNAL_ERROR,
86 A2aErrorCode::TaskNotFound => TASK_NOT_FOUND_ERROR,
87 A2aErrorCode::TaskNotCancelable => TASK_NOT_CANCELABLE_ERROR,
88 A2aErrorCode::PushNotificationNotSupported => PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR,
89 A2aErrorCode::UnsupportedOperation => UNSUPPORTED_OPERATION_ERROR,
90 A2aErrorCode::ContentTypeNotSupported => CONTENT_TYPE_NOT_SUPPORTED_ERROR,
91 A2aErrorCode::InvalidAgentResponse => INVALID_AGENT_RESPONSE_ERROR,
92 A2aErrorCode::AuthenticatedExtendedCardNotConfigured => {
93 AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED_ERROR
94 }
95 A2aErrorCode::Custom(code) => code,
96 }
97 }
98}
99
100impl From<i32> for A2aErrorCode {
101 fn from(code: i32) -> Self {
102 match code {
103 JSON_PARSE_ERROR => A2aErrorCode::JsonParseError,
104 INVALID_REQUEST_ERROR => A2aErrorCode::InvalidRequest,
105 METHOD_NOT_FOUND_ERROR => A2aErrorCode::MethodNotFound,
106 INVALID_PARAMS_ERROR => A2aErrorCode::InvalidParams,
107 INTERNAL_ERROR => A2aErrorCode::InternalError,
108 TASK_NOT_FOUND_ERROR => A2aErrorCode::TaskNotFound,
109 TASK_NOT_CANCELABLE_ERROR => A2aErrorCode::TaskNotCancelable,
110 PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR => A2aErrorCode::PushNotificationNotSupported,
111 UNSUPPORTED_OPERATION_ERROR => A2aErrorCode::UnsupportedOperation,
112 CONTENT_TYPE_NOT_SUPPORTED_ERROR => A2aErrorCode::ContentTypeNotSupported,
113 INVALID_AGENT_RESPONSE_ERROR => A2aErrorCode::InvalidAgentResponse,
114 AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED_ERROR => {
115 A2aErrorCode::AuthenticatedExtendedCardNotConfigured
116 }
117 other => A2aErrorCode::Custom(other),
118 }
119 }
120}
121
122impl fmt::Display for A2aErrorCode {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 match self {
125 A2aErrorCode::JsonParseError => write!(f, "JSON parse error"),
126 A2aErrorCode::InvalidRequest => write!(f, "Invalid request"),
127 A2aErrorCode::MethodNotFound => write!(f, "Method not found"),
128 A2aErrorCode::InvalidParams => write!(f, "Invalid params"),
129 A2aErrorCode::InternalError => write!(f, "Internal error"),
130 A2aErrorCode::TaskNotFound => write!(f, "Task not found"),
131 A2aErrorCode::TaskNotCancelable => write!(f, "Task not cancelable"),
132 A2aErrorCode::PushNotificationNotSupported => {
133 write!(f, "Push notifications not supported")
134 }
135 A2aErrorCode::UnsupportedOperation => write!(f, "Unsupported operation"),
136 A2aErrorCode::ContentTypeNotSupported => write!(f, "Content type not supported"),
137 A2aErrorCode::InvalidAgentResponse => write!(f, "Invalid agent response"),
138 A2aErrorCode::AuthenticatedExtendedCardNotConfigured => {
139 write!(f, "Authenticated extended card not configured")
140 }
141 A2aErrorCode::Custom(code) => write!(f, "Custom error ({code})"),
142 }
143 }
144}
145
146#[derive(Debug, Error)]
148pub enum A2aError {
149 #[error("JSON-RPC error ({code}): {message}")]
150 RpcError {
151 code: A2aErrorCode,
152 message: String,
153 #[source]
154 data: Option<Box<dyn std::error::Error + Send + Sync>>,
155 },
156
157 #[error("Task not found: {0}")]
158 TaskNotFound(String),
159
160 #[error("Task not cancelable: {0}")]
161 TaskNotCancelable(String),
162
163 #[error("Invalid task state transition: {from:?} -> {to:?}")]
164 InvalidStateTransition {
165 from: super::types::TaskState,
166 to: super::types::TaskState,
167 },
168
169 #[error("Unsupported operation: {0}")]
170 UnsupportedOperation(String),
171
172 #[error("Content type not supported: {0}")]
173 ContentTypeNotSupported(String),
174
175 #[error("Serialization error: {0}")]
176 Serialization(#[from] serde_json::Error),
177
178 #[error("Internal error: {0}")]
179 Internal(String),
180}
181
182impl A2aError {
183 pub fn code(&self) -> A2aErrorCode {
185 match self {
186 A2aError::RpcError { code, .. } => *code,
187 A2aError::TaskNotFound(_) => A2aErrorCode::TaskNotFound,
188 A2aError::TaskNotCancelable(_) => A2aErrorCode::TaskNotCancelable,
189 A2aError::InvalidStateTransition { .. } => A2aErrorCode::InvalidParams,
190 A2aError::UnsupportedOperation(_) => A2aErrorCode::UnsupportedOperation,
191 A2aError::ContentTypeNotSupported(_) => A2aErrorCode::ContentTypeNotSupported,
192 A2aError::Serialization(_) => A2aErrorCode::JsonParseError,
193 A2aError::Internal(_) => A2aErrorCode::InternalError,
194 }
195 }
196
197 pub fn rpc(code: A2aErrorCode, message: impl Into<String>) -> Self {
199 A2aError::RpcError {
200 code,
201 message: message.into(),
202 data: None,
203 }
204 }
205
206 pub fn internal(message: impl Into<String>) -> Self {
208 A2aError::Internal(message.into())
209 }
210}
211
212pub type A2aResult<T> = Result<T, A2aError>;
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_error_code_conversion() {
221 assert_eq!(i32::from(A2aErrorCode::TaskNotFound), TASK_NOT_FOUND_ERROR);
222 assert_eq!(
223 A2aErrorCode::from(TASK_NOT_FOUND_ERROR),
224 A2aErrorCode::TaskNotFound
225 );
226 }
227
228 #[test]
229 fn test_error_code_display() {
230 assert_eq!(A2aErrorCode::TaskNotFound.to_string(), "Task not found");
231 assert_eq!(A2aErrorCode::MethodNotFound.to_string(), "Method not found");
232 }
233
234 #[test]
235 fn test_a2a_error_code() {
236 let err = A2aError::TaskNotFound("task-123".to_string());
237 assert_eq!(err.code(), A2aErrorCode::TaskNotFound);
238 }
239
240 #[test]
241 fn test_custom_error_code() {
242 let custom = A2aErrorCode::Custom(-32099);
243 assert_eq!(i32::from(custom), -32099);
244 assert_eq!(custom.to_string(), "Custom error (-32099)");
245 }
246}