agent_client_protocol_schema/v1/
error.rs1use std::{fmt::Display, str};
14
15#[cfg(feature = "schemars")]
16use schemars::Schema;
17use serde::{Deserialize, Serialize};
18use serde_with::{DefaultOnError, serde_as, skip_serializing_none};
19
20use crate::IntoOption;
21
22pub type Result<T, E = Error> = std::result::Result<T, E>;
24
25#[serde_as]
32#[skip_serializing_none]
33#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
35#[non_exhaustive]
36pub struct Error {
37 pub code: ErrorCode,
40 pub message: String,
43 #[serde_as(deserialize_as = "DefaultOnError")]
46 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
47 #[serde(default)]
48 pub data: Option<serde_json::Value>,
49}
50
51impl Error {
52 #[must_use]
56 pub fn new(code: i32, message: impl Into<String>) -> Self {
57 Error {
58 code: code.into(),
59 message: message.into(),
60 data: None,
61 }
62 }
63
64 #[must_use]
69 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
70 self.data = data.into_option();
71 self
72 }
73
74 #[must_use]
76 pub fn parse_error() -> Self {
77 ErrorCode::ParseError.into()
78 }
79
80 #[must_use]
82 pub fn invalid_request() -> Self {
83 ErrorCode::InvalidRequest.into()
84 }
85
86 #[must_use]
88 pub fn method_not_found() -> Self {
89 ErrorCode::MethodNotFound.into()
90 }
91
92 #[must_use]
94 pub fn invalid_params() -> Self {
95 ErrorCode::InvalidParams.into()
96 }
97
98 #[must_use]
100 pub fn internal_error() -> Self {
101 ErrorCode::InternalError.into()
102 }
103
104 #[must_use]
109 pub fn request_cancelled() -> Self {
110 ErrorCode::RequestCancelled.into()
111 }
112
113 #[must_use]
115 pub fn auth_required() -> Self {
116 ErrorCode::AuthRequired.into()
117 }
118
119 #[must_use]
121 pub fn resource_not_found(uri: Option<String>) -> Self {
122 let err: Self = ErrorCode::ResourceNotFound.into();
123 if let Some(uri) = uri {
124 err.data(serde_json::json!({ "uri": uri }))
125 } else {
126 err
127 }
128 }
129
130 #[must_use]
134 pub fn into_internal_error(err: impl std::error::Error) -> Self {
135 Error::internal_error().data(err.to_string())
136 }
137}
138
139#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
144#[derive(Clone, Copy, Deserialize, Eq, PartialEq, Serialize, strum::Display)]
145#[cfg_attr(test, derive(strum::EnumIter))]
146#[serde(from = "i32", into = "i32")]
147#[cfg_attr(feature = "schemars", schemars(!from, !into))]
148#[non_exhaustive]
149pub enum ErrorCode {
150 #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
154 #[strum(to_string = "Parse error")]
155 ParseError, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
158 #[strum(to_string = "Invalid request")]
159 InvalidRequest, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
162 #[strum(to_string = "Method not found")]
163 MethodNotFound, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
166 #[strum(to_string = "Invalid params")]
167 InvalidParams, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
171 #[strum(to_string = "Internal error")]
172 InternalError, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
176 #[strum(to_string = "Request cancelled")]
177 RequestCancelled, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
182 #[strum(to_string = "Authentication required")]
183 AuthRequired, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
186 #[strum(to_string = "Resource not found")]
187 ResourceNotFound, #[cfg_attr(feature = "schemars", schemars(untagged))]
190 #[strum(to_string = "Unknown error")]
191 Other(i32),
192}
193
194impl From<i32> for ErrorCode {
195 fn from(value: i32) -> Self {
196 match value {
197 -32700 => ErrorCode::ParseError,
198 -32600 => ErrorCode::InvalidRequest,
199 -32601 => ErrorCode::MethodNotFound,
200 -32602 => ErrorCode::InvalidParams,
201 -32603 => ErrorCode::InternalError,
202 -32800 => ErrorCode::RequestCancelled,
203 -32000 => ErrorCode::AuthRequired,
204 -32002 => ErrorCode::ResourceNotFound,
205 _ => ErrorCode::Other(value),
206 }
207 }
208}
209
210impl From<ErrorCode> for i32 {
211 fn from(value: ErrorCode) -> Self {
212 match value {
213 ErrorCode::ParseError => -32700,
214 ErrorCode::InvalidRequest => -32600,
215 ErrorCode::MethodNotFound => -32601,
216 ErrorCode::InvalidParams => -32602,
217 ErrorCode::InternalError => -32603,
218 ErrorCode::RequestCancelled => -32800,
219 ErrorCode::AuthRequired => -32000,
220 ErrorCode::ResourceNotFound => -32002,
221 ErrorCode::Other(value) => value,
222 }
223 }
224}
225
226impl std::fmt::Debug for ErrorCode {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 write!(f, "{}: {self}", i32::from(*self))
229 }
230}
231
232#[cfg(feature = "schemars")]
233fn error_code_transform(schema: &mut Schema) {
234 let name = schema
235 .get("const")
236 .expect("Unexpected schema for ErrorCode")
237 .as_str()
238 .expect("unexpected type for schema");
239 let code = match name {
240 "ParseError" => ErrorCode::ParseError,
241 "InvalidRequest" => ErrorCode::InvalidRequest,
242 "MethodNotFound" => ErrorCode::MethodNotFound,
243 "InvalidParams" => ErrorCode::InvalidParams,
244 "InternalError" => ErrorCode::InternalError,
245 "RequestCancelled" => ErrorCode::RequestCancelled,
246 "AuthRequired" => ErrorCode::AuthRequired,
247 "ResourceNotFound" => ErrorCode::ResourceNotFound,
248 _ => panic!("Unexpected error code name {name}"),
249 };
250 let mut description = schema
251 .get("description")
252 .expect("Missing description")
253 .as_str()
254 .expect("Unexpected type for description")
255 .to_owned();
256 schema.insert("title".into(), code.to_string().into());
257 description.insert_str(0, &format!("**{code}**: "));
258 schema.insert("description".into(), description.into());
259 schema.insert("const".into(), i32::from(code).into());
260 schema.insert("type".into(), "integer".into());
261 schema.insert("format".into(), "int32".into());
262}
263
264impl From<ErrorCode> for Error {
265 fn from(error_code: ErrorCode) -> Self {
266 Error::new(error_code.into(), error_code.to_string())
267 }
268}
269
270impl std::error::Error for Error {}
271
272impl Display for Error {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 if self.message.is_empty() {
275 write!(f, "{}", i32::from(self.code))?;
276 } else {
277 write!(f, "{}", self.message)?;
278 }
279
280 if let Some(data) = &self.data {
281 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
282 write!(f, ": {pretty}")?;
283 }
284
285 Ok(())
286 }
287}
288
289impl From<anyhow::Error> for Error {
290 fn from(error: anyhow::Error) -> Self {
291 match error.downcast::<Self>() {
292 Ok(error) => error,
293 Err(error) => Error::into_internal_error(&*error),
294 }
295 }
296}
297
298impl From<serde_json::Error> for Error {
299 fn from(error: serde_json::Error) -> Self {
300 Error::invalid_params().data(error.to_string())
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use strum::IntoEnumIterator;
307
308 use super::*;
309
310 #[test]
311 fn serialize_error_code() {
312 assert_eq!(
313 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
314 ErrorCode::ParseError
315 );
316 assert_eq!(
317 serde_json::to_value(ErrorCode::ParseError).unwrap(),
318 serde_json::json!(-32700)
319 );
320
321 assert_eq!(
322 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
323 ErrorCode::Other(1)
324 );
325 assert_eq!(
326 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
327 serde_json::json!(1)
328 );
329 }
330
331 #[test]
332 fn serialize_error_code_equality() {
333 #[cfg(feature = "schemars")]
335 let _schema = schemars::schema_for!(ErrorCode);
336 for error in ErrorCode::iter() {
337 assert_eq!(
338 error,
339 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
340 );
341 }
342 }
343}