agent_client_protocol_schema/v1/
error.rs1use std::{fmt::Display, str};
14
15use schemars::{JsonSchema, Schema};
16use serde::{Deserialize, Serialize};
17use serde_with::skip_serializing_none;
18
19use crate::IntoOption;
20
21pub type Result<T, E = Error> = std::result::Result<T, E>;
23
24#[skip_serializing_none]
31#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
32#[non_exhaustive]
33pub struct Error {
34 pub code: ErrorCode,
37 pub message: String,
40 pub data: Option<serde_json::Value>,
43}
44
45impl Error {
46 #[must_use]
50 pub fn new(code: i32, message: impl Into<String>) -> Self {
51 Error {
52 code: code.into(),
53 message: message.into(),
54 data: None,
55 }
56 }
57
58 #[must_use]
63 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
64 self.data = data.into_option();
65 self
66 }
67
68 #[must_use]
70 pub fn parse_error() -> Self {
71 ErrorCode::ParseError.into()
72 }
73
74 #[must_use]
76 pub fn invalid_request() -> Self {
77 ErrorCode::InvalidRequest.into()
78 }
79
80 #[must_use]
82 pub fn method_not_found() -> Self {
83 ErrorCode::MethodNotFound.into()
84 }
85
86 #[must_use]
88 pub fn invalid_params() -> Self {
89 ErrorCode::InvalidParams.into()
90 }
91
92 #[must_use]
94 pub fn internal_error() -> Self {
95 ErrorCode::InternalError.into()
96 }
97
98 #[cfg(feature = "unstable_cancel_request")]
107 #[must_use]
108 pub fn request_cancelled() -> Self {
109 ErrorCode::RequestCancelled.into()
110 }
111
112 #[must_use]
114 pub fn auth_required() -> Self {
115 ErrorCode::AuthRequired.into()
116 }
117
118 #[cfg(feature = "unstable_elicitation")]
124 #[must_use]
125 pub fn url_elicitation_required() -> Self {
126 ErrorCode::UrlElicitationRequired.into()
127 }
128
129 #[must_use]
131 pub fn resource_not_found(uri: Option<String>) -> Self {
132 let err: Self = ErrorCode::ResourceNotFound.into();
133 if let Some(uri) = uri {
134 err.data(serde_json::json!({ "uri": uri }))
135 } else {
136 err
137 }
138 }
139
140 #[must_use]
144 pub fn into_internal_error(err: impl std::error::Error) -> Self {
145 Error::internal_error().data(err.to_string())
146 }
147}
148
149#[derive(Clone, Copy, Deserialize, Eq, JsonSchema, PartialEq, Serialize, strum::Display)]
154#[cfg_attr(test, derive(strum::EnumIter))]
155#[serde(from = "i32", into = "i32")]
156#[schemars(!from, !into)]
157#[non_exhaustive]
158pub enum ErrorCode {
159 #[schemars(transform = error_code_transform)]
163 #[strum(to_string = "Parse error")]
164 ParseError, #[schemars(transform = error_code_transform)]
167 #[strum(to_string = "Invalid request")]
168 InvalidRequest, #[schemars(transform = error_code_transform)]
171 #[strum(to_string = "Method not found")]
172 MethodNotFound, #[schemars(transform = error_code_transform)]
175 #[strum(to_string = "Invalid params")]
176 InvalidParams, #[schemars(transform = error_code_transform)]
180 #[strum(to_string = "Internal error")]
181 InternalError, #[cfg(feature = "unstable_cancel_request")]
183 #[schemars(transform = error_code_transform)]
190 #[strum(to_string = "Request cancelled")]
191 RequestCancelled, #[schemars(transform = error_code_transform)]
196 #[strum(to_string = "Authentication required")]
197 AuthRequired, #[schemars(transform = error_code_transform)]
200 #[strum(to_string = "Resource not found")]
201 ResourceNotFound, #[cfg(feature = "unstable_elicitation")]
203 #[schemars(transform = error_code_transform)]
209 #[strum(to_string = "URL elicitation required")]
210 UrlElicitationRequired, #[schemars(untagged)]
214 #[strum(to_string = "Unknown error")]
215 Other(i32),
216}
217
218impl From<i32> for ErrorCode {
219 fn from(value: i32) -> Self {
220 match value {
221 -32700 => ErrorCode::ParseError,
222 -32600 => ErrorCode::InvalidRequest,
223 -32601 => ErrorCode::MethodNotFound,
224 -32602 => ErrorCode::InvalidParams,
225 -32603 => ErrorCode::InternalError,
226 #[cfg(feature = "unstable_cancel_request")]
227 -32800 => ErrorCode::RequestCancelled,
228 -32000 => ErrorCode::AuthRequired,
229 -32002 => ErrorCode::ResourceNotFound,
230 #[cfg(feature = "unstable_elicitation")]
231 -32042 => ErrorCode::UrlElicitationRequired,
232 _ => ErrorCode::Other(value),
233 }
234 }
235}
236
237impl From<ErrorCode> for i32 {
238 fn from(value: ErrorCode) -> Self {
239 match value {
240 ErrorCode::ParseError => -32700,
241 ErrorCode::InvalidRequest => -32600,
242 ErrorCode::MethodNotFound => -32601,
243 ErrorCode::InvalidParams => -32602,
244 ErrorCode::InternalError => -32603,
245 #[cfg(feature = "unstable_cancel_request")]
246 ErrorCode::RequestCancelled => -32800,
247 ErrorCode::AuthRequired => -32000,
248 ErrorCode::ResourceNotFound => -32002,
249 #[cfg(feature = "unstable_elicitation")]
250 ErrorCode::UrlElicitationRequired => -32042,
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 #[cfg(feature = "unstable_elicitation")]
279 "UrlElicitationRequired" => ErrorCode::UrlElicitationRequired,
280 _ => panic!("Unexpected error code name {name}"),
281 };
282 let mut description = schema
283 .get("description")
284 .expect("Missing description")
285 .as_str()
286 .expect("Unexpected type for description")
287 .to_owned();
288 schema.insert("title".into(), code.to_string().into());
289 description.insert_str(0, &format!("**{code}**: "));
290 schema.insert("description".into(), description.into());
291 schema.insert("const".into(), i32::from(code).into());
292 schema.insert("type".into(), "integer".into());
293 schema.insert("format".into(), "int32".into());
294}
295
296impl From<ErrorCode> for Error {
297 fn from(error_code: ErrorCode) -> Self {
298 Error::new(error_code.into(), error_code.to_string())
299 }
300}
301
302impl std::error::Error for Error {}
303
304impl Display for Error {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 if self.message.is_empty() {
307 write!(f, "{}", i32::from(self.code))?;
308 } else {
309 write!(f, "{}", self.message)?;
310 }
311
312 if let Some(data) = &self.data {
313 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
314 write!(f, ": {pretty}")?;
315 }
316
317 Ok(())
318 }
319}
320
321impl From<anyhow::Error> for Error {
322 fn from(error: anyhow::Error) -> Self {
323 match error.downcast::<Self>() {
324 Ok(error) => error,
325 Err(error) => Error::into_internal_error(&*error),
326 }
327 }
328}
329
330impl From<serde_json::Error> for Error {
331 fn from(error: serde_json::Error) -> Self {
332 Error::invalid_params().data(error.to_string())
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use strum::IntoEnumIterator;
339
340 use super::*;
341
342 #[test]
343 fn serialize_error_code() {
344 assert_eq!(
345 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
346 ErrorCode::ParseError
347 );
348 assert_eq!(
349 serde_json::to_value(ErrorCode::ParseError).unwrap(),
350 serde_json::json!(-32700)
351 );
352
353 assert_eq!(
354 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
355 ErrorCode::Other(1)
356 );
357 assert_eq!(
358 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
359 serde_json::json!(1)
360 );
361 }
362
363 #[test]
364 fn serialize_error_code_equality() {
365 let _schema = schemars::schema_for!(ErrorCode);
367 for error in ErrorCode::iter() {
368 assert_eq!(
369 error,
370 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
371 );
372 }
373 }
374}