1use crate::kv::KvError;
2use js_sys::Reflect;
3use strum::IntoStaticStr;
4use wasm_bindgen::{JsCast, JsValue};
5
6#[derive(Debug, IntoStaticStr)]
8#[non_exhaustive]
9pub enum Error {
10 BadEncoding,
11 BodyUsed,
12 Json((String, u16)),
13 JsError(String),
15 #[cfg(feature = "http")]
16 Http(http::Error),
17 Infallible,
18 Internal(JsValue),
20 Io(std::io::Error),
21 BindingError(String),
22 RouteInsertError(matchit::InsertError),
23 RouteNoDataError,
24 RustError(String),
25 SerdeJsonError(serde_json::Error),
26 SerdeWasmBindgenError(serde_wasm_bindgen::Error),
27 #[cfg(feature = "http")]
28 StatusCode(http::status::InvalidStatusCode),
29 #[cfg(feature = "d1")]
30 D1(crate::d1::D1Error),
31 Utf8Error(std::str::Utf8Error),
32 #[cfg(feature = "timezone")]
33 TimezoneError,
34 KvError(KvError),
35
36 EmailRecipientSuppressed(String),
40 EmailRecipientNotAllowed(String),
45
46 RateLimitExceeded(String),
52 DailyLimitExceeded(String),
57 InternalError(String),
64
65 UnknownJsError {
75 original: JsValue,
77 name: Option<String>,
79 message: String,
81 code: Option<String>,
83 cause: Option<Box<Error>>,
85 },
86}
87
88const MAX_CAUSE_DEPTH: u32 = 16;
89fn convert_js_error_with_depth(err: js_sys::Error, depth: u32) -> Error {
90 let message: String = err.message().into();
91 let name = err.name().as_string();
92 let code = Reflect::get_str(&err, &"code".into())
93 .ok()
94 .flatten()
95 .and_then(|v| v.as_string());
96 if let Some(code_str) = &code {
97 match code_str.as_str() {
98 "E_RECIPIENT_SUPPRESSED" => return Error::EmailRecipientSuppressed(message),
99 "RCPT_NOT_ALLOWED" => return Error::EmailRecipientNotAllowed(message),
100 "E_RATE_LIMIT_EXCEEDED" => return Error::RateLimitExceeded(message),
101 "E_DAILY_LIMIT_EXCEEDED" => return Error::DailyLimitExceeded(message),
102 "E_INTERNAL_SERVER_ERROR" => return Error::InternalError(message),
103 _ => {} }
105 }
106 let cause = convert_js_cause(err.cause(), depth);
107 Error::UnknownJsError {
108 original: err.into(),
109 name,
110 message,
111 code,
112 cause,
113 }
114}
115
116fn from_js_value_with_depth(v: JsValue, depth: u32) -> Error {
119 if let Ok(err) = v.clone().dyn_into::<js_sys::Error>() {
120 return convert_js_error_with_depth(err, depth);
121 }
122 if let Some(message) = v.as_string() {
123 return Error::JsError(message);
124 }
125 Error::Internal(v)
126}
127
128fn convert_js_cause(cause: JsValue, depth: u32) -> Option<Box<Error>> {
129 if cause.is_null_or_undefined() || depth == 0 {
130 return None;
131 }
132 Some(Box::new(from_js_value_with_depth(cause, depth - 1)))
133}
134
135impl From<js_sys::Error> for Error {
136 fn from(err: js_sys::Error) -> Self {
137 convert_js_error_with_depth(err, MAX_CAUSE_DEPTH)
138 }
139}
140
141#[cfg(feature = "http")]
142impl From<http::Error> for Error {
143 fn from(value: http::Error) -> Self {
144 Self::Http(value)
145 }
146}
147
148#[cfg(feature = "http")]
149impl From<http::status::InvalidStatusCode> for Error {
150 fn from(value: http::status::InvalidStatusCode) -> Self {
151 Self::StatusCode(value)
152 }
153}
154
155#[cfg(feature = "http")]
156impl From<http::header::InvalidHeaderName> for Error {
157 fn from(value: http::header::InvalidHeaderName) -> Self {
158 Self::RustError(format!("Invalid header name: {value:?}"))
159 }
160}
161
162#[cfg(feature = "http")]
163impl From<http::header::InvalidHeaderValue> for Error {
164 fn from(value: http::header::InvalidHeaderValue) -> Self {
165 Self::RustError(format!("Invalid header value: {value:?}"))
166 }
167}
168
169#[cfg(feature = "timezone")]
170impl From<chrono_tz::ParseError> for Error {
171 fn from(_value: chrono_tz::ParseError) -> Self {
172 Self::RustError("Invalid timezone".to_string())
173 }
174}
175
176impl From<std::str::Utf8Error> for Error {
177 fn from(value: std::str::Utf8Error) -> Self {
178 Self::Utf8Error(value)
179 }
180}
181
182impl From<core::convert::Infallible> for Error {
183 fn from(_value: core::convert::Infallible) -> Self {
184 Error::Infallible
185 }
186}
187
188impl From<KvError> for Error {
189 fn from(e: KvError) -> Self {
190 Self::KvError(e)
191 }
192}
193
194impl From<url::ParseError> for Error {
195 fn from(e: url::ParseError) -> Self {
196 Self::RustError(e.to_string())
197 }
198}
199
200impl From<serde_urlencoded::de::Error> for Error {
201 fn from(e: serde_urlencoded::de::Error) -> Self {
202 Self::RustError(e.to_string())
203 }
204}
205
206impl From<serde_wasm_bindgen::Error> for Error {
207 fn from(e: serde_wasm_bindgen::Error) -> Self {
208 let val: JsValue = e.into();
209 val.into()
210 }
211}
212
213#[cfg(feature = "d1")]
214impl From<crate::d1::D1Error> for Error {
215 fn from(e: crate::d1::D1Error) -> Self {
216 Self::D1(e)
217 }
218}
219
220impl std::fmt::Display for Error {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 match self {
223 Error::BadEncoding => f.write_str("content-type mismatch"),
225 Error::BodyUsed => f.write_str("body has already been read"),
226 Error::Infallible => f.write_str("infallible"),
227 Error::RouteNoDataError => f.write_str("route has no corresponding shared data"),
228 #[cfg(feature = "timezone")]
229 Error::TimezoneError => f.write_str("invalid timezone"),
230 Error::JsError(s) => f.write_str(s),
232 Error::RustError(s) => f.write_str(s),
233 Error::BindingError(name) => write!(f, "no binding found for `{name}`"),
234 Error::Json((msg, status)) => write!(f, "{msg} (status: {status})"),
235 Error::Io(_) => f.write_str("I/O error"),
238 Error::Utf8Error(_) => f.write_str("UTF-8 decoding error"),
239 Error::SerdeJsonError(_) => f.write_str("JSON serialization error"),
240 Error::SerdeWasmBindgenError(_) => f.write_str("wasm-bindgen serialization error"),
241 Error::RouteInsertError(_) => f.write_str("failed to insert route"),
242 #[cfg(feature = "http")]
243 Error::Http(_) => f.write_str("HTTP error"),
244 #[cfg(feature = "http")]
245 Error::StatusCode(_) => f.write_str("invalid HTTP status code"),
246 #[cfg(feature = "d1")]
248 Error::D1(e) => write!(f, "D1: {e:#?}"),
249 Error::KvError(KvError::JavaScript(s)) => write!(f, "js error: {s:?}"),
250 Error::KvError(KvError::Serialization(s)) => {
251 write!(f, "unable to serialize/deserialize: {s}")
252 }
253 Error::KvError(KvError::InvalidKvStore(s)) => write!(f, "invalid kv store: {s}"),
254 Error::Internal(v) => write!(f, "unrecognized JavaScript value: {v:?}"),
256 Error::EmailRecipientSuppressed(msg)
260 | Error::EmailRecipientNotAllowed(msg)
261 | Error::RateLimitExceeded(msg)
262 | Error::DailyLimitExceeded(msg)
263 | Error::InternalError(msg) => {
264 let name: &'static str = self.into();
265 write!(f, "{name}: {msg}")
266 }
267 Error::UnknownJsError {
269 name,
270 message,
271 code,
272 ..
273 } => {
274 let prefix = name.as_deref().unwrap_or("Error");
275 match code {
276 Some(c) => write!(f, "{prefix} [{c}]: {message}"),
277 None => write!(f, "{prefix}: {message}"),
278 }
279 }
280 }
281 }
282}
283
284impl std::error::Error for Error {
285 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
286 match self {
287 Error::Io(e) => Some(e),
289 Error::Utf8Error(e) => Some(e),
290 Error::SerdeJsonError(e) => Some(e),
291 Error::SerdeWasmBindgenError(e) => Some(e),
292 Error::RouteInsertError(e) => Some(e),
293 #[cfg(feature = "http")]
294 Error::Http(e) => Some(e),
295 #[cfg(feature = "http")]
296 Error::StatusCode(e) => Some(e),
297 Error::UnknownJsError { cause: Some(c), .. } => Some(c.as_ref()),
299 _ => None,
301 }
302 }
303}
304
305impl From<JsValue> for Error {
306 fn from(v: JsValue) -> Self {
307 from_js_value_with_depth(v, MAX_CAUSE_DEPTH)
312 }
313}
314
315impl From<std::io::Error> for Error {
316 fn from(error: std::io::Error) -> Self {
317 Self::Io(error)
318 }
319}
320
321impl Error {
322 pub fn to_js_value(&self) -> JsValue {
330 match self {
331 Error::UnknownJsError { original, .. } => original.clone(),
333 Error::Internal(v) => v.clone(),
334 Error::JsError(s) => JsValue::from_str(s),
336 Error::EmailRecipientSuppressed(msg) => {
339 build_coded_error(msg, "E_RECIPIENT_SUPPRESSED")
340 }
341 Error::EmailRecipientNotAllowed(msg) => build_coded_error(msg, "RCPT_NOT_ALLOWED"),
342 Error::RateLimitExceeded(msg) => build_coded_error(msg, "E_RATE_LIMIT_EXCEEDED"),
343 Error::DailyLimitExceeded(msg) => build_coded_error(msg, "E_DAILY_LIMIT_EXCEEDED"),
344 Error::InternalError(msg) => build_coded_error(msg, "E_INTERNAL_SERVER_ERROR"),
345 other => js_sys::Error::new(&format_with_chain(other)).into(),
348 }
349 }
350}
351
352impl From<Error> for JsValue {
353 fn from(e: Error) -> Self {
354 match e {
355 Error::UnknownJsError { original, .. } => original,
358 Error::Internal(v) => v,
359 other => other.to_js_value(),
360 }
361 }
362}
363
364fn format_with_chain(e: &(dyn std::error::Error)) -> String {
369 let mut s = e.to_string();
370 let mut current = e.source();
371 while let Some(src) = current {
372 s.push_str("\nCaused by:\n ");
373 s.push_str(&src.to_string());
374 current = src.source();
375 }
376 s
377}
378
379fn build_coded_error(message: &str, code: &str) -> JsValue {
380 let err = js_sys::Error::new(message);
381 let _ = Reflect::set(&err, &"code".into(), &JsValue::from_str(code));
382 err.into()
383}
384
385impl From<&str> for Error {
386 fn from(a: &str) -> Self {
387 Error::RustError(a.to_string())
388 }
389}
390
391impl From<String> for Error {
392 fn from(a: String) -> Self {
393 Error::RustError(a)
394 }
395}
396
397impl From<matchit::InsertError> for Error {
398 fn from(e: matchit::InsertError) -> Self {
399 Error::RouteInsertError(e)
400 }
401}
402
403impl From<serde_json::Error> for Error {
404 fn from(e: serde_json::Error) -> Self {
405 Error::SerdeJsonError(e)
406 }
407}