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}
42
43impl Error {
44 #[must_use]
48 pub fn new(code: i32, message: impl Into<String>) -> Self {
49 Error {
50 code: code.into(),
51 message: message.into(),
52 data: None,
53 }
54 }
55
56 #[must_use]
61 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
62 self.data = data.into_option();
63 self
64 }
65
66 #[must_use]
68 pub fn parse_error() -> Self {
69 ErrorCode::ParseError.into()
70 }
71
72 #[must_use]
74 pub fn invalid_request() -> Self {
75 ErrorCode::InvalidRequest.into()
76 }
77
78 #[must_use]
80 pub fn method_not_found() -> Self {
81 ErrorCode::MethodNotFound.into()
82 }
83
84 #[must_use]
86 pub fn invalid_params() -> Self {
87 ErrorCode::InvalidParams.into()
88 }
89
90 #[must_use]
92 pub fn internal_error() -> Self {
93 ErrorCode::InternalError.into()
94 }
95
96 #[cfg(feature = "unstable_cancel_request")]
105 #[must_use]
106 pub fn request_cancelled() -> Self {
107 ErrorCode::RequestCancelled.into()
108 }
109
110 #[must_use]
112 pub fn auth_required() -> Self {
113 ErrorCode::AuthRequired.into()
114 }
115
116 #[cfg(feature = "unstable_elicitation")]
122 #[must_use]
123 pub fn url_elicitation_required() -> Self {
124 ErrorCode::UrlElicitationRequired.into()
125 }
126
127 #[must_use]
129 pub fn resource_not_found(uri: Option<String>) -> Self {
130 let err: Self = ErrorCode::ResourceNotFound.into();
131 if let Some(uri) = uri {
132 err.data(serde_json::json!({ "uri": uri }))
133 } else {
134 err
135 }
136 }
137
138 #[must_use]
142 pub fn into_internal_error(err: impl std::error::Error) -> Self {
143 Error::internal_error().data(err.to_string())
144 }
145}
146
147#[derive(Clone, Copy, Deserialize, Eq, JsonSchema, PartialEq, Serialize, strum::Display)]
152#[cfg_attr(test, derive(strum::EnumIter))]
153#[serde(from = "i32", into = "i32")]
154#[schemars(!from, !into)]
155#[non_exhaustive]
156pub enum ErrorCode {
157 #[schemars(transform = error_code_transform)]
161 #[strum(to_string = "Parse error")]
162 ParseError, #[schemars(transform = error_code_transform)]
165 #[strum(to_string = "Invalid request")]
166 InvalidRequest, #[schemars(transform = error_code_transform)]
169 #[strum(to_string = "Method not found")]
170 MethodNotFound, #[schemars(transform = error_code_transform)]
173 #[strum(to_string = "Invalid params")]
174 InvalidParams, #[schemars(transform = error_code_transform)]
178 #[strum(to_string = "Internal error")]
179 InternalError, #[cfg(feature = "unstable_cancel_request")]
181 #[schemars(transform = error_code_transform)]
188 #[strum(to_string = "Request cancelled")]
189 RequestCancelled, #[schemars(transform = error_code_transform)]
194 #[strum(to_string = "Authentication required")]
195 AuthRequired, #[schemars(transform = error_code_transform)]
198 #[strum(to_string = "Resource not found")]
199 ResourceNotFound, #[cfg(feature = "unstable_elicitation")]
201 #[schemars(transform = error_code_transform)]
207 #[strum(to_string = "URL elicitation required")]
208 UrlElicitationRequired, #[schemars(untagged)]
212 #[strum(to_string = "Unknown error")]
213 Other(i32),
214}
215
216impl From<i32> for ErrorCode {
217 fn from(value: i32) -> Self {
218 match value {
219 -32700 => ErrorCode::ParseError,
220 -32600 => ErrorCode::InvalidRequest,
221 -32601 => ErrorCode::MethodNotFound,
222 -32602 => ErrorCode::InvalidParams,
223 -32603 => ErrorCode::InternalError,
224 #[cfg(feature = "unstable_cancel_request")]
225 -32800 => ErrorCode::RequestCancelled,
226 -32000 => ErrorCode::AuthRequired,
227 -32002 => ErrorCode::ResourceNotFound,
228 #[cfg(feature = "unstable_elicitation")]
229 -32042 => ErrorCode::UrlElicitationRequired,
230 _ => ErrorCode::Other(value),
231 }
232 }
233}
234
235impl From<ErrorCode> for i32 {
236 fn from(value: ErrorCode) -> Self {
237 match value {
238 ErrorCode::ParseError => -32700,
239 ErrorCode::InvalidRequest => -32600,
240 ErrorCode::MethodNotFound => -32601,
241 ErrorCode::InvalidParams => -32602,
242 ErrorCode::InternalError => -32603,
243 #[cfg(feature = "unstable_cancel_request")]
244 ErrorCode::RequestCancelled => -32800,
245 ErrorCode::AuthRequired => -32000,
246 ErrorCode::ResourceNotFound => -32002,
247 #[cfg(feature = "unstable_elicitation")]
248 ErrorCode::UrlElicitationRequired => -32042,
249 ErrorCode::Other(value) => value,
250 }
251 }
252}
253
254impl std::fmt::Debug for ErrorCode {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 write!(f, "{}: {self}", i32::from(*self))
257 }
258}
259
260fn error_code_transform(schema: &mut Schema) {
261 let name = schema
262 .get("const")
263 .expect("Unexpected schema for ErrorCode")
264 .as_str()
265 .expect("unexpected type for schema");
266 let code = match name {
267 "ParseError" => ErrorCode::ParseError,
268 "InvalidRequest" => ErrorCode::InvalidRequest,
269 "MethodNotFound" => ErrorCode::MethodNotFound,
270 "InvalidParams" => ErrorCode::InvalidParams,
271 "InternalError" => ErrorCode::InternalError,
272 #[cfg(feature = "unstable_cancel_request")]
273 "RequestCancelled" => ErrorCode::RequestCancelled,
274 "AuthRequired" => ErrorCode::AuthRequired,
275 "ResourceNotFound" => ErrorCode::ResourceNotFound,
276 #[cfg(feature = "unstable_elicitation")]
277 "UrlElicitationRequired" => ErrorCode::UrlElicitationRequired,
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 Ok(())
316 }
317}
318
319impl From<anyhow::Error> for Error {
320 fn from(error: anyhow::Error) -> Self {
321 match error.downcast::<Self>() {
322 Ok(error) => error,
323 Err(error) => Error::into_internal_error(&*error),
324 }
325 }
326}
327
328impl From<serde_json::Error> for Error {
329 fn from(error: serde_json::Error) -> Self {
330 Error::invalid_params().data(error.to_string())
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use strum::IntoEnumIterator;
337
338 use super::*;
339
340 #[test]
341 fn serialize_error_code() {
342 assert_eq!(
343 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
344 ErrorCode::ParseError
345 );
346 assert_eq!(
347 serde_json::to_value(ErrorCode::ParseError).unwrap(),
348 serde_json::json!(-32700)
349 );
350
351 assert_eq!(
352 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
353 ErrorCode::Other(1)
354 );
355 assert_eq!(
356 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
357 serde_json::json!(1)
358 );
359 }
360
361 #[test]
362 fn serialize_error_code_equality() {
363 let _schema = schemars::schema_for!(ErrorCode);
365 for error in ErrorCode::iter() {
366 assert_eq!(
367 error,
368 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
369 );
370 }
371 }
372}