perspective_js/utils/
errors.rs1use std::fmt::Display;
14use std::rc::Rc;
15use std::string::FromUtf8Error;
16
17use perspective_client::{ClientError, ExprValidationResult};
18use thiserror::*;
19use wasm_bindgen::prelude::*;
20
21#[macro_export]
22macro_rules! apierror {
23 ($msg:expr) => {{
24 use $crate::utils::errors::ApiErrorType::*;
25 let js_err_type = $msg;
26 let err = js_sys::Error::new(js_err_type.to_string().as_str());
27 let js_err = $crate::utils::errors::ApiError(
28 js_err_type,
29 $crate::utils::errors::JsBackTrace(std::rc::Rc::new(err.clone())),
30 );
31 js_err
32 }};
33}
34
35fn format_js_error(value: &JsValue) -> String {
36 if let Some(err) = value.dyn_ref::<js_sys::Error>() {
37 let msg = err.message().as_string().unwrap();
38 if let Ok(x) = js_sys::Reflect::get(value, &"stack".into()) {
39 format!("{}\n{}", msg, x.as_string().unwrap())
40 } else {
41 msg
42 }
43 } else {
44 value
45 .as_string()
46 .unwrap_or_else(|| format!("{value:?}"))
47 .to_string()
48 }
49}
50
51fn format_valid_exprs(recs: &ExprValidationResult) -> String {
52 recs.errors
53 .iter()
54 .map(|x| format!("\"{}\": {}", x.0, x.1.error_message))
55 .collect::<Vec<_>>()
56 .join(", ")
57}
58
59#[derive(Clone, Debug, Error)]
62pub enum ApiErrorType {
63 #[error("{}", format_js_error(.0))]
64 JsError(JsValue),
65
66 #[error("{}", format_js_error(.0))]
67 JsRawError(js_sys::Error),
68
69 #[error("Failed to construct table from {0:?}")]
70 TableError(JsValue),
71
72 #[error("{}", format_js_error(.0))]
73 ViewerPluginError(JsValue),
74
75 #[error("{0}")]
76 ExternalError(Rc<Box<dyn std::error::Error>>),
77
78 #[error("{0}")]
79 UnknownError(String),
80
81 #[error("{0}")]
82 ClientError(#[from] ClientError),
83
84 #[error("Cancelled")]
85 CancelledError(#[from] futures::channel::oneshot::Canceled),
86
87 #[error("{0}")]
88 SerdeJsonError(Rc<serde_json::Error>),
89
90 #[error("{0}")]
91 ProstError(#[from] prost::DecodeError),
92
93 #[error("Unknown column \"{1}\" in field `{0}`")]
94 InvalidViewerConfigError(&'static str, String),
95
96 #[error("Invalid `expressions` {}", format_valid_exprs(.0))]
97 InvalidViewerConfigExpressionsError(Rc<ExprValidationResult>),
98
99 #[error("No `Table` attached")]
100 NoTableError,
101
102 #[error(transparent)]
103 SerdeWasmBindgenError(Rc<serde_wasm_bindgen::Error>),
104
105 #[error(transparent)]
106 Utf8Error(#[from] FromUtf8Error),
107
108 #[error(transparent)]
109 StdIoError(Rc<std::io::Error>),
110
111 #[error(transparent)]
112 RmpSerdeEncodeError(Rc<rmp_serde::encode::Error>),
113
114 #[error(transparent)]
115 RmpSerdeDecodeError(Rc<rmp_serde::decode::Error>),
116
117 #[error(transparent)]
118 Base64DecodeError(#[from] base64::DecodeError),
119
120 #[error(transparent)]
121 ChronoParseError(#[from] chrono::ParseError),
122}
123
124#[derive(Clone, Debug, Error)]
125pub struct ApiError(pub ApiErrorType, pub JsBackTrace);
126
127impl ApiError {
128 pub fn new<T: Display>(val: T) -> Self {
129 apierror!(UnknownError(format!("{val}")))
130 }
131
132 pub fn kind(&self) -> &'static str {
134 match self.0 {
135 ApiErrorType::JsError(..) => "[JsError]",
136 ApiErrorType::TableError(_) => "[TableError]",
137 ApiErrorType::ExternalError(_) => "[ExternalError]",
138 ApiErrorType::UnknownError(..) => "[UnknownError]",
139 ApiErrorType::ClientError(_) => "[ClientError]",
140 ApiErrorType::CancelledError(_) => "[CancelledError]",
141 ApiErrorType::SerdeJsonError(_) => "[SerdeJsonError]",
142 ApiErrorType::ProstError(_) => "[ProstError]",
143 ApiErrorType::InvalidViewerConfigError(..) => "[InvalidViewerConfigError]",
144 ApiErrorType::InvalidViewerConfigExpressionsError(_) => "[InvalidViewerConfigError]",
145 ApiErrorType::NoTableError => "[NoTableError]",
146 ApiErrorType::SerdeWasmBindgenError(_) => "[SerdeWasmBindgenError]",
147 ApiErrorType::Utf8Error(_) => "[FromUtf8Error]",
148 ApiErrorType::StdIoError(_) => "[StdIoError]",
149 ApiErrorType::RmpSerdeEncodeError(_) => "[RmpSerdeEncodeError]",
150 ApiErrorType::RmpSerdeDecodeError(_) => "[RmpSerdeDecodeError]",
151 ApiErrorType::Base64DecodeError(_) => "[Base64DecodeError]",
152 ApiErrorType::ChronoParseError(_) => "[ChronoParseError]",
153 ApiErrorType::ViewerPluginError(_) => "[ViewerPluginError]",
154 ApiErrorType::JsRawError(_) => "[JsRawError]",
155 }
156 }
157
158 pub fn inner(&self) -> &'_ ApiErrorType {
160 &self.0
161 }
162
163 pub fn message(&self) -> String {
165 self.0.to_string()
166 }
167
168 pub fn stacktrace(&self) -> String {
170 js_sys::Reflect::get(&self.1.0, &"stack".into())
171 .unwrap()
172 .as_string()
173 .unwrap()
174 .to_string()
175 }
176}
177
178unsafe impl Send for ApiError {}
179unsafe impl Sync for ApiError {}
180
181impl std::fmt::Display for ApiError {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 self.0.fmt(f)
184 }
185}
186
187impl<T: Into<ApiErrorType>> From<T> for ApiError {
188 fn from(value: T) -> Self {
189 let value: ApiErrorType = value.into();
190 let err = js_sys::Error::new(value.to_string().as_str());
191 ApiError(value, JsBackTrace(Rc::new(err.clone())))
192 }
193}
194
195impl From<ApiError> for JsValue {
196 fn from(err: ApiError) -> Self {
197 err.1.0.unchecked_ref::<JsValue>().clone()
198 }
199}
200
201impl From<serde_wasm_bindgen::Error> for ApiError {
202 fn from(value: serde_wasm_bindgen::Error) -> Self {
203 ApiErrorType::SerdeWasmBindgenError(Rc::new(value)).into()
204 }
205}
206
207impl From<std::io::Error> for ApiError {
208 fn from(value: std::io::Error) -> Self {
209 ApiErrorType::StdIoError(Rc::new(value)).into()
210 }
211}
212
213impl From<rmp_serde::decode::Error> for ApiError {
214 fn from(value: rmp_serde::decode::Error) -> Self {
215 ApiErrorType::RmpSerdeDecodeError(Rc::new(value)).into()
216 }
217}
218
219impl From<rmp_serde::encode::Error> for ApiError {
220 fn from(value: rmp_serde::encode::Error) -> Self {
221 ApiErrorType::RmpSerdeEncodeError(Rc::new(value)).into()
222 }
223}
224
225impl From<Box<dyn std::error::Error>> for ApiError {
226 fn from(value: Box<dyn std::error::Error>) -> Self {
227 ApiErrorType::ExternalError(Rc::new(value)).into()
228 }
229}
230
231impl From<serde_json::Error> for ApiError {
232 fn from(value: serde_json::Error) -> Self {
233 ApiErrorType::SerdeJsonError(Rc::new(value)).into()
234 }
235}
236
237impl From<JsValue> for ApiError {
238 fn from(err: JsValue) -> Self {
239 if err.is_instance_of::<js_sys::Error>() {
240 ApiErrorType::JsRawError(err.clone().unchecked_into()).into()
241 } else {
242 apierror!(JsError(err))
243 }
244 }
245}
246
247impl From<String> for ApiError {
248 fn from(value: String) -> Self {
249 apierror!(UnknownError(value.to_owned()))
250 }
251}
252
253impl From<&str> for ApiError {
254 fn from(value: &str) -> Self {
255 apierror!(UnknownError(value.to_owned()))
256 }
257}
258
259pub trait ToApiError<T> {
261 fn into_apierror(self) -> ApiResult<T>;
262}
263
264impl<T> ToApiError<T> for Option<T> {
265 fn into_apierror(self) -> ApiResult<T> {
266 self.ok_or_else(|| "Unwrap on None".into())
267 }
268}
269
270impl ToApiError<JsValue> for Result<(), ApiResult<JsValue>> {
271 fn into_apierror(self) -> ApiResult<JsValue> {
272 self.map_or_else(|x| x, |()| Ok(JsValue::UNDEFINED))
273 }
274}
275
276pub type ApiResult<T> = Result<T, ApiError>;
278
279#[derive(Clone, Debug)]
282pub struct JsBackTrace(pub Rc<js_sys::Error>);
283
284impl std::fmt::Display for JsBackTrace {
285 fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 Ok(())
287 }
288}