1use std::fmt;
23
24use serde::{Deserialize, Serialize};
25
26#[cfg(feature = "unstable_protocol_v2")]
27use crate::v2::schema as v2;
28use crate::{schema::IntoOption, v1::schema as v1};
29
30pub type Result<T, E = Error> = std::result::Result<T, E>;
32
33#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
54#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
55#[serde(transparent)]
56#[repr(transparent)]
57pub struct ErrorCode(i32);
58
59impl ErrorCode {
60 pub const PARSE_ERROR: Self = Self(-32700);
62 pub const INVALID_REQUEST: Self = Self(-32600);
64 pub const METHOD_NOT_FOUND: Self = Self(-32601);
66 pub const INVALID_PARAMS: Self = Self(-32602);
68 pub const INTERNAL_ERROR: Self = Self(-32603);
70 pub const REQUEST_CANCELLED: Self = Self(-32800);
72 pub const AUTH_REQUIRED: Self = Self(-32000);
74 pub const RESOURCE_NOT_FOUND: Self = Self(-32002);
79
80 #[must_use]
82 pub const fn new(code: i32) -> Self {
83 Self(code)
84 }
85
86 #[must_use]
88 pub const fn value(self) -> i32 {
89 self.0
90 }
91
92 fn name(self) -> Option<&'static str> {
94 Some(match self {
95 Self::PARSE_ERROR => "Parse error",
96 Self::INVALID_REQUEST => "Invalid request",
97 Self::METHOD_NOT_FOUND => "Method not found",
98 Self::INVALID_PARAMS => "Invalid params",
99 Self::INTERNAL_ERROR => "Internal error",
100 Self::REQUEST_CANCELLED => "Request cancelled",
101 Self::AUTH_REQUIRED => "Authentication required",
102 Self::RESOURCE_NOT_FOUND => "Resource not found",
103 _ => return None,
104 })
105 }
106}
107
108impl fmt::Display for ErrorCode {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.write_str(self.name().unwrap_or("Unknown error"))
111 }
112}
113
114impl fmt::Debug for ErrorCode {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "{}: {self}", self.0)
117 }
118}
119
120impl From<i32> for ErrorCode {
121 fn from(code: i32) -> Self {
122 Self(code)
123 }
124}
125
126impl From<ErrorCode> for i32 {
127 fn from(code: ErrorCode) -> Self {
128 code.0
129 }
130}
131
132macro_rules! schema_error_code_conversions {
133 ($version:ident) => {
134 impl From<$version::ErrorCode> for ErrorCode {
135 fn from(code: $version::ErrorCode) -> Self {
136 Self(code.into())
137 }
138 }
139
140 impl From<ErrorCode> for $version::ErrorCode {
141 fn from(code: ErrorCode) -> Self {
142 code.0.into()
143 }
144 }
145
146 impl PartialEq<$version::ErrorCode> for ErrorCode {
147 fn eq(&self, other: &$version::ErrorCode) -> bool {
148 self.0 == i32::from(*other)
149 }
150 }
151
152 impl PartialEq<ErrorCode> for $version::ErrorCode {
153 fn eq(&self, other: &ErrorCode) -> bool {
154 i32::from(*self) == other.0
155 }
156 }
157
158 impl From<$version::Error> for Error {
159 fn from(error: $version::Error) -> Self {
160 Self {
161 code: error.code.into(),
162 message: error.message,
163 data: error.data,
164 origin: ErrorOrigin::Unmarked,
165 }
166 }
167 }
168
169 impl From<Error> for $version::Error {
170 fn from(error: Error) -> Self {
171 $version::Error::new(error.code.0, error.message).data(error.data)
172 }
173 }
174 };
175}
176
177schema_error_code_conversions!(v1);
178#[cfg(feature = "unstable_protocol_v2")]
179schema_error_code_conversions!(v2);
180
181#[derive(Clone, Eq, Serialize, Deserialize)]
221#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
222#[non_exhaustive]
223pub struct Error {
224 pub code: ErrorCode,
226 pub message: String,
228 #[serde(default, skip_serializing_if = "Option::is_none")]
233 pub data: Option<serde_json::Value>,
234 #[serde(skip)]
237 pub(crate) origin: ErrorOrigin,
238}
239
240#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
246pub(crate) enum ErrorOrigin {
247 #[default]
249 Unmarked,
250 IncomingTransportClosed,
252 ConnectionTerminated,
254}
255
256impl PartialEq for Error {
259 fn eq(&self, other: &Self) -> bool {
260 self.code == other.code && self.message == other.message && self.data == other.data
261 }
262}
263
264#[expect(
265 clippy::missing_fields_in_debug,
266 reason = "the origin marker is not part of the error's value"
267)]
268impl fmt::Debug for Error {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 f.debug_struct("Error")
271 .field("code", &self.code)
272 .field("message", &self.message)
273 .field("data", &self.data)
274 .finish()
275 }
276}
277
278impl Error {
279 #[must_use]
281 pub fn new(code: impl Into<ErrorCode>, message: impl Into<String>) -> Self {
282 Self {
283 code: code.into(),
284 message: message.into(),
285 data: None,
286 origin: ErrorOrigin::Unmarked,
287 }
288 }
289
290 #[must_use]
292 pub fn data(mut self, data: impl IntoOption<serde_json::Value>) -> Self {
293 self.data = data.into_option();
294 self
295 }
296
297 #[must_use]
299 pub fn parse_error() -> Self {
300 ErrorCode::PARSE_ERROR.into()
301 }
302
303 #[must_use]
305 pub fn invalid_request() -> Self {
306 ErrorCode::INVALID_REQUEST.into()
307 }
308
309 #[must_use]
311 pub fn method_not_found() -> Self {
312 ErrorCode::METHOD_NOT_FOUND.into()
313 }
314
315 #[must_use]
317 pub fn invalid_params() -> Self {
318 ErrorCode::INVALID_PARAMS.into()
319 }
320
321 #[must_use]
323 pub fn internal_error() -> Self {
324 ErrorCode::INTERNAL_ERROR.into()
325 }
326
327 #[must_use]
330 pub fn request_cancelled() -> Self {
331 ErrorCode::REQUEST_CANCELLED.into()
332 }
333
334 #[must_use]
336 pub fn auth_required() -> Self {
337 ErrorCode::AUTH_REQUIRED.into()
338 }
339
340 #[must_use]
343 pub fn into_internal_error(err: impl std::error::Error) -> Self {
344 Self::internal_error().data(err.to_string())
345 }
346}
347
348impl From<ErrorCode> for Error {
349 fn from(code: ErrorCode) -> Self {
351 Self::new(code, code.to_string())
352 }
353}
354
355impl std::error::Error for Error {}
356
357impl fmt::Display for Error {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 if self.message.is_empty() {
360 write!(f, "{}", self.code.0)?;
361 } else {
362 write!(f, "{}", self.message)?;
363 }
364
365 if let Some(data) = &self.data {
366 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
367 write!(f, ": {pretty}")?;
368 }
369
370 Ok(())
371 }
372}
373
374impl From<anyhow::Error> for Error {
375 fn from(error: anyhow::Error) -> Self {
378 let error = match error.downcast::<Self>() {
379 Ok(error) => return error,
380 Err(error) => error,
381 };
382 let error = match error.downcast::<v1::Error>() {
383 Ok(error) => return error.into(),
384 Err(error) => error,
385 };
386 #[cfg(feature = "unstable_protocol_v2")]
387 let error = match error.downcast::<v2::Error>() {
388 Ok(error) => return error.into(),
389 Err(error) => error,
390 };
391 Self::into_internal_error(&*error)
392 }
393}
394
395impl From<serde_json::Error> for Error {
396 fn from(error: serde_json::Error) -> Self {
398 Self::invalid_params().data(error.to_string())
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use serde_json::{Value, json};
405
406 use super::*;
407
408 fn sample_codes() -> [i32; 13] {
409 [
410 -32700,
411 -32600,
412 -32601,
413 -32602,
414 -32603,
415 -32800,
416 -32000,
417 -32002,
418 -32022,
419 0,
420 12345,
421 i32::MIN,
422 i32::MAX,
423 ]
424 }
425
426 fn sample_data() -> [Option<Value>; 6] {
427 [
428 None,
429 Some(Value::Null),
430 Some(json!("text")),
431 Some(json!(42)),
432 Some(json!([1, null, { "a": [] }])),
433 Some(json!({ "sessionId": "s", "_meta": { "x": true }, "extra": null })),
434 ]
435 }
436
437 fn samples() -> impl Iterator<Item = Error> {
438 sample_codes().into_iter().flat_map(|code| {
439 sample_data().into_iter().flat_map(move |data| {
440 ["", "message", "Unknown error"].map(|message| Error {
441 code: code.into(),
442 message: message.into(),
443 data: data.clone(),
444 origin: ErrorOrigin::Unmarked,
445 })
446 })
447 })
448 }
449
450 #[test]
451 fn v1_round_trip_is_lossless() {
452 for error in samples() {
453 let schema = v1::Error::from(error.clone());
454 assert_eq!(i32::from(schema.code), error.code.value());
455 assert_eq!(schema.message, error.message);
456 assert_eq!(schema.data, error.data);
457 assert_eq!(Error::from(schema.clone()), error);
458 assert_eq!(v1::Error::from(Error::from(schema.clone())), schema);
459 assert_eq!(error.to_string(), schema.to_string());
460 }
461 }
462
463 #[cfg(feature = "unstable_protocol_v2")]
464 #[test]
465 fn v2_round_trip_is_lossless() {
466 for error in samples() {
467 let schema = v2::Error::from(error.clone());
468 assert_eq!(i32::from(schema.code), error.code.value());
469 assert_eq!(schema.message, error.message);
470 assert_eq!(schema.data, error.data);
471 assert_eq!(Error::from(schema.clone()), error);
472 assert_eq!(v2::Error::from(Error::from(schema.clone())), schema);
473 assert_eq!(error.to_string(), schema.to_string());
474 }
475 }
476
477 #[cfg(feature = "unstable_protocol_v2")]
478 #[test]
479 fn v1_and_v2_convert_through_the_sdk_error() {
480 for error in samples() {
481 let v1 = v1::Error::from(error.clone());
482 let v2 = v2::Error::from(Error::from(v1));
483 assert_eq!(Error::from(v2), error);
484 }
485 }
486
487 #[test]
488 fn serializes_like_the_schema_errors() {
489 for error in samples() {
490 let json = serde_json::to_value(&error).unwrap();
491 assert_eq!(
492 json,
493 serde_json::to_value(v1::Error::from(error.clone())).unwrap()
494 );
495 #[cfg(feature = "unstable_protocol_v2")]
496 assert_eq!(
497 json,
498 serde_json::to_value(v2::Error::from(error.clone())).unwrap()
499 );
500 let parsed: Error = serde_json::from_value(json.clone()).unwrap();
501 let schema: v1::Error = serde_json::from_value(json).unwrap();
502 assert_eq!(parsed, Error::from(schema));
503 }
504 }
505
506 #[test]
507 fn shared_constructors_match_both_schema_versions() {
508 let pairs = [
509 (Error::parse_error(), v1::Error::parse_error()),
510 (Error::invalid_request(), v1::Error::invalid_request()),
511 (Error::method_not_found(), v1::Error::method_not_found()),
512 (Error::invalid_params(), v1::Error::invalid_params()),
513 (Error::internal_error(), v1::Error::internal_error()),
514 (Error::request_cancelled(), v1::Error::request_cancelled()),
515 (Error::auth_required(), v1::Error::auth_required()),
516 (
517 Error::into_internal_error(std::io::Error::other("io")),
518 v1::Error::into_internal_error(std::io::Error::other("io")),
519 ),
520 ];
521 for (sdk, schema) in pairs {
522 assert_eq!(sdk, Error::from(schema));
523 }
524 #[cfg(feature = "unstable_protocol_v2")]
525 {
526 let pairs = [
527 (Error::parse_error(), v2::Error::parse_error()),
528 (Error::invalid_request(), v2::Error::invalid_request()),
529 (Error::method_not_found(), v2::Error::method_not_found()),
530 (Error::invalid_params(), v2::Error::invalid_params()),
531 (Error::internal_error(), v2::Error::internal_error()),
532 (Error::request_cancelled(), v2::Error::request_cancelled()),
533 (Error::auth_required(), v2::Error::auth_required()),
534 ];
535 for (sdk, schema) in pairs {
536 assert_eq!(sdk, Error::from(schema));
537 }
538 }
539 }
540
541 #[test]
542 fn error_codes_compare_with_schema_codes() {
543 for code in sample_codes() {
544 let sdk = ErrorCode::from(code);
545 let v1 = v1::ErrorCode::from(code);
546 assert_eq!(sdk, v1);
547 assert_eq!(v1, sdk);
548 assert_eq!(ErrorCode::from(v1), sdk);
549 assert_eq!(v1::ErrorCode::from(sdk), v1);
550 assert_eq!(format!("{sdk:?}"), format!("{v1:?}"));
551 assert_eq!(sdk.to_string(), v1.to_string());
552 #[cfg(feature = "unstable_protocol_v2")]
553 {
554 let v2 = v2::ErrorCode::from(code);
555 assert_eq!(sdk, v2);
556 assert_eq!(ErrorCode::from(v2), sdk);
557 assert_eq!(v2::ErrorCode::from(sdk), v2);
558 }
559 }
560 assert_ne!(ErrorCode::INVALID_PARAMS, v1::ErrorCode::InvalidRequest);
561 }
562
563 #[cfg(feature = "unstable_protocol_v2")]
564 #[test]
565 fn typed_v2_data_survives_conversion() {
566 let error: Error = v2::Error::unsupported_protocol_version(
567 crate::schema::ProtocolVersion::V2,
568 &[crate::schema::ProtocolVersion::V1],
569 )
570 .into();
571 assert_eq!(error.code, v2::ErrorCode::UnsupportedProtocolVersion);
572 let data: v2::UnsupportedProtocolVersionErrorData =
573 serde_json::from_value(v2::Error::from(error).data.unwrap()).unwrap();
574 assert_eq!(
575 data,
576 v2::UnsupportedProtocolVersionErrorData::new(
577 crate::schema::ProtocolVersion::V2,
578 vec![crate::schema::ProtocolVersion::V1],
579 )
580 );
581 }
582
583 #[test]
584 fn anyhow_recovers_wrapped_errors() {
585 let error = Error::new(12345, "custom").data(json!({ "kept": true }));
586 assert_eq!(Error::from(anyhow::Error::new(error.clone())), error);
587 let schema = v1::Error::new(-32002, "missing").data(Value::Null);
588 assert_eq!(
589 Error::from(anyhow::Error::new(schema.clone())),
590 Error::from(schema)
591 );
592 #[cfg(feature = "unstable_protocol_v2")]
593 {
594 let schema = v2::Error::session_not_found("sess_1");
595 assert_eq!(
596 Error::from(anyhow::Error::new(schema.clone())),
597 Error::from(schema)
598 );
599 }
600 assert_eq!(
601 Error::from(anyhow::anyhow!("boom")),
602 Error::internal_error().data("boom")
603 );
604 }
605}