agent_client_protocol_schema/
error.rs1use std::{fmt::Display, str};
14
15use schemars::{JsonSchema, Schema};
16use serde::{Deserialize, Serialize};
17
18use crate::IntoOption;
19
20pub type Result<T, E = Error> = std::result::Result<T, E>;
21
22#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
29#[non_exhaustive]
30pub struct Error {
31 pub code: ErrorCode,
34 pub message: String,
37 #[serde(skip_serializing_if = "Option::is_none")]
40 pub data: Option<serde_json::Value>,
41 #[cfg(feature = "unstable_auth_methods")]
49 #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "authMethods")]
50 pub auth_methods: Vec<crate::AuthMethod>,
51}
52
53impl Error {
54 #[must_use]
58 pub fn new(code: i32, message: impl Into<String>) -> Self {
59 Error {
60 code: code.into(),
61 message: message.into(),
62 data: None,
63 #[cfg(feature = "unstable_auth_methods")]
64 auth_methods: Vec::new(),
65 }
66 }
67
68 #[must_use]
73 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
74 self.data = data.into_option();
75 self
76 }
77
78 #[must_use]
80 pub fn parse_error() -> Self {
81 ErrorCode::ParseError.into()
82 }
83
84 #[must_use]
86 pub fn invalid_request() -> Self {
87 ErrorCode::InvalidRequest.into()
88 }
89
90 #[must_use]
92 pub fn method_not_found() -> Self {
93 ErrorCode::MethodNotFound.into()
94 }
95
96 #[must_use]
98 pub fn invalid_params() -> Self {
99 ErrorCode::InvalidParams.into()
100 }
101
102 #[must_use]
104 pub fn internal_error() -> Self {
105 ErrorCode::InternalError.into()
106 }
107
108 #[cfg(feature = "unstable_cancel_request")]
117 #[must_use]
118 pub fn request_cancelled() -> Self {
119 ErrorCode::RequestCancelled.into()
120 }
121
122 #[must_use]
124 pub fn auth_required() -> Self {
125 ErrorCode::AuthRequired.into()
126 }
127
128 #[cfg(feature = "unstable_auth_methods")]
136 #[must_use]
137 pub fn auth_methods(mut self, auth_methods: Vec<crate::AuthMethod>) -> Self {
138 self.auth_methods = auth_methods;
139 self
140 }
141
142 #[must_use]
144 pub fn resource_not_found(uri: Option<String>) -> Self {
145 let err: Self = ErrorCode::ResourceNotFound.into();
146 if let Some(uri) = uri {
147 err.data(serde_json::json!({ "uri": uri }))
148 } else {
149 err
150 }
151 }
152
153 #[must_use]
157 pub fn into_internal_error(err: impl std::error::Error) -> Self {
158 Error::internal_error().data(err.to_string())
159 }
160}
161
162#[derive(Clone, Copy, Deserialize, Eq, JsonSchema, PartialEq, Serialize, strum::Display)]
167#[cfg_attr(test, derive(strum::EnumIter))]
168#[serde(from = "i32", into = "i32")]
169#[schemars(!from, !into)]
170#[non_exhaustive]
171pub enum ErrorCode {
172 #[schemars(transform = error_code_transform)]
176 #[strum(to_string = "Parse error")]
177 ParseError, #[schemars(transform = error_code_transform)]
180 #[strum(to_string = "Invalid request")]
181 InvalidRequest, #[schemars(transform = error_code_transform)]
184 #[strum(to_string = "Method not found")]
185 MethodNotFound, #[schemars(transform = error_code_transform)]
188 #[strum(to_string = "Invalid params")]
189 InvalidParams, #[schemars(transform = error_code_transform)]
193 #[strum(to_string = "Internal error")]
194 InternalError, #[cfg(feature = "unstable_cancel_request")]
196 #[schemars(transform = error_code_transform)]
203 #[strum(to_string = "Request cancelled")]
204 RequestCancelled, #[schemars(transform = error_code_transform)]
209 #[strum(to_string = "Authentication required")]
210 AuthRequired, #[schemars(transform = error_code_transform)]
213 #[strum(to_string = "Resource not found")]
214 ResourceNotFound, #[schemars(untagged)]
218 #[strum(to_string = "Unknown error")]
219 Other(i32),
220}
221
222impl From<i32> for ErrorCode {
223 fn from(value: i32) -> Self {
224 match value {
225 -32700 => ErrorCode::ParseError,
226 -32600 => ErrorCode::InvalidRequest,
227 -32601 => ErrorCode::MethodNotFound,
228 -32602 => ErrorCode::InvalidParams,
229 -32603 => ErrorCode::InternalError,
230 #[cfg(feature = "unstable_cancel_request")]
231 -32800 => ErrorCode::RequestCancelled,
232 -32000 => ErrorCode::AuthRequired,
233 -32002 => ErrorCode::ResourceNotFound,
234 _ => ErrorCode::Other(value),
235 }
236 }
237}
238
239impl From<ErrorCode> for i32 {
240 fn from(value: ErrorCode) -> Self {
241 match value {
242 ErrorCode::ParseError => -32700,
243 ErrorCode::InvalidRequest => -32600,
244 ErrorCode::MethodNotFound => -32601,
245 ErrorCode::InvalidParams => -32602,
246 ErrorCode::InternalError => -32603,
247 #[cfg(feature = "unstable_cancel_request")]
248 ErrorCode::RequestCancelled => -32800,
249 ErrorCode::AuthRequired => -32000,
250 ErrorCode::ResourceNotFound => -32002,
251 ErrorCode::Other(value) => value,
252 }
253 }
254}
255
256impl std::fmt::Debug for ErrorCode {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 write!(f, "{}: {self}", i32::from(*self))
259 }
260}
261
262fn error_code_transform(schema: &mut Schema) {
263 let name = schema
264 .get("const")
265 .expect("Unexpected schema for ErrorCode")
266 .as_str()
267 .expect("unexpected type for schema");
268 let code = match name {
269 "ParseError" => ErrorCode::ParseError,
270 "InvalidRequest" => ErrorCode::InvalidRequest,
271 "MethodNotFound" => ErrorCode::MethodNotFound,
272 "InvalidParams" => ErrorCode::InvalidParams,
273 "InternalError" => ErrorCode::InternalError,
274 #[cfg(feature = "unstable_cancel_request")]
275 "RequestCancelled" => ErrorCode::RequestCancelled,
276 "AuthRequired" => ErrorCode::AuthRequired,
277 "ResourceNotFound" => ErrorCode::ResourceNotFound,
278 _ => panic!("Unexpected error code name {name}"),
279 };
280 let mut description = schema
281 .get("description")
282 .expect("Missing description")
283 .as_str()
284 .expect("Unexpected type for description")
285 .to_owned();
286 schema.insert("title".into(), code.to_string().into());
287 description.insert_str(0, &format!("**{code}**: "));
288 schema.insert("description".into(), description.into());
289 schema.insert("const".into(), i32::from(code).into());
290 schema.insert("type".into(), "integer".into());
291 schema.insert("format".into(), "int32".into());
292}
293
294impl From<ErrorCode> for Error {
295 fn from(error_code: ErrorCode) -> Self {
296 Error::new(error_code.into(), error_code.to_string())
297 }
298}
299
300impl std::error::Error for Error {}
301
302impl Display for Error {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 if self.message.is_empty() {
305 write!(f, "{}", i32::from(self.code))?;
306 } else {
307 write!(f, "{}", self.message)?;
308 }
309
310 if let Some(data) = &self.data {
311 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
312 write!(f, ": {pretty}")?;
313 }
314
315 #[cfg(feature = "unstable_auth_methods")]
316 if !self.auth_methods.is_empty() {
317 let ids: Vec<&str> = self
318 .auth_methods
319 .iter()
320 .map(|m| m.id().0.as_ref())
321 .collect();
322 write!(f, " (auth methods: {})", ids.join(", "))?;
323 }
324
325 Ok(())
326 }
327}
328
329impl From<anyhow::Error> for Error {
330 fn from(error: anyhow::Error) -> Self {
331 match error.downcast::<Self>() {
332 Ok(error) => error,
333 Err(error) => Error::into_internal_error(&*error),
334 }
335 }
336}
337
338impl From<serde_json::Error> for Error {
339 fn from(error: serde_json::Error) -> Self {
340 Error::invalid_params().data(error.to_string())
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use strum::IntoEnumIterator;
347
348 use super::*;
349
350 #[test]
351 fn serialize_error_code() {
352 assert_eq!(
353 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
354 ErrorCode::ParseError
355 );
356 assert_eq!(
357 serde_json::to_value(ErrorCode::ParseError).unwrap(),
358 serde_json::json!(-32700)
359 );
360
361 assert_eq!(
362 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
363 ErrorCode::Other(1)
364 );
365 assert_eq!(
366 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
367 serde_json::json!(1)
368 );
369 }
370
371 #[test]
372 fn serialize_error_code_equality() {
373 let _schema = schemars::schema_for!(ErrorCode);
375 for error in ErrorCode::iter() {
376 assert_eq!(
377 error,
378 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
379 );
380 }
381 }
382
383 #[cfg(feature = "unstable_auth_methods")]
384 #[test]
385 fn serialize_error_auth_methods() {
386 use crate::{AuthMethod, AuthMethodAgent};
387
388 let err = Error::auth_required();
390 let json = serde_json::to_value(&err).unwrap();
391 assert!(
392 !json.as_object().unwrap().contains_key("authMethods"),
393 "authMethods should be omitted when empty"
394 );
395
396 let err = Error::auth_required().auth_methods(vec![AuthMethod::Agent(
398 AuthMethodAgent::new("api-key", "API Key"),
399 )]);
400 let json = serde_json::to_value(&err).unwrap();
401 assert!(
402 json.as_object().unwrap().contains_key("authMethods"),
403 "authMethods should be present"
404 );
405 assert_eq!(json["authMethods"].as_array().unwrap().len(), 1);
406 assert_eq!(json["authMethods"][0]["id"], "api-key");
407 assert_eq!(json["authMethods"][0]["name"], "API Key");
408
409 let deserialized: Error = serde_json::from_value(json).unwrap();
411 assert_eq!(deserialized.auth_methods.len(), 1);
412 assert_eq!(deserialized.auth_methods[0].id().0.as_ref(), "api-key");
413 }
414}