agent_client_protocol_schema/
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>;
22
23#[skip_serializing_none]
30#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
31#[non_exhaustive]
32pub struct Error {
33 pub code: ErrorCode,
36 pub message: String,
39 pub data: Option<serde_json::Value>,
42}
43
44impl Error {
45 #[must_use]
49 pub fn new(code: i32, message: impl Into<String>) -> Self {
50 Error {
51 code: code.into(),
52 message: message.into(),
53 data: None,
54 }
55 }
56
57 #[must_use]
62 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
63 self.data = data.into_option();
64 self
65 }
66
67 #[must_use]
69 pub fn parse_error() -> Self {
70 ErrorCode::ParseError.into()
71 }
72
73 #[must_use]
75 pub fn invalid_request() -> Self {
76 ErrorCode::InvalidRequest.into()
77 }
78
79 #[must_use]
81 pub fn method_not_found() -> Self {
82 ErrorCode::MethodNotFound.into()
83 }
84
85 #[must_use]
87 pub fn invalid_params() -> Self {
88 ErrorCode::InvalidParams.into()
89 }
90
91 #[must_use]
93 pub fn internal_error() -> Self {
94 ErrorCode::InternalError.into()
95 }
96
97 #[cfg(feature = "unstable_cancel_request")]
106 #[must_use]
107 pub fn request_cancelled() -> Self {
108 ErrorCode::RequestCancelled.into()
109 }
110
111 #[must_use]
113 pub fn auth_required() -> Self {
114 ErrorCode::AuthRequired.into()
115 }
116
117 #[cfg(feature = "unstable_elicitation")]
123 #[must_use]
124 pub fn url_elicitation_required() -> Self {
125 ErrorCode::UrlElicitationRequired.into()
126 }
127
128 #[must_use]
130 pub fn resource_not_found(uri: Option<String>) -> Self {
131 let err: Self = ErrorCode::ResourceNotFound.into();
132 if let Some(uri) = uri {
133 err.data(serde_json::json!({ "uri": uri }))
134 } else {
135 err
136 }
137 }
138
139 #[must_use]
143 pub fn into_internal_error(err: impl std::error::Error) -> Self {
144 Error::internal_error().data(err.to_string())
145 }
146}
147
148#[derive(Clone, Copy, Deserialize, Eq, JsonSchema, PartialEq, Serialize, strum::Display)]
153#[cfg_attr(test, derive(strum::EnumIter))]
154#[serde(from = "i32", into = "i32")]
155#[schemars(!from, !into)]
156#[non_exhaustive]
157pub enum ErrorCode {
158 #[schemars(transform = error_code_transform)]
162 #[strum(to_string = "Parse error")]
163 ParseError, #[schemars(transform = error_code_transform)]
166 #[strum(to_string = "Invalid request")]
167 InvalidRequest, #[schemars(transform = error_code_transform)]
170 #[strum(to_string = "Method not found")]
171 MethodNotFound, #[schemars(transform = error_code_transform)]
174 #[strum(to_string = "Invalid params")]
175 InvalidParams, #[schemars(transform = error_code_transform)]
179 #[strum(to_string = "Internal error")]
180 InternalError, #[cfg(feature = "unstable_cancel_request")]
182 #[schemars(transform = error_code_transform)]
189 #[strum(to_string = "Request cancelled")]
190 RequestCancelled, #[schemars(transform = error_code_transform)]
195 #[strum(to_string = "Authentication required")]
196 AuthRequired, #[schemars(transform = error_code_transform)]
199 #[strum(to_string = "Resource not found")]
200 ResourceNotFound, #[cfg(feature = "unstable_elicitation")]
202 #[schemars(transform = error_code_transform)]
208 #[strum(to_string = "URL elicitation required")]
209 UrlElicitationRequired, #[schemars(untagged)]
213 #[strum(to_string = "Unknown error")]
214 Other(i32),
215}
216
217impl From<i32> for ErrorCode {
218 fn from(value: i32) -> Self {
219 match value {
220 -32700 => ErrorCode::ParseError,
221 -32600 => ErrorCode::InvalidRequest,
222 -32601 => ErrorCode::MethodNotFound,
223 -32602 => ErrorCode::InvalidParams,
224 -32603 => ErrorCode::InternalError,
225 #[cfg(feature = "unstable_cancel_request")]
226 -32800 => ErrorCode::RequestCancelled,
227 -32000 => ErrorCode::AuthRequired,
228 -32002 => ErrorCode::ResourceNotFound,
229 #[cfg(feature = "unstable_elicitation")]
230 -32042 => ErrorCode::UrlElicitationRequired,
231 _ => ErrorCode::Other(value),
232 }
233 }
234}
235
236impl From<ErrorCode> for i32 {
237 fn from(value: ErrorCode) -> Self {
238 match value {
239 ErrorCode::ParseError => -32700,
240 ErrorCode::InvalidRequest => -32600,
241 ErrorCode::MethodNotFound => -32601,
242 ErrorCode::InvalidParams => -32602,
243 ErrorCode::InternalError => -32603,
244 #[cfg(feature = "unstable_cancel_request")]
245 ErrorCode::RequestCancelled => -32800,
246 ErrorCode::AuthRequired => -32000,
247 ErrorCode::ResourceNotFound => -32002,
248 #[cfg(feature = "unstable_elicitation")]
249 ErrorCode::UrlElicitationRequired => -32042,
250 ErrorCode::Other(value) => value,
251 }
252 }
253}
254
255impl std::fmt::Debug for ErrorCode {
256 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 write!(f, "{}: {self}", i32::from(*self))
258 }
259}
260
261fn error_code_transform(schema: &mut Schema) {
262 let name = schema
263 .get("const")
264 .expect("Unexpected schema for ErrorCode")
265 .as_str()
266 .expect("unexpected type for schema");
267 let code = match name {
268 "ParseError" => ErrorCode::ParseError,
269 "InvalidRequest" => ErrorCode::InvalidRequest,
270 "MethodNotFound" => ErrorCode::MethodNotFound,
271 "InvalidParams" => ErrorCode::InvalidParams,
272 "InternalError" => ErrorCode::InternalError,
273 #[cfg(feature = "unstable_cancel_request")]
274 "RequestCancelled" => ErrorCode::RequestCancelled,
275 "AuthRequired" => ErrorCode::AuthRequired,
276 "ResourceNotFound" => ErrorCode::ResourceNotFound,
277 #[cfg(feature = "unstable_elicitation")]
278 "UrlElicitationRequired" => ErrorCode::UrlElicitationRequired,
279 _ => panic!("Unexpected error code name {name}"),
280 };
281 let mut description = schema
282 .get("description")
283 .expect("Missing description")
284 .as_str()
285 .expect("Unexpected type for description")
286 .to_owned();
287 schema.insert("title".into(), code.to_string().into());
288 description.insert_str(0, &format!("**{code}**: "));
289 schema.insert("description".into(), description.into());
290 schema.insert("const".into(), i32::from(code).into());
291 schema.insert("type".into(), "integer".into());
292 schema.insert("format".into(), "int32".into());
293}
294
295impl From<ErrorCode> for Error {
296 fn from(error_code: ErrorCode) -> Self {
297 Error::new(error_code.into(), error_code.to_string())
298 }
299}
300
301impl std::error::Error for Error {}
302
303impl Display for Error {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 if self.message.is_empty() {
306 write!(f, "{}", i32::from(self.code))?;
307 } else {
308 write!(f, "{}", self.message)?;
309 }
310
311 if let Some(data) = &self.data {
312 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
313 write!(f, ": {pretty}")?;
314 }
315
316 Ok(())
317 }
318}
319
320impl From<anyhow::Error> for Error {
321 fn from(error: anyhow::Error) -> Self {
322 match error.downcast::<Self>() {
323 Ok(error) => error,
324 Err(error) => Error::into_internal_error(&*error),
325 }
326 }
327}
328
329impl From<serde_json::Error> for Error {
330 fn from(error: serde_json::Error) -> Self {
331 Error::invalid_params().data(error.to_string())
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use strum::IntoEnumIterator;
338
339 use super::*;
340
341 #[test]
342 fn serialize_error_code() {
343 assert_eq!(
344 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
345 ErrorCode::ParseError
346 );
347 assert_eq!(
348 serde_json::to_value(ErrorCode::ParseError).unwrap(),
349 serde_json::json!(-32700)
350 );
351
352 assert_eq!(
353 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
354 ErrorCode::Other(1)
355 );
356 assert_eq!(
357 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
358 serde_json::json!(1)
359 );
360 }
361
362 #[test]
363 fn serialize_error_code_equality() {
364 let _schema = schemars::schema_for!(ErrorCode);
366 for error in ErrorCode::iter() {
367 assert_eq!(
368 error,
369 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
370 );
371 }
372 }
373}