deno_error
A Rust trait and derive macro for representing Rust errors as typed JavaScript errors in Deno's runtime.
Repository Health
Technical Analysis
deno_error is a small Rust crate maintained by the Deno team that bridges Rust’s std::error::Error ecosystem with JavaScript’s error model. It centers on the JsErrorClass trait, which every JS-visible error implements to report its class name (TypeError, RangeError, a custom string, etc.), display message, and any extra structured properties JS code should see on the resulting error object.
Rather than hand-writing that trait for every error type, most consumers apply the JsError derive macro to their existing thiserror-based enums and structs, using #[class], #[inherit], and #[property] attributes to describe how each variant should surface in JavaScript. The crate ships blanket JsErrorClass implementations for common standard-library and ecosystem error types (std::io::Error with OS errno-to-string mapping, VarError, Utf8Error, and optional serde_json, url, and tokio integrations), so Deno’s Rust internals - deno_core, deno_runtime, and CLI subsystems - can consistently rethrow native errors into V8 without each call site re-deriving JS-facing error metadata by hand.
What You Get
- The
JsErrorClasstrait - a stable contract (get_class, get_message, get_additional_properties, get_ref) that any Rust error type can implement to become JS-error-aware. - The
#[derive(JsError)]macro - auto-implementsJsErrorClasson thiserror-based enums/structs using#[class],#[inherit], and#[property]attributes instead of hand-written boilerplate. - Built-in mappings for common error types -
std::io::Error(with full Unix/Windows errno-to-code tables),VarError,Utf8Error,TryFromIntError, and optional serde_json/url/tokio error implementations behind feature flags. JsErrorBox- a type-erased, Box<dyn Error>-style wrapper for holding anyJsErrorClassvalue or an ad hoc standalone class/message pair.- The
js_error_wrapper!macro - a lightweight, non-derive way to wrap a single foreign error type and give it a JS error class in one line.
Common Use Cases
- Surfacing a native Deno subsystem error (filesystem, network, permissions) as the correct JavaScript error class and message in user-facing scripts.
- Deriving JS-compatible error reporting for a new Rust error enum without writing a manual JsErrorClass implementation.
- Propagating OS-level errno codes (e.g. ENOENT, EACCES) as a
codeproperty on the resulting JS error object. - Wrapping a third-party crate’s error type (e.g. AddrParseError) so it can cross the Rust/JS boundary with an explicit or computed error class.
Under The Hood
Architecture
The crate is organized as a two-member Cargo workspace: the core crate (src/lib.rs) defines the JsErrorClass trait plus a PropertyValue enum for representing error properties as either strings or numbers, and a JsErrorBox type for type-erased dynamic dispatch over any JsErrorClass implementor, mirroring Box<dyn Error> patterns. The macros subcrate (macros/lib.rs) implements a proc-macro derive (JsError) built on syn/quote/proc-macro2 that parses #[class], #[property], #[inherit], and #[properties] attributes off enum variants or struct fields to generate JsErrorClass impls at compile time. error_codes.rs is a pure data/lookup module mapping OS errno values (Unix and Windows) to libuv-style string codes, consumed by the built-in std::io::Error implementation. Blanket implementations for common std/ecosystem error types (io::Error, VarError, Utf8Error, TryFromIntError, serde_json::Error behind a serde_json feature, url::ParseError behind url, several tokio error types behind tokio) let the crate act as an interop shim without pulling in those dependencies by default. Because the trait is consumed across Deno’s Rust internals (deno_core, deno_runtime, CLI subsystems that rethrow errors into V8), it is kept intentionally minimal and stable.
Tech Stack Pure Rust, edition 2021, split into a two-crate workspace (deno_error + deno_error_macro, pinned to the same version via a path dependency). Core dependencies are minimal: libc (0.2) for raw OS error-code lookups, plus optional feature-gated integrations for tokio (1.x, sync+rt features), url (2.x), and serde/serde_json (1.x), each gated so consumers only pay for what they use. The macro crate depends on the standard proc-macro trio: syn, quote, and proc-macro2. Dev-dependencies pull in thiserror (2.x) for tests, since the crate is explicitly designed to compose with thiserror-derived error enums rather than replace them. There is no build script, no mandatory async runtime, and no network/database integration - this is a compile-time interop utility crate published to crates.io and docs.rs, versioned outside strict semver and instead gated on compatibility with deno_graph and the main Deno repo.
Code Quality
Testing is minimal but targeted: tests/properties.rs exercises the derive macro’s #[property]/#[inherit]/#[properties(inherit|no_inherit)] attribute combinations end-to-end against get_additional_properties() output, and src/lib.rs has a small #[cfg(test)] module asserting stable io::Error class-mapping, including a Unix-only errno case. There is no broad test suite covering every builtin error implementation or malformed-macro-input error path, so coverage is adequate for the happy path but not exhaustive. The derive macro returns syn::Error for malformed input, converted to compile_error! output rather than panicking. The crate enforces #![deny(clippy::unnecessary_wraps)], #![deny(clippy::print_stderr)], and #![deny(clippy::print_stdout)] at the crate root, and a checked-in rust-toolchain.toml and .rustfmt.toml pin the toolchain and formatting rules; a Cirrus CI badge in the README indicates CI is configured. Naming is consistent (JsErrorClass, JsErrorBox, PropertyValue), and the public API deliberately uses Cow<‘static, str> to avoid unnecessary allocation.
API Design
The crate’s core ergonomic move is letting consumers derive JsErrorClass on their existing thiserror-based error enums with a single #[derive(..., deno_error::JsError)] plus lightweight #[class(...)] annotations, instead of hand-writing JS-interop boilerplate on every error type - the macro infers the class from a single wrapped field when there’s no ambiguity, trimming annotation overhead for the common wrap-one-error-type case. The #[class(inherit)] plus #[inherit]/#[properties(inherit|no_inherit)] attribute vocabulary is small and composable, and js_error_wrapper! gives a macro-free escape hatch for wrapping a single foreign error type in one line. JsErrorBox mirrors the familiar Box<dyn Error> pattern, plus a Standalone{class,message} variant for ad hoc errors without a backing Rust type. Documentation is delivered almost entirely as inline module-level rustdoc with runnable examples visible on docs.rs, rather than a separate guide, which keeps docs versioned alongside code but offers limited narrative onboarding outside the crate itself.