deno_error/
lib.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2#![deny(clippy::unnecessary_wraps)]
3#![deny(clippy::print_stderr)]
4#![deny(clippy::print_stdout)]
5
6//! Trait and macros to represent Rust errors in JavaScript.
7//!
8//! ## The [`JsError`] macro
9//!
10//! Macro to define the `JsErrorClass` trait on a struct or enum.
11//!
12//! The macro does not provide functionality to related to the `get_message`
13//! function, as one can combine the [`thiserror`](https://crates.io/crates/thiserror) well with this macro.
14//!
15//! ### Attributes
16//!
17//! #### `#[class]`
18//! This attribute accepts 3 possible kinds of value:
19//!   1. `GENERIC`, `TYPE`, and a few more that are defined in the `builtin_classes`
20//!      module, without the `_ERROR` suffix.
21//!   2. A text value ie `"NotFound"`. If a text value is passed that is a valid
22//!      builtin (see the previous point), it will error out as the special
23//!      identifiers are preferred to avoid mistakes.
24//!   3. `inherit`: this will inherit the class from whatever field is marked with
25//!      the `#[inherit]` attribute. Alternatively, the `#[inherit]` attribute
26//!      can be omitted if only one field is present in the enum variant or struct.
27//!      This value is inferred if the class attribute is missing and only a single
28//!      field is present on a struct, however for enums this inferring is not done.
29//!
30//! #### `#[property]`
31//! This attribute allows defining fields as additional properties that should be
32//! defined on the JavaScript error.
33//!
34//! The type of the field needs to implement a `.to_string()` function for it
35//! being able to be inherited.
36//!
37//! #### `#[inherit]`
38//! This attribute allows defining a field that should be used to inherit the class
39//! and properties.
40//!
41//! This is inferred if only one field is present in the enum variant or struct.
42//!
43//! The class is only inherited if the `class` attribute is set to `inherit`.
44//!
45//! ### Examples
46//!
47//! #### Basic usage
48//! ```
49//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
50//! pub enum SomeError {
51//!   #[class(generic)]
52//!   #[error("Failure")]
53//!   Failure,
54//!   #[class(inherit)]
55//!   #[error(transparent)]
56//!   Io(#[inherit] std::io::Error),
57//! }
58//! ```
59//!
60//! #### Top-level class
61//! ```
62//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
63//! #[class(generic)]
64//! pub enum SomeError {
65//!   #[error("Failure")]
66//!   Failure,
67//!   #[class(inherit)] // overwrite the top-level
68//!   #[error(transparent)]
69//!   Io(#[inherit] std::io::Error),
70//! }
71//! ```
72//!
73//! #### Defining properties
74//! ```
75//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
76//! #[class(generic)]
77//! pub enum SomeError {
78//!   #[class(not_supported)]
79//!   #[error("Failure")]
80//!   Failure {
81//!     #[property]
82//!     code: u32,
83//!   },
84//!   #[error("Warning")]
85//!   Warning(#[property = "code"] u32),
86//!   #[class(inherit)] // inherit properties from `std::io::Error`
87//!   #[error(transparent)]
88//!   Io(#[inherit] std::io::Error),
89//! }
90//! ```
91//!
92//! ##### Defining external properties
93//! ```
94//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
95//! #[property("code" = 10)]
96//! #[property("kind" = self.get_name())]
97//! #[class(generic)]
98//! #[error(transparent)]
99//! pub struct SomeError(std::io::Error);
100//!
101//! impl SomeError {
102//!   fn get_name(&self) -> String {
103//!     self.0.kind().to_string()
104//!   }
105//! }
106//! ```
107//!
108//! #### Explicit property inheritance
109//!
110//! ```
111//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
112//! #[class(generic)]
113//! #[properties(inherit)]
114//! #[error(transparent)]
115//! pub struct SomeError(std::io::Error);
116//! ```
117//!
118//! ```
119//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
120//! #[class(inherit)]
121//! #[properties(no_inherit)]
122//! #[error(transparent)]
123//! pub struct SomeError(std::io::Error);
124//! ```
125//!
126//! #### Inferred inheritance
127//! ```
128//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
129//! #[error("My io error")]
130//! pub struct SomeError(std::io::Error);
131//! ```
132//!
133//! ```
134//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
135//! #[class(inherit)]
136//! #[error("My io error")]
137//! pub struct SomeError(std::io::Error);
138//! ```
139//!
140//! ```
141//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
142//! #[class(generic)] // don't inherit the error
143//! #[error("My io error")]
144//! pub struct SomeError(std::io::Error);
145//! ```
146//!
147//! ```
148//! #[derive(Debug, thiserror::Error, deno_error::JsError)]
149//! #[class(type)]
150//! pub enum SomeError {
151//!   #[error("Failure")]
152//!   Failure,
153//!   #[class(inherit)]
154//!   #[error(transparent)]
155//!   Io(std::io::Error),
156//! }
157//! ```
158
159mod error_codes;
160
161pub use deno_error_macro::*;
162pub use error_codes::*;
163use std::any::Any;
164use std::borrow::Cow;
165
166/// Various built-in error classes, mainly related to the JavaScript specification.
167/// May include some error classes that are non-standard.
168pub mod builtin_classes {
169  // keep in sync with macros/lib.rs
170  pub const GENERIC_ERROR: &str = "Error";
171  pub const RANGE_ERROR: &str = "RangeError";
172  pub const TYPE_ERROR: &str = "TypeError";
173  pub const SYNTAX_ERROR: &str = "SyntaxError";
174  pub const URI_ERROR: &str = "URIError";
175  pub const REFERENCE_ERROR: &str = "ReferenceError";
176
177  /// Non-standard
178  pub const NOT_SUPPORTED_ERROR: &str = "NotSupported";
179}
180use builtin_classes::*;
181
182/// Represents a property value that can be either a string or a number
183#[derive(Debug, Clone, PartialEq)]
184pub enum PropertyValue {
185  String(Cow<'static, str>),
186  Number(f64),
187}
188
189impl std::fmt::Display for PropertyValue {
190  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191    match self {
192      PropertyValue::String(s) => write!(f, "{}", s),
193      PropertyValue::Number(n) => write!(f, "{}", n),
194    }
195  }
196}
197
198impl From<String> for PropertyValue {
199  fn from(s: String) -> Self {
200    PropertyValue::String(Cow::Owned(s))
201  }
202}
203
204impl From<&'static str> for PropertyValue {
205  fn from(s: &'static str) -> Self {
206    PropertyValue::String(Cow::Borrowed(s))
207  }
208}
209
210impl From<f64> for PropertyValue {
211  fn from(n: f64) -> Self {
212    PropertyValue::Number(n)
213  }
214}
215
216impl From<&f64> for PropertyValue {
217  fn from(n: &f64) -> Self {
218    PropertyValue::Number(*n)
219  }
220}
221
222impl From<i32> for PropertyValue {
223  fn from(n: i32) -> Self {
224    PropertyValue::Number(n as f64)
225  }
226}
227
228impl From<&i32> for PropertyValue {
229  fn from(n: &i32) -> Self {
230    PropertyValue::Number(*n as f64)
231  }
232}
233
234impl From<u32> for PropertyValue {
235  fn from(n: u32) -> Self {
236    PropertyValue::Number(n as f64)
237  }
238}
239
240impl From<&u32> for PropertyValue {
241  fn from(n: &u32) -> Self {
242    PropertyValue::Number(*n as f64)
243  }
244}
245
246pub type AdditionalProperties =
247  Box<dyn Iterator<Item = (Cow<'static, str>, PropertyValue)>>;
248
249/// Trait to implement how an error should be represented in JavaScript.
250///
251/// **Note**:
252/// it is not recommended to manually implement this type, but instead
253/// rather use the [`JsError`] macro.
254pub trait JsErrorClass:
255  std::error::Error + Send + Sync + Any + 'static
256{
257  /// Represents the error class used in JavaScript side.
258  fn get_class(&self) -> Cow<'static, str>;
259
260  /// Represents the error message used in JavaScript side.
261  fn get_message(&self) -> Cow<'static, str>;
262
263  /// Additional properties that should be defined on the error in JavaScript side.
264  fn get_additional_properties(&self) -> AdditionalProperties;
265
266  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static);
267}
268
269/// Macro which lets you wrap an existing error in a new error that implements
270/// the [`JsErrorClass`] trait. This macro however does currently not support
271/// the special identifiers that the [`JsError`] macro supports.
272///
273/// ## Examples
274///
275/// ```rust
276/// # use deno_error::js_error_wrapper;
277/// js_error_wrapper!(std::net::AddrParseError, JsAddrParseError, "TypeError");
278/// ```
279///
280/// ```rust
281/// # use deno_error::js_error_wrapper;
282/// js_error_wrapper!(std::net::AddrParseError, JsAddrParseError, |err| {
283///   // match or do some logic to get the error class
284///   "TypeError"
285/// });
286/// ```
287#[macro_export]
288macro_rules! js_error_wrapper {
289  ($err_path:path, $err_name:ident, $js_err_type:tt) => {
290    deno_error::js_error_wrapper!($err_path, $err_name, |_error| $js_err_type);
291  };
292  ($err_path:path, $err_name:ident, |$inner:ident| $js_err_type:tt) => {
293    #[derive(Debug)]
294    pub struct $err_name(pub $err_path);
295    impl From<$err_path> for $err_name {
296      fn from(err: $err_path) -> Self {
297        Self(err)
298      }
299    }
300    impl $err_name {
301      pub fn get_error_class(
302        $inner: &$err_path,
303      ) -> impl Into<std::borrow::Cow<'static, str>> {
304        $js_err_type
305      }
306    }
307    impl std::error::Error for $err_name {
308      fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
309        std::error::Error::source(&self.0)
310      }
311    }
312    impl std::fmt::Display for $err_name {
313      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314        std::fmt::Display::fmt(&self.0, f)
315      }
316    }
317    impl deno_error::JsErrorClass for $err_name {
318      fn get_class(&self) -> std::borrow::Cow<'static, str> {
319        Self::get_error_class(&self.0).into()
320      }
321      fn get_message(&self) -> std::borrow::Cow<'static, str> {
322        self.to_string().into()
323      }
324      fn get_additional_properties(&self) -> deno_error::AdditionalProperties {
325        Box::new(std::iter::empty())
326      }
327      fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
328        self
329      }
330    }
331    impl std::ops::Deref for $err_name {
332      type Target = $err_path;
333
334      fn deref(&self) -> &Self::Target {
335        &self.0
336      }
337    }
338  };
339}
340
341impl<T: JsErrorClass> JsErrorClass for Box<T> {
342  fn get_class(&self) -> Cow<'static, str> {
343    (**self).get_class()
344  }
345
346  fn get_message(&self) -> Cow<'static, str> {
347    (**self).get_message()
348  }
349
350  fn get_additional_properties(&self) -> AdditionalProperties {
351    (**self).get_additional_properties()
352  }
353
354  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
355    self
356  }
357}
358
359impl JsErrorClass for std::io::Error {
360  fn get_class(&self) -> Cow<'static, str> {
361    use std::io::ErrorKind::*;
362
363    let class = match self.kind() {
364      NotFound => "NotFound",
365      PermissionDenied => "PermissionDenied",
366      ConnectionRefused => "ConnectionRefused",
367      ConnectionReset => "ConnectionReset",
368      ConnectionAborted => "ConnectionAborted",
369      NotConnected => "NotConnected",
370      AddrInUse => "AddrInUse",
371      AddrNotAvailable => "AddrNotAvailable",
372      BrokenPipe => "BrokenPipe",
373      AlreadyExists => "AlreadyExists",
374      InvalidInput => TYPE_ERROR,
375      InvalidData => "InvalidData",
376      TimedOut => "TimedOut",
377      Interrupted => "Interrupted",
378      WriteZero => "WriteZero",
379      UnexpectedEof => "UnexpectedEof",
380      Other => GENERIC_ERROR,
381      WouldBlock => "WouldBlock",
382      IsADirectory => "IsADirectory",
383      NetworkUnreachable => "NetworkUnreachable",
384      NotADirectory => "NotADirectory",
385      kind => match format!("{kind:?}").as_str() {
386        "FilesystemLoop" => "FilesystemLoop",
387        _ => GENERIC_ERROR,
388      },
389    };
390
391    Cow::Borrowed(class)
392  }
393
394  fn get_message(&self) -> Cow<'static, str> {
395    self.to_string().into()
396  }
397
398  fn get_additional_properties(&self) -> AdditionalProperties {
399    if let Some(code) = get_error_code(self) {
400      Box::new(std::iter::once((
401        "code".into(),
402        PropertyValue::String(code.into()),
403      )))
404    } else {
405      Box::new(Box::new(std::iter::empty()))
406    }
407  }
408
409  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
410    self
411  }
412}
413
414impl JsErrorClass for std::env::VarError {
415  fn get_class(&self) -> Cow<'static, str> {
416    Cow::Borrowed(match self {
417      std::env::VarError::NotPresent => "NotFound",
418      std::env::VarError::NotUnicode(..) => "InvalidData",
419    })
420  }
421
422  fn get_message(&self) -> Cow<'static, str> {
423    self.to_string().into()
424  }
425
426  fn get_additional_properties(&self) -> AdditionalProperties {
427    Box::new(std::iter::empty())
428  }
429
430  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
431    self
432  }
433}
434
435impl JsErrorClass for std::sync::mpsc::RecvError {
436  fn get_class(&self) -> Cow<'static, str> {
437    Cow::Borrowed(GENERIC_ERROR)
438  }
439
440  fn get_message(&self) -> Cow<'static, str> {
441    self.to_string().into()
442  }
443
444  fn get_additional_properties(&self) -> AdditionalProperties {
445    Box::new(std::iter::empty())
446  }
447
448  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
449    self
450  }
451}
452
453impl JsErrorClass for std::str::Utf8Error {
454  fn get_class(&self) -> Cow<'static, str> {
455    Cow::Borrowed(GENERIC_ERROR)
456  }
457
458  fn get_message(&self) -> Cow<'static, str> {
459    self.to_string().into()
460  }
461
462  fn get_additional_properties(&self) -> AdditionalProperties {
463    Box::new(std::iter::empty())
464  }
465
466  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
467    self
468  }
469}
470
471impl JsErrorClass for std::num::TryFromIntError {
472  fn get_class(&self) -> Cow<'static, str> {
473    Cow::Borrowed(TYPE_ERROR)
474  }
475
476  fn get_message(&self) -> Cow<'static, str> {
477    self.to_string().into()
478  }
479
480  fn get_additional_properties(&self) -> AdditionalProperties {
481    Box::new(std::iter::empty())
482  }
483
484  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
485    self
486  }
487}
488
489impl JsErrorClass for std::convert::Infallible {
490  fn get_class(&self) -> Cow<'static, str> {
491    unreachable!()
492  }
493
494  fn get_message(&self) -> Cow<'static, str> {
495    unreachable!()
496  }
497
498  fn get_additional_properties(&self) -> AdditionalProperties {
499    unreachable!();
500  }
501
502  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
503    unreachable!()
504  }
505}
506
507#[cfg(all(feature = "serde", feature = "serde_json"))]
508impl JsErrorClass for serde_json::Error {
509  fn get_class(&self) -> Cow<'static, str> {
510    use serde::de::StdError;
511    use serde_json::error::*;
512
513    match self.classify() {
514      Category::Io => self
515        .source()
516        .and_then(|e| e.downcast_ref::<std::io::Error>())
517        .unwrap()
518        .get_class(),
519      Category::Syntax => Cow::Borrowed(SYNTAX_ERROR),
520      Category::Data => Cow::Borrowed("InvalidData"),
521      Category::Eof => Cow::Borrowed("UnexpectedEof"),
522    }
523  }
524
525  fn get_message(&self) -> Cow<'static, str> {
526    self.to_string().into()
527  }
528
529  fn get_additional_properties(&self) -> AdditionalProperties {
530    Box::new(std::iter::empty()) // TODO: could be io error code
531  }
532
533  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
534    self
535  }
536}
537
538#[cfg(feature = "url")]
539impl JsErrorClass for url::ParseError {
540  fn get_class(&self) -> Cow<'static, str> {
541    Cow::Borrowed(URI_ERROR)
542  }
543
544  fn get_message(&self) -> Cow<'static, str> {
545    self.to_string().into()
546  }
547
548  fn get_additional_properties(&self) -> AdditionalProperties {
549    Box::new(std::iter::empty())
550  }
551
552  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
553    self
554  }
555}
556
557#[cfg(feature = "tokio")]
558impl<T: Send + Sync + 'static> JsErrorClass
559  for tokio::sync::mpsc::error::SendError<T>
560{
561  fn get_class(&self) -> Cow<'static, str> {
562    Cow::Borrowed(GENERIC_ERROR)
563  }
564
565  fn get_message(&self) -> Cow<'static, str> {
566    self.to_string().into()
567  }
568
569  fn get_additional_properties(&self) -> AdditionalProperties {
570    Box::new(std::iter::empty())
571  }
572
573  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
574    self
575  }
576}
577
578#[cfg(feature = "tokio")]
579impl JsErrorClass for tokio::task::JoinError {
580  fn get_class(&self) -> Cow<'static, str> {
581    Cow::Borrowed(GENERIC_ERROR)
582  }
583
584  fn get_message(&self) -> Cow<'static, str> {
585    self.to_string().into()
586  }
587
588  fn get_additional_properties(&self) -> AdditionalProperties {
589    Box::new(std::iter::empty())
590  }
591
592  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
593    self
594  }
595}
596
597#[cfg(feature = "tokio")]
598impl JsErrorClass for tokio::sync::broadcast::error::RecvError {
599  fn get_class(&self) -> Cow<'static, str> {
600    Cow::Borrowed(GENERIC_ERROR)
601  }
602
603  fn get_message(&self) -> Cow<'static, str> {
604    self.to_string().into()
605  }
606
607  fn get_additional_properties(&self) -> AdditionalProperties {
608    Box::new(std::iter::empty())
609  }
610
611  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
612    self
613  }
614}
615
616enum JsErrorBoxInner {
617  Standalone {
618    class: Cow<'static, str>,
619    message: Cow<'static, str>,
620  },
621  Wrap(Box<dyn JsErrorClass>),
622}
623
624pub struct JsErrorBox(JsErrorBoxInner);
625
626impl std::fmt::Debug for JsErrorBox {
627  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
628    let mut debug = f.debug_struct("JsErrorBox");
629
630    match &self.0 {
631      JsErrorBoxInner::Standalone { class, message } => {
632        debug.field("class", class);
633        debug.field("message", message);
634      }
635      JsErrorBoxInner::Wrap(inner) => {
636        debug.field("inner", inner);
637      }
638    }
639
640    debug.finish()
641  }
642}
643
644impl std::fmt::Display for JsErrorBox {
645  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646    write!(f, "{}", self.get_message())
647  }
648}
649
650impl std::error::Error for JsErrorBox {
651  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
652    match &self.0 {
653      JsErrorBoxInner::Standalone { .. } => None,
654      JsErrorBoxInner::Wrap(inner) => inner.source(),
655    }
656  }
657}
658
659impl JsErrorClass for JsErrorBox {
660  fn get_class(&self) -> Cow<'static, str> {
661    match &self.0 {
662      JsErrorBoxInner::Standalone { class, .. } => class.clone(),
663      JsErrorBoxInner::Wrap(inner) => inner.get_class(),
664    }
665  }
666
667  fn get_message(&self) -> Cow<'static, str> {
668    match &self.0 {
669      JsErrorBoxInner::Standalone { message, .. } => message.clone(),
670      JsErrorBoxInner::Wrap(inner) => inner.get_message(),
671    }
672  }
673
674  fn get_additional_properties(&self) -> AdditionalProperties {
675    match &self.0 {
676      JsErrorBoxInner::Standalone { .. } => Box::new(std::iter::empty()),
677      JsErrorBoxInner::Wrap(inner) => inner.get_additional_properties(),
678    }
679  }
680
681  fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
682    match &self.0 {
683      JsErrorBoxInner::Standalone { .. } => self,
684      JsErrorBoxInner::Wrap(inner) => inner.get_ref(),
685    }
686  }
687}
688
689impl JsErrorBox {
690  pub fn new(
691    class: impl Into<Cow<'static, str>>,
692    message: impl Into<Cow<'static, str>>,
693  ) -> Self {
694    Self(JsErrorBoxInner::Standalone {
695      class: class.into(),
696      message: message.into(),
697    })
698  }
699
700  pub fn from_err<T: JsErrorClass>(err: T) -> Self {
701    Self(JsErrorBoxInner::Wrap(Box::new(err)))
702  }
703
704  pub fn generic(message: impl Into<Cow<'static, str>>) -> JsErrorBox {
705    Self::new(GENERIC_ERROR, message)
706  }
707
708  pub fn type_error(message: impl Into<Cow<'static, str>>) -> JsErrorBox {
709    Self::new(TYPE_ERROR, message)
710  }
711
712  pub fn range_error(message: impl Into<Cow<'static, str>>) -> JsErrorBox {
713    Self::new(RANGE_ERROR, message)
714  }
715
716  pub fn uri_error(message: impl Into<Cow<'static, str>>) -> JsErrorBox {
717    Self::new(URI_ERROR, message)
718  }
719
720  // Non-standard errors
721  pub fn not_supported() -> JsErrorBox {
722    Self::new(NOT_SUPPORTED_ERROR, "The operation is not supported")
723  }
724
725  pub fn get_inner_ref(
726    &self,
727  ) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
728    match &self.0 {
729      JsErrorBoxInner::Standalone { .. } => None,
730      JsErrorBoxInner::Wrap(inner) => Some(inner.get_ref()),
731    }
732  }
733}
734
735#[cfg(test)]
736mod tests {
737  use std::io;
738
739  use super::JsErrorClass;
740
741  #[test]
742  fn test_io_error_class_stable() {
743    assert_eq!(
744      io::Error::new(io::ErrorKind::NotFound, "").get_class(),
745      "NotFound",
746    );
747  }
748
749  #[test]
750  #[cfg(unix)]
751  fn test_io_error_class_unstable() {
752    assert_eq!(
753      io::Error::from_raw_os_error(libc::ELOOP).get_class(),
754      "FilesystemLoop",
755    );
756  }
757}