use crate::{
Context, JsString, JsValue,
builtins::{
Array,
error::{Error, ErrorKind},
},
js_string,
object::JsObject,
property::PropertyDescriptor,
realm::Realm,
vm::{
NativeSourceInfo,
shadow_stack::{Backtrace, ShadowEntry},
},
};
use boa_gc::{Finalize, Trace, custom_trace};
use std::{
borrow::Cow,
error,
fmt::{self},
};
use thiserror::Error;
#[macro_export]
macro_rules! js_error {
(Error: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::ERROR.with_message($value)
)
};
(Error: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::ERROR
.with_message(format!($value $(, $args)*))
)
};
(TypeError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::TYP.with_message($value)
)
};
(TypeError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::TYP
.with_message(format!($value $(, $args)*))
)
};
(SyntaxError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::SYNTAX.with_message($value)
)
};
(SyntaxError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::SYNTAX.with_message(format!($value $(, $args)*))
)
};
(RangeError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::RANGE.with_message($value)
)
};
(RangeError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::RANGE.with_message(format!($value $(, $args)*))
)
};
(EvalError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::EVAL.with_message($value)
)
};
(EvalError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::EVAL.with_message(format!($value $(, $args)*))
)
};
(ReferenceError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::REFERENCE.with_message($value)
)
};
(ReferenceError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::REFERENCE.with_message(format!($value $(, $args)*))
)
};
(URIError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::URI.with_message($value)
)
};
(URIError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::URI.with_message(format!($value $(, $args)*))
)
};
($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: expr)* $(,)?) => {
$crate::JsError::from_opaque($crate::JsValue::from(
$crate::JsString::from(format!($value $(, $args)*))
))
};
}
#[derive(Debug, Clone, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub struct JsError {
inner: Repr,
pub(crate) backtrace: Option<Backtrace>,
}
impl Eq for JsError {}
impl PartialEq for JsError {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
enum Repr {
Native(Box<JsNativeError>),
Opaque(JsValue),
}
#[derive(Debug, Clone, Error)]
pub enum TryNativeError {
#[error("invalid type of property `{0}`")]
InvalidPropertyType(&'static str),
#[error("property `message` cannot contain unpaired surrogates")]
InvalidMessageEncoding,
#[error("invalid `constructor` property of Error object")]
InvalidConstructor,
#[error("could not access property `{property}`")]
InaccessibleProperty {
property: &'static str,
source: JsError,
},
#[error("could not get element `{index}` of property `errors`")]
InvalidErrorsIndex {
index: u64,
source: JsError,
},
#[error("opaque error of type `{:?}` is not an Error object", .0.get_type())]
NotAnErrorObject(JsValue),
#[error("could not access realm of Error object")]
InaccessibleRealm {
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 {
#[must_use]
pub fn from_native(err: JsNativeError) -> Self {
Self {
inner: Repr::Native(Box::new(err)),
backtrace: 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)
}
#[must_use]
pub const fn from_opaque(value: JsValue) -> Self {
Self {
inner: Repr::Opaque(value),
backtrace: None,
}
}
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(),
}
}
pub fn try_native(&self, context: &mut Context) -> Result<JsNativeError, TryNativeError> {
match &self.inner {
Repr::Native(e) => Ok(e.as_ref().clone()),
Repr::Opaque(val) => {
let obj = val
.as_object()
.ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?;
let error_data: Error = obj
.downcast_ref::<Error>()
.ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?
.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)?
{
Cow::Owned(
msg.as_string()
.as_ref()
.map(JsString::to_std_string)
.transpose()
.map_err(|_| TryNativeError::InvalidMessageEncoding)?
.ok_or(TryNativeError::InvalidPropertyType("message"))?,
)
} else {
Cow::Borrowed("")
};
let cause = try_get_property(js_string!("cause"), "cause", context)?;
let position = error_data.position.clone();
let kind = match error_data.tag {
ErrorKind::Error => JsNativeErrorKind::Error,
ErrorKind::Eval => JsNativeErrorKind::Eval,
ErrorKind::Type => JsNativeErrorKind::Type,
ErrorKind::Range => JsNativeErrorKind::Range,
ErrorKind::Reference => JsNativeErrorKind::Reference,
ErrorKind::Syntax => JsNativeErrorKind::Syntax,
ErrorKind::Uri => JsNativeErrorKind::Uri,
ErrorKind::Aggregate => {
let errors = obj.get(js_string!("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),
position,
})
}
}
}
#[must_use]
pub const fn as_opaque(&self) -> Option<&JsValue> {
match self.inner {
Repr::Native(_) => None,
Repr::Opaque(ref v) => Some(v),
}
}
#[must_use]
pub const fn as_native(&self) -> Option<&JsNativeError> {
match &self.inner {
Repr::Native(e) => Some(e),
Repr::Opaque(_) => None,
}
}
pub fn into_erased(self, context: &mut Context) -> JsErasedError {
let Ok(native) = self.try_native(context) else {
return JsErasedError {
inner: ErasedRepr::Opaque(Cow::Owned(self.to_string())),
};
};
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,
}),
}
}
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
}
#[inline]
pub(crate) fn is_catchable(&self) -> bool {
self.as_native().is_none_or(JsNativeError::is_catchable)
}
}
impl From<boa_parser::Error> for JsError {
#[cfg_attr(feature = "native-backtrace", track_caller)]
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(Box::new(error)),
backtrace: None,
}
}
}
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)?,
}
if let Some(shadow_stack) = &self.backtrace {
for entry in shadow_stack.iter().rev() {
write!(f, "\n at ")?;
match entry {
ShadowEntry::Native {
function_name,
source_info,
} => {
if let Some(function_name) = function_name {
write!(f, "{}", function_name.to_std_string_escaped())?;
} else {
f.write_str("<anonymous>")?;
}
if let Some(loc) = source_info.as_location() {
write!(
f,
" (native at {}:{}:{})",
loc.file(),
loc.line(),
loc.column()
)?;
} else {
f.write_str(" (native)")?;
}
}
ShadowEntry::Bytecode { pc, source_info } => {
let has_function_name = !source_info.function_name().is_empty();
if has_function_name {
write!(f, "{}", source_info.function_name().to_std_string_escaped(),)?;
} else {
f.write_str("<anonymous>")?;
}
f.write_str(" (")?;
source_info.map().path().fmt(f)?;
if let Some(position) = source_info.map().find(*pc) {
write!(
f,
":{}:{}",
position.line_number(),
position.column_number()
)?;
} else {
f.write_str(":?:?")?;
}
f.write_str(")")?;
}
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, Finalize)]
pub(crate) struct IgnoreEq<T>(pub(crate) T);
impl<T> Eq for IgnoreEq<T> {}
impl<T> PartialEq for IgnoreEq<T> {
#[inline]
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<T> std::hash::Hash for IgnoreEq<T> {
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
}
impl<T> From<T> for IgnoreEq<T> {
#[inline]
fn from(value: T) -> Self {
Self(value)
}
}
#[derive(Clone, Finalize, Error, PartialEq, Eq)]
pub struct JsNativeError {
pub kind: JsNativeErrorKind,
message: Cow<'static, str>,
#[source]
cause: Option<Box<JsError>>,
realm: Option<Realm>,
position: IgnoreEq<Option<ShadowEntry>>,
}
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}")?;
}
if let Some(position) = &self.position.0 {
position.fmt(f)?;
}
Ok(())
}
}
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 {
pub const AGGREGATE: Self = Self::aggregate(Vec::new());
pub const ERROR: Self = Self::error();
pub const EVAL: Self = Self::eval();
pub const RANGE: Self = Self::range();
pub const REFERENCE: Self = Self::reference();
pub const SYNTAX: Self = Self::syntax();
pub const TYP: Self = Self::typ();
pub const URI: Self = Self::uri();
#[cfg(feature = "fuzz")]
pub const NO_INSTRUCTIONS_REMAIN: Self = Self::no_instructions_remain();
pub const RUNTIME_LIMIT: Self = Self::runtime_limit();
#[cfg_attr(feature = "native-backtrace", track_caller)]
const fn new(
kind: JsNativeErrorKind,
message: Cow<'static, str>,
cause: Option<Box<JsError>>,
) -> Self {
Self {
kind,
message,
cause,
realm: None,
position: IgnoreEq(Some(ShadowEntry::Native {
function_name: None,
source_info: NativeSourceInfo::caller(),
})),
}
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn aggregate(errors: Vec<JsError>) -> Self {
Self::new(
JsNativeErrorKind::Aggregate(errors),
Cow::Borrowed(""),
None,
)
}
#[must_use]
#[inline]
pub const fn is_aggregate(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Aggregate(_))
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn error() -> Self {
Self::new(JsNativeErrorKind::Error, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_error(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Error)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn eval() -> Self {
Self::new(JsNativeErrorKind::Eval, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_eval(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Eval)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn range() -> Self {
Self::new(JsNativeErrorKind::Range, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_range(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Range)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn reference() -> Self {
Self::new(JsNativeErrorKind::Reference, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_reference(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Reference)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn syntax() -> Self {
Self::new(JsNativeErrorKind::Syntax, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_syntax(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Syntax)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn typ() -> Self {
Self::new(JsNativeErrorKind::Type, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_type(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Type)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn uri() -> Self {
Self::new(JsNativeErrorKind::Uri, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_uri(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Uri)
}
#[cfg(feature = "fuzz")]
#[must_use]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn no_instructions_remain() -> Self {
Self::new(
JsNativeErrorKind::NoInstructionsRemain,
Cow::Borrowed(""),
None,
)
}
#[must_use]
#[inline]
#[cfg(feature = "fuzz")]
pub const fn is_no_instructions_remain(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::NoInstructionsRemain)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn runtime_limit() -> Self {
Self::new(JsNativeErrorKind::RuntimeLimit, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_runtime_limit(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::RuntimeLimit)
}
#[must_use]
#[inline]
pub fn with_message<S>(mut self, message: S) -> Self
where
S: Into<Cow<'static, str>>,
{
self.message = message.into();
self
}
#[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
}
#[must_use]
#[inline]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
#[inline]
pub fn cause(&self) -> Option<&JsError> {
self.cause.as_deref()
}
#[inline]
pub fn to_opaque(&self, context: &mut Context) -> JsObject {
let Self {
kind,
message,
cause,
realm,
position,
} = 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(),
ErrorKind::Aggregate,
),
JsNativeErrorKind::Error => (constructors.error().prototype(), ErrorKind::Error),
JsNativeErrorKind::Eval => (constructors.eval_error().prototype(), ErrorKind::Eval),
JsNativeErrorKind::Range => (constructors.range_error().prototype(), ErrorKind::Range),
JsNativeErrorKind::Reference => (
constructors.reference_error().prototype(),
ErrorKind::Reference,
),
JsNativeErrorKind::Syntax => {
(constructors.syntax_error().prototype(), ErrorKind::Syntax)
}
JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorKind::Type),
JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorKind::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,
Error::with_shadow_entry(tag, position.0.clone()),
);
o.create_non_enumerable_data_property_or_throw(
js_string!("message"),
js_string!(message.as_ref()),
context,
);
if let Some(cause) = cause {
o.create_non_enumerable_data_property_or_throw(
js_string!("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_string!("errors"),
PropertyDescriptor::builder()
.configurable(true)
.enumerable(false)
.writable(true)
.value(errors),
context,
)
.expect("The spec guarantees this succeeds for a newly created object ");
}
o
}
pub(crate) fn with_realm(mut self, realm: Realm) -> Self {
self.realm = Some(realm);
self
}
#[inline]
pub(crate) fn is_catchable(&self) -> bool {
self.kind.is_catchable()
}
}
impl From<boa_parser::Error> for JsNativeError {
#[cfg_attr(feature = "native-backtrace", track_caller)]
fn from(err: boa_parser::Error) -> Self {
Self::syntax().with_message(err.to_string())
}
}
#[derive(Debug, Clone, Finalize, PartialEq, Eq)]
#[non_exhaustive]
pub enum JsNativeErrorKind {
Aggregate(Vec<JsError>),
Error,
Eval,
Range,
Reference,
Syntax,
Type,
Uri,
#[cfg(feature = "fuzz")]
NoInstructionsRemain,
RuntimeLimit,
}
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 {
#[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<ErrorKind> for JsNativeErrorKind {
fn eq(&self, other: &ErrorKind) -> bool {
matches!(
(self, other),
(Self::Aggregate(_), ErrorKind::Aggregate)
| (Self::Error, ErrorKind::Error)
| (Self::Eval, ErrorKind::Eval)
| (Self::Range, ErrorKind::Range)
| (Self::Reference, ErrorKind::Reference)
| (Self::Syntax, ErrorKind::Syntax)
| (Self::Type, ErrorKind::Type)
| (Self::Uri, ErrorKind::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)
}
}
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
pub struct JsErasedError {
inner: ErasedRepr,
}
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
enum ErasedRepr {
Native(JsErasedNativeError),
Opaque(Cow<'static, 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 {
#[must_use]
pub fn as_opaque(&self) -> Option<&str> {
match self.inner {
ErasedRepr::Native(_) => None,
ErasedRepr::Opaque(ref v) => Some(v),
}
}
#[must_use]
pub const fn as_native(&self) -> Option<&JsErasedNativeError> {
match &self.inner {
ErasedRepr::Native(e) => Some(e),
ErasedRepr::Opaque(_) => None,
}
}
}
#[derive(Debug, Clone, Trace, Finalize, Error, PartialEq, Eq)]
pub struct JsErasedNativeError {
pub kind: JsErasedNativeErrorKind,
message: Cow<'static, 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(())
}
}
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
#[non_exhaustive]
pub enum JsErasedNativeErrorKind {
Aggregate(Vec<JsErasedError>),
Error,
Eval,
Range,
Reference,
Syntax,
Type,
Uri,
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)
}
}