mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1413 lines
47 KiB
1413 lines
47 KiB
//! Error-related types and conversions. |
|
|
|
use crate::{ |
|
builtins::{error::ErrorObject, Array}, |
|
js_string, |
|
object::JsObject, |
|
property::PropertyDescriptor, |
|
realm::Realm, |
|
Context, JsString, JsValue, |
|
}; |
|
use boa_gc::{custom_trace, Finalize, Trace}; |
|
use boa_macros::js_str; |
|
use std::{error, fmt}; |
|
use thiserror::Error; |
|
|
|
/// Create an opaque error object from a value or string literal. |
|
/// |
|
/// Can be used with an expression that converts into `JsValue` or a format |
|
/// string with arguments. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ``` |
|
/// # use boa_engine::{js_str, Context, JsValue}; |
|
/// use boa_engine::{js_error}; |
|
/// let context = &mut Context::default(); |
|
/// |
|
/// let error = js_error!("error!"); |
|
/// assert!(error.as_opaque().is_some()); |
|
/// assert_eq!(error.as_opaque().unwrap().to_string(context).unwrap(), "error!"); |
|
/// |
|
/// let error = js_error!("error: {}", 5); |
|
/// assert_eq!(error.as_opaque().unwrap().to_string(context).unwrap(), "error: 5"); |
|
/// |
|
/// // Non-string literals must be used as an expression. |
|
/// let error = js_error!({ true }); |
|
/// assert_eq!(error.as_opaque().unwrap(), &JsValue::from(true)); |
|
/// ``` |
|
#[macro_export] |
|
macro_rules! js_error { |
|
($value: literal) => { |
|
$crate::JsError::from_opaque($crate::JsValue::from( |
|
$crate::js_string!($value) |
|
)) |
|
}; |
|
($value: expr) => { |
|
$crate::JsError::from_opaque( |
|
$crate::JsValue::from($value) |
|
) |
|
}; |
|
($value: literal $(, $args: tt)* $(,)?) => { |
|
$crate::JsError::from_opaque($crate::JsValue::from( |
|
$crate::JsString::from(format!($value $(, $args)*)) |
|
)) |
|
}; |
|
} |
|
|
|
/// The error type returned by all operations related |
|
/// to the execution of Javascript code. |
|
/// |
|
/// This is essentially an enum that can store either [`JsNativeError`]s (for ideal |
|
/// native errors) or opaque [`JsValue`]s, since Javascript allows throwing any valid |
|
/// `JsValue`. |
|
/// |
|
/// The implementation doesn't provide a [`From`] conversion |
|
/// for `JsValue`. This is with the intent of encouraging the usage of proper |
|
/// `JsNativeError`s instead of plain `JsValue`s. However, if you |
|
/// do need a proper opaque error, you can construct one using the |
|
/// [`JsError::from_opaque`] method. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsError, JsNativeError, JsNativeErrorKind, JsValue, js_str}; |
|
/// let cause = JsError::from_opaque(js_str!("error!").into()); |
|
/// |
|
/// assert!(cause.as_opaque().is_some()); |
|
/// assert_eq!( |
|
/// cause.as_opaque().unwrap(), |
|
/// &JsValue::from(js_str!("error!")) |
|
/// ); |
|
/// |
|
/// let native_error: JsError = JsNativeError::typ() |
|
/// .with_message("invalid type!") |
|
/// .with_cause(cause) |
|
/// .into(); |
|
/// |
|
/// assert!(native_error.as_native().is_some()); |
|
/// |
|
/// let kind = &native_error.as_native().unwrap().kind; |
|
/// assert!(matches!(kind, JsNativeErrorKind::Type)); |
|
/// ``` |
|
#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize)] |
|
#[boa_gc(unsafe_no_drop)] |
|
pub struct JsError { |
|
inner: Repr, |
|
} |
|
|
|
/// Internal representation of a [`JsError`]. |
|
/// |
|
/// `JsError` is represented by an opaque enum because it restricts |
|
/// matching against `JsError` without calling `try_native` first. |
|
/// This allows us to provide a safe API for `Error` objects that extracts |
|
/// their info as a native `Rust` type ([`JsNativeError`]). |
|
/// |
|
/// This should never be used outside of this module. If that's not the case, |
|
/// you should add methods to either `JsError` or `JsNativeError` to |
|
/// represent that special use case. |
|
#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize)] |
|
#[boa_gc(unsafe_no_drop)] |
|
enum Repr { |
|
Native(JsNativeError), |
|
Opaque(JsValue), |
|
} |
|
|
|
/// The error type returned by the [`JsError::try_native`] method. |
|
#[derive(Debug, Clone, Error)] |
|
pub enum TryNativeError { |
|
/// A property of the error object has an invalid type. |
|
#[error("invalid type of property `{0}`")] |
|
InvalidPropertyType(&'static str), |
|
|
|
/// The message of the error object could not be decoded. |
|
#[error("property `message` cannot contain unpaired surrogates")] |
|
InvalidMessageEncoding, |
|
|
|
/// The constructor property of the error object was invalid. |
|
#[error("invalid `constructor` property of Error object")] |
|
InvalidConstructor, |
|
|
|
/// A property of the error object is not accessible. |
|
#[error("could not access property `{property}`")] |
|
InaccessibleProperty { |
|
/// The name of the property that could not be accessed. |
|
property: &'static str, |
|
|
|
/// The source error. |
|
source: JsError, |
|
}, |
|
|
|
/// An inner error of an aggregate error is not accessible. |
|
#[error("could not get element `{index}` of property `errors`")] |
|
InvalidErrorsIndex { |
|
/// The index of the error that could not be accessed. |
|
index: u64, |
|
|
|
/// The source error. |
|
source: JsError, |
|
}, |
|
|
|
/// The error value is not an error object. |
|
#[error("opaque error of type `{:?}` is not an Error object", .0.get_type())] |
|
NotAnErrorObject(JsValue), |
|
|
|
/// The original realm of the error object was inaccessible. |
|
#[error("could not access realm of Error object")] |
|
InaccessibleRealm { |
|
/// The source error. |
|
source: JsError, |
|
}, |
|
} |
|
|
|
impl error::Error for JsError { |
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
|
match &self.inner { |
|
Repr::Native(err) => err.source(), |
|
Repr::Opaque(_) => None, |
|
} |
|
} |
|
} |
|
|
|
impl JsError { |
|
/// Creates a new `JsError` from a native error `err`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsError, JsNativeError}; |
|
/// let error = JsError::from_native(JsNativeError::syntax()); |
|
/// |
|
/// assert!(error.as_native().is_some()); |
|
/// ``` |
|
#[must_use] |
|
pub const fn from_native(err: JsNativeError) -> Self { |
|
Self { |
|
inner: Repr::Native(err), |
|
} |
|
} |
|
|
|
/// Creates a new `JsError` from a Rust standard error `err`. |
|
/// This will create a new `JsNativeError` with the message of the standard error. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ``` |
|
/// # use boa_engine::JsError; |
|
/// let error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!"); |
|
/// let js_error: JsError = JsError::from_rust(error); |
|
/// |
|
/// assert_eq!(js_error.as_native().unwrap().message(), "oh no!"); |
|
/// assert!(js_error.as_native().unwrap().cause().is_none()); |
|
/// ``` |
|
#[must_use] |
|
pub fn from_rust(err: impl error::Error) -> Self { |
|
let mut native_err = JsNativeError::error().with_message(err.to_string()); |
|
if let Some(source) = err.source() { |
|
native_err = native_err.with_cause(Self::from_rust(source)); |
|
} |
|
|
|
Self::from_native(native_err) |
|
} |
|
|
|
/// Creates a new `JsError` from an opaque error `value`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::JsError; |
|
/// let error = JsError::from_opaque(5.0f64.into()); |
|
/// |
|
/// assert!(error.as_opaque().is_some()); |
|
/// ``` |
|
#[must_use] |
|
pub const fn from_opaque(value: JsValue) -> Self { |
|
Self { |
|
inner: Repr::Opaque(value), |
|
} |
|
} |
|
|
|
/// Converts the error to an opaque `JsValue` error |
|
/// |
|
/// Unwraps the inner `JsValue` if the error is already an opaque error. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{Context, JsError, JsNativeError}; |
|
/// # use boa_engine::builtins::error::ErrorObject; |
|
/// let context = &mut Context::default(); |
|
/// let error: JsError = JsNativeError::eval().with_message("invalid script").into(); |
|
/// let error_val = error.to_opaque(context); |
|
/// |
|
/// assert!(error_val.as_object().unwrap().is::<ErrorObject>()); |
|
/// ``` |
|
pub fn to_opaque(&self, context: &mut Context) -> JsValue { |
|
match &self.inner { |
|
Repr::Native(e) => e.to_opaque(context).into(), |
|
Repr::Opaque(v) => v.clone(), |
|
} |
|
} |
|
|
|
/// Unwraps the inner error if this contains a native error. |
|
/// Otherwise, inspects the opaque error and tries to extract the |
|
/// necessary information to construct a native error similar to the provided |
|
/// opaque error. If the conversion fails, returns a [`TryNativeError`] |
|
/// with the cause of the failure. |
|
/// |
|
/// # Note 1 |
|
/// |
|
/// This method won't try to make any conversions between JS types. |
|
/// In other words, for this conversion to succeed: |
|
/// - `message` **MUST** be a `JsString` value. |
|
/// - `errors` (in the case of `AggregateError`s) **MUST** be an `Array` object. |
|
/// |
|
/// # Note 2 |
|
/// |
|
/// This operation should be considered a lossy conversion, since it |
|
/// won't store any additional properties of the opaque |
|
/// error, other than `message`, `cause` and `errors` (in the case of |
|
/// `AggregateError`s). If you cannot affort a lossy conversion, clone |
|
/// the object before calling [`from_opaque`][JsError::from_opaque] |
|
/// to preserve its original properties. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{Context, JsError, JsNativeError, JsNativeErrorKind}; |
|
/// let context = &mut Context::default(); |
|
/// |
|
/// // create a new, opaque Error object |
|
/// let error: JsError = JsNativeError::typ().with_message("type error!").into(); |
|
/// let error_val = error.to_opaque(context); |
|
/// |
|
/// // then, try to recover the original |
|
/// let error = JsError::from_opaque(error_val).try_native(context).unwrap(); |
|
/// |
|
/// assert!(matches!(error.kind, JsNativeErrorKind::Type)); |
|
/// assert_eq!(error.message(), "type error!"); |
|
/// ``` |
|
pub fn try_native(&self, context: &mut Context) -> Result<JsNativeError, TryNativeError> { |
|
match &self.inner { |
|
Repr::Native(e) => Ok(e.clone()), |
|
Repr::Opaque(val) => { |
|
let obj = val |
|
.as_object() |
|
.ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; |
|
let error = *obj |
|
.downcast_ref::<ErrorObject>() |
|
.ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; |
|
|
|
let try_get_property = |key: JsString, name, context: &mut Context| { |
|
obj.try_get(key, context) |
|
.map_err(|e| TryNativeError::InaccessibleProperty { |
|
property: name, |
|
source: e, |
|
}) |
|
}; |
|
|
|
let message = if let Some(msg) = |
|
try_get_property(js_string!("message"), "message", context)? |
|
{ |
|
msg.as_string() |
|
.map(JsString::to_std_string) |
|
.transpose() |
|
.map_err(|_| TryNativeError::InvalidMessageEncoding)? |
|
.ok_or(TryNativeError::InvalidPropertyType("message"))? |
|
.into() |
|
} else { |
|
Box::default() |
|
}; |
|
|
|
let cause = try_get_property(js_string!("cause"), "cause", context)?; |
|
|
|
let kind = match error { |
|
ErrorObject::Error => JsNativeErrorKind::Error, |
|
ErrorObject::Eval => JsNativeErrorKind::Eval, |
|
ErrorObject::Type => JsNativeErrorKind::Type, |
|
ErrorObject::Range => JsNativeErrorKind::Range, |
|
ErrorObject::Reference => JsNativeErrorKind::Reference, |
|
ErrorObject::Syntax => JsNativeErrorKind::Syntax, |
|
ErrorObject::Uri => JsNativeErrorKind::Uri, |
|
ErrorObject::Aggregate => { |
|
let errors = obj.get(js_str!("errors"), context).map_err(|e| { |
|
TryNativeError::InaccessibleProperty { |
|
property: "errors", |
|
source: e, |
|
} |
|
})?; |
|
let mut error_list = Vec::new(); |
|
match errors.as_object() { |
|
Some(errors) if errors.is_array() => { |
|
let length = errors.length_of_array_like(context).map_err(|e| { |
|
TryNativeError::InaccessibleProperty { |
|
property: "errors.length", |
|
source: e, |
|
} |
|
})?; |
|
for i in 0..length { |
|
error_list.push(Self::from_opaque( |
|
errors.get(i, context).map_err(|e| { |
|
TryNativeError::InvalidErrorsIndex { |
|
index: i, |
|
source: e, |
|
} |
|
})?, |
|
)); |
|
} |
|
} |
|
_ => return Err(TryNativeError::InvalidPropertyType("errors")), |
|
} |
|
|
|
JsNativeErrorKind::Aggregate(error_list) |
|
} |
|
}; |
|
|
|
let realm = try_get_property(js_string!("constructor"), "constructor", context)? |
|
.as_ref() |
|
.and_then(JsValue::as_constructor) |
|
.ok_or(TryNativeError::InvalidConstructor)? |
|
.get_function_realm(context) |
|
.map_err(|err| TryNativeError::InaccessibleRealm { source: err })?; |
|
|
|
Ok(JsNativeError { |
|
kind, |
|
message, |
|
cause: cause.map(|v| Box::new(Self::from_opaque(v))), |
|
realm: Some(realm), |
|
}) |
|
} |
|
} |
|
} |
|
|
|
/// Gets the inner [`JsValue`] if the error is an opaque error, |
|
/// or `None` otherwise. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsError, JsNativeError}; |
|
/// let error: JsError = JsNativeError::reference() |
|
/// .with_message("variable not found!") |
|
/// .into(); |
|
/// |
|
/// assert!(error.as_opaque().is_none()); |
|
/// |
|
/// let error = JsError::from_opaque(256u32.into()); |
|
/// |
|
/// assert!(error.as_opaque().is_some()); |
|
/// ``` |
|
#[must_use] |
|
pub const fn as_opaque(&self) -> Option<&JsValue> { |
|
match self.inner { |
|
Repr::Native(_) => None, |
|
Repr::Opaque(ref v) => Some(v), |
|
} |
|
} |
|
|
|
/// Gets the inner [`JsNativeError`] if the error is a native |
|
/// error, or `None` otherwise. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsError, JsNativeError, JsValue}; |
|
/// let error: JsError = JsNativeError::error().with_message("Unknown error").into(); |
|
/// |
|
/// assert!(error.as_native().is_some()); |
|
/// |
|
/// let error = JsError::from_opaque(JsValue::undefined()); |
|
/// |
|
/// assert!(error.as_native().is_none()); |
|
/// ``` |
|
#[must_use] |
|
pub const fn as_native(&self) -> Option<&JsNativeError> { |
|
match &self.inner { |
|
Repr::Native(e) => Some(e), |
|
Repr::Opaque(_) => None, |
|
} |
|
} |
|
|
|
/// Converts this error into its thread-safe, erased version. |
|
/// |
|
/// Even though this operation is lossy, converting into a `JsErasedError` |
|
/// is useful since it implements `Send` and `Sync`, making it compatible with |
|
/// error reporting frameworks such as `anyhow`, `eyre` or `miette`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{js_string, Context, JsError, JsNativeError, JsSymbol, JsValue}; |
|
/// # use std::error::Error; |
|
/// let context = &mut Context::default(); |
|
/// let cause = JsError::from_opaque(JsSymbol::new(Some(js_string!("error!"))).unwrap().into()); |
|
/// |
|
/// let native_error: JsError = JsNativeError::typ() |
|
/// .with_message("invalid type!") |
|
/// .with_cause(cause) |
|
/// .into(); |
|
/// |
|
/// let erased_error = native_error.into_erased(context); |
|
/// |
|
/// assert_eq!(erased_error.to_string(), "TypeError: invalid type!"); |
|
/// |
|
/// let send_sync_error: Box<dyn Error + Send + Sync> = Box::new(erased_error); |
|
/// |
|
/// assert_eq!( |
|
/// send_sync_error.source().unwrap().to_string(), |
|
/// "Symbol(error!)" |
|
/// ); |
|
/// ``` |
|
pub fn into_erased(self, context: &mut Context) -> JsErasedError { |
|
let Ok(native) = self.try_native(context) else { |
|
return JsErasedError { |
|
inner: ErasedRepr::Opaque(self.to_string().into_boxed_str()), |
|
}; |
|
}; |
|
|
|
let JsNativeError { |
|
kind, |
|
message, |
|
cause, |
|
.. |
|
} = native; |
|
|
|
let cause = cause.map(|err| Box::new(err.into_erased(context))); |
|
|
|
let kind = match kind { |
|
JsNativeErrorKind::Aggregate(errors) => JsErasedNativeErrorKind::Aggregate( |
|
errors |
|
.into_iter() |
|
.map(|err| err.into_erased(context)) |
|
.collect(), |
|
), |
|
JsNativeErrorKind::Error => JsErasedNativeErrorKind::Error, |
|
JsNativeErrorKind::Eval => JsErasedNativeErrorKind::Eval, |
|
JsNativeErrorKind::Range => JsErasedNativeErrorKind::Range, |
|
JsNativeErrorKind::Reference => JsErasedNativeErrorKind::Reference, |
|
JsNativeErrorKind::Syntax => JsErasedNativeErrorKind::Syntax, |
|
JsNativeErrorKind::Type => JsErasedNativeErrorKind::Type, |
|
JsNativeErrorKind::Uri => JsErasedNativeErrorKind::Uri, |
|
JsNativeErrorKind::RuntimeLimit => JsErasedNativeErrorKind::RuntimeLimit, |
|
#[cfg(feature = "fuzz")] |
|
JsNativeErrorKind::NoInstructionsRemain => unreachable!( |
|
"The NoInstructionsRemain native error cannot be converted to an erased kind." |
|
), |
|
}; |
|
|
|
JsErasedError { |
|
inner: ErasedRepr::Native(JsErasedNativeError { |
|
kind, |
|
message, |
|
cause, |
|
}), |
|
} |
|
} |
|
|
|
/// Injects a realm on the `realm` field of a native error. |
|
/// |
|
/// This is a no-op if the error is not native or if the `realm` field of the error is already |
|
/// set. |
|
pub(crate) fn inject_realm(mut self, realm: Realm) -> Self { |
|
match &mut self.inner { |
|
Repr::Native(err) if err.realm.is_none() => { |
|
err.realm = Some(realm); |
|
} |
|
_ => {} |
|
} |
|
self |
|
} |
|
|
|
/// Is the [`JsError`] catchable in JavaScript. |
|
#[inline] |
|
pub(crate) fn is_catchable(&self) -> bool { |
|
self.as_native().map_or(true, JsNativeError::is_catchable) |
|
} |
|
} |
|
|
|
impl From<boa_parser::Error> for JsError { |
|
fn from(err: boa_parser::Error) -> Self { |
|
Self::from(JsNativeError::from(err)) |
|
} |
|
} |
|
|
|
impl From<JsNativeError> for JsError { |
|
fn from(error: JsNativeError) -> Self { |
|
Self { |
|
inner: Repr::Native(error), |
|
} |
|
} |
|
} |
|
|
|
impl fmt::Display for JsError { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
match &self.inner { |
|
Repr::Native(e) => e.fmt(f), |
|
Repr::Opaque(v) => v.display().fmt(f), |
|
} |
|
} |
|
} |
|
|
|
/// Native representation of an ideal `Error` object from Javascript. |
|
/// |
|
/// This representation is more space efficient than its [`JsObject`] equivalent, |
|
/// since it doesn't need to create a whole new `JsObject` to be instantiated. |
|
/// Prefer using this over [`JsError`] when you don't need to throw |
|
/// plain [`JsValue`]s as errors, or when you need to inspect the error type |
|
/// of a `JsError`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let native_error = JsNativeError::uri().with_message("cannot decode uri"); |
|
/// |
|
/// match native_error.kind { |
|
/// JsNativeErrorKind::Uri => { /* handle URI error*/ } |
|
/// _ => unreachable!(), |
|
/// } |
|
/// |
|
/// assert_eq!(native_error.message(), "cannot decode uri"); |
|
/// ``` |
|
#[derive(Clone, Finalize, Error, PartialEq, Eq)] |
|
pub struct JsNativeError { |
|
/// The kind of native error (e.g. `TypeError`, `SyntaxError`, etc.) |
|
pub kind: JsNativeErrorKind, |
|
message: Box<str>, |
|
#[source] |
|
cause: Option<Box<JsError>>, |
|
realm: Option<Realm>, |
|
} |
|
|
|
impl fmt::Display for JsNativeError { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
write!(f, "{}", self.kind)?; |
|
|
|
let message = self.message.trim(); |
|
if !message.is_empty() { |
|
write!(f, ": {message}")?; |
|
} |
|
|
|
Ok(()) |
|
} |
|
} |
|
|
|
// SAFETY: just mirroring the default derive to allow destructuring. |
|
unsafe impl Trace for JsNativeError { |
|
custom_trace!(this, mark, { |
|
mark(&this.kind); |
|
mark(&this.cause); |
|
mark(&this.realm); |
|
}); |
|
} |
|
|
|
impl fmt::Debug for JsNativeError { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
f.debug_struct("JsNativeError") |
|
.field("kind", &self.kind) |
|
.field("message", &self.message) |
|
.field("cause", &self.cause) |
|
.finish_non_exhaustive() |
|
} |
|
} |
|
|
|
impl JsNativeError { |
|
/// Creates a new `JsNativeError` from its `kind`, `message` and (optionally) its `cause`. |
|
fn new(kind: JsNativeErrorKind, message: Box<str>, cause: Option<Box<JsError>>) -> Self { |
|
Self { |
|
kind, |
|
message, |
|
cause, |
|
realm: None, |
|
} |
|
} |
|
|
|
/// Creates a new `JsNativeError` of kind `AggregateError` from a list of [`JsError`]s, with |
|
/// empty `message` and undefined `cause`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let inner_errors = vec![ |
|
/// JsNativeError::typ().into(), |
|
/// JsNativeError::syntax().into() |
|
/// ]; |
|
/// let error = JsNativeError::aggregate(inner_errors); |
|
/// |
|
/// assert!(matches!( |
|
/// error.kind, |
|
/// JsNativeErrorKind::Aggregate(ref errors) if errors.len() == 2 |
|
/// )); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn aggregate(errors: Vec<JsError>) -> Self { |
|
Self::new(JsNativeErrorKind::Aggregate(errors), Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::Aggregate`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_aggregate(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::Aggregate(_)) |
|
} |
|
|
|
/// Creates a new `JsNativeError` of kind `Error`, with empty `message` and undefined `cause`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let error = JsNativeError::error(); |
|
/// |
|
/// assert!(matches!(error.kind, JsNativeErrorKind::Error)); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn error() -> Self { |
|
Self::new(JsNativeErrorKind::Error, Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::Error`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_error(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::Error) |
|
} |
|
|
|
/// Creates a new `JsNativeError` of kind `EvalError`, with empty `message` and undefined `cause`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let error = JsNativeError::eval(); |
|
/// |
|
/// assert!(matches!(error.kind, JsNativeErrorKind::Eval)); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn eval() -> Self { |
|
Self::new(JsNativeErrorKind::Eval, Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::Eval`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_eval(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::Eval) |
|
} |
|
|
|
/// Creates a new `JsNativeError` of kind `RangeError`, with empty `message` and undefined `cause`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let error = JsNativeError::range(); |
|
/// |
|
/// assert!(matches!(error.kind, JsNativeErrorKind::Range)); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn range() -> Self { |
|
Self::new(JsNativeErrorKind::Range, Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::Range`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_range(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::Range) |
|
} |
|
|
|
/// Creates a new `JsNativeError` of kind `ReferenceError`, with empty `message` and undefined `cause`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let error = JsNativeError::reference(); |
|
/// |
|
/// assert!(matches!(error.kind, JsNativeErrorKind::Reference)); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn reference() -> Self { |
|
Self::new(JsNativeErrorKind::Reference, Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::Reference`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_reference(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::Reference) |
|
} |
|
|
|
/// Creates a new `JsNativeError` of kind `SyntaxError`, with empty `message` and undefined `cause`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let error = JsNativeError::syntax(); |
|
/// |
|
/// assert!(matches!(error.kind, JsNativeErrorKind::Syntax)); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn syntax() -> Self { |
|
Self::new(JsNativeErrorKind::Syntax, Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::Syntax`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_syntax(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::Syntax) |
|
} |
|
|
|
/// Creates a new `JsNativeError` of kind `TypeError`, with empty `message` and undefined `cause`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let error = JsNativeError::typ(); |
|
/// |
|
/// assert!(matches!(error.kind, JsNativeErrorKind::Type)); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn typ() -> Self { |
|
Self::new(JsNativeErrorKind::Type, Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::Type`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_type(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::Type) |
|
} |
|
|
|
/// Creates a new `JsNativeError` of kind `UriError`, with empty `message` and undefined `cause`. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; |
|
/// let error = JsNativeError::uri(); |
|
/// |
|
/// assert!(matches!(error.kind, JsNativeErrorKind::Uri)); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn uri() -> Self { |
|
Self::new(JsNativeErrorKind::Uri, Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::Uri`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_uri(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::Uri) |
|
} |
|
|
|
/// Creates a new `JsNativeError` that indicates that the context hit its execution limit. This |
|
/// is only used in a fuzzing context. |
|
#[cfg(feature = "fuzz")] |
|
#[must_use] |
|
pub fn no_instructions_remain() -> Self { |
|
Self::new( |
|
JsNativeErrorKind::NoInstructionsRemain, |
|
Box::default(), |
|
None, |
|
) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::NoInstructionsRemain`]. |
|
#[must_use] |
|
#[inline] |
|
#[cfg(feature = "fuzz")] |
|
pub const fn is_no_instructions_remain(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::NoInstructionsRemain) |
|
} |
|
|
|
/// Creates a new `JsNativeError` that indicates that the context exceeded the runtime limits. |
|
#[must_use] |
|
#[inline] |
|
pub fn runtime_limit() -> Self { |
|
Self::new(JsNativeErrorKind::RuntimeLimit, Box::default(), None) |
|
} |
|
|
|
/// Check if it's a [`JsNativeErrorKind::RuntimeLimit`]. |
|
#[must_use] |
|
#[inline] |
|
pub const fn is_runtime_limit(&self) -> bool { |
|
matches!(self.kind, JsNativeErrorKind::RuntimeLimit) |
|
} |
|
|
|
/// Sets the message of this error. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::JsNativeError; |
|
/// let error = JsNativeError::range().with_message("number too large"); |
|
/// |
|
/// assert_eq!(error.message(), "number too large"); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn with_message<S>(mut self, message: S) -> Self |
|
where |
|
S: Into<Box<str>>, |
|
{ |
|
self.message = message.into(); |
|
self |
|
} |
|
|
|
/// Sets the cause of this error. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::JsNativeError; |
|
/// let cause = JsNativeError::syntax(); |
|
/// let error = JsNativeError::error().with_cause(cause); |
|
/// |
|
/// assert!(error.cause().unwrap().as_native().is_some()); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn with_cause<V>(mut self, cause: V) -> Self |
|
where |
|
V: Into<JsError>, |
|
{ |
|
self.cause = Some(Box::new(cause.into())); |
|
self |
|
} |
|
|
|
/// Gets the `message` of this error. |
|
/// |
|
/// This is equivalent to the [`NativeError.prototype.message`][spec] |
|
/// property. |
|
/// |
|
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::JsNativeError; |
|
/// let error = JsNativeError::range().with_message("number too large"); |
|
/// |
|
/// assert_eq!(error.message(), "number too large"); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub const fn message(&self) -> &str { |
|
&self.message |
|
} |
|
|
|
/// Gets the `cause` of this error. |
|
/// |
|
/// This is equivalent to the [`NativeError.prototype.cause`][spec] |
|
/// property. |
|
/// |
|
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::JsNativeError; |
|
/// let cause = JsNativeError::syntax(); |
|
/// let error = JsNativeError::error().with_cause(cause); |
|
/// |
|
/// assert!(error.cause().unwrap().as_native().is_some()); |
|
/// ``` |
|
#[must_use] |
|
#[inline] |
|
pub fn cause(&self) -> Option<&JsError> { |
|
self.cause.as_deref() |
|
} |
|
|
|
/// Converts this native error to its opaque representation as a [`JsObject`]. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ```rust |
|
/// # use boa_engine::{Context, JsError, JsNativeError, js_str}; |
|
/// # use boa_engine::builtins::error::ErrorObject; |
|
/// let context = &mut Context::default(); |
|
/// |
|
/// let error = JsNativeError::error().with_message("error!"); |
|
/// let error_obj = error.to_opaque(context); |
|
/// |
|
/// assert!(error_obj.is::<ErrorObject>()); |
|
/// assert_eq!( |
|
/// error_obj.get(js_str!("message"), context).unwrap(), |
|
/// js_str!("error!").into() |
|
/// ) |
|
/// ``` |
|
/// |
|
/// # Panics |
|
/// |
|
/// If converting a [`JsNativeErrorKind::RuntimeLimit`] to an opaque object. |
|
#[inline] |
|
pub fn to_opaque(&self, context: &mut Context) -> JsObject { |
|
let Self { |
|
kind, |
|
message, |
|
cause, |
|
realm, |
|
} = self; |
|
let constructors = realm.as_ref().map_or_else( |
|
|| context.intrinsics().constructors(), |
|
|realm| realm.intrinsics().constructors(), |
|
); |
|
let (prototype, tag) = match kind { |
|
JsNativeErrorKind::Aggregate(_) => ( |
|
constructors.aggregate_error().prototype(), |
|
ErrorObject::Aggregate, |
|
), |
|
JsNativeErrorKind::Error => (constructors.error().prototype(), ErrorObject::Error), |
|
JsNativeErrorKind::Eval => (constructors.eval_error().prototype(), ErrorObject::Eval), |
|
JsNativeErrorKind::Range => { |
|
(constructors.range_error().prototype(), ErrorObject::Range) |
|
} |
|
JsNativeErrorKind::Reference => ( |
|
constructors.reference_error().prototype(), |
|
ErrorObject::Reference, |
|
), |
|
JsNativeErrorKind::Syntax => { |
|
(constructors.syntax_error().prototype(), ErrorObject::Syntax) |
|
} |
|
JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorObject::Type), |
|
JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorObject::Uri), |
|
#[cfg(feature = "fuzz")] |
|
JsNativeErrorKind::NoInstructionsRemain => { |
|
unreachable!( |
|
"The NoInstructionsRemain native error cannot be converted to an opaque type." |
|
) |
|
} |
|
JsNativeErrorKind::RuntimeLimit => { |
|
panic!("The RuntimeLimit native error cannot be converted to an opaque type.") |
|
} |
|
}; |
|
|
|
let o = |
|
JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, tag); |
|
|
|
o.create_non_enumerable_data_property_or_throw( |
|
js_str!("message"), |
|
js_string!(&**message), |
|
context, |
|
); |
|
|
|
if let Some(cause) = cause { |
|
o.create_non_enumerable_data_property_or_throw( |
|
js_str!("cause"), |
|
cause.to_opaque(context), |
|
context, |
|
); |
|
} |
|
|
|
if let JsNativeErrorKind::Aggregate(errors) = kind { |
|
let errors = errors |
|
.iter() |
|
.map(|e| e.to_opaque(context)) |
|
.collect::<Vec<_>>(); |
|
let errors = Array::create_array_from_list(errors, context); |
|
o.define_property_or_throw( |
|
js_str!("errors"), |
|
PropertyDescriptor::builder() |
|
.configurable(true) |
|
.enumerable(false) |
|
.writable(true) |
|
.value(errors), |
|
context, |
|
) |
|
.expect("The spec guarantees this succeeds for a newly created object "); |
|
} |
|
o |
|
} |
|
|
|
/// Sets the realm of this error. |
|
pub(crate) fn with_realm(mut self, realm: Realm) -> Self { |
|
self.realm = Some(realm); |
|
self |
|
} |
|
|
|
/// Is the [`JsNativeError`] catchable in JavaScript. |
|
#[inline] |
|
pub(crate) fn is_catchable(&self) -> bool { |
|
self.kind.is_catchable() |
|
} |
|
} |
|
|
|
impl From<boa_parser::Error> for JsNativeError { |
|
fn from(err: boa_parser::Error) -> Self { |
|
Self::syntax().with_message(err.to_string()) |
|
} |
|
} |
|
|
|
/// The list of possible error types a [`JsNativeError`] can be. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-error-objects |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error |
|
#[derive(Debug, Clone, Finalize, PartialEq, Eq)] |
|
#[non_exhaustive] |
|
pub enum JsNativeErrorKind { |
|
/// A collection of errors wrapped in a single error. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-aggregate-error-objects |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError |
|
Aggregate(Vec<JsError>), |
|
/// A generic error. Commonly used as the base for custom exceptions. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-error-constructor |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error |
|
Error, |
|
/// An error related to the global function [`eval()`][eval]. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError |
|
/// [eval]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval |
|
Eval, |
|
/// An error thrown when a value is outside its valid range. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError |
|
Range, |
|
/// An error representing an invalid de-reference of a variable. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError |
|
Reference, |
|
/// An error representing an invalid syntax in the Javascript language. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError |
|
Syntax, |
|
/// An error thrown when a variable or argument is not of a valid type. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError |
|
Type, |
|
/// An error thrown when the [`encodeURI()`][e_uri] and [`decodeURI()`][d_uri] functions receive |
|
/// invalid parameters. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError |
|
/// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI |
|
/// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI |
|
Uri, |
|
|
|
/// Error thrown when no instructions remain. Only used in a fuzzing context; not a valid JS |
|
/// error variant. |
|
#[cfg(feature = "fuzz")] |
|
NoInstructionsRemain, |
|
|
|
/// Error thrown when a runtime limit is exceeded. It's not a valid JS error variant. |
|
RuntimeLimit, |
|
} |
|
|
|
// SAFETY: just mirroring the default derive to allow destructuring. |
|
unsafe impl Trace for JsNativeErrorKind { |
|
custom_trace!( |
|
this, |
|
mark, |
|
match &this { |
|
Self::Aggregate(errors) => mark(errors), |
|
Self::Error |
|
| Self::Eval |
|
| Self::Range |
|
| Self::Reference |
|
| Self::Syntax |
|
| Self::Type |
|
| Self::Uri |
|
| Self::RuntimeLimit => {} |
|
#[cfg(feature = "fuzz")] |
|
Self::NoInstructionsRemain => {} |
|
} |
|
); |
|
} |
|
|
|
impl JsNativeErrorKind { |
|
/// Is the [`JsNativeErrorKind`] catchable in JavaScript. |
|
#[inline] |
|
pub(crate) fn is_catchable(&self) -> bool { |
|
match self { |
|
Self::Aggregate(_) |
|
| Self::Error |
|
| Self::Eval |
|
| Self::Range |
|
| Self::Reference |
|
| Self::Syntax |
|
| Self::Type |
|
| Self::Uri => true, |
|
Self::RuntimeLimit => false, |
|
#[cfg(feature = "fuzz")] |
|
Self::NoInstructionsRemain => false, |
|
} |
|
} |
|
} |
|
|
|
impl PartialEq<ErrorObject> for JsNativeErrorKind { |
|
fn eq(&self, other: &ErrorObject) -> bool { |
|
matches!( |
|
(self, other), |
|
(Self::Aggregate(_), ErrorObject::Aggregate) |
|
| (Self::Error, ErrorObject::Error) |
|
| (Self::Eval, ErrorObject::Eval) |
|
| (Self::Range, ErrorObject::Range) |
|
| (Self::Reference, ErrorObject::Reference) |
|
| (Self::Syntax, ErrorObject::Syntax) |
|
| (Self::Type, ErrorObject::Type) |
|
| (Self::Uri, ErrorObject::Uri) |
|
) |
|
} |
|
} |
|
|
|
impl fmt::Display for JsNativeErrorKind { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
match self { |
|
Self::Aggregate(_) => "AggregateError", |
|
Self::Error => "Error", |
|
Self::Eval => "EvalError", |
|
Self::Range => "RangeError", |
|
Self::Reference => "ReferenceError", |
|
Self::Syntax => "SyntaxError", |
|
Self::Type => "TypeError", |
|
Self::Uri => "UriError", |
|
Self::RuntimeLimit => "RuntimeLimit", |
|
#[cfg(feature = "fuzz")] |
|
Self::NoInstructionsRemain => "NoInstructionsRemain", |
|
} |
|
.fmt(f) |
|
} |
|
} |
|
|
|
/// Erased version of [`JsError`]. |
|
/// |
|
/// This is mainly useful to convert a `JsError` into an `Error` that also |
|
/// implements `Send + Sync`, which makes it compatible with error reporting tools |
|
/// such as `anyhow`, `eyre` or `miette`. |
|
/// |
|
/// Generally, the conversion from `JsError` to `JsErasedError` is unidirectional, |
|
/// since any `JsError` that is a [`JsValue`] is converted to its string representation |
|
/// instead. This will lose information if that value was an object, a symbol or a big int. |
|
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)] |
|
pub struct JsErasedError { |
|
inner: ErasedRepr, |
|
} |
|
|
|
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)] |
|
enum ErasedRepr { |
|
Native(JsErasedNativeError), |
|
Opaque(Box<str>), |
|
} |
|
|
|
impl fmt::Display for JsErasedError { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
match &self.inner { |
|
ErasedRepr::Native(e) => e.fmt(f), |
|
ErasedRepr::Opaque(v) => fmt::Display::fmt(v, f), |
|
} |
|
} |
|
} |
|
|
|
impl error::Error for JsErasedError { |
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
|
match &self.inner { |
|
ErasedRepr::Native(err) => err.source(), |
|
ErasedRepr::Opaque(_) => None, |
|
} |
|
} |
|
} |
|
|
|
impl JsErasedError { |
|
/// Gets the inner [`str`] if the error is an opaque error, |
|
/// or `None` otherwise. |
|
#[must_use] |
|
pub const fn as_opaque(&self) -> Option<&str> { |
|
match self.inner { |
|
ErasedRepr::Native(_) => None, |
|
ErasedRepr::Opaque(ref v) => Some(v), |
|
} |
|
} |
|
|
|
/// Gets the inner [`JsErasedNativeError`] if the error is a native |
|
/// error, or `None` otherwise. |
|
#[must_use] |
|
pub const fn as_native(&self) -> Option<&JsErasedNativeError> { |
|
match &self.inner { |
|
ErasedRepr::Native(e) => Some(e), |
|
ErasedRepr::Opaque(_) => None, |
|
} |
|
} |
|
} |
|
|
|
/// Erased version of [`JsNativeError`]. |
|
#[derive(Debug, Clone, Trace, Finalize, Error, PartialEq, Eq)] |
|
pub struct JsErasedNativeError { |
|
/// The kind of native error (e.g. `TypeError`, `SyntaxError`, etc.) |
|
pub kind: JsErasedNativeErrorKind, |
|
message: Box<str>, |
|
#[source] |
|
cause: Option<Box<JsErasedError>>, |
|
} |
|
|
|
impl fmt::Display for JsErasedNativeError { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
write!(f, "{}", self.kind)?; |
|
|
|
let message = self.message.trim(); |
|
if !message.is_empty() { |
|
write!(f, ": {message}")?; |
|
} |
|
|
|
Ok(()) |
|
} |
|
} |
|
|
|
/// Erased version of [`JsNativeErrorKind`] |
|
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)] |
|
#[non_exhaustive] |
|
pub enum JsErasedNativeErrorKind { |
|
/// A collection of errors wrapped in a single error. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-aggregate-error-objects |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError |
|
Aggregate(Vec<JsErasedError>), |
|
/// A generic error. Commonly used as the base for custom exceptions. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-error-constructor |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error |
|
Error, |
|
/// An error related to the global function [`eval()`][eval]. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError |
|
/// [eval]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval |
|
Eval, |
|
/// An error thrown when a value is outside its valid range. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError |
|
Range, |
|
/// An error representing an invalid de-reference of a variable. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError |
|
Reference, |
|
/// An error representing an invalid syntax in the Javascript language. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError |
|
Syntax, |
|
/// An error thrown when a variable or argument is not of a valid type. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError |
|
Type, |
|
/// An error thrown when the [`encodeURI()`][e_uri] and [`decodeURI()`][d_uri] functions receive |
|
/// invalid parameters. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError |
|
/// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI |
|
/// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI |
|
Uri, |
|
|
|
/// Error thrown when a runtime limit is exceeded. It's not a valid JS error variant. |
|
RuntimeLimit, |
|
} |
|
|
|
impl fmt::Display for JsErasedNativeErrorKind { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
match &self { |
|
Self::Aggregate(errors) => { |
|
return write!(f, "AggregateError(error count: {})", errors.len()); |
|
} |
|
Self::Error => "Error", |
|
Self::Eval => "EvalError", |
|
Self::Range => "RangeError", |
|
Self::Reference => "ReferenceError", |
|
Self::Syntax => "SyntaxError", |
|
Self::Type => "TypeError", |
|
Self::Uri => "UriError", |
|
Self::RuntimeLimit => "RuntimeLimit", |
|
} |
|
.fmt(f) |
|
} |
|
}
|
|
|