lumina_node_wasm/
error.rs

1//! Error type and utilities.
2
3use std::fmt::{self, Display};
4
5use serde::{Deserialize, Serialize};
6use wasm_bindgen::convert::IntoWasmAbi;
7use wasm_bindgen::describe::WasmDescribe;
8use wasm_bindgen::{JsCast, JsValue};
9
10/// Alias for a `Result` with the error type [`Error`].
11pub type Result<T, E = Error> = std::result::Result<T, E>;
12
13/// An error that can cross the WASM ABI border.
14#[derive(Debug, Serialize, Deserialize)]
15pub struct Error(#[serde(with = "serde_wasm_bindgen::preserve")] JsValue);
16
17impl Error {
18    /// Create a new `Error` with the specified message.
19    pub fn new(msg: &str) -> Error {
20        Error(js_sys::Error::new(msg).into())
21    }
22
23    /// Create a new `Error` from `JsValue` without losing any information.
24    pub fn from_js_value<T>(value: T) -> Error
25    where
26        T: Into<JsValue>,
27    {
28        Error(value.into())
29    }
30
31    /// Create a new `Error` from any type that implements `Display`.
32    ///
33    /// This can be used on types that implement [`std::error::Error`] but
34    /// some information is lost.
35    pub fn from_display<T>(value: T) -> Error
36    where
37        T: Display,
38    {
39        Error::new(&value.to_string())
40    }
41
42    /// Add more context to the `Error`.
43    pub fn context<C>(self, context: C) -> Error
44    where
45        C: Display,
46    {
47        let e = js_sys::Error::new(&context.to_string());
48        e.set_cause(&self.0);
49        Error(e.into())
50    }
51}
52
53impl Display for Error {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
55        let Some(error) = self.0.dyn_ref::<js_sys::Error>() else {
56            return write!(f, "{:?}", self.0.as_string());
57        };
58
59        write!(f, "{} ({})", error.name(), error.message())?;
60
61        let mut cause = error.cause();
62        loop {
63            if let Some(error) = cause.dyn_ref::<js_sys::Error>() {
64                write!(f, "\n{} ({})", error.name(), error.message())?;
65                cause = error.cause();
66            } else {
67                write!(f, "\n{:?}", cause.as_string())?;
68                break;
69            }
70        }
71
72        Ok(())
73    }
74}
75
76// Similar to https://github.com/rustwasm/wasm-bindgen/blob/9b37613bbab0cd71f55dcc782d59dd00c1d4d200/src/describe.rs#L218-L222
77//
78// This is needed to impl IntoWasmAbi.
79impl WasmDescribe for Error {
80    fn describe() {
81        JsValue::describe()
82    }
83}
84
85// Similar to https://github.com/rustwasm/wasm-bindgen/blob/9b37613bbab0cd71f55dcc782d59dd00c1d4d200/src/convert/impls.rs#L468-L474
86//
87// This is needed to cross the ABI border.
88impl IntoWasmAbi for Error {
89    type Abi = <JsValue as IntoWasmAbi>::Abi;
90
91    fn into_abi(self) -> Self::Abi {
92        self.0.into_abi()
93    }
94}
95
96impl From<Error> for JsValue {
97    fn from(error: Error) -> JsValue {
98        error.0
99    }
100}
101
102/// Lossless convertion to `Error`.
103///
104/// Should be used for types that implement `Into<JsValue>`.
105macro_rules! from_js_value {
106    ($($t:ty,)*) => ($(
107        impl From<$t> for Error {
108            fn from(value: $t) -> Error {
109                Error::from_js_value(value)
110            }
111        }
112    )*)
113}
114
115/// Lossy convertion to `Error`.
116///
117/// Should be used for types that do not implement `Into<JsValue>`.
118macro_rules! from_display {
119    ($($t:ty,)*) => ($(
120        impl From<$t> for Error {
121            fn from(value: $t) -> Error {
122                Error::from_display(value)
123            }
124        }
125    )*)
126}
127
128from_js_value! {
129    JsValue,
130    serde_wasm_bindgen::Error,
131    wasm_bindgen::JsError,
132}
133
134from_display! {
135    blockstore::Error,
136    tendermint::error::Error,
137    libp2p::identity::ParseError,
138    libp2p::multiaddr::Error,
139    libp2p::identity::DecodingError,
140    celestia_types::Error,
141    lumina_node::node::NodeError,
142    lumina_node::store::StoreError,
143    crate::worker::WorkerError,
144    tokio::sync::oneshot::error::RecvError,
145}
146
147impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
148    fn from(value: tokio::sync::mpsc::error::SendError<T>) -> Error {
149        Error::from_display(value)
150    }
151}
152
153impl<T> From<tokio::sync::broadcast::error::SendError<T>> for Error {
154    fn from(value: tokio::sync::broadcast::error::SendError<T>) -> Error {
155        Error::from_display(value)
156    }
157}
158
159/// Utility to add more context to the [`Error`].
160pub trait Context<T> {
161    /// Adds more context to the [`Error`].
162    fn context<C>(self, context: C) -> Result<T, Error>
163    where
164        C: Display;
165
166    /// Adds more context to the [`Error`] that is evaluated lazily.
167    fn with_context<F, C>(self, context_fn: F) -> Result<T, Error>
168    where
169        C: Display,
170        F: FnOnce() -> C,
171        Self: Sized,
172    {
173        self.context(context_fn())
174    }
175}
176
177impl<T, E> Context<T> for Result<T, E>
178where
179    E: Into<Error>,
180{
181    fn context<C>(self, context: C) -> Result<T, Error>
182    where
183        C: Display,
184    {
185        self.map_err(|e| e.into().context(context))
186    }
187}
188
189impl<T> Context<T> for Option<T> {
190    fn context<C>(self, context: C) -> Result<T, Error>
191    where
192        C: Display,
193    {
194        self.ok_or_else(|| Error::new(&context.to_string()))
195    }
196}