1use serde::{Deserialize, Serialize};
4
5pub const JSON_PARSE_ERROR: i32 = -32700;
11pub const INVALID_REQUEST: i32 = -32600;
13pub const METHOD_NOT_FOUND: i32 = -32601;
15pub const INVALID_PARAMS: i32 = -32602;
17pub const INTERNAL_ERROR: i32 = -32603;
19pub const TASK_NOT_FOUND: i32 = -32001;
21pub const TASK_NOT_CANCELABLE: i32 = -32002;
23pub const PUSH_NOT_SUPPORTED: i32 = -32003;
25pub const UNSUPPORTED_OPERATION: i32 = -32004;
27pub const CONTENT_TYPE_NOT_SUPPORTED: i32 = -32005;
29pub const INVALID_AGENT_RESPONSE: i32 = -32006;
31pub const EXTENDED_CARD_NOT_CONFIGURED: i32 = -32007;
33pub const EXTENSION_SUPPORT_REQUIRED: i32 = -32008;
35pub const VERSION_NOT_SUPPORTED: i32 = -32009;
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct A2aError {
45 pub code: i32,
47 pub message: String,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub data: Option<serde_json::Value>,
52}
53
54impl A2aError {
55 pub fn new(code: i32, message: impl Into<String>) -> Self {
57 Self {
58 code,
59 message: message.into(),
60 data: None,
61 }
62 }
63
64 pub fn with_data(mut self, data: serde_json::Value) -> Self {
66 self.data = Some(data);
67 self
68 }
69
70 pub fn task_not_found(task_id: &str) -> Self {
72 Self::new(TASK_NOT_FOUND, format!("Task not found: {task_id}"))
73 }
74
75 pub fn task_not_cancelable(task_id: &str) -> Self {
77 Self::new(
78 TASK_NOT_CANCELABLE,
79 format!("Task cannot be canceled: {task_id}"),
80 )
81 }
82
83 pub fn push_not_supported() -> Self {
85 Self::new(PUSH_NOT_SUPPORTED, "Push notifications are not supported")
86 }
87
88 pub fn unsupported_operation(detail: &str) -> Self {
90 Self::new(
91 UNSUPPORTED_OPERATION,
92 format!("Unsupported operation: {detail}"),
93 )
94 }
95
96 pub fn content_type_not_supported(detail: &str) -> Self {
98 Self::new(
99 CONTENT_TYPE_NOT_SUPPORTED,
100 format!("Content type not supported: {detail}"),
101 )
102 }
103
104 pub fn invalid_request(detail: impl Into<String>) -> Self {
106 Self::new(INVALID_REQUEST, detail)
107 }
108
109 pub fn internal(message: impl Into<String>) -> Self {
111 Self::new(INTERNAL_ERROR, message)
112 }
113
114 pub fn method_not_found(method: &str) -> Self {
116 Self::new(METHOD_NOT_FOUND, format!("Method not found: {method}"))
117 }
118
119 pub fn invalid_params(detail: impl Into<String>) -> Self {
121 Self::new(INVALID_PARAMS, detail)
122 }
123
124 pub fn parse_error(detail: impl Into<String>) -> Self {
126 Self::new(JSON_PARSE_ERROR, detail)
127 }
128
129 pub fn extended_card_not_configured() -> Self {
131 Self::new(
132 EXTENDED_CARD_NOT_CONFIGURED,
133 "Authenticated Extended Card is not configured",
134 )
135 }
136
137 pub fn extension_support_required() -> Self {
139 Self::new(
140 EXTENSION_SUPPORT_REQUIRED,
141 "Extension support is required but not available",
142 )
143 }
144
145 pub fn version_not_supported() -> Self {
147 Self::new(VERSION_NOT_SUPPORTED, "Protocol version is not supported")
148 }
149}
150
151impl std::fmt::Display for A2aError {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 write!(f, "A2A error {}: {}", self.code, self.message)
154 }
155}
156
157impl std::error::Error for A2aError {}
158
159impl From<serde_json::Error> for A2aError {
160 fn from(err: serde_json::Error) -> Self {
161 Self::parse_error(err.to_string())
162 }
163}
164
165impl From<anyhow::Error> for A2aError {
166 fn from(err: anyhow::Error) -> Self {
167 Self::internal(err.to_string())
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn new_sets_code_and_message() {
177 let e = A2aError::new(TASK_NOT_FOUND, "missing");
178 assert_eq!(e.code, TASK_NOT_FOUND);
179 assert_eq!(e.message, "missing");
180 assert!(e.data.is_none());
181 }
182
183 #[test]
184 fn with_data_attaches_payload() {
185 let e = A2aError::new(INTERNAL_ERROR, "boom").with_data(serde_json::json!({"detail": "x"}));
186 assert!(e.data.is_some());
187 }
188
189 #[test]
190 fn constructors_use_correct_codes() {
191 assert_eq!(A2aError::task_not_found("t1").code, TASK_NOT_FOUND);
192 assert_eq!(
193 A2aError::task_not_cancelable("t1").code,
194 TASK_NOT_CANCELABLE
195 );
196 assert_eq!(A2aError::push_not_supported().code, PUSH_NOT_SUPPORTED);
197 assert_eq!(
198 A2aError::unsupported_operation("x").code,
199 UNSUPPORTED_OPERATION
200 );
201 assert_eq!(
202 A2aError::content_type_not_supported("x").code,
203 CONTENT_TYPE_NOT_SUPPORTED
204 );
205 assert_eq!(A2aError::invalid_request("x").code, INVALID_REQUEST);
206 assert_eq!(A2aError::internal("x").code, INTERNAL_ERROR);
207 assert_eq!(A2aError::method_not_found("m").code, METHOD_NOT_FOUND);
208 assert_eq!(A2aError::invalid_params("x").code, INVALID_PARAMS);
209 assert_eq!(A2aError::parse_error("x").code, JSON_PARSE_ERROR);
210 assert_eq!(
211 A2aError::extended_card_not_configured().code,
212 EXTENDED_CARD_NOT_CONFIGURED
213 );
214 assert_eq!(
215 A2aError::extension_support_required().code,
216 EXTENSION_SUPPORT_REQUIRED
217 );
218 assert_eq!(
219 A2aError::version_not_supported().code,
220 VERSION_NOT_SUPPORTED
221 );
222 }
223
224 #[test]
225 fn task_not_found_includes_id_in_message() {
226 let e = A2aError::task_not_found("abc-123");
227 assert!(e.message.contains("abc-123"));
228 }
229
230 #[test]
231 fn display_includes_code_and_message() {
232 let e = A2aError::new(INTERNAL_ERROR, "oops");
233 let s = e.to_string();
234 assert!(s.contains(&INTERNAL_ERROR.to_string()));
235 assert!(s.contains("oops"));
236 }
237
238 #[test]
239 fn serde_roundtrip_without_data() {
240 let original = A2aError::task_not_found("t42");
241 let json = serde_json::to_string(&original).unwrap();
242 let decoded: A2aError = serde_json::from_str(&json).unwrap();
243 assert_eq!(decoded.code, original.code);
244 assert_eq!(decoded.message, original.message);
245 assert!(decoded.data.is_none());
246 }
247
248 #[test]
249 fn serde_roundtrip_with_data() {
250 let original = A2aError::internal("err").with_data(serde_json::json!({"k": 1}));
251 let json = serde_json::to_string(&original).unwrap();
252 let decoded: A2aError = serde_json::from_str(&json).unwrap();
253 assert_eq!(decoded.code, original.code);
254 assert!(decoded.data.is_some());
255 }
256
257 #[test]
258 fn from_serde_json_error() {
259 let bad: Result<serde_json::Value, _> = serde_json::from_str("{bad json}");
260 let a2a_err: A2aError = bad.unwrap_err().into();
261 assert_eq!(a2a_err.code, JSON_PARSE_ERROR);
262 }
263
264 #[test]
265 fn from_anyhow_error() {
266 let anyhow_err = anyhow::anyhow!("something went wrong");
267 let a2a_err: A2aError = anyhow_err.into();
268 assert_eq!(a2a_err.code, INTERNAL_ERROR);
269 assert!(a2a_err.message.contains("something went wrong"));
270 }
271
272 #[test]
273 fn data_field_omitted_when_none_in_json() {
274 let e = A2aError::new(TASK_NOT_FOUND, "x");
275 let json = serde_json::to_string(&e).unwrap();
276 assert!(!json.contains("data"));
277 }
278}