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