oxirs_wasm/error.rs
1//! WASM error types
2
3use thiserror::Error;
4use wasm_bindgen::prelude::*;
5
6/// WASM error type
7#[derive(Error, Debug)]
8pub enum WasmError {
9 #[error("Parse error: {0}")]
10 ParseError(String),
11
12 #[error("Query error: {0}")]
13 QueryError(String),
14
15 #[error("Store error: {0}")]
16 StoreError(String),
17
18 #[error("Validation error: {0}")]
19 ValidationError(String),
20
21 #[error("Serialization error: {0}")]
22 SerializationError(String),
23
24 #[error("Not implemented: {0}")]
25 NotImplemented(String),
26}
27
28impl WasmError {
29 /// A stable, machine-readable name for this error's variant.
30 ///
31 /// This is set as both the `name` and `code` properties of the
32 /// [`js_sys::Error`] built by `From<WasmError> for JsValue`, so a JS
33 /// `catch` block can branch on error kind (`err.code === "ParseError"`)
34 /// instead of pattern-matching the human-readable message text.
35 pub fn code(&self) -> &'static str {
36 match self {
37 WasmError::ParseError(_) => "ParseError",
38 WasmError::QueryError(_) => "QueryError",
39 WasmError::StoreError(_) => "StoreError",
40 WasmError::ValidationError(_) => "ValidationError",
41 WasmError::SerializationError(_) => "SerializationError",
42 WasmError::NotImplemented(_) => "NotImplemented",
43 }
44 }
45}
46
47/// Convert a [`WasmError`] into a real `js_sys::Error` (so JS sees
48/// `instanceof Error` with a stack trace) carrying a stable `name`/`code` so
49/// callers can distinguish a `ParseError` from a `StoreError` from a
50/// `ValidationError` etc. without parsing the message string.
51///
52/// `js_sys::Error::new` calls into an imported JS binding that only exists
53/// when actually running inside a `wasm32` + JS host — calling it from a
54/// native (non-`wasm32`) test binary panics with "cannot call wasm-bindgen
55/// imported functions on non-wasm targets". Since this crate's own design
56/// keeps its Rust-level logic natively testable (see `api::wasm_api`'s module
57/// doc), non-`wasm32` builds fall back to the plain string this conversion
58/// used before, so `cargo test`/`cargo nextest` keeps working; only real
59/// `wasm32` builds — the only place a JS `catch` block ever actually sees
60/// this value — get the richer `js_sys::Error`.
61impl From<WasmError> for JsValue {
62 #[cfg(target_arch = "wasm32")]
63 fn from(error: WasmError) -> Self {
64 let code = error.code();
65 let js_error = js_sys::Error::new(&error.to_string());
66 js_error.set_name(code);
67 // Best-effort: `Reflect::set` on a freshly constructed `Error` object
68 // cannot fail in a spec-compliant JS engine, but we degrade gracefully
69 // (still a proper Error, just without the extra `code` property)
70 // rather than panicking if it somehow does.
71 let _ = js_sys::Reflect::set(
72 &js_error,
73 &JsValue::from_str("code"),
74 &JsValue::from_str(code),
75 );
76 js_error.into()
77 }
78
79 #[cfg(not(target_arch = "wasm32"))]
80 fn from(error: WasmError) -> Self {
81 JsValue::from_str(&error.to_string())
82 }
83}
84
85pub type WasmResult<T> = Result<T, WasmError>;
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn regression_error_code_matches_variant() {
93 assert_eq!(WasmError::ParseError("x".into()).code(), "ParseError");
94 assert_eq!(WasmError::QueryError("x".into()).code(), "QueryError");
95 assert_eq!(WasmError::StoreError("x".into()).code(), "StoreError");
96 assert_eq!(
97 WasmError::ValidationError("x".into()).code(),
98 "ValidationError"
99 );
100 assert_eq!(
101 WasmError::SerializationError("x".into()).code(),
102 "SerializationError"
103 );
104 assert_eq!(
105 WasmError::NotImplemented("x".into()).code(),
106 "NotImplemented"
107 );
108 }
109}