1use js_sys::Reflect;
2use chrono::ParseError;
3use gloo_console::externs::error as console_error;
4use serde_wasm_bindgen::{Error as SerdeError, to_value};
5use wasm_bindgen::JsValue;
6use std::{
7 fmt::{Debug, Display, Formatter, Result as FmtResult},
8 error::Error as StdError,
9};
10
11#[derive(Debug, Clone)]
12pub struct JsError {
13 message: String,
14 prefix: Option<String>,
15 data: Option<JsValue>,
16}
17
18impl JsError {
19 pub fn new(js: JsValue) -> Self {
20 let msg_prop_name = JsValue::from_str("message");
21 let msg_prop_value = Reflect::get(&js, &msg_prop_name);
22 let message = match msg_prop_value.as_ref() {
23 Ok(msg) => msg,
24 Err(_) => &js,
25 };
26
27 Self {
28 message: message.as_string().unwrap_or_else(|| format!("{message:?}")),
29 prefix: None,
30 data: None,
31 }
32 }
33
34 pub fn from_displayable<E: Display>(err: E) -> Self {
35 Self {
36 message: err.to_string(),
37 prefix: None,
38 data: None,
39 }
40 }
41
42 pub fn new_from_str(message: impl AsRef<str>) -> Self {
43 Self {
44 message: message.as_ref().to_string(),
45 prefix: None,
46 data: None,
47 }
48 }
49
50 pub fn with_prefix(self, prefix: impl AsRef<str>) -> Self {
51 Self {
52 prefix: Some(prefix.as_ref().to_string()),
53 ..self
54 }
55 }
56
57 pub fn with_data(self, data: JsValue) -> Self {
58 Self {
59 data: Some(data),
60 ..self
61 }
62 }
63
64 pub fn with_serializable_data(self, data: impl serde::Serialize) -> Self {
65 let data =
66 to_value(&data).unwrap_or_else(|err| JsValue::from_str(&format!("Serialization error on data: {err}")));
67
68 self.with_data(data)
69 }
70
71 #[allow(dead_code)]
72 pub fn message(&self) -> &str {
73 &self.message
74 }
75
76 pub fn log(&self) {
77 let msg = match &self.prefix {
78 None => JsValue::from_str(&self.message),
79 Some(prefix) => JsValue::from_str(&format!("{prefix}: {}", self.message)),
80 };
81
82 let mut args = vec![msg];
83 if let Some(data) = &self.data {
84 args.push(JsValue::from_str(" - with data"));
85 args.push(data.clone());
86 };
87
88 console_error(args.into_boxed_slice());
89 }
90}
91
92impl Display for JsError {
93 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
94 <String as Display>::fmt(&self.message, f)
95 }
96}
97
98impl StdError for JsError {}
99
100impl From<JsValue> for JsError {
101 fn from(js: JsValue) -> Self {
102 Self::new(js)
103 }
104}
105
106impl From<SerdeError> for JsError {
107 fn from(err: SerdeError) -> Self {
108 Self {
109 message: format!("JSON error: {err}"),
110 prefix: None,
111 data: None,
112 }
113 }
114}
115
116impl From<ParseError> for JsError {
117 fn from(err: ParseError) -> Self {
118 Self {
119 message: format!("Parse error: {err}"),
120 prefix: None,
121 data: None,
122 }
123 }
124}