Skip to main content

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}