1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Structures for handling JavaScript errors. Specifically this module
//! provides a `JsErrorData` struct which is used to extract information
//! from a `JsValue` that represents a JavaScript error.

use std::sync::Arc;
use wasm_bindgen::prelude::*;
use workflow_core::sendable::Sendable;

pub trait JsErrorExtension {
    fn message(&self) -> String;
}

impl JsErrorExtension for JsError {
    fn message(&self) -> String {
        let inner = JsValue::from(self.clone());
        let msg = js_sys::Reflect::get(&inner, &JsValue::from_str("message"))
            .expect("unable to get error message");
        msg.as_string()
            .expect("unable to convert error message to string")
    }
}

pub trait JsValueErrorTrait {
    fn message(&self) -> String;
}

impl JsValueErrorTrait for JsValue {
    fn message(&self) -> String {
        let msg = js_sys::Reflect::get(self, &JsValue::from_str("message"))
            .expect("unable to get error message");
        msg.as_string()
            .expect("unable to convert error message to string")
    }
}

struct Inner {
    name: Option<String>,
    message: Option<String>,
    cause: Option<String>,
    stack: Option<String>,
    code: Option<String>,
    // origin
    origin: Sendable<JsValue>,
}

#[derive(Clone)]
pub struct JsErrorData {
    inner: Arc<Inner>,
}

impl std::error::Error for JsErrorData {}

impl JsErrorData {
    pub fn name(&self) -> &Option<String> {
        &self.inner.name
    }

    pub fn message(&self) -> &Option<String> {
        &self.inner.message
    }

    pub fn cause(&self) -> &Option<String> {
        &self.inner.cause
    }

    pub fn stack(&self) -> &Option<String> {
        &self.inner.stack
    }

    pub fn code(&self) -> &Option<String> {
        &self.inner.code
    }
}

impl std::fmt::Debug for JsErrorData {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("JsErrorData")
            .field("name", &self.inner.name)
            .field("message", &self.inner.message)
            .field("cause", &self.inner.cause)
            .field("stack", &self.inner.stack)
            .field("code", &self.inner.code)
            .finish()
    }
}

impl std::fmt::Display for JsErrorData {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            self.inner
                .message
                .clone()
                .unwrap_or_else(|| "N/A".to_string())
        )
    }
}

impl From<JsValue> for JsErrorData {
    fn from(error: JsValue) -> Self {
        let name = js_sys::Reflect::get(&error, &"name".into())
            .ok()
            .and_then(|v| v.as_string());
        let message = js_sys::Reflect::get(&error, &"message".into())
            .ok()
            .and_then(|v| v.as_string());
        let cause = js_sys::Reflect::get(&error, &"cause".into())
            .ok()
            .and_then(|v| v.as_string());
        let stack = js_sys::Reflect::get(&error, &"stack".into())
            .ok()
            .and_then(|v| v.as_string());
        let code = js_sys::Reflect::get(&error, &"code".into())
            .ok()
            .and_then(|v| v.as_string());

        Self {
            inner: Arc::new(Inner {
                name,
                message,
                cause,
                stack,
                code,
                origin: Sendable::new(error),
            }),
        }
    }
}

impl From<JsError> for JsErrorData {
    fn from(error: JsError) -> Self {
        JsValue::from(error).into()
    }
}

impl From<JsErrorData> for JsValue {
    fn from(error: JsErrorData) -> Self {
        error.inner.origin.as_ref().clone()
    }
}