agent_client_protocol_schema/v2/
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, ProtocolVersion};
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")]
48 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
49 #[serde(default)]
50 pub data: Option<serde_json::Value>,
51}
52
53impl Error {
54 #[must_use]
58 pub fn new(code: i32, message: impl Into<String>) -> Self {
59 Error {
60 code: code.into(),
61 message: message.into(),
62 data: None,
63 }
64 }
65
66 #[must_use]
71 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
72 self.data = data.into_option();
73 self
74 }
75
76 #[must_use]
78 pub fn parse_error() -> Self {
79 ErrorCode::ParseError.into()
80 }
81
82 #[must_use]
84 pub fn invalid_request() -> Self {
85 ErrorCode::InvalidRequest.into()
86 }
87
88 #[must_use]
90 pub fn method_not_found() -> Self {
91 ErrorCode::MethodNotFound.into()
92 }
93
94 #[must_use]
96 pub fn invalid_params() -> Self {
97 ErrorCode::InvalidParams.into()
98 }
99
100 #[must_use]
102 pub fn internal_error() -> Self {
103 ErrorCode::InternalError.into()
104 }
105
106 #[must_use]
111 pub fn request_cancelled() -> Self {
112 ErrorCode::RequestCancelled.into()
113 }
114
115 #[must_use]
117 pub fn auth_required() -> Self {
118 ErrorCode::AuthRequired.into()
119 }
120
121 #[must_use]
123 pub fn resource_not_found(uri: Option<String>) -> Self {
124 let err: Self = ErrorCode::ResourceNotFound.into();
125 if let Some(uri) = uri {
126 err.data(serde_json::json!({ "uri": uri }))
127 } else {
128 err
129 }
130 }
131
132 #[must_use]
134 pub fn session_not_found(session_id: impl Into<super::SessionId>) -> Self {
135 Error::from(ErrorCode::ResourceNotFound)
136 .data(serde_json::json!({ "sessionId": session_id.into() }))
137 }
138
139 #[must_use]
141 pub fn unsupported_protocol_version(
142 requested: ProtocolVersion,
143 supported: &[ProtocolVersion],
144 ) -> Self {
145 let error: Self = ErrorCode::UnsupportedProtocolVersion.into();
146 error.data(serde_json::json!({ "requested": requested, "supported": supported }))
147 }
148
149 #[must_use]
153 pub fn into_internal_error(err: impl std::error::Error) -> Self {
154 Error::internal_error().data(err.to_string())
155 }
156}
157
158#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
164#[derive(Clone, Copy, Deserialize, Eq, PartialEq, Serialize, strum::Display)]
165#[cfg_attr(test, derive(strum::EnumIter))]
166#[serde(from = "i32", into = "i32")]
167#[cfg_attr(feature = "schemars", schemars(!from, !into))]
168#[non_exhaustive]
169pub enum ErrorCode {
170 #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
174 #[strum(to_string = "Parse error")]
175 ParseError, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
178 #[strum(to_string = "Invalid request")]
179 InvalidRequest, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
182 #[strum(to_string = "Method not found")]
183 MethodNotFound, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
186 #[strum(to_string = "Invalid params")]
187 InvalidParams, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
190 #[strum(to_string = "Internal error")]
191 InternalError, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
196 #[strum(to_string = "Request cancelled")]
197 RequestCancelled, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
202 #[strum(to_string = "Authentication required")]
203 AuthRequired, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
206 #[strum(to_string = "Resource not found")]
207 ResourceNotFound, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
211 #[strum(to_string = "Unsupported protocol version")]
212 UnsupportedProtocolVersion, #[cfg_attr(feature = "schemars", schemars(untagged))]
215 #[strum(to_string = "Unknown error")]
216 Other(i32),
217}
218
219impl From<i32> for ErrorCode {
220 fn from(value: i32) -> Self {
221 match value {
222 -32700 => ErrorCode::ParseError,
223 -32600 => ErrorCode::InvalidRequest,
224 -32601 => ErrorCode::MethodNotFound,
225 -32602 => ErrorCode::InvalidParams,
226 -32603 => ErrorCode::InternalError,
227 -32800 => ErrorCode::RequestCancelled,
228 -32000 => ErrorCode::AuthRequired,
229 -32002 => ErrorCode::ResourceNotFound,
230 -32022 => ErrorCode::UnsupportedProtocolVersion,
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 ErrorCode::RequestCancelled => -32800,
245 ErrorCode::AuthRequired => -32000,
246 ErrorCode::ResourceNotFound => -32002,
247 ErrorCode::UnsupportedProtocolVersion => -32022,
248 ErrorCode::Other(value) => value,
249 }
250 }
251}
252
253impl std::fmt::Debug for ErrorCode {
254 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255 write!(f, "{}: {self}", i32::from(*self))
256 }
257}
258
259#[cfg(feature = "schemars")]
260fn error_code_transform(schema: &mut Schema) {
261 let name = schema
262 .get("const")
263 .expect("Unexpected schema for ErrorCode")
264 .as_str()
265 .expect("unexpected type for schema");
266 let code = match name {
267 "ParseError" => ErrorCode::ParseError,
268 "InvalidRequest" => ErrorCode::InvalidRequest,
269 "MethodNotFound" => ErrorCode::MethodNotFound,
270 "InvalidParams" => ErrorCode::InvalidParams,
271 "InternalError" => ErrorCode::InternalError,
272 "RequestCancelled" => ErrorCode::RequestCancelled,
273 "AuthRequired" => ErrorCode::AuthRequired,
274 "ResourceNotFound" => ErrorCode::ResourceNotFound,
275 "UnsupportedProtocolVersion" => ErrorCode::UnsupportedProtocolVersion,
276 _ => panic!("Unexpected error code name {name}"),
277 };
278 let mut description = schema
279 .get("description")
280 .expect("Missing description")
281 .as_str()
282 .expect("Unexpected type for description")
283 .to_owned();
284 schema.insert("title".into(), code.to_string().into());
285 description.insert_str(0, &format!("**{code}**: "));
286 schema.insert("description".into(), description.into());
287 schema.insert("const".into(), i32::from(code).into());
288 schema.insert("type".into(), "integer".into());
289 schema.insert("format".into(), "int32".into());
290}
291
292impl From<ErrorCode> for Error {
293 fn from(error_code: ErrorCode) -> Self {
294 Error::new(error_code.into(), error_code.to_string())
295 }
296}
297
298impl std::error::Error for Error {}
299
300impl Display for Error {
301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302 if self.message.is_empty() {
303 write!(f, "{}", i32::from(self.code))?;
304 } else {
305 write!(f, "{}", self.message)?;
306 }
307
308 if let Some(data) = &self.data {
309 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
310 write!(f, ": {pretty}")?;
311 }
312
313 Ok(())
314 }
315}
316
317impl From<anyhow::Error> for Error {
318 fn from(error: anyhow::Error) -> Self {
319 match error.downcast::<Self>() {
320 Ok(error) => error,
321 Err(error) => Error::into_internal_error(&*error),
322 }
323 }
324}
325
326impl From<serde_json::Error> for Error {
327 fn from(error: serde_json::Error) -> Self {
328 Error::invalid_params().data(error.to_string())
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use strum::IntoEnumIterator;
335
336 use super::*;
337
338 #[test]
339 fn unsupported_protocol_version_includes_requested_and_supported() {
340 let error = Error::unsupported_protocol_version(
341 ProtocolVersion::V1,
342 &[ProtocolVersion::from(2_u16)],
343 );
344 assert_eq!(
345 serde_json::to_value(error).unwrap(),
346 serde_json::json!({
347 "code": -32022,
348 "message": "Unsupported protocol version",
349 "data": { "requested": 1, "supported": [2] },
350 })
351 );
352 assert_eq!(
353 Error::unsupported_protocol_version(ProtocolVersion::V1, &[]).data,
354 Some(serde_json::json!({ "requested": 1, "supported": [] }))
355 );
356 }
357
358 #[test]
359 fn serialize_error_code() {
360 assert_eq!(
361 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
362 ErrorCode::ParseError
363 );
364 assert_eq!(
365 serde_json::to_value(ErrorCode::ParseError).unwrap(),
366 serde_json::json!(-32700)
367 );
368
369 assert_eq!(
370 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
371 ErrorCode::Other(1)
372 );
373 assert_eq!(
374 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
375 serde_json::json!(1)
376 );
377 }
378
379 #[test]
380 fn serialize_error_code_equality() {
381 #[cfg(feature = "schemars")]
383 let _schema = schemars::schema_for!(ErrorCode);
384 for error in ErrorCode::iter() {
385 assert_eq!(
386 error,
387 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
388 );
389 }
390 }
391}