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 super::{Meta, SessionId};
21use crate::{IntoOption, ProtocolVersion};
22
23pub type Result<T, E = Error> = std::result::Result<T, E>;
25
26#[serde_as]
33#[skip_serializing_none]
34#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
36#[non_exhaustive]
37pub struct Error {
38 pub code: ErrorCode,
41 pub message: String,
44 #[serde_as(deserialize_as = "DefaultOnError")]
51 #[cfg_attr(feature = "schemars", schemars(with = "Option<ErrorData>", extend("x-deserialize-default-on-error" = true)))]
52 #[serde(default)]
53 pub data: Option<serde_json::Value>,
54}
55
56#[cfg(feature = "schemars")]
58#[derive(schemars::JsonSchema)]
59#[serde(untagged)]
60#[expect(
61 dead_code,
62 reason = "Only used to describe the schema of `Error::data`"
63)]
64enum ErrorData {
65 UnsupportedProtocolVersion(UnsupportedProtocolVersionErrorData),
66 SessionNotFound(SessionNotFoundErrorData),
67 Other(serde_json::Value),
68}
69
70#[serde_as]
74#[skip_serializing_none]
75#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
77#[serde(rename_all = "camelCase")]
78#[non_exhaustive]
79pub struct UnsupportedProtocolVersionErrorData {
80 pub requested: ProtocolVersion,
82 pub supported: Vec<ProtocolVersion>,
84 #[serde_as(deserialize_as = "DefaultOnError")]
90 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
91 #[serde(default, rename = "_meta")]
92 pub meta: Option<Meta>,
93}
94
95impl UnsupportedProtocolVersionErrorData {
96 #[must_use]
98 pub fn new(requested: ProtocolVersion, supported: Vec<ProtocolVersion>) -> Self {
99 Self {
100 requested,
101 supported,
102 meta: None,
103 }
104 }
105
106 #[must_use]
108 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
109 self.meta = meta.into_option();
110 self
111 }
112}
113
114#[serde_as]
119#[skip_serializing_none]
120#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
122#[serde(rename_all = "camelCase")]
123#[non_exhaustive]
124pub struct SessionNotFoundErrorData {
125 pub session_id: SessionId,
127 #[serde_as(deserialize_as = "DefaultOnError")]
133 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
134 #[serde(default, rename = "_meta")]
135 pub meta: Option<Meta>,
136}
137
138impl SessionNotFoundErrorData {
139 #[must_use]
141 pub fn new(session_id: impl Into<SessionId>) -> Self {
142 Self {
143 session_id: session_id.into(),
144 meta: None,
145 }
146 }
147
148 #[must_use]
150 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
151 self.meta = meta.into_option();
152 self
153 }
154}
155
156impl Error {
157 #[must_use]
161 pub fn new(code: i32, message: impl Into<String>) -> Self {
162 Error {
163 code: code.into(),
164 message: message.into(),
165 data: None,
166 }
167 }
168
169 #[must_use]
174 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
175 self.data = data.into_option();
176 self
177 }
178
179 #[must_use]
181 pub fn parse_error() -> Self {
182 ErrorCode::ParseError.into()
183 }
184
185 #[must_use]
187 pub fn invalid_request() -> Self {
188 ErrorCode::InvalidRequest.into()
189 }
190
191 #[must_use]
193 pub fn method_not_found() -> Self {
194 ErrorCode::MethodNotFound.into()
195 }
196
197 #[must_use]
199 pub fn invalid_params() -> Self {
200 ErrorCode::InvalidParams.into()
201 }
202
203 #[must_use]
205 pub fn internal_error() -> Self {
206 ErrorCode::InternalError.into()
207 }
208
209 #[must_use]
214 pub fn request_cancelled() -> Self {
215 ErrorCode::RequestCancelled.into()
216 }
217
218 #[must_use]
220 pub fn auth_required() -> Self {
221 ErrorCode::AuthRequired.into()
222 }
223
224 #[must_use]
226 pub fn resource_not_found() -> Self {
227 ErrorCode::ResourceNotFound.into()
228 }
229
230 #[must_use]
232 pub fn session_not_found(session_id: impl Into<super::SessionId>) -> Self {
233 Error::from(ErrorCode::ResourceNotFound)
234 .data(serde_json::to_value(SessionNotFoundErrorData::new(session_id)).ok())
235 }
236
237 #[must_use]
239 pub fn unsupported_protocol_version(
240 requested: ProtocolVersion,
241 supported: &[ProtocolVersion],
242 ) -> Self {
243 let error: Self = ErrorCode::UnsupportedProtocolVersion.into();
244 error.data(
245 serde_json::to_value(UnsupportedProtocolVersionErrorData::new(
246 requested,
247 supported.to_vec(),
248 ))
249 .ok(),
250 )
251 }
252
253 #[must_use]
257 pub fn into_internal_error(err: impl std::error::Error) -> Self {
258 Error::internal_error().data(err.to_string())
259 }
260}
261
262#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
268#[derive(Clone, Copy, Deserialize, Eq, PartialEq, Serialize, strum::Display)]
269#[cfg_attr(test, derive(strum::EnumIter))]
270#[serde(from = "i32", into = "i32")]
271#[cfg_attr(feature = "schemars", schemars(!from, !into))]
272#[non_exhaustive]
273pub enum ErrorCode {
274 #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
278 #[strum(to_string = "Parse error")]
279 ParseError, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
282 #[strum(to_string = "Invalid request")]
283 InvalidRequest, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
286 #[strum(to_string = "Method not found")]
287 MethodNotFound, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
290 #[strum(to_string = "Invalid params")]
291 InvalidParams, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
294 #[strum(to_string = "Internal error")]
295 InternalError, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
300 #[strum(to_string = "Request cancelled")]
301 RequestCancelled, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
306 #[strum(to_string = "Authentication required")]
307 AuthRequired, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
310 #[strum(to_string = "Resource not found")]
311 ResourceNotFound, #[cfg_attr(feature = "schemars", schemars(transform = error_code_transform))]
315 #[strum(to_string = "Unsupported protocol version")]
316 UnsupportedProtocolVersion, #[cfg_attr(feature = "schemars", schemars(untagged))]
319 #[strum(to_string = "Unknown error")]
320 Other(i32),
321}
322
323impl From<i32> for ErrorCode {
324 fn from(value: i32) -> Self {
325 match value {
326 -32700 => ErrorCode::ParseError,
327 -32600 => ErrorCode::InvalidRequest,
328 -32601 => ErrorCode::MethodNotFound,
329 -32602 => ErrorCode::InvalidParams,
330 -32603 => ErrorCode::InternalError,
331 -32800 => ErrorCode::RequestCancelled,
332 -32000 => ErrorCode::AuthRequired,
333 -32002 => ErrorCode::ResourceNotFound,
334 -32022 => ErrorCode::UnsupportedProtocolVersion,
335 _ => ErrorCode::Other(value),
336 }
337 }
338}
339
340impl From<ErrorCode> for i32 {
341 fn from(value: ErrorCode) -> Self {
342 match value {
343 ErrorCode::ParseError => -32700,
344 ErrorCode::InvalidRequest => -32600,
345 ErrorCode::MethodNotFound => -32601,
346 ErrorCode::InvalidParams => -32602,
347 ErrorCode::InternalError => -32603,
348 ErrorCode::RequestCancelled => -32800,
349 ErrorCode::AuthRequired => -32000,
350 ErrorCode::ResourceNotFound => -32002,
351 ErrorCode::UnsupportedProtocolVersion => -32022,
352 ErrorCode::Other(value) => value,
353 }
354 }
355}
356
357impl std::fmt::Debug for ErrorCode {
358 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359 write!(f, "{}: {self}", i32::from(*self))
360 }
361}
362
363#[cfg(feature = "schemars")]
364fn error_code_transform(schema: &mut Schema) {
365 let name = schema
366 .get("const")
367 .expect("Unexpected schema for ErrorCode")
368 .as_str()
369 .expect("unexpected type for schema");
370 let code = match name {
371 "ParseError" => ErrorCode::ParseError,
372 "InvalidRequest" => ErrorCode::InvalidRequest,
373 "MethodNotFound" => ErrorCode::MethodNotFound,
374 "InvalidParams" => ErrorCode::InvalidParams,
375 "InternalError" => ErrorCode::InternalError,
376 "RequestCancelled" => ErrorCode::RequestCancelled,
377 "AuthRequired" => ErrorCode::AuthRequired,
378 "ResourceNotFound" => ErrorCode::ResourceNotFound,
379 "UnsupportedProtocolVersion" => ErrorCode::UnsupportedProtocolVersion,
380 _ => panic!("Unexpected error code name {name}"),
381 };
382 let mut description = schema
383 .get("description")
384 .expect("Missing description")
385 .as_str()
386 .expect("Unexpected type for description")
387 .to_owned();
388 schema.insert("title".into(), code.to_string().into());
389 description.insert_str(0, &format!("**{code}**: "));
390 schema.insert("description".into(), description.into());
391 schema.insert("const".into(), i32::from(code).into());
392 schema.insert("type".into(), "integer".into());
393 schema.insert("format".into(), "int32".into());
394}
395
396impl From<ErrorCode> for Error {
397 fn from(error_code: ErrorCode) -> Self {
398 Error::new(error_code.into(), error_code.to_string())
399 }
400}
401
402impl std::error::Error for Error {}
403
404impl Display for Error {
405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406 if self.message.is_empty() {
407 write!(f, "{}", i32::from(self.code))?;
408 } else {
409 write!(f, "{}", self.message)?;
410 }
411
412 if let Some(data) = &self.data {
413 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
414 write!(f, ": {pretty}")?;
415 }
416
417 Ok(())
418 }
419}
420
421impl From<anyhow::Error> for Error {
422 fn from(error: anyhow::Error) -> Self {
423 match error.downcast::<Self>() {
424 Ok(error) => error,
425 Err(error) => Error::into_internal_error(&*error),
426 }
427 }
428}
429
430impl From<serde_json::Error> for Error {
431 fn from(error: serde_json::Error) -> Self {
432 Error::invalid_params().data(error.to_string())
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use strum::IntoEnumIterator;
439
440 #[test]
441 fn helper_data_parses_as_typed_error_data() {
442 let error = Error::session_not_found("sess_1");
443 let data: SessionNotFoundErrorData = serde_json::from_value(error.data.unwrap()).unwrap();
444 assert_eq!(data, SessionNotFoundErrorData::new("sess_1"));
445
446 let error =
447 Error::unsupported_protocol_version(ProtocolVersion::V2, &[ProtocolVersion::V1]);
448 let data: UnsupportedProtocolVersionErrorData =
449 serde_json::from_value(error.data.unwrap()).unwrap();
450 assert_eq!(
451 data,
452 UnsupportedProtocolVersionErrorData::new(
453 ProtocolVersion::V2,
454 vec![ProtocolVersion::V1]
455 )
456 );
457 }
458
459 use super::*;
460
461 #[test]
462 fn unsupported_protocol_version_includes_requested_and_supported() {
463 let error = Error::unsupported_protocol_version(
464 ProtocolVersion::V1,
465 &[ProtocolVersion::from(2_u16)],
466 );
467 assert_eq!(
468 serde_json::to_value(error).unwrap(),
469 serde_json::json!({
470 "code": -32022,
471 "message": "Unsupported protocol version",
472 "data": { "requested": 1, "supported": [2] },
473 })
474 );
475 assert_eq!(
476 Error::unsupported_protocol_version(ProtocolVersion::V1, &[]).data,
477 Some(serde_json::json!({ "requested": 1, "supported": [] }))
478 );
479 }
480
481 #[test]
482 fn serialize_error_code() {
483 assert_eq!(
484 serde_json::from_value::<ErrorCode>(serde_json::json!(-32700)).unwrap(),
485 ErrorCode::ParseError
486 );
487 assert_eq!(
488 serde_json::to_value(ErrorCode::ParseError).unwrap(),
489 serde_json::json!(-32700)
490 );
491
492 assert_eq!(
493 serde_json::from_value::<ErrorCode>(serde_json::json!(1)).unwrap(),
494 ErrorCode::Other(1)
495 );
496 assert_eq!(
497 serde_json::to_value(ErrorCode::Other(1)).unwrap(),
498 serde_json::json!(1)
499 );
500 }
501
502 #[test]
503 fn serialize_error_code_equality() {
504 #[cfg(feature = "schemars")]
506 let _schema = schemars::schema_for!(ErrorCode);
507 for error in ErrorCode::iter() {
508 assert_eq!(
509 error,
510 serde_json::from_value(serde_json::to_value(error).unwrap()).unwrap()
511 );
512 }
513 }
514}