Skip to main content

workflow_wasm/
jserror.rs

1//! Structures for handling JavaScript errors. Specifically this module
2//! provides a `JsErrorData` struct which is used to extract information
3//! from a `JsValue` that represents a JavaScript error.
4
5use std::sync::Arc;
6use wasm_bindgen::prelude::*;
7use workflow_core::sendable::Sendable;
8
9/// Extension trait that exposes the `message` property of a [`JsError`].
10pub trait JsErrorExtension {
11    /// Returns the `message` property of the error as a string.
12    fn message(&self) -> String;
13}
14
15impl JsErrorExtension for JsError {
16    fn message(&self) -> String {
17        let inner = JsValue::from(self.clone());
18        let msg = js_sys::Reflect::get(&inner, &JsValue::from_str("message"))
19            .expect("unable to get error message");
20        msg.as_string()
21            .expect("unable to convert error message to string")
22    }
23}
24
25/// Extension trait for reading the `message` property from a [`JsValue`]
26/// that represents a JavaScript error.
27pub trait JsValueErrorTrait {
28    /// Returns the `message` property of the error value as a string.
29    fn message(&self) -> String;
30}
31
32impl JsValueErrorTrait for JsValue {
33    fn message(&self) -> String {
34        let msg = js_sys::Reflect::get(self, &JsValue::from_str("message"))
35            .expect("unable to get error message");
36        msg.as_string()
37            .expect("unable to convert error message to string")
38    }
39}
40
41struct Inner {
42    name: Option<String>,
43    message: Option<String>,
44    cause: Option<String>,
45    stack: Option<String>,
46    code: Option<String>,
47    // origin
48    origin: Sendable<JsValue>,
49}
50
51/// Owned, cloneable snapshot of a JavaScript error, capturing its `name`,
52/// `message`, `cause`, `stack` and `code` fields along with the original
53/// `JsValue`. Implements [`std::error::Error`] so it can be used in Rust
54/// error handling.
55#[derive(Clone)]
56pub struct JsErrorData {
57    inner: Arc<Inner>,
58}
59
60impl std::error::Error for JsErrorData {}
61
62impl JsErrorData {
63    /// The error name (e.g. `"TypeError"`), if present on the JavaScript error.
64    pub fn name(&self) -> &Option<String> {
65        &self.inner.name
66    }
67
68    /// The human-readable error message, if present on the JavaScript error.
69    pub fn message(&self) -> &Option<String> {
70        &self.inner.message
71    }
72
73    /// The underlying cause of the error, if present on the JavaScript error.
74    pub fn cause(&self) -> &Option<String> {
75        &self.inner.cause
76    }
77
78    /// The captured stack trace, if present on the JavaScript error.
79    pub fn stack(&self) -> &Option<String> {
80        &self.inner.stack
81    }
82
83    /// The error code, if present on the JavaScript error.
84    pub fn code(&self) -> &Option<String> {
85        &self.inner.code
86    }
87}
88
89impl std::fmt::Debug for JsErrorData {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("JsErrorData")
92            .field("name", &self.inner.name)
93            .field("message", &self.inner.message)
94            .field("cause", &self.inner.cause)
95            .field("stack", &self.inner.stack)
96            .field("code", &self.inner.code)
97            .finish()
98    }
99}
100
101impl std::fmt::Display for JsErrorData {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        write!(
104            f,
105            "{}",
106            self.inner
107                .message
108                .clone()
109                .unwrap_or_else(|| "N/A".to_string())
110        )
111    }
112}
113
114impl From<JsValue> for JsErrorData {
115    fn from(error: JsValue) -> Self {
116        let name = js_sys::Reflect::get(&error, &"name".into())
117            .ok()
118            .and_then(|v| v.as_string());
119        let message = js_sys::Reflect::get(&error, &"message".into())
120            .ok()
121            .and_then(|v| v.as_string());
122        let cause = js_sys::Reflect::get(&error, &"cause".into())
123            .ok()
124            .and_then(|v| v.as_string());
125        let stack = js_sys::Reflect::get(&error, &"stack".into())
126            .ok()
127            .and_then(|v| v.as_string());
128        let code = js_sys::Reflect::get(&error, &"code".into())
129            .ok()
130            .and_then(|v| v.as_string());
131
132        Self {
133            inner: Arc::new(Inner {
134                name,
135                message,
136                cause,
137                stack,
138                code,
139                origin: Sendable::new(error),
140            }),
141        }
142    }
143}
144
145impl From<JsError> for JsErrorData {
146    fn from(error: JsError) -> Self {
147        JsValue::from(error).into()
148    }
149}
150
151impl From<JsErrorData> for JsValue {
152    fn from(error: JsErrorData) -> Self {
153        error.inner.origin.as_ref().clone()
154    }
155}