1use core::{
2 error::Error as StdError,
3 ffi::FromBytesWithNulError,
4 fmt::{self, Display, Formatter, Result as FmtResult},
5 panic::UnwindSafe,
6 str::{FromStr, Utf8Error},
7};
8
9use alloc::{
10 ffi::{CString, NulError},
11 string::{FromUtf8Error, ToString as _},
12};
13
14#[cfg(feature = "std")]
15use std::io::Error as IoError;
16
17#[cfg(feature = "futures")]
18use crate::context::AsyncContext;
19use crate::value::array_buffer::AsSliceError;
20use crate::{
21 atom::PredefinedAtom, qjs, runtime::UserDataError, value::exception::ERROR_FORMAT_STR, Context,
22 Ctx, Exception, Object, StdResult, StdString, Type, Value,
23};
24
25pub type Result<T> = StdResult<T, Error>;
27
28pub type CaughtResult<'js, T> = StdResult<T, CaughtError<'js>>;
30
31#[derive(Debug)]
32pub enum BorrowError {
33 NotWritable,
35 AlreadyBorrowed,
37 AlreadyUsed,
39}
40
41impl fmt::Display for BorrowError {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match *self {
44 BorrowError::NotWritable => write!(f, "tried to borrow a value which is not writable"),
45 BorrowError::AlreadyBorrowed => {
46 write!(f, "can't borrow a value as it is already borrowed")
47 }
48 BorrowError::AlreadyUsed => {
49 write!(
50 f,
51 "tried to use a value, which can only be used once, again."
52 )
53 }
54 }
55 }
56}
57
58impl core::error::Error for BorrowError {}
59
60#[derive(Debug)]
62#[non_exhaustive]
63pub enum Error {
64 Allocation,
67 DuplicateExports,
69 InvalidExport,
71 InvalidString(NulError),
74 InvalidCStr(FromBytesWithNulError),
77 Utf8(Utf8Error),
79 #[cfg(feature = "std")]
81 Io(IoError),
82 ClassBorrow(BorrowError),
84 FunctionBorrow(BorrowError),
86 Exception,
92 FromJs {
94 from: &'static str,
95 to: &'static str,
96 message: Option<StdString>,
97 },
98 IntoJs {
100 from: &'static str,
101 to: &'static str,
102 message: Option<StdString>,
103 },
104 MissingArgs {
106 expected: usize,
107 given: usize,
108 },
109 TooManyArgs {
110 expected: usize,
111 given: usize,
112 },
113 #[cfg(feature = "loader")]
114 Resolving {
116 base: StdString,
117 name: StdString,
118 message: Option<StdString>,
119 },
120 #[cfg(feature = "loader")]
121 Loading {
123 name: StdString,
124 message: Option<StdString>,
125 },
126 AsSlice(AsSliceError),
127 UnrelatedRuntime,
129 WouldBlock,
132 UserData(UserDataError<()>),
134 Unknown,
137}
138
139impl Error {
140 #[cfg(feature = "loader")]
141 pub fn new_resolving<B, N>(base: B, name: N) -> Self
143 where
144 StdString: From<B> + From<N>,
145 {
146 Error::Resolving {
147 base: base.into(),
148 name: name.into(),
149 message: None,
150 }
151 }
152
153 #[cfg(feature = "loader")]
154 pub fn new_resolving_message<B, N, M>(base: B, name: N, msg: M) -> Self
156 where
157 StdString: From<B> + From<N> + From<M>,
158 {
159 Error::Resolving {
160 base: base.into(),
161 name: name.into(),
162 message: Some(msg.into()),
163 }
164 }
165
166 #[cfg(feature = "loader")]
167 pub fn is_resolving(&self) -> bool {
169 matches!(self, Error::Resolving { .. })
170 }
171
172 #[cfg(feature = "loader")]
173 pub fn new_loading<N>(name: N) -> Self
175 where
176 StdString: From<N>,
177 {
178 Error::Loading {
179 name: name.into(),
180 message: None,
181 }
182 }
183
184 #[cfg(feature = "loader")]
185 pub fn new_loading_message<N, M>(name: N, msg: M) -> Self
187 where
188 StdString: From<N> + From<M>,
189 {
190 Error::Loading {
191 name: name.into(),
192 message: Some(msg.into()),
193 }
194 }
195
196 #[cfg(feature = "loader")]
197 pub fn is_loading(&self) -> bool {
199 matches!(self, Error::Loading { .. })
200 }
201
202 pub fn is_exception(&self) -> bool {
204 matches!(self, Error::Exception)
205 }
206
207 pub fn new_from_js(from: &'static str, to: &'static str) -> Self {
209 Error::FromJs {
210 from,
211 to,
212 message: None,
213 }
214 }
215
216 pub fn new_from_js_message<M>(from: &'static str, to: &'static str, msg: M) -> Self
218 where
219 StdString: From<M>,
220 {
221 Error::FromJs {
222 from,
223 to,
224 message: Some(msg.into()),
225 }
226 }
227
228 pub fn new_into_js(from: &'static str, to: &'static str) -> Self {
230 Error::IntoJs {
231 from,
232 to,
233 message: None,
234 }
235 }
236
237 pub fn new_into_js_message<M>(from: &'static str, to: &'static str, msg: M) -> Self
239 where
240 StdString: From<M>,
241 {
242 Error::IntoJs {
243 from,
244 to,
245 message: Some(msg.into()),
246 }
247 }
248
249 pub fn is_from_js(&self) -> bool {
251 matches!(self, Self::FromJs { .. })
252 }
253
254 pub fn is_from_js_to_js(&self) -> bool {
256 matches!(self, Self::FromJs { to, .. } if Type::from_str(to).is_ok())
257 }
258
259 pub fn is_into_js(&self) -> bool {
261 matches!(self, Self::IntoJs { .. })
262 }
263
264 pub fn is_num_args(&self) -> bool {
266 matches!(self, Self::TooManyArgs { .. } | Self::MissingArgs { .. })
267 }
268
269 pub(crate) fn to_cstring(&self) -> CString {
271 let mut message = alloc::format!("{self}\0").into_bytes();
273
274 message.pop(); unsafe { CString::from_vec_unchecked(message) }
278 }
279
280 pub(crate) fn throw(&self, ctx: &Ctx) -> qjs::JSValue {
282 use Error::*;
283 match self {
284 Exception => qjs::JS_EXCEPTION,
285 Allocation => unsafe { qjs::JS_ThrowOutOfMemory(ctx.as_ptr()) },
286 InvalidString(_)
287 | Utf8(_)
288 | FromJs { .. }
289 | IntoJs { .. }
290 | TooManyArgs { .. }
291 | MissingArgs { .. } => {
292 let message = self.to_cstring();
293 unsafe {
294 qjs::JS_ThrowTypeError(
295 ctx.as_ptr(),
296 ERROR_FORMAT_STR.as_ptr(),
297 message.as_ptr(),
298 )
299 }
300 }
301 AsSlice(_) => {
302 let message = self.to_cstring();
303 unsafe {
304 qjs::JS_ThrowReferenceError(
305 ctx.as_ptr(),
306 ERROR_FORMAT_STR.as_ptr(),
307 message.as_ptr(),
308 )
309 }
310 }
311 #[cfg(feature = "loader")]
312 Resolving { .. } | Loading { .. } => {
313 let message = self.to_cstring();
314 unsafe {
315 qjs::JS_ThrowReferenceError(
316 ctx.as_ptr(),
317 ERROR_FORMAT_STR.as_ptr(),
318 message.as_ptr(),
319 )
320 }
321 }
322 Unknown => {
323 let message = self.to_cstring();
324 unsafe {
325 qjs::JS_ThrowInternalError(
326 ctx.as_ptr(),
327 ERROR_FORMAT_STR.as_ptr(),
328 message.as_ptr(),
329 )
330 }
331 }
332 error => {
333 unsafe {
334 let value = qjs::JS_NewError(ctx.as_ptr());
335 if qjs::JS_VALUE_GET_NORM_TAG(value) == qjs::JS_TAG_EXCEPTION {
336 return value;
339 }
340 let obj = Object::from_js_value(ctx.clone(), value);
341 match obj.set(PredefinedAtom::Message, error.to_string()) {
342 Ok(_) => {}
343 Err(Error::Exception) => return qjs::JS_EXCEPTION,
344 Err(e) => {
345 panic!("generated error while throwing error: {}", e);
346 }
347 }
348 let js_val = (obj).into_js_value();
349 qjs::JS_Throw(ctx.as_ptr(), js_val)
350 }
351 }
352 }
353 }
354}
355
356impl StdError for Error {}
357
358impl Display for Error {
359 fn fmt(&self, f: &mut Formatter) -> FmtResult {
360 match self {
361 Error::Allocation => "Allocation failed while creating object".fmt(f)?,
362 Error::DuplicateExports => {
363 "Tried to export two values with the same name from one module".fmt(f)?
364 }
365 Error::InvalidExport => {
366 "Tried to export a value which was not previously declared".fmt(f)?
367 }
368 Error::InvalidString(error) => {
369 "String contained internal null bytes: ".fmt(f)?;
370 error.fmt(f)?;
371 }
372 Error::InvalidCStr(error) => {
373 "CStr didn't end in a null byte: ".fmt(f)?;
374 error.fmt(f)?;
375 }
376 Error::Utf8(error) => {
377 "Conversion from string failed: ".fmt(f)?;
378 error.fmt(f)?;
379 }
380 Error::Unknown => "QuickJS library created a unknown error".fmt(f)?,
381 Error::Exception => "Exception generated by QuickJS".fmt(f)?,
382 Error::FromJs { from, to, message } => {
383 "Error converting from js '".fmt(f)?;
384 from.fmt(f)?;
385 "' into type '".fmt(f)?;
386 to.fmt(f)?;
387 "'".fmt(f)?;
388 if let Some(message) = message {
389 if !message.is_empty() {
390 ": ".fmt(f)?;
391 message.fmt(f)?;
392 }
393 }
394 }
395 Error::IntoJs { from, to, message } => {
396 "Error converting from '".fmt(f)?;
397 from.fmt(f)?;
398 "' into js '".fmt(f)?;
399 to.fmt(f)?;
400 "'".fmt(f)?;
401 if let Some(message) = message {
402 if !message.is_empty() {
403 ": ".fmt(f)?;
404 message.fmt(f)?;
405 }
406 }
407 }
408 Error::MissingArgs { expected, given } => {
409 "Error calling function with ".fmt(f)?;
410 given.fmt(f)?;
411 " argument(s) while ".fmt(f)?;
412 expected.fmt(f)?;
413 " where expected".fmt(f)?;
414 }
415 Error::TooManyArgs { expected, given } => {
416 "Error calling function with ".fmt(f)?;
417 given.fmt(f)?;
418 " argument(s), function is exhaustive and cannot be called with more then "
419 .fmt(f)?;
420 expected.fmt(f)?;
421 " arguments".fmt(f)?;
422 }
423 #[cfg(feature = "loader")]
424 Error::Resolving {
425 base,
426 name,
427 message,
428 } => {
429 "Error resolving module '".fmt(f)?;
430 name.fmt(f)?;
431 "' from '".fmt(f)?;
432 base.fmt(f)?;
433 "'".fmt(f)?;
434 if let Some(message) = message {
435 if !message.is_empty() {
436 ": ".fmt(f)?;
437 message.fmt(f)?;
438 }
439 }
440 }
441 #[cfg(feature = "loader")]
442 Error::Loading { name, message } => {
443 "Error loading module '".fmt(f)?;
444 name.fmt(f)?;
445 "'".fmt(f)?;
446 if let Some(message) = message {
447 if !message.is_empty() {
448 ": ".fmt(f)?;
449 message.fmt(f)?;
450 }
451 }
452 }
453 #[cfg(feature = "std")]
454 Error::Io(error) => {
455 "IO Error: ".fmt(f)?;
456 error.fmt(f)?;
457 }
458 Error::ClassBorrow(x) => {
459 "Error borrowing class: ".fmt(f)?;
460 x.fmt(f)?;
461 }
462 Error::FunctionBorrow(x) => {
463 "Error borrowing function: ".fmt(f)?;
464 x.fmt(f)?;
465 }
466 Error::WouldBlock => "Error blocking on a promise resulted in a dead lock".fmt(f)?,
467 Error::UserData(x) => x.fmt(f)?,
468 Error::AsSlice(x) => {
469 "Could not convert array buffer to slice: ".fmt(f)?;
470 x.fmt(f)?;
471 }
472 Error::UnrelatedRuntime => "Restoring Persistent in an unrelated runtime".fmt(f)?,
473 }
474 Ok(())
475 }
476}
477
478macro_rules! from_impls {
479 ($($type:ty => $variant:ident,)*) => {
480 $(
481 impl From<$type> for Error {
482 fn from(error: $type) -> Self {
483 Error::$variant(error)
484 }
485 }
486 )*
487 };
488}
489
490from_impls! {
491 NulError => InvalidString,
492 FromBytesWithNulError => InvalidCStr,
493 Utf8Error => Utf8,
494}
495
496#[cfg(feature = "std")]
497from_impls! {
498 IoError => Io,
499}
500
501impl From<FromUtf8Error> for Error {
502 fn from(error: FromUtf8Error) -> Self {
503 Error::Utf8(error.utf8_error())
504 }
505}
506
507impl<T> From<UserDataError<T>> for Error {
508 fn from(_: UserDataError<T>) -> Self {
509 Error::UserData(UserDataError(()))
510 }
511}
512
513impl From<AsSliceError> for Error {
514 fn from(value: AsSliceError) -> Self {
515 Error::AsSlice(value)
516 }
517}
518
519#[derive(Debug)]
521pub enum CaughtError<'js> {
522 Error(Error),
524 Exception(Exception<'js>),
526 Value(Value<'js>),
528}
529
530impl<'js> Display for CaughtError<'js> {
531 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
532 match *self {
533 CaughtError::Error(ref e) => e.fmt(f),
534 CaughtError::Exception(ref e) => e.fmt(f),
535 CaughtError::Value(ref e) => {
536 writeln!(f, "Exception generated by quickjs: {e:?}")
537 }
538 }
539 }
540}
541
542impl<'js> StdError for CaughtError<'js> {}
543
544impl<'js> CaughtError<'js> {
545 pub fn from_error(ctx: &Ctx<'js>, error: Error) -> Self {
548 if let Error::Exception = error {
549 let value = ctx.catch();
550 if let Some(ex) = value
551 .as_object()
552 .and_then(|x| Exception::from_object(x.clone()))
553 {
554 CaughtError::Exception(ex)
555 } else {
556 CaughtError::Value(value)
557 }
558 } else {
559 CaughtError::Error(error)
560 }
561 }
562
563 pub fn catch<T>(ctx: &Ctx<'js>, error: Result<T>) -> CaughtResult<'js, T> {
566 error.map_err(|error| Self::from_error(ctx, error))
567 }
568
569 pub fn throw(self, ctx: &Ctx<'js>) -> Error {
571 match self {
572 CaughtError::Error(e) => e,
573 CaughtError::Exception(ex) => ctx.throw(ex.into_value()),
574 CaughtError::Value(ex) => ctx.throw(ex),
575 }
576 }
577
578 pub fn is_exception(&self) -> bool {
580 matches!(self, CaughtError::Exception(_))
581 }
582
583 pub fn is_js_error(&self) -> bool {
585 matches!(self, CaughtError::Exception(_) | CaughtError::Value(_))
586 }
587}
588
589pub trait CatchResultExt<'js, T> {
606 fn catch(self, ctx: &Ctx<'js>) -> CaughtResult<'js, T>;
607}
608
609impl<'js, T> CatchResultExt<'js, T> for Result<T> {
610 fn catch(self, ctx: &Ctx<'js>) -> CaughtResult<'js, T> {
611 CaughtError::catch(ctx, self)
612 }
613}
614
615pub trait ThrowResultExt<'js, T> {
620 fn throw(self, ctx: &Ctx<'js>) -> Result<T>;
621}
622
623impl<'js, T> ThrowResultExt<'js, T> for CaughtResult<'js, T> {
624 fn throw(self, ctx: &Ctx<'js>) -> Result<T> {
625 self.map_err(|e| e.throw(ctx))
626 }
627}
628
629#[derive(Clone)]
634pub struct JobException(pub Context);
635
636impl fmt::Debug for JobException {
637 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
638 f.debug_tuple("JobException")
639 .field(&"TODO: Context")
640 .finish()
641 }
642}
643
644impl Display for JobException {
645 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
646 write!(f, "Job raised an exception")?;
647 Ok(())
649 }
650}
651
652#[cfg(feature = "futures")]
657#[derive(Clone)]
658pub struct AsyncJobException(pub AsyncContext);
659
660#[cfg(feature = "futures")]
661impl fmt::Debug for AsyncJobException {
662 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
663 f.debug_tuple("AsyncJobException")
664 .field(&"TODO: Context")
665 .finish()
666 }
667}
668
669#[cfg(feature = "futures")]
670impl Display for AsyncJobException {
671 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
672 write!(f, "Async job raised an exception")?;
673 Ok(())
675 }
676}
677
678impl<'js> Ctx<'js> {
679 pub(crate) fn handle_panic<F>(&self, f: F) -> qjs::JSValue
680 where
681 F: FnOnce() -> qjs::JSValue + UnwindSafe,
682 {
683 match crate::util::catch_unwind(f) {
684 Ok(x) => x,
685 Err(e) => unsafe {
686 self.get_opaque().set_panic(e);
687 qjs::JS_Throw(self.as_ptr(), qjs::JS_MKVAL(qjs::JS_TAG_EXCEPTION, 0))
688 },
689 }
690 }
691
692 pub(crate) unsafe fn handle_exception(&self, js_val: qjs::JSValue) -> Result<qjs::JSValue> {
698 if qjs::JS_VALUE_GET_NORM_TAG(js_val) != qjs::JS_TAG_EXCEPTION {
699 Ok(js_val)
700 } else {
701 if let Some(x) = self.get_opaque().take_panic() {
702 crate::util::resume_unwind(x);
703 }
704 Err(Error::Exception)
705 }
706 }
707
708 pub(crate) fn raise_exception(&self) -> Error {
711 unsafe {
713 if let Some(x) = self.get_opaque().take_panic() {
714 crate::util::resume_unwind(x);
715 }
716 Error::Exception
717 }
718 }
719}