1use serde_json::Value;
4use std::collections::HashMap;
5
6pub mod error_code {
8 pub const TASK_NOT_FOUND: i32 = -32001;
10 pub const TASK_NOT_CANCELABLE: i32 = -32002;
11 pub const PUSH_NOTIFICATION_NOT_SUPPORTED: i32 = -32003;
12 pub const UNSUPPORTED_OPERATION: i32 = -32004;
13 pub const CONTENT_TYPE_NOT_SUPPORTED: i32 = -32005;
14 pub const INVALID_AGENT_RESPONSE: i32 = -32006;
15 pub const EXTENDED_CARD_NOT_CONFIGURED: i32 = -32007;
16 pub const EXTENSION_SUPPORT_REQUIRED: i32 = -32008;
17 pub const VERSION_NOT_SUPPORTED: i32 = -32009;
18
19 pub const PARSE_ERROR: i32 = -32700;
21 pub const INVALID_REQUEST: i32 = -32600;
22 pub const METHOD_NOT_FOUND: i32 = -32601;
23 pub const INVALID_PARAMS: i32 = -32602;
24 pub const INTERNAL_ERROR: i32 = -32603;
25}
26
27#[derive(Debug, Clone, thiserror::Error)]
29#[error("{message}")]
30pub struct A2AError {
31 pub code: i32,
32 pub message: String,
33 pub details: Option<HashMap<String, Value>>,
34}
35
36impl A2AError {
37 pub fn new(code: i32, message: impl Into<String>) -> Self {
38 A2AError {
39 code,
40 message: message.into(),
41 details: None,
42 }
43 }
44
45 pub fn with_details(mut self, details: HashMap<String, Value>) -> Self {
46 self.details = Some(details);
47 self
48 }
49
50 pub fn task_not_found(task_id: &str) -> Self {
53 A2AError::new(
54 error_code::TASK_NOT_FOUND,
55 format!("task not found: {task_id}"),
56 )
57 }
58
59 pub fn task_not_cancelable(task_id: &str) -> Self {
60 A2AError::new(
61 error_code::TASK_NOT_CANCELABLE,
62 format!("task cannot be canceled: {task_id}"),
63 )
64 }
65
66 pub fn push_notification_not_supported() -> Self {
67 A2AError::new(
68 error_code::PUSH_NOTIFICATION_NOT_SUPPORTED,
69 "push notification not supported",
70 )
71 }
72
73 pub fn unsupported_operation(msg: impl Into<String>) -> Self {
74 A2AError::new(error_code::UNSUPPORTED_OPERATION, msg)
75 }
76
77 pub fn content_type_not_supported() -> Self {
78 A2AError::new(
79 error_code::CONTENT_TYPE_NOT_SUPPORTED,
80 "incompatible content types",
81 )
82 }
83
84 pub fn invalid_agent_response() -> Self {
85 A2AError::new(error_code::INVALID_AGENT_RESPONSE, "invalid agent response")
86 }
87
88 pub fn version_not_supported(version: &str) -> Self {
89 A2AError::new(
90 error_code::VERSION_NOT_SUPPORTED,
91 format!("version not supported: {version}"),
92 )
93 }
94
95 pub fn internal(msg: impl Into<String>) -> Self {
96 A2AError::new(error_code::INTERNAL_ERROR, msg)
97 }
98
99 pub fn invalid_params(msg: impl Into<String>) -> Self {
100 A2AError::new(error_code::INVALID_PARAMS, msg)
101 }
102
103 pub fn parse_error(msg: impl Into<String>) -> Self {
104 A2AError::new(error_code::PARSE_ERROR, msg)
105 }
106
107 pub fn invalid_request(msg: impl Into<String>) -> Self {
108 A2AError::new(error_code::INVALID_REQUEST, msg)
109 }
110
111 pub fn method_not_found(method: &str) -> Self {
112 A2AError::new(
113 error_code::METHOD_NOT_FOUND,
114 format!("method not found: {method}"),
115 )
116 }
117
118 pub fn http_status_code(&self) -> u16 {
120 match self.code {
121 error_code::TASK_NOT_FOUND => 404,
122 error_code::TASK_NOT_CANCELABLE => 409,
123 error_code::PUSH_NOTIFICATION_NOT_SUPPORTED => 400,
124 error_code::UNSUPPORTED_OPERATION => 400,
125 error_code::CONTENT_TYPE_NOT_SUPPORTED => 415,
126 error_code::VERSION_NOT_SUPPORTED => 400,
127 error_code::PARSE_ERROR => 400,
128 error_code::INVALID_REQUEST => 400,
129 error_code::METHOD_NOT_FOUND => 404,
130 error_code::INVALID_PARAMS => 400,
131 error_code::INTERNAL_ERROR => 500,
132 _ => 500,
133 }
134 }
135
136 pub fn to_jsonrpc_error(&self) -> crate::JsonRpcError {
138 crate::JsonRpcError {
139 code: self.code,
140 message: self.message.clone(),
141 data: self
142 .details
143 .as_ref()
144 .map(|d| serde_json::to_value(d).unwrap_or_default()),
145 }
146 }
147}
148
149impl From<A2AError> for crate::JsonRpcError {
150 fn from(e: A2AError) -> Self {
151 e.to_jsonrpc_error()
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_error_constructors() {
161 let e = A2AError::task_not_found("t1");
162 assert_eq!(e.code, error_code::TASK_NOT_FOUND);
163 assert!(e.message.contains("t1"));
164
165 let e = A2AError::task_not_cancelable("t2");
166 assert_eq!(e.code, error_code::TASK_NOT_CANCELABLE);
167
168 let e = A2AError::push_notification_not_supported();
169 assert_eq!(e.code, error_code::PUSH_NOTIFICATION_NOT_SUPPORTED);
170
171 let e = A2AError::unsupported_operation("nope");
172 assert_eq!(e.code, error_code::UNSUPPORTED_OPERATION);
173
174 let e = A2AError::content_type_not_supported();
175 assert_eq!(e.code, error_code::CONTENT_TYPE_NOT_SUPPORTED);
176
177 let e = A2AError::invalid_agent_response();
178 assert_eq!(e.code, error_code::INVALID_AGENT_RESPONSE);
179
180 let e = A2AError::version_not_supported("2.0");
181 assert_eq!(e.code, error_code::VERSION_NOT_SUPPORTED);
182
183 let e = A2AError::internal("boom");
184 assert_eq!(e.code, error_code::INTERNAL_ERROR);
185
186 let e = A2AError::invalid_params("bad param");
187 assert_eq!(e.code, error_code::INVALID_PARAMS);
188
189 let e = A2AError::parse_error("bad json");
190 assert_eq!(e.code, error_code::PARSE_ERROR);
191
192 let e = A2AError::invalid_request("bad req");
193 assert_eq!(e.code, error_code::INVALID_REQUEST);
194
195 let e = A2AError::method_not_found("foo");
196 assert_eq!(e.code, error_code::METHOD_NOT_FOUND);
197 }
198
199 #[test]
200 fn test_http_status_codes() {
201 assert_eq!(A2AError::task_not_found("x").http_status_code(), 404);
202 assert_eq!(A2AError::task_not_cancelable("x").http_status_code(), 409);
203 assert_eq!(A2AError::internal("x").http_status_code(), 500);
204 assert_eq!(A2AError::invalid_params("x").http_status_code(), 400);
205 assert_eq!(
206 A2AError::content_type_not_supported().http_status_code(),
207 415
208 );
209 assert_eq!(A2AError::new(9999, "unknown").http_status_code(), 500);
210 }
211
212 #[test]
213 fn test_http_status_codes_for_remaining_a2a_mappings() {
214 assert_eq!(
215 A2AError::push_notification_not_supported().http_status_code(),
216 400
217 );
218 assert_eq!(
219 A2AError::unsupported_operation("nope").http_status_code(),
220 400
221 );
222 assert_eq!(
223 A2AError::version_not_supported("9.9").http_status_code(),
224 400
225 );
226 assert_eq!(A2AError::parse_error("bad").http_status_code(), 400);
227 assert_eq!(A2AError::invalid_request("bad").http_status_code(), 400);
228 assert_eq!(
229 A2AError::method_not_found("missing").http_status_code(),
230 404
231 );
232 }
233
234 #[test]
235 fn test_to_jsonrpc_error() {
236 let e = A2AError::task_not_found("t1");
237 let rpc = e.to_jsonrpc_error();
238 assert_eq!(rpc.code, error_code::TASK_NOT_FOUND);
239 assert!(rpc.message.contains("t1"));
240 assert!(rpc.data.is_none());
241 }
242
243 #[test]
244 fn test_with_details() {
245 let mut details = HashMap::new();
246 details.insert("key".to_string(), Value::String("val".to_string()));
247 let e = A2AError::internal("err").with_details(details.clone());
248 assert_eq!(e.details.as_ref().unwrap(), &details);
249
250 let rpc = e.to_jsonrpc_error();
251 assert!(rpc.data.is_some());
252 }
253
254 #[test]
255 fn test_error_display() {
256 let e = A2AError::internal("test message");
257 assert_eq!(format!("{e}"), "test message");
258 }
259
260 #[test]
261 fn test_jsonrpc_error_from() {
262 let e = A2AError::internal("test");
263 let rpc: crate::JsonRpcError = e.into();
264 assert_eq!(rpc.code, error_code::INTERNAL_ERROR);
265 }
266}