├── .gitignore ├── .rustfmt.toml ├── .gitattributes ├── rust-toolchain.toml ├── .editorconfig ├── macros ├── Cargo.toml └── lib.rs ├── Cargo.toml ├── LICENSE ├── .github └── workflows │ ├── release.yml │ └── ci.yml ├── tests └── properties.rs ├── README.md ├── src ├── error_codes.rs └── lib.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | edition = "2021" 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Use Unix line endings in all text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.83.0" 3 | components = [ "clippy", "rustfmt" ] 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | [package] 4 | name = "deno_error_macro" 5 | version = "0.7.3" 6 | description = "Macro for writing Deno errors" 7 | homepage = "https://deno.land/" 8 | repository = "https://github.com/denoland/deno_error" 9 | documentation = "https://docs.rs/deno_error" 10 | authors = ["the Deno authors"] 11 | edition = "2021" 12 | license = "MIT" 13 | 14 | [lib] 15 | path = "./lib.rs" 16 | proc-macro = true 17 | 18 | [dependencies] 19 | proc-macro2 = "1" 20 | quote = "1" 21 | syn = { version = "2", features = ["full", "extra-traits"] } 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "deno_error" 3 | version = "0.7.3" 4 | description = "Error representation to JavaScript for deno" 5 | homepage = "https://deno.land/" 6 | repository = "https://github.com/denoland/deno_error" 7 | documentation = "https://docs.rs/deno_error" 8 | authors = ["the Deno authors"] 9 | edition = "2021" 10 | license = "MIT" 11 | 12 | [workspace] 13 | members = ["macros"] 14 | 15 | [dependencies] 16 | deno_error_macro = { version = "=0.7.3", path = "macros" } 17 | libc = "0.2.126" 18 | 19 | tokio = { version = "1", features = ["sync", "rt"], optional = true } 20 | url = { version = "2", optional = true } 21 | serde = { version = "1", optional = true } 22 | serde_json = { version = "1", optional = true } 23 | 24 | [dev-dependencies] 25 | thiserror = "2" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2024 the Deno authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseKind: 7 | description: "Kind of release" 8 | default: "minor" 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | required: true 14 | 15 | jobs: 16 | rust: 17 | name: release 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 30 20 | 21 | steps: 22 | - name: Clone repository 23 | uses: actions/checkout@v3 24 | with: 25 | token: ${{ secrets.DENOBOT_PAT }} 26 | 27 | - uses: denoland/setup-deno@v1 28 | with: 29 | deno-version: canary 30 | - uses: dsherret/rust-toolchain-file@v1 31 | 32 | - name: Tag and release 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.DENOBOT_PAT }} 35 | GH_WORKFLOW_ACTOR: ${{ github.actor }} 36 | run: | 37 | git config user.email "denobot@users.noreply.github.com" 38 | git config user.name "denobot" 39 | deno run -A jsr:@deno/rust-automation@0.22.3/tasks/publish-release --${{github.event.inputs.releaseKind}} deno_error_macro --skip-release 40 | deno run -A jsr:@deno/rust-automation@0.22.3/tasks/publish-release --${{github.event.inputs.releaseKind}} deno_error 41 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | rust: 7 | name: deno_error-${{ matrix.os }} 8 | if: | 9 | (github.event_name == 'push' || !startsWith(github.event.pull_request.head.label, 'denoland:')) 10 | && github.ref_name != 'deno_registry' 11 | && !startsWith(github.ref, 'refs/tags/deno/') 12 | runs-on: ${{ matrix.os }} 13 | permissions: 14 | contents: read 15 | id-token: write 16 | timeout-minutes: 30 17 | strategy: 18 | matrix: 19 | os: [macOS-latest, ubuntu-latest, windows-2022] 20 | 21 | env: 22 | CARGO_INCREMENTAL: 0 23 | RUST_BACKTRACE: full 24 | RUSTFLAGS: -D warnings 25 | 26 | steps: 27 | - name: Clone repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Install rust 31 | uses: dsherret/rust-toolchain-file@v1 32 | 33 | - uses: Swatinem/rust-cache@v2 34 | with: 35 | save-if: ${{ github.ref == 'refs/heads/main' }} 36 | 37 | - name: Format 38 | if: contains(matrix.os, 'ubuntu') 39 | run: | 40 | cargo fmt -- --check 41 | 42 | - name: Clippy 43 | if: contains(matrix.os, 'ubuntu') 44 | run: cargo clippy --locked --all-features --all-targets -- -D clippy::all 45 | 46 | - name: Cargo Build 47 | run: cargo build --locked --all-features --all-targets 48 | 49 | - name: Cargo Test 50 | run: cargo test --locked --all-features --all-targets 51 | 52 | # ensure we build with no default features, but only bother testing on linux 53 | - name: Cargo Build (no-default-features) 54 | if: contains(matrix.os, 'ubuntu') 55 | run: cargo build --locked --no-default-features 56 | 57 | - name: Cargo publish 58 | if: | 59 | contains(matrix.os, 'ubuntu') && 60 | github.repository == 'denoland/deno_error' && 61 | startsWith(github.ref, 'refs/tags/') 62 | env: 63 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 64 | run: cargo publish -p deno_error_macro && cargo publish 65 | -------------------------------------------------------------------------------- /tests/properties.rs: -------------------------------------------------------------------------------- 1 | use deno_error::{JsErrorClass, PropertyValue}; 2 | use std::borrow::Cow; 3 | use std::io::ErrorKind; 4 | 5 | #[test] 6 | fn test_properties() { 7 | #[derive(Debug, thiserror::Error, deno_error::JsError)] 8 | #[class(type)] 9 | pub enum SomeError { 10 | #[error("Failure")] 11 | Failure, 12 | #[class(inherit)] 13 | #[property("foo" = "1")] 14 | #[error(transparent)] 15 | Io(std::io::Error), 16 | #[class(type)] 17 | #[properties(inherit)] 18 | #[property("foo" = "1")] 19 | #[error(transparent)] 20 | Io2(std::io::Error), 21 | #[class(inherit)] 22 | #[properties(no_inherit)] 23 | #[error(transparent)] 24 | Io3(std::io::Error), 25 | #[error("")] 26 | Foo { 27 | #[property] 28 | errcode: f64, 29 | #[property] 30 | errcode2: f64, 31 | }, 32 | } 33 | 34 | assert_eq!( 35 | SomeError::Failure 36 | .get_additional_properties() 37 | .collect::>(), 38 | [] 39 | ); 40 | assert_eq!( 41 | SomeError::Io(std::io::Error::new(ErrorKind::AddrInUse, "foo")) 42 | .get_additional_properties() 43 | .collect::>(), 44 | [ 45 | ( 46 | Cow::Borrowed("code"), 47 | PropertyValue::String(Cow::Borrowed("EADDRINUSE")) 48 | ), 49 | ( 50 | Cow::Borrowed("foo"), 51 | PropertyValue::String(Cow::Borrowed("1")) 52 | ) 53 | ] 54 | ); 55 | assert_eq!( 56 | SomeError::Io2(std::io::Error::new(ErrorKind::AddrInUse, "foo")) 57 | .get_additional_properties() 58 | .collect::>(), 59 | [ 60 | ( 61 | Cow::Borrowed("code"), 62 | PropertyValue::String(Cow::Borrowed("EADDRINUSE")) 63 | ), 64 | ( 65 | Cow::Borrowed("foo"), 66 | PropertyValue::String(Cow::Borrowed("1")) 67 | ) 68 | ] 69 | ); 70 | assert_eq!( 71 | SomeError::Io3(std::io::Error::new(ErrorKind::AddrInUse, "foo")) 72 | .get_additional_properties() 73 | .collect::>(), 74 | [] 75 | ); 76 | assert_eq!( 77 | SomeError::Foo { 78 | errcode: 1.0, 79 | errcode2: 1.0 80 | } 81 | .get_additional_properties() 82 | .collect::>(), 83 | [ 84 | (Cow::Borrowed("errcode"), PropertyValue::Number(1.0)), 85 | (Cow::Borrowed("errcode2"), PropertyValue::Number(1.0)) 86 | ] 87 | ); 88 | } 89 | 90 | #[test] 91 | fn test_property_values() { 92 | // Test direct creation of PropertyValue 93 | let str_value = PropertyValue::String(Cow::Borrowed("test")); 94 | let num_value = PropertyValue::Number(42.5); 95 | 96 | assert_eq!(str_value.to_string(), "test"); 97 | assert_eq!(num_value.to_string(), "42.5"); 98 | 99 | // Test From implementations 100 | let from_static_str: PropertyValue = "static".into(); 101 | let from_string: PropertyValue = "owned".to_string().into(); 102 | let from_f64: PropertyValue = 123.45.into(); 103 | let from_i32: PropertyValue = 42.into(); 104 | 105 | assert_eq!( 106 | from_static_str, 107 | PropertyValue::String(Cow::Borrowed("static")) 108 | ); 109 | assert_eq!( 110 | from_string, 111 | PropertyValue::String(Cow::Owned("owned".to_string())) 112 | ); 113 | assert_eq!(from_f64, PropertyValue::Number(123.45)); 114 | assert_eq!(from_i32, PropertyValue::Number(42.0)); 115 | 116 | // Custom error with numeric property 117 | #[derive(Debug, thiserror::Error, deno_error::JsError)] 118 | #[class(type)] 119 | #[property("code" = 404)] 120 | #[error("Not found")] 121 | struct NotFoundError; 122 | 123 | let error = NotFoundError; 124 | let properties = error.get_additional_properties().collect::>(); 125 | 126 | assert_eq!(properties.len(), 1); 127 | assert_eq!(properties[0].0, "code"); 128 | 129 | if let PropertyValue::Number(code) = &properties[0].1 { 130 | assert_eq!(*code, 404.0); 131 | } else { 132 | panic!("Expected PropertyValue::Number"); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deno_error 2 | 3 | [![Build Status - Cirrus][]][Build status] [![Twitter handle][]][Twitter badge] 4 | [![Discord Chat](https://img.shields.io/discord/684898665143206084?logo=discord&style=social)](https://discord.gg/deno) 5 | 6 | Trait and macros to represent Rust errors in JS. 7 | 8 | ## Usage 9 | 10 | The `JsErrorClass` trait is available to be manually implemented, which includes 11 | 3 functions: `get_class`, `get_message` and `get_additional_properties`. 12 | 13 | It is however advised to not implement this trait manually, but rather go 14 | through the `JsError` derive macro that is exposed, which provides the following 15 | functionality: 16 | 17 | - Define the class via the `#[class()]` attribute, that can be defined on either 18 | variants of an enum, or the top-level of an enum to define for all variants, 19 | where on individual variants overwrite can still be applied. Structs are also 20 | supported. 21 | 22 | This attribute accepts 3 possible kinds of value: 23 | 1. `GENERIC`, `TYPE`, and a few more that are defined in the `builtin_classes` 24 | module, without the `_ERROR` suffix. 25 | 2. A text value ie `"NotFound"`. If a text value is passed that is a valid 26 | builtin (see the previous point), it will error out as the special 27 | identifiers are preferred to avoid mistakes. 28 | 3. `inherit`: this will inherit the class from whatever field is marked with 29 | the `#[inherit]` attribute. Alternatively, the `#[inherit]` attribute can 30 | be omitted if only one field is present in the enum variant or struct. 31 | This value is inferred if the class attribute is missing and only a single 32 | field is present on a struct, however for enums this inferring is not done. 33 | 34 | - Define additional properties via the `#[property]` attribute that can be 35 | defined on individual fields. The type of the field needs to implement a 36 | `.to_string()` function for it being able to be inherited. 37 | - Inherit class and properties via the `#[inherit]` attribute which can be 38 | specified on fields that contain a value that implements `JsErrorClass`. This 39 | is inferred if only one field is present in the enum variant or struct. 40 | 41 | The macro does not provide functionality to related to the `get_message` 42 | function, as one can combine the 43 | [`thiserror`](https://crates.io/crates/thiserror) well with this macro. 44 | 45 | There also is the `js_error_wrapper` macro which lets you wrap an existing error 46 | in a new error that implements the `JsErrorClass` trait. This macro however does 47 | currently not support the special identifiers that the `JsError` macro supports. 48 | Here are two examples on how to use it: 49 | 50 | ```rust 51 | js_error_wrapper!(std::net::AddrParseError, JsAddrParseError, "TypeError"); 52 | ``` 53 | 54 | ```rust 55 | js_error_wrapper!(std::net::AddrParseError, JsAddrParseError, |err| { 56 | // match or do some logic to get the error class 57 | }); 58 | ``` 59 | 60 | Additionally, this crate provides some features which related to some commonly 61 | used crates in Deno, which implements the `JsErrorClass` trait on some of their 62 | errors. 63 | 64 | ## Versioning Strategy 65 | 66 | This crate does not follow semver so make sure to pin it to a patch version. 67 | Instead a versioning strategy that optimizes for more efficient maintenance is 68 | used: 69 | 70 | - Does [deno_graph](https://github.com/denoland/deno_deno_graph) still compile 71 | in the [Deno](https://github.com/denoland/deno) repo? 72 | - If yes, is this a change that would break something at runtime? 73 | - If yes, it's a minor release. 74 | - If no, it's a patch release. 75 | - If no, it's a minor release. 76 | 77 | ### Contributing 78 | 79 | We appreciate your help! 80 | 81 | To contribute, please read our 82 | [contributing instructions](https://deno.land/manual/contributing). 83 | 84 | [Build Status - Cirrus]: https://github.com/denoland/deno_error/workflows/ci/badge.svg?branch=main&event=push 85 | [Build status]: https://github.com/denoland/deno_error/actions 86 | [Twitter badge]: https://twitter.com/intent/follow?screen_name=deno_land 87 | [Twitter handle]: https://img.shields.io/twitter/follow/deno_land.svg?style=social&label=Follow 88 | -------------------------------------------------------------------------------- /src/error_codes.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | /// Get the error code for a provided [`std::io::Error`]. 4 | pub fn get_error_code(err: &std::io::Error) -> Option<&'static str> { 5 | let code = match err.raw_os_error() { 6 | #[cfg(any(unix, windows))] 7 | Some(code) => get_os_error_code(code), 8 | #[cfg(all(not(unix), not(windows)))] 9 | Some(_) => return None, 10 | None => get_io_error_code(err), 11 | }; 12 | 13 | match code.is_empty() { 14 | true => None, 15 | false => Some(code), 16 | } 17 | } 18 | 19 | fn get_io_error_code(err: &std::io::Error) -> &'static str { 20 | // not exhaustive but simple and possibly sufficient once `io_error_more` is stabilized (https://github.com/rust-lang/rust/issues/86442) 21 | // inversion of https://github.com/rust-lang/rust/blob/dca3f1b786efd27be3b325ed1e01e247aa589c3b/library/std/src/sys/unix/mod.rs#L138-L185 22 | // TODO(@AaronO): revisit as `io_error_more` lands in rust stable 23 | use std::io::ErrorKind; 24 | match err.kind() { 25 | // ErrorKind::ArgumentListTooLong => "E2BIG", 26 | ErrorKind::AddrInUse => "EADDRINUSE", 27 | ErrorKind::AddrNotAvailable => "EADDRNOTAVAIL", 28 | // ErrorKind::ResourceBusy => "EBUSY", 29 | ErrorKind::ConnectionAborted => "ECONNABORTED", 30 | ErrorKind::ConnectionRefused => "ECONNREFUSED", 31 | ErrorKind::ConnectionReset => "ECONNRESET", 32 | // ErrorKind::Deadlock => "EDEADLK", 33 | // ErrorKind::FilesystemQuotaExceeded => "EDQUOT", 34 | ErrorKind::AlreadyExists => "EEXIST", 35 | // ErrorKind::FileTooLarge => "EFBIG", 36 | // ErrorKind::HostUnreachable => "EHOSTUNREACH", 37 | ErrorKind::Interrupted => "EINTR", 38 | ErrorKind::InvalidInput => "EINVAL", 39 | // ErrorKind::IsADirectory => "EISDIR", 40 | // ErrorKind::FilesystemLoop => "ELOOP", 41 | ErrorKind::NotFound => "ENOENT", 42 | ErrorKind::OutOfMemory => "ENOMEM", 43 | // ErrorKind::StorageFull => "ENOSPC", 44 | ErrorKind::Unsupported => "ENOSYS", 45 | // ErrorKind::TooManyLinks => "EMLINK", 46 | // ErrorKind::FilenameTooLong => "ENAMETOOLONG", 47 | // ErrorKind::NetworkDown => "ENETDOWN", 48 | // ErrorKind::NetworkUnreachable => "ENETUNREACH", 49 | ErrorKind::NotConnected => "ENOTCONN", 50 | // ErrorKind::NotADirectory => "ENOTDIR", 51 | // ErrorKind::DirectoryNotEmpty => "ENOTEMPTY", 52 | ErrorKind::BrokenPipe => "EPIPE", 53 | // ErrorKind::ReadOnlyFilesystem => "EROFS", 54 | // ErrorKind::NotSeekable => "ESPIPE", 55 | // ErrorKind::StaleNetworkFileHandle => "ESTALE", 56 | ErrorKind::TimedOut => "ETIMEDOUT", 57 | // ErrorKind::ExecutableFileBusy => "ETXTBSY", 58 | // ErrorKind::CrossesDevices => "EXDEV", 59 | ErrorKind::PermissionDenied => "EACCES", // NOTE: Collides with EPERM ... 60 | ErrorKind::WouldBlock => "EWOULDBLOCK", // NOTE: Collides with EAGAIN ... 61 | _ => "", 62 | } 63 | } 64 | 65 | /// Maps OS errno codes to string names 66 | /// derived from libuv: https://github.com/libuv/libuv/blob/26b2e5dbb6301756644d6e4cf6ca9c49c00513d3/include/uv/errno.h 67 | /// generated with tools/codegen_error_codes.js 68 | #[cfg(unix)] 69 | fn get_os_error_code(errno: i32) -> &'static str { 70 | match errno { 71 | libc::E2BIG => "E2BIG", 72 | libc::EACCES => "EACCES", 73 | libc::EADDRINUSE => "EADDRINUSE", 74 | libc::EADDRNOTAVAIL => "EADDRNOTAVAIL", 75 | libc::EAFNOSUPPORT => "EAFNOSUPPORT", 76 | libc::EAGAIN => "EAGAIN", 77 | libc::EALREADY => "EALREADY", 78 | libc::EBADF => "EBADF", 79 | libc::EBUSY => "EBUSY", 80 | libc::ECANCELED => "ECANCELED", 81 | libc::ECONNABORTED => "ECONNABORTED", 82 | libc::ECONNREFUSED => "ECONNREFUSED", 83 | libc::ECONNRESET => "ECONNRESET", 84 | libc::EEXIST => "EEXIST", 85 | libc::EFAULT => "EFAULT", 86 | libc::EHOSTUNREACH => "EHOSTUNREACH", 87 | libc::EINVAL => "EINVAL", 88 | libc::EIO => "EIO", 89 | libc::EISCONN => "EISCONN", 90 | libc::EISDIR => "EISDIR", 91 | libc::ELOOP => "ELOOP", 92 | libc::EMFILE => "EMFILE", 93 | libc::EMSGSIZE => "EMSGSIZE", 94 | libc::ENAMETOOLONG => "ENAMETOOLONG", 95 | libc::ENETUNREACH => "ENETUNREACH", 96 | libc::ENOBUFS => "ENOBUFS", 97 | libc::ENOENT => "ENOENT", 98 | libc::ENOMEM => "ENOMEM", 99 | libc::ENOSPC => "ENOSPC", 100 | libc::ENOTCONN => "ENOTCONN", 101 | libc::ENOTDIR => "ENOTDIR", 102 | libc::ENOTEMPTY => "ENOTEMPTY", 103 | libc::ENOTSOCK => "ENOTSOCK", 104 | libc::ENOTSUP => "ENOTSUP", 105 | libc::EPERM => "EPERM", 106 | libc::EPIPE => "EPIPE", 107 | libc::EPROTONOSUPPORT => "EPROTONOSUPPORT", 108 | libc::EROFS => "EROFS", 109 | libc::ETIMEDOUT => "ETIMEDOUT", 110 | libc::EXDEV => "EXDEV", 111 | libc::ESOCKTNOSUPPORT => "ESOCKTNOSUPPORT", 112 | _ => "", 113 | } 114 | } 115 | 116 | #[cfg(windows)] 117 | fn get_os_error_code(errno: i32) -> &'static str { 118 | match errno { 119 | 998 => "EACCES", // ERROR_NOACCESS 120 | 10013 => "EACCES", // WSAEACCES 121 | 1920 => "EACCES", // ERROR_CANT_ACCESS_FILE 122 | 1227 => "EADDRINUSE", // ERROR_ADDRESS_ALREADY_ASSOCIATED 123 | 10048 => "EADDRINUSE", // WSAEADDRINUSE 124 | 10049 => "EADDRNOTAVAIL", // WSAEADDRNOTAVAIL 125 | 10047 => "EAFNOSUPPORT", // WSAEAFNOSUPPORT 126 | 10035 => "EAGAIN", // WSAEWOULDBLOCK 127 | 10037 => "EALREADY", // WSAEALREADY 128 | 1004 => "EBADF", // ERROR_INVALID_FLAGS 129 | 6 => "EBADF", // ERROR_INVALID_HANDLE 130 | 33 => "EBUSY", // ERROR_LOCK_VIOLATION 131 | 231 => "EBUSY", // ERROR_PIPE_BUSY 132 | 32 => "EBUSY", // ERROR_SHARING_VIOLATION 133 | 995 => "ECANCELED", // ERROR_OPERATION_ABORTED 134 | 10004 => "ECANCELED", // WSAEINTR 135 | 1236 => "ECONNABORTED", // ERROR_CONNECTION_ABORTED 136 | 10053 => "ECONNABORTED", // WSAECONNABORTED 137 | 1225 => "ECONNREFUSED", // ERROR_CONNECTION_REFUSED 138 | 10061 => "ECONNREFUSED", // WSAECONNREFUSED 139 | 64 => "ECONNRESET", // ERROR_NETNAME_DELETED 140 | 10054 => "ECONNRESET", // WSAECONNRESET 141 | 183 => "EEXIST", // ERROR_ALREADY_EXISTS 142 | 80 => "EEXIST", // ERROR_FILE_EXISTS 143 | 111 => "EFAULT", // ERROR_BUFFER_OVERFLOW 144 | 10014 => "EFAULT", // WSAEFAULT 145 | 1232 => "EHOSTUNREACH", // ERROR_HOST_UNREACHABLE 146 | 10065 => "EHOSTUNREACH", // WSAEHOSTUNREACH 147 | 122 => "EINVAL", // ERROR_INSUFFICIENT_BUFFER 148 | 13 => "EINVAL", // ERROR_INVALID_DATA 149 | 87 => "EINVAL", // ERROR_INVALID_PARAMETER 150 | 1464 => "EINVAL", // ERROR_SYMLINK_NOT_SUPPORTED 151 | 10022 => "EINVAL", // WSAEINVAL 152 | 10046 => "EINVAL", // WSAEPFNOSUPPORT 153 | 1102 => "EIO", // ERROR_BEGINNING_OF_MEDIA 154 | 1111 => "EIO", // ERROR_BUS_RESET 155 | 23 => "EIO", // ERROR_CRC 156 | 1166 => "EIO", // ERROR_DEVICE_DOOR_OPEN 157 | 1165 => "EIO", // ERROR_DEVICE_REQUIRES_CLEANING 158 | 1393 => "EIO", // ERROR_DISK_CORRUPT 159 | 1129 => "EIO", // ERROR_EOM_OVERFLOW 160 | 1101 => "EIO", // ERROR_FILEMARK_DETECTED 161 | 31 => "EIO", // ERROR_GEN_FAILURE 162 | 1106 => "EIO", // ERROR_INVALID_BLOCK_LENGTH 163 | 1117 => "EIO", // ERROR_IO_DEVICE 164 | 1104 => "EIO", // ERROR_NO_DATA_DETECTED 165 | 205 => "EIO", // ERROR_NO_SIGNAL_SENT 166 | 110 => "EIO", // ERROR_OPEN_FAILED 167 | 1103 => "EIO", // ERROR_SETMARK_DETECTED 168 | 156 => "EIO", // ERROR_SIGNAL_REFUSED 169 | 10056 => "EISCONN", // WSAEISCONN 170 | 1921 => "ELOOP", // ERROR_CANT_RESOLVE_FILENAME 171 | 4 => "EMFILE", // ERROR_TOO_MANY_OPEN_FILES 172 | 10024 => "EMFILE", // WSAEMFILE 173 | 10040 => "EMSGSIZE", // WSAEMSGSIZE 174 | 206 => "ENAMETOOLONG", // ERROR_FILENAME_EXCED_RANGE 175 | 1231 => "ENETUNREACH", // ERROR_NETWORK_UNREACHABLE 176 | 10051 => "ENETUNREACH", // WSAENETUNREACH 177 | 10055 => "ENOBUFS", // WSAENOBUFS 178 | 161 => "ENOENT", // ERROR_BAD_PATHNAME 179 | 267 => "ENOENT", // ERROR_DIRECTORY 180 | 203 => "ENOENT", // ERROR_ENVVAR_NOT_FOUND 181 | 2 => "ENOENT", // ERROR_FILE_NOT_FOUND 182 | 123 => "ENOENT", // ERROR_INVALID_NAME 183 | 15 => "ENOENT", // ERROR_INVALID_DRIVE 184 | 4392 => "ENOENT", // ERROR_INVALID_REPARSE_DATA 185 | 126 => "ENOENT", // ERROR_MOD_NOT_FOUND 186 | 3 => "ENOENT", // ERROR_PATH_NOT_FOUND 187 | 11001 => "ENOENT", // WSAHOST_NOT_FOUND 188 | 11004 => "ENOENT", // WSANO_DATA 189 | 8 => "ENOMEM", // ERROR_NOT_ENOUGH_MEMORY 190 | 14 => "ENOMEM", // ERROR_OUTOFMEMORY 191 | 82 => "ENOSPC", // ERROR_CANNOT_MAKE 192 | 112 => "ENOSPC", // ERROR_DISK_FULL 193 | 277 => "ENOSPC", // ERROR_EA_TABLE_FULL 194 | 1100 => "ENOSPC", // ERROR_END_OF_MEDIA 195 | 39 => "ENOSPC", // ERROR_HANDLE_DISK_FULL 196 | 2250 => "ENOTCONN", // ERROR_NOT_CONNECTED 197 | 10057 => "ENOTCONN", // WSAENOTCONN 198 | 145 => "ENOTEMPTY", // ERROR_DIR_NOT_EMPTY 199 | 10038 => "ENOTSOCK", // WSAENOTSOCK 200 | 50 => "ENOTSUP", // ERROR_NOT_SUPPORTED 201 | 5 => "EPERM", // ERROR_ACCESS_DENIED 202 | 1314 => "EPERM", // ERROR_PRIVILEGE_NOT_HELD 203 | 230 => "EPIPE", // ERROR_BAD_PIPE 204 | 232 => "EPIPE", // ERROR_NO_DATA 205 | 233 => "EPIPE", // ERROR_PIPE_NOT_CONNECTED 206 | 10058 => "EPIPE", // WSAESHUTDOWN 207 | 10043 => "EPROTONOSUPPORT", // WSAEPROTONOSUPPORT 208 | 19 => "EROFS", // ERROR_WRITE_PROTECT 209 | 121 => "ETIMEDOUT", // ERROR_SEM_TIMEOUT 210 | 10060 => "ETIMEDOUT", // WSAETIMEDOUT 211 | 17 => "EXDEV", // ERROR_NOT_SAME_DEVICE 212 | 1 => "EISDIR", // ERROR_INVALID_FUNCTION 213 | 208 => "E2BIG", // ERROR_META_EXPANSION_TOO_LONG 214 | 10044 => "ESOCKTNOSUPPORT", // WSAESOCKTNOSUPPORT 215 | _ => "", 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "backtrace" 22 | version = "0.3.74" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 25 | dependencies = [ 26 | "addr2line", 27 | "cfg-if", 28 | "libc", 29 | "miniz_oxide", 30 | "object", 31 | "rustc-demangle", 32 | "windows-targets", 33 | ] 34 | 35 | [[package]] 36 | name = "cfg-if" 37 | version = "1.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 40 | 41 | [[package]] 42 | name = "deno_error" 43 | version = "0.7.3" 44 | dependencies = [ 45 | "deno_error_macro", 46 | "libc", 47 | "serde", 48 | "serde_json", 49 | "thiserror", 50 | "tokio", 51 | "url", 52 | ] 53 | 54 | [[package]] 55 | name = "deno_error_macro" 56 | version = "0.7.3" 57 | dependencies = [ 58 | "proc-macro2", 59 | "quote", 60 | "syn", 61 | ] 62 | 63 | [[package]] 64 | name = "displaydoc" 65 | version = "0.2.5" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 68 | dependencies = [ 69 | "proc-macro2", 70 | "quote", 71 | "syn", 72 | ] 73 | 74 | [[package]] 75 | name = "form_urlencoded" 76 | version = "1.2.1" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 79 | dependencies = [ 80 | "percent-encoding", 81 | ] 82 | 83 | [[package]] 84 | name = "gimli" 85 | version = "0.31.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 88 | 89 | [[package]] 90 | name = "icu_collections" 91 | version = "1.5.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 94 | dependencies = [ 95 | "displaydoc", 96 | "yoke", 97 | "zerofrom", 98 | "zerovec", 99 | ] 100 | 101 | [[package]] 102 | name = "icu_locid" 103 | version = "1.5.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 106 | dependencies = [ 107 | "displaydoc", 108 | "litemap", 109 | "tinystr", 110 | "writeable", 111 | "zerovec", 112 | ] 113 | 114 | [[package]] 115 | name = "icu_locid_transform" 116 | version = "1.5.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 119 | dependencies = [ 120 | "displaydoc", 121 | "icu_locid", 122 | "icu_locid_transform_data", 123 | "icu_provider", 124 | "tinystr", 125 | "zerovec", 126 | ] 127 | 128 | [[package]] 129 | name = "icu_locid_transform_data" 130 | version = "1.5.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 133 | 134 | [[package]] 135 | name = "icu_normalizer" 136 | version = "1.5.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 139 | dependencies = [ 140 | "displaydoc", 141 | "icu_collections", 142 | "icu_normalizer_data", 143 | "icu_properties", 144 | "icu_provider", 145 | "smallvec", 146 | "utf16_iter", 147 | "utf8_iter", 148 | "write16", 149 | "zerovec", 150 | ] 151 | 152 | [[package]] 153 | name = "icu_normalizer_data" 154 | version = "1.5.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 157 | 158 | [[package]] 159 | name = "icu_properties" 160 | version = "1.5.1" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 163 | dependencies = [ 164 | "displaydoc", 165 | "icu_collections", 166 | "icu_locid_transform", 167 | "icu_properties_data", 168 | "icu_provider", 169 | "tinystr", 170 | "zerovec", 171 | ] 172 | 173 | [[package]] 174 | name = "icu_properties_data" 175 | version = "1.5.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 178 | 179 | [[package]] 180 | name = "icu_provider" 181 | version = "1.5.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 184 | dependencies = [ 185 | "displaydoc", 186 | "icu_locid", 187 | "icu_provider_macros", 188 | "stable_deref_trait", 189 | "tinystr", 190 | "writeable", 191 | "yoke", 192 | "zerofrom", 193 | "zerovec", 194 | ] 195 | 196 | [[package]] 197 | name = "icu_provider_macros" 198 | version = "1.5.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 201 | dependencies = [ 202 | "proc-macro2", 203 | "quote", 204 | "syn", 205 | ] 206 | 207 | [[package]] 208 | name = "idna" 209 | version = "1.0.3" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 212 | dependencies = [ 213 | "idna_adapter", 214 | "smallvec", 215 | "utf8_iter", 216 | ] 217 | 218 | [[package]] 219 | name = "idna_adapter" 220 | version = "1.2.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 223 | dependencies = [ 224 | "icu_normalizer", 225 | "icu_properties", 226 | ] 227 | 228 | [[package]] 229 | name = "itoa" 230 | version = "1.0.11" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 233 | 234 | [[package]] 235 | name = "libc" 236 | version = "0.2.162" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 239 | 240 | [[package]] 241 | name = "litemap" 242 | version = "0.7.3" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" 245 | 246 | [[package]] 247 | name = "memchr" 248 | version = "2.7.4" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 251 | 252 | [[package]] 253 | name = "miniz_oxide" 254 | version = "0.8.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 257 | dependencies = [ 258 | "adler2", 259 | ] 260 | 261 | [[package]] 262 | name = "object" 263 | version = "0.36.5" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 266 | dependencies = [ 267 | "memchr", 268 | ] 269 | 270 | [[package]] 271 | name = "percent-encoding" 272 | version = "2.3.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 275 | 276 | [[package]] 277 | name = "pin-project-lite" 278 | version = "0.2.15" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 281 | 282 | [[package]] 283 | name = "proc-macro2" 284 | version = "1.0.89" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 287 | dependencies = [ 288 | "unicode-ident", 289 | ] 290 | 291 | [[package]] 292 | name = "quote" 293 | version = "1.0.37" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 296 | dependencies = [ 297 | "proc-macro2", 298 | ] 299 | 300 | [[package]] 301 | name = "rustc-demangle" 302 | version = "0.1.24" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 305 | 306 | [[package]] 307 | name = "ryu" 308 | version = "1.0.18" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 311 | 312 | [[package]] 313 | name = "serde" 314 | version = "1.0.215" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 317 | dependencies = [ 318 | "serde_derive", 319 | ] 320 | 321 | [[package]] 322 | name = "serde_derive" 323 | version = "1.0.215" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 326 | dependencies = [ 327 | "proc-macro2", 328 | "quote", 329 | "syn", 330 | ] 331 | 332 | [[package]] 333 | name = "serde_json" 334 | version = "1.0.132" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 337 | dependencies = [ 338 | "itoa", 339 | "memchr", 340 | "ryu", 341 | "serde", 342 | ] 343 | 344 | [[package]] 345 | name = "smallvec" 346 | version = "1.13.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 349 | 350 | [[package]] 351 | name = "stable_deref_trait" 352 | version = "1.2.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 355 | 356 | [[package]] 357 | name = "syn" 358 | version = "2.0.87" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 361 | dependencies = [ 362 | "proc-macro2", 363 | "quote", 364 | "unicode-ident", 365 | ] 366 | 367 | [[package]] 368 | name = "synstructure" 369 | version = "0.13.1" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 372 | dependencies = [ 373 | "proc-macro2", 374 | "quote", 375 | "syn", 376 | ] 377 | 378 | [[package]] 379 | name = "thiserror" 380 | version = "2.0.3" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" 383 | dependencies = [ 384 | "thiserror-impl", 385 | ] 386 | 387 | [[package]] 388 | name = "thiserror-impl" 389 | version = "2.0.3" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" 392 | dependencies = [ 393 | "proc-macro2", 394 | "quote", 395 | "syn", 396 | ] 397 | 398 | [[package]] 399 | name = "tinystr" 400 | version = "0.7.6" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 403 | dependencies = [ 404 | "displaydoc", 405 | "zerovec", 406 | ] 407 | 408 | [[package]] 409 | name = "tokio" 410 | version = "1.41.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" 413 | dependencies = [ 414 | "backtrace", 415 | "pin-project-lite", 416 | ] 417 | 418 | [[package]] 419 | name = "unicode-ident" 420 | version = "1.0.13" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 423 | 424 | [[package]] 425 | name = "url" 426 | version = "2.5.3" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" 429 | dependencies = [ 430 | "form_urlencoded", 431 | "idna", 432 | "percent-encoding", 433 | ] 434 | 435 | [[package]] 436 | name = "utf16_iter" 437 | version = "1.0.5" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 440 | 441 | [[package]] 442 | name = "utf8_iter" 443 | version = "1.0.4" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 446 | 447 | [[package]] 448 | name = "windows-targets" 449 | version = "0.52.6" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 452 | dependencies = [ 453 | "windows_aarch64_gnullvm", 454 | "windows_aarch64_msvc", 455 | "windows_i686_gnu", 456 | "windows_i686_gnullvm", 457 | "windows_i686_msvc", 458 | "windows_x86_64_gnu", 459 | "windows_x86_64_gnullvm", 460 | "windows_x86_64_msvc", 461 | ] 462 | 463 | [[package]] 464 | name = "windows_aarch64_gnullvm" 465 | version = "0.52.6" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 468 | 469 | [[package]] 470 | name = "windows_aarch64_msvc" 471 | version = "0.52.6" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 474 | 475 | [[package]] 476 | name = "windows_i686_gnu" 477 | version = "0.52.6" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 480 | 481 | [[package]] 482 | name = "windows_i686_gnullvm" 483 | version = "0.52.6" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 486 | 487 | [[package]] 488 | name = "windows_i686_msvc" 489 | version = "0.52.6" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 492 | 493 | [[package]] 494 | name = "windows_x86_64_gnu" 495 | version = "0.52.6" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 498 | 499 | [[package]] 500 | name = "windows_x86_64_gnullvm" 501 | version = "0.52.6" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 504 | 505 | [[package]] 506 | name = "windows_x86_64_msvc" 507 | version = "0.52.6" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 510 | 511 | [[package]] 512 | name = "write16" 513 | version = "1.0.0" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 516 | 517 | [[package]] 518 | name = "writeable" 519 | version = "0.5.5" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 522 | 523 | [[package]] 524 | name = "yoke" 525 | version = "0.7.4" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" 528 | dependencies = [ 529 | "serde", 530 | "stable_deref_trait", 531 | "yoke-derive", 532 | "zerofrom", 533 | ] 534 | 535 | [[package]] 536 | name = "yoke-derive" 537 | version = "0.7.4" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" 540 | dependencies = [ 541 | "proc-macro2", 542 | "quote", 543 | "syn", 544 | "synstructure", 545 | ] 546 | 547 | [[package]] 548 | name = "zerofrom" 549 | version = "0.1.4" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" 552 | dependencies = [ 553 | "zerofrom-derive", 554 | ] 555 | 556 | [[package]] 557 | name = "zerofrom-derive" 558 | version = "0.1.4" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" 561 | dependencies = [ 562 | "proc-macro2", 563 | "quote", 564 | "syn", 565 | "synstructure", 566 | ] 567 | 568 | [[package]] 569 | name = "zerovec" 570 | version = "0.10.4" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 573 | dependencies = [ 574 | "yoke", 575 | "zerofrom", 576 | "zerovec-derive", 577 | ] 578 | 579 | [[package]] 580 | name = "zerovec-derive" 581 | version = "0.10.3" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 584 | dependencies = [ 585 | "proc-macro2", 586 | "quote", 587 | "syn", 588 | ] 589 | -------------------------------------------------------------------------------- /macros/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 | use proc_macro2::Ident; 7 | use proc_macro2::TokenStream; 8 | use quote::format_ident; 9 | use quote::quote; 10 | use quote::ToTokens; 11 | use syn::parse::Parse; 12 | use syn::parse::ParseStream; 13 | use syn::parse2; 14 | use syn::spanned::Spanned; 15 | use syn::Attribute; 16 | use syn::Data; 17 | use syn::DeriveInput; 18 | use syn::Error; 19 | use syn::Field; 20 | use syn::Fields; 21 | use syn::LitStr; 22 | use syn::Member; 23 | use syn::Meta; 24 | use syn::Token; 25 | use syn::Type; 26 | 27 | const IDENTIFIABLE_ERRORS: [&str; 7] = [ 28 | "Error", 29 | "RangeError", 30 | "TypeError", 31 | "SyntaxError", 32 | "URIError", 33 | "ReferenceError", 34 | "NotSupportedError", 35 | ]; 36 | 37 | #[proc_macro_derive(JsError, attributes(class, property, properties, inherit))] 38 | pub fn derive_js_error( 39 | item: proc_macro::TokenStream, 40 | ) -> proc_macro::TokenStream { 41 | match js_error(item.into()) { 42 | Ok(output) => output.into(), 43 | Err(err) => err.into_compile_error().into(), 44 | } 45 | } 46 | 47 | fn js_error(item: TokenStream) -> Result { 48 | let input = parse2::(item)?; 49 | 50 | let additional_properties = input 51 | .attrs 52 | .iter() 53 | .filter_map(|attr| { 54 | if attr.path().is_ident("property") { 55 | Some(attr.parse_args()) 56 | } else { 57 | None 58 | } 59 | }) 60 | .collect::, Error>>()?; 61 | 62 | let (class, out_properties) = match input.data { 63 | Data::Enum(data) => { 64 | let top_class_attr = input 65 | .attrs 66 | .into_iter() 67 | .find_map(|attr| ClassAttrValue::from_attribute(attr).transpose()) 68 | .transpose()?; 69 | if let Some(top_class_attr) = &top_class_attr { 70 | if matches!(top_class_attr, ClassAttrValue::Inherit(_)) { 71 | return Err(Error::new( 72 | top_class_attr.to_tokens(&None).unwrap_err().span(), 73 | "top level class attribute cannot be inherit", 74 | )); 75 | } 76 | } 77 | 78 | let mut get_class = vec![]; 79 | let mut get_properties = vec![]; 80 | 81 | for variant in data.variants { 82 | let variant_additional_properties = variant 83 | .attrs 84 | .iter() 85 | .filter_map(|attr| { 86 | if attr.path().is_ident("property") { 87 | Some(attr.parse_args()) 88 | } else { 89 | None 90 | } 91 | }) 92 | .collect::, Error>>()?; 93 | 94 | let inherit_properties = variant 95 | .attrs 96 | .iter() 97 | .find_map(|attr| { 98 | if attr.path().is_ident("properties") { 99 | Some(attr.parse_args::()) 100 | } else { 101 | None 102 | } 103 | }) 104 | .transpose()?; 105 | 106 | let class_attr = variant 107 | .attrs 108 | .into_iter() 109 | .find_map(|attr| ClassAttrValue::from_attribute(attr).transpose()) 110 | .unwrap_or_else(|| { 111 | top_class_attr.clone().ok_or_else(|| { 112 | Error::new(variant.ident.span(), "class attribute is missing") 113 | }) 114 | })?; 115 | 116 | let ( 117 | class, 118 | properties, 119 | _inherit_class_member, 120 | inherit_property_member, 121 | parsed_properties, 122 | ) = handle_variant_or_struct( 123 | inherit_properties, 124 | class_attr, 125 | variant_additional_properties, 126 | variant.fields, 127 | )?; 128 | 129 | let variant_ident = variant.ident; 130 | 131 | let class_match_arm_identifiers = { 132 | let mut parsed_properties = parsed_properties 133 | .iter() 134 | .enumerate() 135 | .map(|(i, property)| { 136 | let i = format_ident!("__{i}"); 137 | let member = &property.ident; 138 | quote!(#member: #i,) 139 | }) 140 | .collect::>(); 141 | 142 | if let Some((member, _)) = &_inherit_class_member { 143 | parsed_properties.push(quote!(#member: inherit,)); 144 | } 145 | 146 | parsed_properties 147 | }; 148 | 149 | let class_match_arm = 150 | quote!(Self::#variant_ident { #(#class_match_arm_identifiers)* .. }); 151 | 152 | let match_arm_identifiers = { 153 | let mut parsed_properties = parsed_properties 154 | .into_iter() 155 | .enumerate() 156 | .map(|(i, property)| { 157 | let i = format_ident!("__{i}"); 158 | let member = property.ident; 159 | quote!(#member: #i,) 160 | }) 161 | .collect::>(); 162 | 163 | if let Some((member, _)) = &inherit_property_member { 164 | parsed_properties.push(quote!(#member: inherit,)); 165 | } 166 | 167 | parsed_properties 168 | }; 169 | 170 | let match_arm = 171 | quote!(Self::#variant_ident { #(#match_arm_identifiers)* .. }); 172 | 173 | get_class.push(quote! { 174 | #class_match_arm => #class, 175 | }); 176 | 177 | let properties = 178 | properties.unwrap_or_else(|| quote!(std::iter::empty())); 179 | get_properties.push(quote! { 180 | #match_arm => Box::new(#properties), 181 | }); 182 | } 183 | 184 | ( 185 | quote! { 186 | match self { 187 | #(#get_class)* 188 | } 189 | }, 190 | Some(quote! { 191 | match self { 192 | #(#get_properties)* 193 | } 194 | }), 195 | ) 196 | } 197 | Data::Struct(data) => { 198 | let inherit_properties = input 199 | .attrs 200 | .iter() 201 | .find_map(|attr| { 202 | if attr.path().is_ident("properties") { 203 | Some(attr.parse_args::()) 204 | } else { 205 | None 206 | } 207 | }) 208 | .transpose()?; 209 | 210 | let class_attr = input 211 | .attrs 212 | .into_iter() 213 | .find_map(|attr| ClassAttrValue::from_attribute(attr).transpose()) 214 | .unwrap_or_else(|| { 215 | if data.fields.len() == 1 { 216 | Ok(ClassAttrValue::Inherit(kw::inherit::default())) 217 | } else { 218 | Err(Error::new( 219 | input.ident.span(), 220 | "class attribute is missing and could not be inferred", 221 | )) 222 | } 223 | })?; 224 | 225 | let ( 226 | class, 227 | properties, 228 | inherit_class_member, 229 | inherit_property_member, 230 | parsed_properties, 231 | ) = handle_variant_or_struct( 232 | inherit_properties, 233 | class_attr, 234 | vec![], 235 | data.fields, 236 | )?; 237 | 238 | let class_specifier_var = inherit_class_member.map(|(member, _)| { 239 | quote! { 240 | let inherit = &self.#member; 241 | } 242 | }); 243 | 244 | let property_specifier_var = 245 | inherit_property_member.map(|(member, _)| { 246 | quote! { 247 | let inherit = &self.#member; 248 | } 249 | }); 250 | 251 | let parsed_properties = parsed_properties 252 | .into_iter() 253 | .enumerate() 254 | .map(|(i, property)| { 255 | let i = format_ident!("__{i}"); 256 | let member = property.ident; 257 | quote! { 258 | let #i = &self.#member; 259 | } 260 | }) 261 | .collect::>(); 262 | 263 | let out_properties = if property_specifier_var.is_none() 264 | && parsed_properties.is_empty() 265 | && properties.is_none() 266 | { 267 | None 268 | } else { 269 | let properties = 270 | properties.unwrap_or_else(|| quote!(std::iter::empty())); 271 | Some(quote! { 272 | Box::new({ 273 | #property_specifier_var 274 | #(#parsed_properties)* 275 | #properties 276 | }) 277 | }) 278 | }; 279 | 280 | ( 281 | quote! { 282 | #class_specifier_var 283 | #class 284 | }, 285 | out_properties, 286 | ) 287 | } 288 | Data::Union(_) => { 289 | return Err(Error::new(input.span(), "Unions are not supported")) 290 | } 291 | }; 292 | 293 | let properties = if !additional_properties.is_empty() { 294 | let additional_properties = additional_properties 295 | .into_iter() 296 | .map(|AdditionalProperty { name, value, .. }| quote!((#name.into(), ::deno_error::PropertyValue::from(#value)))); 297 | 298 | let additional_properties = 299 | quote!([#(#additional_properties),*].into_iter()); 300 | if let Some(out_properties) = out_properties { 301 | quote!(Box::new({ *#out_properties }.chain(#additional_properties))) 302 | } else { 303 | quote!(Box::new(#additional_properties)) 304 | } 305 | } else { 306 | let out_properties = 307 | out_properties.unwrap_or_else(|| quote!(Box::new(std::iter::empty()))); 308 | quote!(#out_properties) 309 | }; 310 | 311 | let ident = input.ident; 312 | 313 | Ok(quote! { 314 | #[allow(unused_qualifications)] 315 | impl ::deno_error::JsErrorClass for #ident { 316 | fn get_class(&self) -> ::std::borrow::Cow<'static, str> { 317 | #class 318 | } 319 | fn get_message(&self) -> ::std::borrow::Cow<'static, str> { 320 | self.to_string().into() 321 | } 322 | fn get_additional_properties( 323 | &self 324 | ) -> ::deno_error::AdditionalProperties { 325 | #properties 326 | } 327 | fn get_ref(&self) -> &(dyn ::std::error::Error + Send + Sync + 'static) { 328 | self 329 | } 330 | } 331 | }) 332 | } 333 | 334 | #[allow(clippy::type_complexity)] 335 | fn handle_variant_or_struct( 336 | inherit_properties: Option, 337 | class_attr: ClassAttrValue, 338 | additional_properties: Vec, 339 | fields: Fields, 340 | ) -> Result< 341 | ( 342 | TokenStream, 343 | Option, 344 | Option<(Member, TokenStream)>, 345 | Option<(Member, TokenStream)>, 346 | Vec, 347 | ), 348 | Error, 349 | > { 350 | let parsed_properties = get_properties_from_fields(&fields)?; 351 | 352 | let inherit_properties = 353 | inherit_properties.unwrap_or_else(|| match &class_attr { 354 | ClassAttrValue::Inherit(kw) => InheritProperties::Inherit(*kw), 355 | _ => InheritProperties::NoInherit(Default::default()), 356 | }); 357 | 358 | let properties = if !parsed_properties.is_empty() { 359 | let properties = parsed_properties 360 | .iter() 361 | .enumerate() 362 | .map(|(i, property)| { 363 | let i = format_ident!("__{i}"); 364 | let ident_str = &property.name; 365 | 366 | quote! { 367 | (::std::borrow::Cow::Borrowed(#ident_str), #i.into()) 368 | } 369 | }) 370 | .collect::>(); 371 | 372 | Some(quote!([#(#properties),*].into_iter())) 373 | } else { 374 | None 375 | }; 376 | 377 | let (inherit_class_member, inherit_property_member) = match fields { 378 | Fields::Named(fields_named) => { 379 | let class_field = if fields_named.named.len() == 1 380 | && matches!(class_attr, ClassAttrValue::Inherit(_)) 381 | { 382 | fields_named.named.first() 383 | } else { 384 | fields_named.named.iter().find(get_inherit_attr_field) 385 | }; 386 | 387 | let class_field = class_field.map(|field| { 388 | ( 389 | Member::Named(field.ident.clone().unwrap()), 390 | field_inherit_reference(field), 391 | ) 392 | }); 393 | 394 | let property_field = if fields_named.named.len() == 1 395 | && matches!(inherit_properties, InheritProperties::Inherit(_)) 396 | { 397 | fields_named.named.first() 398 | } else { 399 | fields_named.named.iter().find(get_inherit_attr_field) 400 | }; 401 | 402 | let property_field = property_field.map(|field| { 403 | ( 404 | Member::Named(field.ident.clone().unwrap()), 405 | field_inherit_reference(field), 406 | ) 407 | }); 408 | 409 | (class_field, property_field) 410 | } 411 | Fields::Unnamed(fields_unnamed) => { 412 | let class_field = if fields_unnamed.unnamed.len() == 1 413 | && matches!(class_attr, ClassAttrValue::Inherit(_)) 414 | { 415 | fields_unnamed.unnamed.first().map(|field| (0, field)) 416 | } else { 417 | fields_unnamed 418 | .unnamed 419 | .iter() 420 | .enumerate() 421 | .find(|(_, field)| get_inherit_attr_field(field)) 422 | }; 423 | 424 | let class_field = class_field.map(|(i, field)| { 425 | ( 426 | Member::Unnamed(syn::Index::from(i)), 427 | field_inherit_reference(field), 428 | ) 429 | }); 430 | 431 | let property_field = if fields_unnamed.unnamed.len() == 1 432 | && matches!(inherit_properties, InheritProperties::Inherit(_)) 433 | { 434 | fields_unnamed.unnamed.first().map(|field| (0, field)) 435 | } else { 436 | fields_unnamed 437 | .unnamed 438 | .iter() 439 | .enumerate() 440 | .find(|(_, field)| get_inherit_attr_field(field)) 441 | }; 442 | 443 | let property_field = property_field.map(|(i, field)| { 444 | ( 445 | Member::Unnamed(syn::Index::from(i)), 446 | field_inherit_reference(field), 447 | ) 448 | }); 449 | 450 | (class_field, property_field) 451 | } 452 | Fields::Unit => (None, None), 453 | }; 454 | 455 | let class = class_attr.to_tokens(&inherit_class_member)?; 456 | 457 | let properties = if let Some((_, tokens)) = &inherit_property_member { 458 | let inherited_properties = quote!(::deno_error::JsErrorClass::get_additional_properties( 459 | #tokens 460 | )); 461 | 462 | if let Some(properties) = properties { 463 | Some(quote!(#properties.chain(#inherited_properties))) 464 | } else { 465 | Some(inherited_properties) 466 | } 467 | } else { 468 | properties 469 | }; 470 | 471 | let properties = if !additional_properties.is_empty() { 472 | let additional_properties = additional_properties 473 | .into_iter() 474 | .map(|AdditionalProperty { name, value, .. }| { 475 | // Check if the value is a literal number 476 | match value { 477 | syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(int_lit), .. }) => { 478 | quote!((#name.into(), ::deno_error::PropertyValue::Number(#int_lit as f64))) 479 | } 480 | syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Float(float_lit), .. }) => { 481 | quote!((#name.into(), ::deno_error::PropertyValue::Number(#float_lit))) 482 | } 483 | _ => { 484 | quote!((#name.into(), #value.into())) 485 | } 486 | } 487 | }); 488 | 489 | let additional_properties = 490 | quote!([#(#additional_properties),*].into_iter()); 491 | 492 | if let Some(properties) = properties { 493 | Some(quote!(#properties.chain(#additional_properties))) 494 | } else { 495 | Some(additional_properties) 496 | } 497 | } else { 498 | properties 499 | }; 500 | 501 | Ok(( 502 | class, 503 | properties, 504 | inherit_class_member, 505 | inherit_property_member, 506 | parsed_properties, 507 | )) 508 | } 509 | 510 | fn get_inherit_attr_field(field: &&Field) -> bool { 511 | field 512 | .attrs 513 | .iter() 514 | .any(|attr| attr.path().is_ident("inherit")) 515 | } 516 | 517 | mod kw { 518 | syn::custom_keyword!(class); 519 | syn::custom_keyword!(property); 520 | syn::custom_keyword!(inherit); 521 | syn::custom_keyword!(no_inherit); 522 | } 523 | 524 | #[derive(Debug, Clone)] 525 | enum ClassAttrValue { 526 | Lit(syn::LitStr), 527 | Ident(Ident), 528 | Inherit(kw::inherit), 529 | } 530 | 531 | impl ClassAttrValue { 532 | fn from_attribute(attr: Attribute) -> Result, Error> { 533 | if attr.path().is_ident("class") { 534 | let list = attr.meta.require_list()?; 535 | let value = list.parse_args::()?; 536 | 537 | match &value { 538 | ClassAttrValue::Lit(lit) => { 539 | if IDENTIFIABLE_ERRORS.contains(&lit.value().as_str()) { 540 | return Err(Error::new( 541 | lit.span(), 542 | format!("An identifier can be used instead of '{}'", lit.value()), 543 | )); 544 | } 545 | } 546 | ClassAttrValue::Ident(ident) => { 547 | let ident_str = ident.to_string(); 548 | 549 | // needs to call to_lowercase to handle _ since checking if its both 550 | // lower or uppercase returns false 551 | if ident_str.to_lowercase() != ident_str { 552 | return Err(Error::new( 553 | ident.span(), 554 | "Identifier passed is not lowercase", 555 | )); 556 | } 557 | } 558 | ClassAttrValue::Inherit(_) => {} 559 | } 560 | 561 | return Ok(Some(value)); 562 | } 563 | 564 | Ok(None) 565 | } 566 | 567 | fn to_tokens( 568 | &self, 569 | inherit_member: &Option<(Member, TokenStream)>, 570 | ) -> Result { 571 | let class_tokens = match self { 572 | ClassAttrValue::Lit(lit) => quote!(::std::borrow::Cow::Borrowed(#lit)), 573 | ClassAttrValue::Ident(ident) => { 574 | let error_name = 575 | format_ident!("{}_ERROR", ident.to_string().to_uppercase()); 576 | quote!(::std::borrow::Cow::Borrowed(::deno_error::builtin_classes::#error_name)) 577 | } 578 | ClassAttrValue::Inherit(inherit) => { 579 | let (_, tokens) = inherit_member.as_ref().ok_or_else(|| { 580 | Error::new( 581 | inherit.span, 582 | "class attribute was set to inherit, but multiple fields are available and none was marked as inherit", 583 | ) 584 | })?; 585 | 586 | quote!(::deno_error::JsErrorClass::get_class(#tokens)) 587 | } 588 | }; 589 | 590 | Ok(class_tokens) 591 | } 592 | } 593 | 594 | impl Parse for ClassAttrValue { 595 | fn parse(input: ParseStream) -> syn::Result { 596 | let lookahead = input.lookahead1(); 597 | 598 | if lookahead.peek(syn::LitStr) { 599 | Ok(Self::Lit(input.parse()?)) 600 | } else if lookahead.peek(kw::inherit) { 601 | Ok(Self::Inherit(input.parse()?)) 602 | } else if lookahead.peek(syn::Ident) { 603 | Ok(Self::Ident(input.parse()?)) 604 | } else if lookahead.peek(Token![type]) { 605 | let type_token = input.parse::()?; 606 | Ok(Self::Ident(Ident::new("type", type_token.span))) 607 | } else { 608 | Err(lookahead.error()) 609 | } 610 | } 611 | } 612 | 613 | #[derive(Debug)] 614 | struct ParsedFieldProperty { 615 | ident: Member, 616 | name: String, 617 | } 618 | 619 | fn get_properties_from_fields( 620 | fields: &Fields, 621 | ) -> Result, Error> { 622 | const PROPERTY_IDENT: &str = "property"; 623 | let mut out_fields = vec![]; 624 | 625 | match fields { 626 | Fields::Named(named) => { 627 | for field in &named.named { 628 | for attr in &field.attrs { 629 | if attr.path().is_ident(PROPERTY_IDENT) { 630 | let name = match &attr.meta { 631 | Meta::Path(_) => None, 632 | Meta::List(list) => { 633 | return Err(Error::new( 634 | list.delimiter.span().open(), 635 | "expected `=`", 636 | )); 637 | } 638 | Meta::NameValue(meta) => { 639 | Some(parse2::(meta.value.to_token_stream())?.value()) 640 | } 641 | }; 642 | 643 | let ident = field.ident.clone().unwrap(); 644 | let name = name.unwrap_or_else(|| ident.to_string()); 645 | let ident = Member::Named(field.ident.clone().unwrap()); 646 | out_fields.push(ParsedFieldProperty { name, ident }); 647 | 648 | break; 649 | } 650 | } 651 | } 652 | } 653 | Fields::Unnamed(unnamed) => { 654 | for (i, field) in unnamed.unnamed.iter().enumerate() { 655 | for attr in &field.attrs { 656 | if attr.path().is_ident(PROPERTY_IDENT) { 657 | let name_value = attr.meta.require_name_value()?; 658 | let name = 659 | parse2::(name_value.value.to_token_stream())?.value(); 660 | 661 | let ident = Member::Unnamed(syn::Index::from(i)); 662 | out_fields.push(ParsedFieldProperty { name, ident }); 663 | 664 | break; 665 | } 666 | } 667 | } 668 | } 669 | Fields::Unit => {} 670 | } 671 | 672 | Ok(out_fields) 673 | } 674 | 675 | #[derive(Debug)] 676 | struct AdditionalProperty { 677 | name: LitStr, 678 | _eq: Token![=], 679 | value: syn::Expr, 680 | } 681 | 682 | impl Parse for AdditionalProperty { 683 | fn parse(input: ParseStream) -> syn::Result { 684 | Ok(Self { 685 | name: input.parse()?, 686 | _eq: input.parse()?, 687 | value: input.parse()?, 688 | }) 689 | } 690 | } 691 | 692 | #[derive(Debug)] 693 | #[allow(dead_code)] 694 | enum InheritProperties { 695 | Inherit(kw::inherit), 696 | NoInherit(kw::no_inherit), 697 | } 698 | 699 | impl Parse for InheritProperties { 700 | fn parse(input: ParseStream) -> syn::Result { 701 | let lookahead = input.lookahead1(); 702 | 703 | if lookahead.peek(kw::inherit) { 704 | Ok(InheritProperties::Inherit(input.parse()?)) 705 | } else if lookahead.peek(kw::no_inherit) { 706 | Ok(InheritProperties::NoInherit(input.parse()?)) 707 | } else { 708 | Err(lookahead.error()) 709 | } 710 | } 711 | } 712 | 713 | fn field_inherit_reference(field: &Field) -> TokenStream { 714 | let is_wrapped = match &field.ty { 715 | Type::Path(e) => { 716 | if let Some(first) = e.path.segments.last() { 717 | matches!(first.ident.to_string().as_str(), "Box" | "Rc" | "Arc") 718 | } else { 719 | false 720 | } 721 | } 722 | _ => false, 723 | }; 724 | 725 | if is_wrapped { 726 | quote!(&**inherit) 727 | } else { 728 | quote!(inherit) 729 | } 730 | } 731 | -------------------------------------------------------------------------------- /src/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 | 159 | mod error_codes; 160 | 161 | pub use deno_error_macro::*; 162 | pub use error_codes::*; 163 | use std::any::Any; 164 | use 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. 168 | pub 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 | } 180 | use builtin_classes::*; 181 | 182 | /// Represents a property value that can be either a string or a number 183 | #[derive(Debug, Clone, PartialEq)] 184 | pub enum PropertyValue { 185 | String(Cow<'static, str>), 186 | Number(f64), 187 | } 188 | 189 | impl 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 | 198 | impl From for PropertyValue { 199 | fn from(s: String) -> Self { 200 | PropertyValue::String(Cow::Owned(s)) 201 | } 202 | } 203 | 204 | impl From<&'static str> for PropertyValue { 205 | fn from(s: &'static str) -> Self { 206 | PropertyValue::String(Cow::Borrowed(s)) 207 | } 208 | } 209 | 210 | impl From for PropertyValue { 211 | fn from(n: f64) -> Self { 212 | PropertyValue::Number(n) 213 | } 214 | } 215 | 216 | impl From<&f64> for PropertyValue { 217 | fn from(n: &f64) -> Self { 218 | PropertyValue::Number(*n) 219 | } 220 | } 221 | 222 | impl From for PropertyValue { 223 | fn from(n: i32) -> Self { 224 | PropertyValue::Number(n as f64) 225 | } 226 | } 227 | 228 | impl From<&i32> for PropertyValue { 229 | fn from(n: &i32) -> Self { 230 | PropertyValue::Number(*n as f64) 231 | } 232 | } 233 | 234 | impl From for PropertyValue { 235 | fn from(n: u32) -> Self { 236 | PropertyValue::Number(n as f64) 237 | } 238 | } 239 | 240 | impl From<&u32> for PropertyValue { 241 | fn from(n: &u32) -> Self { 242 | PropertyValue::Number(*n as f64) 243 | } 244 | } 245 | 246 | pub type AdditionalProperties = 247 | Box, 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. 254 | pub 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] 288 | macro_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> { 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 | 341 | impl JsErrorClass for Box { 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 | 359 | impl 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 | 414 | impl 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 | 435 | impl 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 | 453 | impl 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 | 471 | impl 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 | 489 | impl 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"))] 508 | impl 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::()) 517 | .map(|e| e.get_class()) 518 | .unwrap_or_else(|| Cow::Borrowed("Error")), 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")] 539 | impl 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")] 558 | impl JsErrorClass 559 | for tokio::sync::mpsc::error::SendError 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")] 579 | impl 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")] 598 | impl 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 | 616 | enum JsErrorBoxInner { 617 | Standalone { 618 | class: Cow<'static, str>, 619 | message: Cow<'static, str>, 620 | }, 621 | Wrap(Box), 622 | } 623 | 624 | pub struct JsErrorBox(JsErrorBoxInner); 625 | 626 | impl 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 | 644 | impl 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 | 650 | impl 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 | 659 | impl 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 | 689 | impl JsErrorBox { 690 | pub fn new( 691 | class: impl Into>, 692 | message: impl Into>, 693 | ) -> Self { 694 | Self(JsErrorBoxInner::Standalone { 695 | class: class.into(), 696 | message: message.into(), 697 | }) 698 | } 699 | 700 | pub fn from_err(err: T) -> Self { 701 | Self(JsErrorBoxInner::Wrap(Box::new(err))) 702 | } 703 | 704 | pub fn generic(message: impl Into>) -> JsErrorBox { 705 | Self::new(GENERIC_ERROR, message) 706 | } 707 | 708 | pub fn type_error(message: impl Into>) -> JsErrorBox { 709 | Self::new(TYPE_ERROR, message) 710 | } 711 | 712 | pub fn range_error(message: impl Into>) -> JsErrorBox { 713 | Self::new(RANGE_ERROR, message) 714 | } 715 | 716 | pub fn uri_error(message: impl Into>) -> 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)] 736 | mod 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 | } 758 | --------------------------------------------------------------------------------