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 #[must_use]
118 pub fn resource_not_found(uri: Option<String>) -> Self {
119 let err: Self = ErrorCode::ResourceNotFound.into();
120 if let Some(uri) = uri {
121 err.data(serde_json::json!({ "uri": uri }))
122 } else {
123 err
124 }
125 }
126
127 #[must_use]
131 pub fn into_internal_error(err: impl std::error::Error) -> Self {
132 Error::internal_error().data(err.to_string())
133 }
134}
135
136#[derive(Clone, Copy, Deserialize, Eq, JsonSchema, PartialEq, Serialize, strum::Display)]
141#[cfg_attr(test, derive(strum::EnumIter))]
142#[serde(from = "i32", into = "i32")]
143#[schemars(!from, !into)]
144#[non_exhaustive]
145pub enum ErrorCode {
146 #[schemars(transform = error_code_transform)]
150 #[strum(to_string = "Parse error")]
151 ParseError, #[schemars(transform = error_code_transform)]
154 #[strum(to_string = "Invalid request")]
155 InvalidRequest, #[schemars(transform = error_code_transform)]
158 #[strum(to_string = "Method not found")]
159 MethodNotFound, #[schemars(transform = error_code_transform)]
162 #[strum(to_string = "Invalid params")]
163 InvalidParams, #[schemars(transform = error_code_transform)]
167 #[strum(to_string = "Internal error")]
168 InternalError, #[cfg(feature = "unstable_cancel_request")]
170 #[schemars(transform = error_code_transform)]
177 #[strum(to_string = "Request cancelled")]
178 RequestCancelled, #[schemars(transform = error_code_transform)]
183 #[strum(to_string = "Authentication required")]
184 AuthRequired, #[schemars(transform = error_code_transform)]
187 #[strum(to_string = "Resource not found")]
188 ResourceNotFound, #[schemars(untagged)]
192 #[strum(to_string = "Unknown error")]
193 Other(i32),
194}
195
196impl From<i32> for ErrorCode {
197 fn from(value: i32) -> Self {
198 match value {
199 -32700 => ErrorCode::ParseError,
200 -32600 => ErrorCode::InvalidRequest,
201 -32601 => ErrorCode::MethodNotFound,
202 -32602 => ErrorCode::InvalidParams,
203 -32603 => ErrorCode::InternalError,
204 #[cfg(feature = "unstable_cancel_request")]
205 -32800 => ErrorCode::RequestCancelled,
206 -32000 => ErrorCode::AuthRequired,
207 -32002 => ErrorCode::ResourceNotFound,
208 _ => ErrorCode::Other(value),
209 }
210 }
211}
212
213impl From<ErrorCode> for i32 {
214 fn from(value: ErrorCode) -> Self {
215 match value {
216 ErrorCode::ParseError => -32700,
217 ErrorCode::InvalidRequest => -32600,
218 ErrorCode::MethodNotFound => -32601,
219 ErrorCode::InvalidParams => -32602,
220 ErrorCode::InternalError => -32603,
221 #[cfg(feature = "unstable_cancel_request")]
222 ErrorCode::RequestCancelled => -32800,
223 ErrorCode::AuthRequired => -32000,
224 ErrorCode::ResourceNotFound => -32002,
225 ErrorCode::Other(value) => value,
226 }
227 }
228}
229
230impl std::fmt::Debug for ErrorCode {
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232 write!(f, "{}: {self}", i32::from(*self))
233 }
234}
235
236fn error_code_transform(schema: &mut Schema) {
237 let name = schema
238 .get("const")
239 .expect("Unexpected schema for ErrorCode")
240 .as_str()
241 .expect("unexpected type for schema");
242 let code = match name {
243 "ParseError" => ErrorCode::ParseError,
244 "InvalidRequest" => ErrorCode::InvalidRequest,
245 "MethodNotFound" => ErrorCode::MethodNotFound,
246 "InvalidParams" => ErrorCode::InvalidParams,
247 "InternalError" => ErrorCode::InternalError,
248 #[cfg(feature = "unstable_cancel_request")]
249 "RequestCancelled" => ErrorCode::RequestCancelled,
250 "AuthRequired" => ErrorCode::AuthRequired,
251 "ResourceNotFound" => ErrorCode::ResourceNotFound,
252 _ => panic!("Unexpected error code name {name}"),
253 };
254 let mut description = schema
255 .get("description")
256 .expect("Missing description")
257 .as_str()
258 .expect("Unexpected type for description")
259 .to_owned();
260 schema.insert("title".into(), code.to_string().into());
261 description.insert_str(0, &format!("**{code}**: "));
262 schema.insert("description".into(), description.into());
263 schema.insert("const".into(), i32::from(code).into());
264 schema.insert("type".into(), "integer".into());
265 schema.insert("format".into(), "int32".into());
266}
267
268impl From<ErrorCode> for Error {
269 fn from(error_code: ErrorCode) -> Self {
270 Error::new(error_code.into(), error_code.to_string())
271 }
272}
273
274impl std::error::Error for Error {}
275
276impl Display for Error {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 if self.message.is_empty() {
279 write!(f, "{}", i32::from(self.code))?;
280 } else {
281 write!(f, "{}", self.message)?;
282 }
283
284 if let Some(data) = &self.data {
285 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
286 write!(f, ": {pretty}")?;
287 }
288
289 Ok(())
290 }
291}
292
293impl From<anyhow::Error> for Error {
294 fn from(error: anyhow::Error) -> Self {
295 match error.downcast::<Self>() {
296 Ok(error) => error,
297 Err(error) => Error::into_internal_error(&*error),
298 }
299 }
300}
301
302impl From<serde_json::Error> for Error {
303 fn from(error: serde_json::Error) -> Self {
304 Error::invalid_params().data(error.to_string())
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use strum::IntoEnumIterator;
311
312 use super::*;
313
314 #[test]
315 fn serialize_error_code() {
316 assert_eq!(
317 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
318 ErrorCode::ParseError
319 );
320 assert_eq!(
321 serde_json::to_value(ErrorCode::ParseError).unwrap(),
322 serde_json::json!(-32700)
323 );
324
325 assert_eq!(
326 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
327 ErrorCode::Other(1)
328 );
329 assert_eq!(
330 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
331 serde_json::json!(1)
332 );
333 }
334
335 #[test]
336 fn serialize_error_code_equality() {
337 let _schema = schemars::schema_for!(ErrorCode);
339 for error in ErrorCode::iter() {
340 assert_eq!(
341 error,
342 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
343 );
344 }
345 }
346}