agent_client_protocol_schema/v2/
error.rs1use std::{fmt::Display, str};
14
15use schemars::{JsonSchema, Schema};
16use serde::{Deserialize, Serialize};
17use serde_with::{DefaultOnError, serde_as, skip_serializing_none};
18
19use crate::IntoOption;
20
21pub type Result<T, E = Error> = std::result::Result<T, E>;
23
24#[serde_as]
31#[skip_serializing_none]
32#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
33#[non_exhaustive]
34pub struct Error {
35 pub code: ErrorCode,
38 pub message: String,
41 #[serde_as(deserialize_as = "DefaultOnError")]
44 #[schemars(extend("x-deserialize-default-on-error" = true))]
45 #[serde(default)]
46 pub data: Option<serde_json::Value>,
47}
48
49impl Error {
50 #[must_use]
54 pub fn new(code: i32, message: impl Into<String>) -> Self {
55 Error {
56 code: code.into(),
57 message: message.into(),
58 data: None,
59 }
60 }
61
62 #[must_use]
67 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
68 self.data = data.into_option();
69 self
70 }
71
72 #[must_use]
74 pub fn parse_error() -> Self {
75 ErrorCode::ParseError.into()
76 }
77
78 #[must_use]
80 pub fn invalid_request() -> Self {
81 ErrorCode::InvalidRequest.into()
82 }
83
84 #[must_use]
86 pub fn method_not_found() -> Self {
87 ErrorCode::MethodNotFound.into()
88 }
89
90 #[must_use]
92 pub fn invalid_params() -> Self {
93 ErrorCode::InvalidParams.into()
94 }
95
96 #[must_use]
98 pub fn internal_error() -> Self {
99 ErrorCode::InternalError.into()
100 }
101
102 #[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, #[schemars(transform = error_code_transform)]
184 #[strum(to_string = "Request cancelled")]
185 RequestCancelled, #[schemars(transform = error_code_transform)]
190 #[strum(to_string = "Authentication required")]
191 AuthRequired, #[schemars(transform = error_code_transform)]
194 #[strum(to_string = "Resource not found")]
195 ResourceNotFound, #[cfg(feature = "unstable_elicitation")]
197 #[schemars(transform = error_code_transform)]
203 #[strum(to_string = "URL elicitation required")]
204 UrlElicitationRequired, #[schemars(untagged)]
208 #[strum(to_string = "Unknown error")]
209 Other(i32),
210}
211
212impl From<i32> for ErrorCode {
213 fn from(value: i32) -> Self {
214 match value {
215 -32700 => ErrorCode::ParseError,
216 -32600 => ErrorCode::InvalidRequest,
217 -32601 => ErrorCode::MethodNotFound,
218 -32602 => ErrorCode::InvalidParams,
219 -32603 => ErrorCode::InternalError,
220 -32800 => ErrorCode::RequestCancelled,
221 -32000 => ErrorCode::AuthRequired,
222 -32002 => ErrorCode::ResourceNotFound,
223 #[cfg(feature = "unstable_elicitation")]
224 -32042 => ErrorCode::UrlElicitationRequired,
225 _ => ErrorCode::Other(value),
226 }
227 }
228}
229
230impl From<ErrorCode> for i32 {
231 fn from(value: ErrorCode) -> Self {
232 match value {
233 ErrorCode::ParseError => -32700,
234 ErrorCode::InvalidRequest => -32600,
235 ErrorCode::MethodNotFound => -32601,
236 ErrorCode::InvalidParams => -32602,
237 ErrorCode::InternalError => -32603,
238 ErrorCode::RequestCancelled => -32800,
239 ErrorCode::AuthRequired => -32000,
240 ErrorCode::ResourceNotFound => -32002,
241 #[cfg(feature = "unstable_elicitation")]
242 ErrorCode::UrlElicitationRequired => -32042,
243 ErrorCode::Other(value) => value,
244 }
245 }
246}
247
248impl std::fmt::Debug for ErrorCode {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 write!(f, "{}: {self}", i32::from(*self))
251 }
252}
253
254fn error_code_transform(schema: &mut Schema) {
255 let name = schema
256 .get("const")
257 .expect("Unexpected schema for ErrorCode")
258 .as_str()
259 .expect("unexpected type for schema");
260 let code = match name {
261 "ParseError" => ErrorCode::ParseError,
262 "InvalidRequest" => ErrorCode::InvalidRequest,
263 "MethodNotFound" => ErrorCode::MethodNotFound,
264 "InvalidParams" => ErrorCode::InvalidParams,
265 "InternalError" => ErrorCode::InternalError,
266 "RequestCancelled" => ErrorCode::RequestCancelled,
267 "AuthRequired" => ErrorCode::AuthRequired,
268 "ResourceNotFound" => ErrorCode::ResourceNotFound,
269 #[cfg(feature = "unstable_elicitation")]
270 "UrlElicitationRequired" => ErrorCode::UrlElicitationRequired,
271 _ => panic!("Unexpected error code name {name}"),
272 };
273 let mut description = schema
274 .get("description")
275 .expect("Missing description")
276 .as_str()
277 .expect("Unexpected type for description")
278 .to_owned();
279 schema.insert("title".into(), code.to_string().into());
280 description.insert_str(0, &format!("**{code}**: "));
281 schema.insert("description".into(), description.into());
282 schema.insert("const".into(), i32::from(code).into());
283 schema.insert("type".into(), "integer".into());
284 schema.insert("format".into(), "int32".into());
285}
286
287impl From<ErrorCode> for Error {
288 fn from(error_code: ErrorCode) -> Self {
289 Error::new(error_code.into(), error_code.to_string())
290 }
291}
292
293impl std::error::Error for Error {}
294
295impl Display for Error {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 if self.message.is_empty() {
298 write!(f, "{}", i32::from(self.code))?;
299 } else {
300 write!(f, "{}", self.message)?;
301 }
302
303 if let Some(data) = &self.data {
304 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
305 write!(f, ": {pretty}")?;
306 }
307
308 Ok(())
309 }
310}
311
312impl From<anyhow::Error> for Error {
313 fn from(error: anyhow::Error) -> Self {
314 match error.downcast::<Self>() {
315 Ok(error) => error,
316 Err(error) => Error::into_internal_error(&*error),
317 }
318 }
319}
320
321impl From<serde_json::Error> for Error {
322 fn from(error: serde_json::Error) -> Self {
323 Error::invalid_params().data(error.to_string())
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use strum::IntoEnumIterator;
330
331 use super::*;
332
333 #[test]
334 fn serialize_error_code() {
335 assert_eq!(
336 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
337 ErrorCode::ParseError
338 );
339 assert_eq!(
340 serde_json::to_value(ErrorCode::ParseError).unwrap(),
341 serde_json::json!(-32700)
342 );
343
344 assert_eq!(
345 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
346 ErrorCode::Other(1)
347 );
348 assert_eq!(
349 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
350 serde_json::json!(1)
351 );
352 }
353
354 #[test]
355 fn serialize_error_code_equality() {
356 let _schema = schemars::schema_for!(ErrorCode);
358 for error in ErrorCode::iter() {
359 assert_eq!(
360 error,
361 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
362 );
363 }
364 }
365}