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
9pub trait JsErrorExtension {
10    fn message(&self) -> String;
11}
12
13impl JsErrorExtension for JsError {
14    fn message(&self) -> String {
15        let inner = JsValue::from(self.clone());
16        let msg = js_sys::Reflect::get(&inner, &JsValue::from_str("message"))
17            .expect("unable to get error message");
18        msg.as_string()
19            .expect("unable to convert error message to string")
20    }
21}
22
23pub trait JsValueErrorTrait {
24    fn message(&self) -> String;
25}
26
27impl JsValueErrorTrait for JsValue {
28    fn message(&self) -> String {
29        let msg = js_sys::Reflect::get(self, &JsValue::from_str("message"))
30            .expect("unable to get error message");
31        msg.as_string()
32            .expect("unable to convert error message to string")
33    }
34}
35
36struct Inner {
37    name: Option<String>,
38    message: Option<String>,
39    cause: Option<String>,
40    stack: Option<String>,
41    code: Option<String>,
42    // origin
43    origin: Sendable<JsValue>,
44}
45
46#[derive(Clone)]
47pub struct JsErrorData {
48    inner: Arc<Inner>,
49}
50
51impl std::error::Error for JsErrorData {}
52
53impl JsErrorData {
54    pub fn name(&self) -> &Option<String> {
55        &self.inner.name
56    }
57
58    pub fn message(&self) -> &Option<String> {
59        &self.inner.message
60    }
61
62    pub fn cause(&self) -> &Option<String> {
63        &self.inner.cause
64    }
65
66    pub fn stack(&self) -> &Option<String> {
67        &self.inner.stack
68    }
69
70    pub fn code(&self) -> &Option<String> {
71        &self.inner.code
72    }
73}
74
75impl std::fmt::Debug for JsErrorData {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        f.debug_struct("JsErrorData")
78            .field("name", &self.inner.name)
79            .field("message", &self.inner.message)
80            .field("cause", &self.inner.cause)
81            .field("stack", &self.inner.stack)
82            .field("code", &self.inner.code)
83            .finish()
84    }
85}
86
87impl std::fmt::Display for JsErrorData {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        write!(
90            f,
91            "{}",
92            self.inner
93                .message
94                .clone()
95                .unwrap_or_else(|| "N/A".to_string())
96        )
97    }
98}
99
100impl From<JsValue> for JsErrorData {
101    fn from(error: JsValue) -> Self {
102        let name = js_sys::Reflect::get(&error, &"name".into())
103            .ok()
104            .and_then(|v| v.as_string());
105        let message = js_sys::Reflect::get(&error, &"message".into())
106            .ok()
107            .and_then(|v| v.as_string());
108        let cause = js_sys::Reflect::get(&error, &"cause".into())
109            .ok()
110            .and_then(|v| v.as_string());
111        let stack = js_sys::Reflect::get(&error, &"stack".into())
112            .ok()
113            .and_then(|v| v.as_string());
114        let code = js_sys::Reflect::get(&error, &"code".into())
115            .ok()
116            .and_then(|v| v.as_string());
117
118        Self {
119            inner: Arc::new(Inner {
120                name,
121                message,
122                cause,
123                stack,
124                code,
125                origin: Sendable::new(error),
126            }),
127        }
128    }
129}
130
131impl From<JsError> for JsErrorData {
132    fn from(error: JsError) -> Self {
133        JsValue::from(error).into()
134    }
135}
136
137impl From<JsErrorData> for JsValue {
138    fn from(error: JsErrorData) -> Self {
139        error.inner.origin.as_ref().clone()
140    }
141}