pub const E_ABORT: &str = "E_ABORT";
pub const E_ALREADY_EXISTS: &str = "E_ALREADY_EXISTS";
pub const E_COMPILE: &str = "E_COMPILE";
pub const E_ERROR: &str = "E_ERROR";
pub const E_ILLEGAL_CONSTRUCTOR: &str = "E_ILLEGAL_CONSTRUCTOR";
pub const E_INTERNAL: &str = "E_INTERNAL";
pub const E_INVALID_ARG: &str = "E_INVALID_ARG";
pub const E_INVALID_DATA: &str = "E_INVALID_DATA";
pub const E_INVALID_STATE: &str = "E_INVALID_STATE";
pub const E_IO: &str = "E_IO";
pub const E_JS_THROWN: &str = "E_JS_THROWN";
pub const E_MISSING_PROPERTY: &str = "E_MISSING_PROPERTY";
pub const E_NETWORK: &str = "E_NETWORK";
pub const E_NOT_ARRAY: &str = "E_NOT_ARRAY";
pub const E_NOT_ARRAY_BUFFER: &str = "E_NOT_ARRAY_BUFFER";
pub const E_NOT_EXCEPTION: &str = "E_NOT_EXCEPTION";
pub const E_NOT_FOUND: &str = "E_NOT_FOUND";
pub const E_NOT_FUNCTION: &str = "E_NOT_FUNCTION";
pub const E_NOT_SUPPORTED: &str = "E_NOT_SUPPORTED";
pub const E_NOT_TYPED_ARRAY: &str = "E_NOT_TYPED_ARRAY";
pub const E_OUT_OF_RANGE: &str = "E_OUT_OF_RANGE";
pub const E_PERMISSION_DENIED: &str = "E_PERMISSION_DENIED";
pub const E_STREAM: &str = "E_STREAM";
pub const E_TIMEOUT: &str = "E_TIMEOUT";
pub const E_TYPE: &str = "E_TYPE";
use crate::context::thrown_store::ThrownValueHandle;
use crate::{
FromJSValue, IntoJSValue, JSArray, JSArrayOps, JSContext, JSContextImpl, JSErrorFactory,
JSExceptionThrower, JSObject, JSObjectOps, JSValue, JSValueImpl,
};
use std::collections::BTreeMap;
use thiserror::Error;
use tokio::sync::oneshot;
pub type JSResult<T> = Result<T, RongJSError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorNumber {
I64(i64),
U64(u64),
F64(u64),
}
impl ErrorNumber {
pub fn from_f64(n: f64) -> Self {
Self::F64(n.to_bits())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorData {
Null,
Bool(bool),
Number(ErrorNumber),
String(String),
Array(Vec<ErrorData>),
Object(BTreeMap<String, ErrorData>),
}
impl ErrorData {
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s.as_str()),
_ => None,
}
}
}
impl From<bool> for ErrorData {
fn from(v: bool) -> Self {
Self::Bool(v)
}
}
impl From<String> for ErrorData {
fn from(v: String) -> Self {
Self::String(v)
}
}
impl From<&str> for ErrorData {
fn from(v: &str) -> Self {
Self::String(v.to_string())
}
}
impl From<f64> for ErrorData {
fn from(v: f64) -> Self {
Self::Number(ErrorNumber::from_f64(v))
}
}
impl From<f32> for ErrorData {
fn from(v: f32) -> Self {
Self::from(v as f64)
}
}
impl From<i64> for ErrorData {
fn from(v: i64) -> Self {
Self::Number(ErrorNumber::I64(v))
}
}
impl From<i32> for ErrorData {
fn from(v: i32) -> Self {
Self::from(v as i64)
}
}
impl From<i16> for ErrorData {
fn from(v: i16) -> Self {
Self::from(v as i64)
}
}
impl From<i8> for ErrorData {
fn from(v: i8) -> Self {
Self::from(v as i64)
}
}
impl From<isize> for ErrorData {
fn from(v: isize) -> Self {
Self::from(v as i64)
}
}
impl From<u64> for ErrorData {
fn from(v: u64) -> Self {
Self::Number(ErrorNumber::U64(v))
}
}
impl From<u32> for ErrorData {
fn from(v: u32) -> Self {
Self::from(v as u64)
}
}
impl From<u16> for ErrorData {
fn from(v: u16) -> Self {
Self::from(v as u64)
}
}
impl From<u8> for ErrorData {
fn from(v: u8) -> Self {
Self::from(v as u64)
}
}
impl From<usize> for ErrorData {
fn from(v: usize) -> Self {
Self::from(v as u64)
}
}
#[macro_export]
macro_rules! err_data {
(null) => {
$crate::error::ErrorData::Null
};
(true) => {
$crate::error::ErrorData::Bool(true)
};
(false) => {
$crate::error::ErrorData::Bool(false)
};
([$($tt:tt)*]) => {{
let mut vec = ::std::vec::Vec::<$crate::error::ErrorData>::new();
$crate::err_data!(@array vec $($tt)*);
$crate::error::ErrorData::Array(vec)
}};
({$($tt:tt)*}) => {{
let mut map = ::std::collections::BTreeMap::<::std::string::String, $crate::error::ErrorData>::new();
$crate::err_data!(@object map $($tt)*);
$crate::error::ErrorData::Object(map)
}};
(@array $vec:ident) => {};
(@array $vec:ident , $($rest:tt)*) => {
$crate::err_data!(@array $vec $($rest)*);
};
(@array $vec:ident $value:tt , $($rest:tt)*) => {{
$vec.push($crate::err_data!($value));
$crate::err_data!(@array $vec $($rest)*);
}};
(@array $vec:ident $value:tt) => {{
$vec.push($crate::err_data!($value));
}};
(@object $map:ident) => {};
(@object $map:ident , $($rest:tt)*) => {
$crate::err_data!(@object $map $($rest)*);
};
(@object $map:ident $key:tt : $value:tt , $($rest:tt)*) => {{
$map.insert($crate::err_data!(@key $key), $crate::err_data!($value));
$crate::err_data!(@object $map $($rest)*);
}};
(@object $map:ident $key:tt : $value:tt) => {{
$map.insert($crate::err_data!(@key $key), $crate::err_data!($value));
}};
(@key $key:ident) => {
::std::string::ToString::to_string(stringify!($key))
};
(@key $key:literal) => {
::std::string::ToString::to_string($key)
};
($other:expr) => {
$crate::error::ErrorData::from($other)
};
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("{code}: {message}")]
pub struct HostError {
pub name: &'static str,
pub code: &'static str,
pub message: String,
pub data: Option<ErrorData>,
pub(crate) thrown: Option<ThrownValueHandle>,
}
impl HostError {
pub fn new(code: &'static str, message: impl Into<String>) -> Self {
Self {
name: "Error",
code,
message: message.into(),
data: None,
thrown: None,
}
}
pub fn invalid_arg_count(expected: u32, got: u32) -> Self {
Self::new(
E_INVALID_ARG,
format!("{expected} arguments required, but {got} found"),
)
.with_name("TypeError")
.with_data(crate::err_data!({ expected: expected, got: got }))
}
pub fn with_name(mut self, name: &'static str) -> Self {
self.name = name;
self
}
pub fn with_data(mut self, data: ErrorData) -> Self {
self.data = Some(data);
self
}
pub fn aborted(reason: Option<String>) -> Self {
let mut err = Self::new(E_ABORT, "Operation aborted").with_name("AbortError");
if let Some(reason) = reason {
err.data = Some(crate::err_data!({ reason: reason }));
}
err
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ThrownValue {
handle: ThrownValueHandle,
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error(transparent)]
pub struct RongJSError(pub(crate) RongJSErrorKind);
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub(crate) enum RongJSErrorKind {
#[error("{0}")]
Host(HostError),
#[error("JavaScript threw a value")]
Thrown(ThrownValue),
}
#[allow(non_snake_case, non_upper_case_globals)]
impl RongJSError {
pub fn Borrow(ty: &'static str) -> Self {
HostError::new(E_INTERNAL, format!("Failed to borrow for type {ty}")).into()
}
pub fn InvalidParameter(expected: u32, got: u32) -> Self {
HostError::invalid_arg_count(expected, got).into()
}
pub fn PropertyNotFound(name: String) -> Self {
HostError::new(E_MISSING_PROPERTY, format!("Property '{name}' Not Found"))
.with_name("ReferenceError")
.into()
}
pub fn NotObject() -> Self {
HostError::new(E_TYPE, "Not JS Object")
.with_name("TypeError")
.into()
}
pub fn NotSymbol() -> Self {
HostError::new(E_TYPE, "Not JS Symbol")
.with_name("TypeError")
.into()
}
pub fn NotJSFunc() -> Self {
HostError::new(E_NOT_FUNCTION, "Not JS Function")
.with_name("TypeError")
.into()
}
pub fn NotJSArray() -> Self {
HostError::new(E_NOT_ARRAY, "Not JS Array")
.with_name("TypeError")
.into()
}
pub fn NotJSArrayBuffer() -> Self {
HostError::new(E_NOT_ARRAY_BUFFER, "Not JS ArrayBuffer")
.with_name("TypeError")
.into()
}
pub fn NotJSTypedArray() -> Self {
HostError::new(E_NOT_TYPED_ARRAY, "Not JS TypedArray")
.with_name("TypeError")
.into()
}
pub fn TypedArrayAlignmentError() -> Self {
HostError::new(
E_OUT_OF_RANGE,
"Invalid TypedArray alignment: byte_offset must be a multiple of element size",
)
.with_name("RangeError")
.into()
}
pub fn TypedArrayRangeError() -> Self {
HostError::new(
E_OUT_OF_RANGE,
"Invalid TypedArray range: offset or length exceeds buffer size",
)
.with_name("RangeError")
.into()
}
pub fn NotJSExcep() -> Self {
HostError::new(E_NOT_EXCEPTION, "Not JS Exception Object")
.with_name("TypeError")
.into()
}
pub fn CompileToByteErr() -> Self {
HostError::new(E_COMPILE, "Failed to compile JS code to bytecode").into()
}
pub fn NotSupportByteCode() -> Self {
HostError::new(E_NOT_SUPPORTED, "Does not support bytecode")
.with_data(crate::err_data!({ feature: "bytecode" }))
.into()
}
pub fn OnceFnCalled() -> Self {
HostError::new(E_INVALID_STATE, "OnceFn had been called").into()
}
fn error_data_to_js_value<V>(ctx: &JSContext<V::Context>, data: &ErrorData) -> JSValue<V>
where
V: JSValueImpl + JSObjectOps + JSArrayOps,
{
const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
match data {
ErrorData::Null => JSValue::null(ctx),
ErrorData::Bool(b) => JSValue::from(ctx, *b),
ErrorData::String(s) => JSValue::from(ctx, s.as_str()),
ErrorData::Number(n) => match *n {
ErrorNumber::I64(v) => {
let abs = v.unsigned_abs();
if abs <= MAX_SAFE_INTEGER {
JSValue::from(ctx, v as f64)
} else {
JSValue::from(ctx, v.to_string())
}
}
ErrorNumber::U64(v) => {
if v <= MAX_SAFE_INTEGER {
JSValue::from(ctx, v as f64)
} else {
JSValue::from(ctx, v.to_string())
}
}
ErrorNumber::F64(bits) => JSValue::from(ctx, f64::from_bits(bits)),
},
ErrorData::Array(items) => {
let Ok(array) = JSArray::<V>::new(ctx) else {
return JSValue::undefined(ctx);
};
for (i, item) in items.iter().enumerate() {
let _ = array.set(i as u32, Self::error_data_to_js_value::<V>(ctx, item));
}
JSValue::from(ctx, array)
}
ErrorData::Object(map) => {
let obj = JSObject::<V>::new(ctx);
for (k, v) in map.iter() {
let _ = obj.set(k.as_str(), Self::error_data_to_js_value::<V>(ctx, v));
}
JSValue::from(ctx, obj)
}
}
}
fn host_error_object<V>(host: &HostError, ctx: &JSContext<V::Context>) -> JSObject<V>
where
V: JSValueImpl + JSObjectOps + JSArrayOps,
V::Context: JSErrorFactory,
{
let raw = ctx
.as_ref()
.new_error(host.name, &host.message, Some(host.code));
let obj = JSObject::from_js_value(ctx, JSValue::from_raw(ctx, raw)).unwrap();
if host.code == E_JS_THROWN {
let data_obj = host
.data
.as_ref()
.and_then(|data| Self::error_data_to_js_value::<V>(ctx, data).into_object())
.unwrap_or_else(|| JSObject::new(ctx));
if let Some(handle) = host.thrown
&& let Some(thrown) = ctx.resolve_thrown(handle)
{
let _ = data_obj.set("thrown", thrown.clone());
if thrown.is_error() {
let _ = obj.set("cause", thrown);
}
}
let _ = obj.set("data", data_obj);
} else if let Some(data) = host.data.as_ref() {
let _ = obj.set("data", Self::error_data_to_js_value::<V>(ctx, data));
}
obj
}
pub fn into_host_in<C>(self, ctx: &JSContext<C>) -> Self
where
C: JSContextImpl,
C::Value: JSObjectOps,
{
match self.0 {
RongJSErrorKind::Host(host) => Self(RongJSErrorKind::Host(host)),
RongJSErrorKind::Thrown(thrown) => {
let handle = thrown.handle;
let thrown = ctx.resolve_thrown(handle);
let mut data = BTreeMap::<String, ErrorData>::new();
if let Some(thrown) = &thrown {
if let Ok(s) = String::from_js_value(ctx, thrown.clone()) {
data.insert("thrown".to_string(), ErrorData::from(s));
}
data.insert("is_error".to_string(), ErrorData::from(thrown.is_error()));
} else {
data.insert("thrown".to_string(), ErrorData::from("<unavailable>"));
}
let message = thrown
.clone()
.and_then(|v| {
v.into_object()
.and_then(|o| o.get::<_, String>("message").ok())
})
.or_else(|| thrown.and_then(|v| String::from_js_value(ctx, v).ok()))
.unwrap_or_else(|| "JavaScript threw a value".to_string());
Self(RongJSErrorKind::Host(HostError {
name: "Error",
code: E_JS_THROWN,
message,
data: Some(ErrorData::Object(data)),
thrown: Some(handle),
}))
}
}
}
pub fn throw_js_exception<V>(self, ctx: &JSContext<V::Context>) -> V
where
V: JSValueImpl + JSObjectOps + JSArrayOps,
V::Context: JSErrorFactory + JSExceptionThrower,
{
match self.0 {
RongJSErrorKind::Thrown(thrown) => {
let handle = thrown.handle;
let Some(value) = ctx.take_thrown(handle) else {
let raw = ctx.as_ref().new_error(
"Error",
"Invalid thrown value handle",
Some(E_INTERNAL),
);
return ctx.as_ref().throw(raw);
};
ctx.throw(value).into_value()
}
RongJSErrorKind::Host(host) => {
let obj = Self::host_error_object::<V>(&host, ctx);
if host.code == E_JS_THROWN
&& let Some(handle) = host.thrown
{
let _ = ctx.take_thrown(handle);
}
ctx.as_ref().throw(obj.into_value())
}
}
}
pub fn into_catch_value<V>(self, ctx: &JSContext<V::Context>) -> JSValue<V>
where
V: JSValueImpl + JSObjectOps + JSArrayOps,
V::Context: JSErrorFactory,
{
match self.0 {
RongJSErrorKind::Thrown(thrown) => {
let handle = thrown.handle;
let Some(value) = ctx.take_thrown(handle) else {
let raw = ctx.as_ref().new_error(
"Error",
"Invalid thrown value handle",
Some(E_INTERNAL),
);
return JSValue::from_raw(ctx, raw);
};
value
}
RongJSErrorKind::Host(host) => {
let obj = Self::host_error_object::<V>(&host, ctx);
if host.code == E_JS_THROWN
&& let Some(handle) = host.thrown
{
let _ = ctx.take_thrown(handle);
}
obj.into_js_value()
}
}
}
pub fn from_thrown_value<V: JSValueImpl>(value: JSValue<V>) -> Self {
let ctx: JSContext<V::Context> =
JSContext::from_borrowed_raw_ptr(value.as_value().as_raw_context());
Self(RongJSErrorKind::Thrown(ThrownValue {
handle: ctx.capture_thrown(value),
}))
}
pub fn thrown_value<C>(&self, ctx: &JSContext<C>) -> Option<JSValue<C::Value>>
where
C: JSContextImpl,
{
match &self.0 {
RongJSErrorKind::Thrown(thrown) => ctx.resolve_thrown(thrown.handle),
_ => None,
}
}
pub fn as_host_error(&self) -> Option<&HostError> {
match &self.0 {
RongJSErrorKind::Host(host) => Some(host),
RongJSErrorKind::Thrown(_) => None,
}
}
pub fn into_host_error(self) -> Option<HostError> {
match self.0 {
RongJSErrorKind::Host(host) => Some(host),
RongJSErrorKind::Thrown(_) => None,
}
}
pub fn is_thrown(&self) -> bool {
matches!(self.0, RongJSErrorKind::Thrown(_))
}
pub fn is_property_not_found(&self) -> bool {
matches!(self.0, RongJSErrorKind::Host(ref host) if host.code == E_MISSING_PROPERTY)
}
pub fn is_not_support_bytecode(&self) -> bool {
match &self.0 {
RongJSErrorKind::Host(host) if host.code == E_NOT_SUPPORTED => matches!(
host.data.as_ref(),
Some(ErrorData::Object(map))
if map.get("feature").and_then(|v| v.as_str()) == Some("bytecode")
),
_ => false,
}
}
}
impl<V: JSValueImpl> FromJSValue<V> for RongJSError
where
V: JSObjectOps,
{
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
Ok(RongJSError::from_thrown_value(value))
}
}
impl<V: JSValueImpl> IntoJSValue<V> for RongJSError
where
V::Context: JSErrorFactory + JSExceptionThrower,
V: JSObjectOps + JSArrayOps,
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
JSValue::from_raw(ctx, self.throw_js_exception(ctx))
}
}
impl From<HostError> for RongJSError {
fn from(err: HostError) -> Self {
RongJSError(RongJSErrorKind::Host(err))
}
}
impl<V, T> IntoJSValue<V> for JSResult<T>
where
V: JSObjectOps + JSArrayOps,
V::Context: JSErrorFactory + JSExceptionThrower,
T: IntoJSValue<V>,
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
match self {
Ok(value) => <T as IntoJSValue<V>>::into_js_value(value, ctx),
Err(err) => err.into_js_value(ctx),
}
}
}
impl From<oneshot::error::RecvError> for RongJSError {
fn from(err: oneshot::error::RecvError) -> Self {
HostError::new(E_INTERNAL, format!("Tokio oneshot error: {}", err)).into()
}
}
impl From<std::io::Error> for RongJSError {
fn from(err: std::io::Error) -> Self {
HostError::new(E_IO, err.to_string()).into()
}
}