use std::convert::{From, TryFrom};
use std::error;
use std::ffi::CStr;
use std::fmt;
#[cfg(feature = "serde-json")]
use std::fmt::Display;
use std::os::raw::c_void;
use std::ptr;
#[cfg(feature = "serde-json")]
use serde::{de, ser};
#[cfg(feature = "serde-json")]
use serde_json::Error as SerdeJSONError;
#[cfg(target_family = "wasm")]
use crate::bindgen_runtime::JsObjectValue;
use crate::ValueType;
use crate::{bindgen_runtime::ToNapiValue, sys, Env, JsValue, Status, Unknown};
pub type Result<T, S = Status> = std::result::Result<T, Error<S>>;
const ERROR_VALUE_KEY: &CStr = c"[[ErrorValue]]";
pub struct Error<S: AsRef<str> = Status> {
pub status: S,
pub reason: String,
pub cause: Option<Box<Error>>,
pub(crate) maybe_ref: Option<std::sync::Arc<ErrorRef>>,
}
pub(crate) struct ErrorRef {
raw: sys::napi_ref,
indirect: bool,
env: sys::napi_env,
owner_thread: std::thread::ThreadId,
#[cfg(all(feature = "napi4", not(feature = "noop")))]
custom_gc: Option<std::sync::Arc<crate::bindgen_prelude::CustomGcHandle>>,
}
unsafe impl Send for ErrorRef {}
unsafe impl Sync for ErrorRef {}
impl ErrorRef {
pub(crate) fn new(raw: sys::napi_ref, env: sys::napi_env) -> Self {
debug_assert!(!raw.is_null(), "ErrorRef must wrap a non-null napi_ref");
#[cfg(all(not(feature = "noop"), not(target_family = "wasm")))]
crate::bindgen_runtime::retain_current_module_for_unload_safety();
Self {
raw,
indirect: false,
env,
owner_thread: std::thread::current().id(),
#[cfg(all(feature = "napi4", not(feature = "noop")))]
custom_gc: crate::bindgen_prelude::current_custom_gc_handle(),
}
}
fn new_indirect(raw: sys::napi_ref, env: sys::napi_env) -> Self {
let mut value = Self::new(raw, env);
value.indirect = true;
value
}
}
#[cfg(not(feature = "noop"))]
fn release_error_reference(env: sys::napi_env, reference: sys::napi_ref) {
let mut ref_count = 0;
let status = unsafe { sys::napi_reference_unref(env, reference, &mut ref_count) };
if status != sys::Status::napi_ok {
eprintln!("unref error reference failed: {}", Status::from(status));
return;
}
if ref_count == 0 {
let status = unsafe { sys::napi_delete_reference(env, reference) };
if status != sys::Status::napi_ok {
eprintln!("delete error reference failed: {}", Status::from(status));
}
}
}
#[cfg(not(feature = "noop"))]
impl Drop for ErrorRef {
fn drop(&mut self) {
#[cfg(all(feature = "napi4", not(feature = "noop")))]
if let Some(handle) = self.custom_gc.take() {
let env = self.env;
let raw = self.raw;
handle.with_read_aborted(|aborted| {
if aborted {
return;
}
if crate::bindgen_prelude::current_thread_owns_custom_gc(&handle) {
release_error_reference(env, raw);
} else {
let status =
unsafe { sys::napi_call_threadsafe_function(handle.get_raw(), raw.cast(), 1) };
assert!(
status == sys::Status::napi_ok || status == sys::Status::napi_closing,
"Call custom GC in ErrorRef::drop failed {}",
Status::from(status)
);
}
});
return;
}
if self.owner_thread != std::thread::current().id() {
return;
}
release_error_reference(self.env, self.raw);
}
}
impl<S: AsRef<str>> Error<S> {
pub fn set_cause(&mut self, cause: Error) {
self.cause = Some(Box::new(cause));
}
}
impl<S: AsRef<str>> std::fmt::Debug for Error<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Error {{ status: {:?}, reason: {:?} }}",
self.status.as_ref(),
self.reason
)
}
}
impl<S: AsRef<str>> ToNapiValue for Error<S> {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
if let Some(value) = unsafe { val.referenced_value(env) } {
Ok(value)
} else {
Ok(unsafe { JsError::from(val).into_value(env) })
}
}
}
unsafe impl<S> Send for Error<S> where S: Send + AsRef<str> {}
unsafe impl<S> Sync for Error<S> where S: Sync + AsRef<str> {}
impl<S: AsRef<str> + std::fmt::Debug> error::Error for Error<S> {}
impl<S: AsRef<str>> From<std::convert::Infallible> for Error<S> {
fn from(_: std::convert::Infallible) -> Self {
unreachable!()
}
}
#[cfg(feature = "serde-json")]
impl ser::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Error::new(Status::InvalidArg, msg.to_string())
}
}
#[cfg(feature = "serde-json")]
impl de::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Error::new(Status::InvalidArg, msg.to_string())
}
}
#[cfg(feature = "serde-json")]
impl From<SerdeJSONError> for Error {
fn from(value: SerdeJSONError) -> Self {
Error::new(Status::InvalidArg, format!("{value}"))
}
}
#[cfg(not(target_family = "wasm"))]
impl From<Unknown<'_>> for Error {
fn from(value: Unknown) -> Self {
let mut result = std::ptr::null_mut();
let status = unsafe { sys::napi_create_reference(value.0.env, value.0.value, 1, &mut result) };
if status != sys::Status::napi_ok {
return Error::new(
Status::from(status),
"Create Error reference failed".to_owned(),
);
}
let maybe_env = value.0.env;
let maybe_error_message = value
.coerce_to_string()
.and_then(|a| a.into_utf8().and_then(|a| a.into_owned()));
let maybe_cause = extract_error_cause(value).unwrap_or(None);
if let Ok(error_message) = maybe_error_message {
return Self {
status: Status::GenericFailure,
reason: error_message,
cause: maybe_cause,
maybe_ref: Some(std::sync::Arc::new(ErrorRef::new(result, maybe_env))),
};
}
Self {
status: Status::GenericFailure,
reason: "".to_string(),
cause: maybe_cause,
maybe_ref: Some(std::sync::Arc::new(ErrorRef::new(result, maybe_env))),
}
}
}
#[cfg(target_family = "wasm")]
impl From<Unknown<'_>> for Error {
fn from(value: Unknown) -> Self {
let value_type = value.get_type();
let maybe_error_message;
if let Ok(vt) = value_type {
if vt == ValueType::Object {
maybe_error_message = value
.coerce_to_object()
.and_then(|obj| obj.get_named_property::<Unknown>("message"))
.and_then(|message| {
message
.coerce_to_string()
.and_then(|message| message.into_utf8().and_then(|message| message.into_owned()))
});
} else {
maybe_error_message = value
.coerce_to_string()
.and_then(|a| a.into_utf8().and_then(|a| a.into_owned()));
}
} else {
maybe_error_message = value
.coerce_to_string()
.and_then(|a| a.into_utf8().and_then(|a| a.into_owned()));
};
let maybe_cause = extract_error_cause(value).unwrap_or(None);
if let Ok(error_message) = maybe_error_message {
return Self {
status: Status::GenericFailure,
reason: error_message,
cause: maybe_cause,
maybe_ref: None,
};
}
Self {
status: Status::GenericFailure,
reason: "".to_string(),
cause: maybe_cause,
maybe_ref: None,
}
}
}
impl Error {
pub fn from_unknown_without_coercion(value: Unknown<'_>) -> Self {
error_without_coercion(value, MAX_CAUSE_DEPTH)
}
}
const MAX_CAUSE_DEPTH: usize = 8;
const MAX_PROTOTYPE_DEPTH: usize = 32;
fn error_without_coercion(value: Unknown<'_>, cause_budget: usize) -> Error {
Error {
status: Status::GenericFailure,
reason: owned_reason_without_coercion(value),
cause: cause_without_coercion(value, cause_budget),
maybe_ref: retain_value_without_coercion(value),
}
}
fn retain_value_without_coercion(value: Unknown<'_>) -> Option<std::sync::Arc<ErrorRef>> {
let env = value.0.env;
let mut holder = ptr::null_mut();
if unsafe { sys::napi_create_object(env, &mut holder) } != sys::Status::napi_ok {
clear_pending_exception(env);
return None;
}
let properties = [sys::napi_property_descriptor {
utf8name: ERROR_VALUE_KEY.as_ptr().cast(),
name: ptr::null_mut(),
method: None,
getter: None,
setter: None,
value: value.0.value,
attributes: sys::PropertyAttributes::default,
data: ptr::null_mut(),
}];
let status =
unsafe { sys::napi_define_properties(env, holder, properties.len(), properties.as_ptr()) };
if status != sys::Status::napi_ok {
clear_pending_exception(env);
return None;
}
let mut reference = ptr::null_mut();
if unsafe { sys::napi_create_reference(env, holder, 1, &mut reference) } != sys::Status::napi_ok {
clear_pending_exception(env);
return None;
}
Some(std::sync::Arc::new(ErrorRef::new_indirect(reference, env)))
}
fn owned_reason_without_coercion(value: Unknown<'_>) -> String {
let env = value.0.env;
if is_error_without_coercion(value) {
return data_property_without_get(env, value.0.value, c"message")
.and_then(|message| owned_string_without_coercion(env, message))
.unwrap_or_else(|| "JavaScript Error".to_owned());
}
owned_string_without_coercion(env, value.0.value).unwrap_or_default()
}
fn cause_without_coercion(value: Unknown<'_>, budget: usize) -> Option<Box<Error>> {
if budget == 0 {
return None;
}
let env = value.0.env;
if !matches!(
type_without_coercion(env, value.0.value)?,
sys::ValueType::napi_object | sys::ValueType::napi_function
) {
return None;
}
let raw_cause = data_property_without_get(env, value.0.value, c"cause")?;
if matches!(
type_without_coercion(env, raw_cause)?,
sys::ValueType::napi_undefined | sys::ValueType::napi_null
) {
return None;
}
let cause = unsafe { Unknown::from_raw_unchecked(env, raw_cause) };
Some(Box::new(error_without_coercion(cause, budget - 1)))
}
fn is_error_without_coercion(value: Unknown<'_>) -> bool {
let env = value.0.env;
let mut is_error = false;
if unsafe { sys::napi_is_error(env, value.0.value, &mut is_error) } != sys::Status::napi_ok {
clear_pending_exception(env);
return false;
}
is_error
}
fn type_without_coercion(
env: sys::napi_env,
value: sys::napi_value,
) -> Option<sys::napi_valuetype> {
let mut value_type = -1;
if unsafe { sys::napi_typeof(env, value, &mut value_type) } != sys::Status::napi_ok {
clear_pending_exception(env);
return None;
}
Some(value_type)
}
struct ReflectIntrinsics {
env: sys::napi_env,
holder: sys::napi_ref,
cleanup_hook_installed: bool,
}
thread_local! {
static REFLECT_INTRINSICS: std::cell::RefCell<Option<ReflectIntrinsics>> =
const { std::cell::RefCell::new(None) };
}
#[cfg(not(feature = "noop"))]
thread_local! {
static PROBE_ACCESSOR_HITS: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
}
#[cfg(not(feature = "noop"))]
unsafe extern "C" fn probe_accessor_getter(
env: sys::napi_env,
_info: sys::napi_callback_info,
) -> sys::napi_value {
PROBE_ACCESSOR_HITS.with(|hits| hits.set(hits.get().saturating_add(1)));
let mut undefined = ptr::null_mut();
if unsafe { sys::napi_get_undefined(env, &mut undefined) } != sys::Status::napi_ok {
return ptr::null_mut();
}
undefined
}
#[cfg(not(feature = "noop"))]
fn probe_get_own_property_descriptor(
env: sys::napi_env,
reflect: sys::napi_value,
get_own_property_descriptor: sys::napi_value,
) -> bool {
let create_string = |key: &CStr| -> Option<sys::napi_value> {
let bytes = key.to_bytes();
let mut value = ptr::null_mut();
if unsafe {
sys::napi_create_string_utf8(env, bytes.as_ptr().cast(), bytes.len() as isize, &mut value)
} != sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
Some(value)
};
let Some(sentinel) = create_string(c"napi-rs reflect probe") else {
return false;
};
let mut probe = ptr::null_mut();
if unsafe { sys::napi_create_object(env, &mut probe) } != sys::Status::napi_ok {
clear_pending_exception(env);
return false;
}
let properties = [
sys::napi_property_descriptor {
utf8name: c"data".as_ptr().cast(),
name: ptr::null_mut(),
method: None,
getter: None,
setter: None,
value: sentinel,
attributes: sys::PropertyAttributes::default,
data: ptr::null_mut(),
},
sys::napi_property_descriptor {
utf8name: c"accessor".as_ptr().cast(),
name: ptr::null_mut(),
method: None,
getter: Some(probe_accessor_getter),
setter: None,
value: ptr::null_mut(),
attributes: sys::PropertyAttributes::default,
data: ptr::null_mut(),
},
];
if unsafe { sys::napi_define_properties(env, probe, properties.len(), properties.as_ptr()) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return false;
}
let call_candidate = |key: &CStr| -> Option<sys::napi_value> {
let key_value = create_string(key)?;
let args = [probe, key_value];
let mut descriptor = ptr::null_mut();
if unsafe {
sys::napi_call_function(
env,
reflect,
get_own_property_descriptor,
args.len(),
args.as_ptr(),
&mut descriptor,
)
} != sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
Some(descriptor)
};
let has_own = |object: sys::napi_value, key: &CStr| -> Option<bool> {
let key_value = create_string(key)?;
let mut result = false;
if unsafe { sys::napi_has_own_property(env, object, key_value, &mut result) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
Some(result)
};
let hits_before = PROBE_ACCESSOR_HITS.with(|hits| hits.get());
let Some(descriptor) = call_candidate(c"data") else {
return false;
};
if type_without_coercion(env, descriptor) != Some(sys::ValueType::napi_object) {
return false;
}
if has_own(descriptor, c"value") != Some(true) {
return false;
}
let mut reported = ptr::null_mut();
if unsafe { sys::napi_get_named_property(env, descriptor, c"value".as_ptr(), &mut reported) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return false;
}
let mut is_sentinel = false;
if unsafe { sys::napi_strict_equals(env, reported, sentinel, &mut is_sentinel) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return false;
}
if !is_sentinel {
return false;
}
let Some(descriptor) = call_candidate(c"accessor") else {
return false;
};
if type_without_coercion(env, descriptor) != Some(sys::ValueType::napi_object) {
return false;
}
if has_own(descriptor, c"value") != Some(false) {
return false;
}
if has_own(descriptor, c"get") != Some(true) {
return false;
}
let Some(descriptor) = call_candidate(c"missing") else {
return false;
};
if type_without_coercion(env, descriptor) != Some(sys::ValueType::napi_undefined) {
return false;
}
PROBE_ACCESSOR_HITS.with(|hits| hits.get()) == hits_before
}
#[cfg(not(feature = "noop"))]
pub(crate) fn cache_reflect_intrinsics_for_env(env: sys::napi_env) {
let mut global = ptr::null_mut();
if unsafe { sys::napi_get_global(env, &mut global) } != sys::Status::napi_ok {
clear_pending_exception(env);
return;
}
let mut reflect = ptr::null_mut();
if unsafe { sys::napi_get_named_property(env, global, c"Reflect".as_ptr(), &mut reflect) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return;
}
if type_without_coercion(env, reflect) != Some(sys::ValueType::napi_object) {
return;
}
let mut get_own_property_descriptor = ptr::null_mut();
if unsafe {
sys::napi_get_named_property(
env,
reflect,
c"getOwnPropertyDescriptor".as_ptr(),
&mut get_own_property_descriptor,
)
} != sys::Status::napi_ok
{
clear_pending_exception(env);
return;
}
if type_without_coercion(env, get_own_property_descriptor) != Some(sys::ValueType::napi_function)
{
return;
}
if !probe_get_own_property_descriptor(env, reflect, get_own_property_descriptor) {
return;
}
let mut holder = ptr::null_mut();
if unsafe { sys::napi_create_object(env, &mut holder) } != sys::Status::napi_ok {
clear_pending_exception(env);
return;
}
let properties = [
sys::napi_property_descriptor {
utf8name: c"Reflect".as_ptr().cast(),
name: ptr::null_mut(),
method: None,
getter: None,
setter: None,
value: reflect,
attributes: sys::PropertyAttributes::default,
data: ptr::null_mut(),
},
sys::napi_property_descriptor {
utf8name: c"getOwnPropertyDescriptor".as_ptr().cast(),
name: ptr::null_mut(),
method: None,
getter: None,
setter: None,
value: get_own_property_descriptor,
attributes: sys::PropertyAttributes::default,
data: ptr::null_mut(),
},
];
if unsafe { sys::napi_define_properties(env, holder, properties.len(), properties.as_ptr()) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return;
}
let mut reference = ptr::null_mut();
if unsafe { sys::napi_create_reference(env, holder, 1, &mut reference) } != sys::Status::napi_ok {
clear_pending_exception(env);
return;
}
REFLECT_INTRINSICS.with(|slot| {
let mut slot = slot.borrow_mut();
let previous = slot.take();
let cleanup_hook_installed = match previous {
Some(superseded) if superseded.env == env => {
if superseded.cleanup_hook_installed || cfg!(target_family = "wasm") {
let _ = unsafe { sys::napi_delete_reference(env, superseded.holder) };
clear_pending_exception(env);
}
superseded.cleanup_hook_installed
}
_ => {
#[cfg(all(feature = "napi3", not(target_family = "wasm")))]
{
let hook_status = unsafe {
sys::napi_add_env_cleanup_hook(env, Some(reflect_intrinsics_env_cleanup), env.cast())
};
hook_status == sys::Status::napi_ok
}
#[cfg(not(all(feature = "napi3", not(target_family = "wasm"))))]
{
false
}
}
};
*slot = Some(ReflectIntrinsics {
env,
holder: reference,
cleanup_hook_installed,
});
});
}
#[cfg(all(not(feature = "noop"), feature = "napi3", not(target_family = "wasm")))]
unsafe extern "C" fn reflect_intrinsics_env_cleanup(arg: *mut c_void) {
REFLECT_INTRINSICS.with(|slot| {
let mut slot = slot.borrow_mut();
if slot
.as_ref()
.is_some_and(|cache| cache.env.cast::<c_void>() == arg)
{
if let Some(cache) = slot.take() {
let _ = unsafe { sys::napi_delete_reference(cache.env, cache.holder) };
clear_pending_exception(cache.env);
}
}
});
}
fn reflect_get_own_property_descriptor(
env: sys::napi_env,
) -> Option<(sys::napi_value, sys::napi_value)> {
let holder = REFLECT_INTRINSICS.with(|slot| {
slot
.borrow()
.as_ref()
.and_then(|cache| (cache.env == env).then_some(cache.holder))
})?;
let mut holder_value = ptr::null_mut();
if unsafe { sys::napi_get_reference_value(env, holder, &mut holder_value) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
if holder_value.is_null() {
return None;
}
let mut reflect = ptr::null_mut();
if unsafe { sys::napi_get_named_property(env, holder_value, c"Reflect".as_ptr(), &mut reflect) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
let mut get_own_property_descriptor = ptr::null_mut();
if unsafe {
sys::napi_get_named_property(
env,
holder_value,
c"getOwnPropertyDescriptor".as_ptr(),
&mut get_own_property_descriptor,
)
} != sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
if type_without_coercion(env, get_own_property_descriptor) != Some(sys::ValueType::napi_function)
{
return None;
}
Some((reflect, get_own_property_descriptor))
}
fn data_property_without_get(
env: sys::napi_env,
object: sys::napi_value,
key: &CStr,
) -> Option<sys::napi_value> {
let (reflect, get_own_property_descriptor) = reflect_get_own_property_descriptor(env)?;
let key_bytes = key.to_bytes();
let mut key_value = ptr::null_mut();
if unsafe {
sys::napi_create_string_utf8(
env,
key_bytes.as_ptr().cast(),
key_bytes.len() as isize,
&mut key_value,
)
} != sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
let mut value_key = ptr::null_mut();
if unsafe { sys::napi_create_string_utf8(env, c"value".as_ptr(), 5, &mut value_key) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
let mut current = object;
for _ in 0..MAX_PROTOTYPE_DEPTH {
let args = [current, key_value];
let mut descriptor = ptr::null_mut();
if unsafe {
sys::napi_call_function(
env,
reflect,
get_own_property_descriptor,
args.len(),
args.as_ptr(),
&mut descriptor,
)
} != sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
if type_without_coercion(env, descriptor)? == sys::ValueType::napi_object {
let mut is_data_descriptor = false;
if unsafe { sys::napi_has_own_property(env, descriptor, value_key, &mut is_data_descriptor) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
if !is_data_descriptor {
return None;
}
let mut property = ptr::null_mut();
if unsafe { sys::napi_get_named_property(env, descriptor, c"value".as_ptr(), &mut property) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
return Some(property);
}
let mut prototype = ptr::null_mut();
if unsafe { sys::napi_get_prototype(env, current, &mut prototype) } != sys::Status::napi_ok {
clear_pending_exception(env);
return None;
}
if !matches!(
type_without_coercion(env, prototype)?,
sys::ValueType::napi_object | sys::ValueType::napi_function
) {
return None;
}
current = prototype;
}
None
}
fn owned_string_without_coercion(env: sys::napi_env, value: sys::napi_value) -> Option<String> {
if type_without_coercion(env, value)? != sys::ValueType::napi_string {
return None;
}
let mut length = 0;
if unsafe { sys::napi_get_value_string_utf8(env, value, ptr::null_mut(), 0, &mut length) }
!= sys::Status::napi_ok
{
clear_pending_exception(env);
return None;
}
let mut bytes = vec![0; length + 1];
let mut written = 0;
let status = unsafe {
sys::napi_get_value_string_utf8(
env,
value,
bytes.as_mut_ptr().cast(),
bytes.len(),
&mut written,
)
};
if status != sys::Status::napi_ok {
clear_pending_exception(env);
return None;
}
bytes.truncate(written);
String::from_utf8(bytes).ok()
}
fn clear_pending_exception(env: sys::napi_env) {
let mut is_pending = false;
if unsafe { sys::napi_is_exception_pending(env, &mut is_pending) } != sys::Status::napi_ok
|| !is_pending
{
return;
}
let mut exception = ptr::null_mut();
let _ = unsafe { sys::napi_get_and_clear_last_exception(env, &mut exception) };
}
#[cfg(feature = "anyhow")]
impl From<anyhow::Error> for Error {
fn from(value: anyhow::Error) -> Self {
Error::new(Status::GenericFailure, format!("{:?}", value))
}
}
impl<S: AsRef<str> + std::fmt::Debug> fmt::Display for Error<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.reason.is_empty() {
write!(f, "{:?}, {}", self.status, self.reason)
} else {
write!(f, "{:?}", self.status)
}
}
}
impl<S: AsRef<str>> Error<S> {
pub fn new<R: ToString>(status: S, reason: R) -> Self {
Error {
status,
reason: reason.to_string(),
cause: None,
maybe_ref: None,
}
}
pub fn from_status(status: S) -> Self {
Error {
status,
reason: "".to_owned(),
cause: None,
maybe_ref: None,
}
}
}
impl<S: AsRef<str> + Clone> Error<S> {
fn reference_less_clone(&self) -> Self {
Self {
status: self.status.clone(),
reason: self.reason.clone(),
cause: self
.cause
.as_ref()
.map(|cause| Box::new(cause.reference_less_clone())),
maybe_ref: None,
}
}
pub fn try_clone(&self) -> Result<Self> {
match &self.maybe_ref {
#[cfg(all(feature = "napi4", not(feature = "noop")))]
Some(error_ref) if error_ref.custom_gc.is_some() => Ok(Self {
status: self.status.clone(),
reason: self.reason.clone(),
cause: self
.cause
.as_ref()
.map(|cause| Box::new(cause.reference_less_clone())),
maybe_ref: Some(error_ref.clone()),
}),
_ => Ok(self.reference_less_clone()),
}
}
}
impl<S: AsRef<str>> Error<S> {
pub(crate) unsafe fn referenced_value(&self, env: sys::napi_env) -> Option<sys::napi_value> {
let error_ref = self.maybe_ref.as_ref()?;
if error_ref.env != env || error_ref.owner_thread != std::thread::current().id() {
return None;
}
#[cfg(all(feature = "napi4", not(feature = "noop")))]
if let Some(handle) = &error_ref.custom_gc {
if !crate::bindgen_prelude::current_thread_owns_custom_gc(handle) {
return None;
}
}
let mut result = ptr::null_mut();
let status = unsafe { sys::napi_get_reference_value(env, error_ref.raw, &mut result) };
if status != sys::Status::napi_ok {
return None;
}
if error_ref.indirect {
let status = unsafe {
sys::napi_get_named_property(env, result, ERROR_VALUE_KEY.as_ptr().cast(), &mut result)
};
if status != sys::Status::napi_ok {
return None;
}
}
Some(result)
}
}
#[cold]
#[inline(never)]
#[doc(hidden)]
pub fn decorate_field_error(mut err: Error, struct_name: &str, field: &str) -> Error {
err.reason = format!("{} on {}.{}", err.reason, struct_name, field);
err
}
#[cold]
#[inline(never)]
#[doc(hidden)]
pub fn missing_field_error(field: &str) -> Error {
Error::new(Status::InvalidArg, format!("Missing field `{}`", field))
}
impl Error {
pub fn from_reason<T: Into<String>>(reason: T) -> Self {
Error {
status: Status::GenericFailure,
reason: reason.into(),
cause: None,
maybe_ref: None,
}
}
}
impl From<std::ffi::NulError> for Error {
fn from(error: std::ffi::NulError) -> Self {
Error {
status: Status::GenericFailure,
reason: format!("{error}"),
cause: None,
maybe_ref: None,
}
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error {
status: Status::GenericFailure,
reason: format!("{error}"),
cause: None,
maybe_ref: None,
}
}
}
#[derive(Clone, Debug)]
pub struct ExtendedErrorInfo {
pub message: String,
pub engine_reserved: *mut c_void,
pub engine_error_code: u32,
pub error_code: Status,
}
impl TryFrom<sys::napi_extended_error_info> for ExtendedErrorInfo {
type Error = Error;
fn try_from(value: sys::napi_extended_error_info) -> Result<Self> {
Ok(Self {
message: if value.error_message.is_null() {
String::new()
} else {
unsafe {
CStr::from_ptr(value.error_message.cast())
.to_str()
.map_err(|e| Error::new(Status::GenericFailure, format!("{e}")))?
.to_owned()
}
},
engine_error_code: value.engine_error_code,
engine_reserved: value.engine_reserved,
error_code: Status::from(value.error_code),
})
}
}
pub(crate) unsafe fn is_js_error(env: sys::napi_env, value: sys::napi_value) -> bool {
let mut is_error = false;
let status = unsafe { sys::napi_is_error(env, value, &mut is_error) };
debug_assert!(status == sys::Status::napi_ok, "Check Error failed");
status == sys::Status::napi_ok && is_error
}
pub struct JsError<S: AsRef<str> = Status>(Error<S>);
#[cfg(feature = "anyhow")]
impl From<anyhow::Error> for JsError {
fn from(value: anyhow::Error) -> Self {
JsError(Error::new(Status::GenericFailure, value.to_string()))
}
}
pub struct JsTypeError<S: AsRef<str> = Status>(Error<S>);
pub struct JsRangeError<S: AsRef<str> = Status>(Error<S>);
#[cfg(feature = "napi9")]
pub struct JsSyntaxError<S: AsRef<str> = Status>(Error<S>);
macro_rules! impl_object_methods {
($js_value:ident, $kind:expr) => {
impl<S: AsRef<str>> $js_value<S> {
pub unsafe fn into_value(mut self, env: sys::napi_env) -> sys::napi_value {
if let Some(err) = unsafe { self.0.referenced_value(env) } {
if unsafe { is_js_error(env, err) } {
return err;
}
}
let error_status = self.0.status.as_ref();
let status_len = error_status.len();
let reason_len = self.0.reason.len();
let mut error_code = ptr::null_mut();
let mut reason_string = ptr::null_mut();
let mut js_error = ptr::null_mut();
let create_code_status = unsafe {
sys::napi_create_string_utf8(
env,
error_status.as_ptr().cast(),
status_len as isize,
&mut error_code,
)
};
debug_assert!(create_code_status == sys::Status::napi_ok);
let create_reason_status = unsafe {
sys::napi_create_string_utf8(
env,
self.0.reason.as_ptr().cast(),
reason_len as isize,
&mut reason_string,
)
};
debug_assert!(create_reason_status == sys::Status::napi_ok);
let create_error_status = unsafe { $kind(env, error_code, reason_string, &mut js_error) };
debug_assert!(create_error_status == sys::Status::napi_ok);
if let Some(cause_error) = self.0.cause.take() {
let cause = ToNapiValue::to_napi_value(env, *cause_error)
.expect("Convert cause Error to napi_value should never error");
let set_cause_status =
unsafe { sys::napi_set_named_property(env, js_error, c"cause".as_ptr().cast(), cause) };
debug_assert!(
set_cause_status == sys::Status::napi_ok,
"Set cause property failed"
);
}
js_error
}
pub fn into_unknown<'env>(self, env: Env) -> Unknown<'env> {
let value = unsafe { self.into_value(env.raw()) };
unsafe { Unknown::from_raw_unchecked(env.raw(), value) }
}
pub unsafe fn throw_into(self, env: sys::napi_env) {
#[cfg(debug_assertions)]
let reason = self.0.reason.clone();
let status = self.0.status.as_ref().to_string();
let mut is_pending_exception = false;
assert_eq!(
unsafe { $crate::sys::napi_is_exception_pending(env, &mut is_pending_exception) },
$crate::sys::Status::napi_ok,
"Check exception status failed"
);
if is_pending_exception && status == Status::PendingException.as_ref() {
return;
}
let js_error = match is_pending_exception {
true => {
let mut error_result = std::ptr::null_mut();
assert_eq!(
unsafe { $crate::sys::napi_get_and_clear_last_exception(env, &mut error_result) },
$crate::sys::Status::napi_ok,
"Get and clear last exception failed"
);
error_result
}
false => unsafe { self.into_value(env) },
};
#[cfg(debug_assertions)]
let throw_status = unsafe { sys::napi_throw(env, js_error) };
unsafe { sys::napi_throw(env, js_error) };
#[cfg(debug_assertions)]
assert!(
throw_status == sys::Status::napi_ok,
"Throw error failed, status: [{}], raw message: \"{}\", raw status: [{}]",
Status::from(throw_status),
reason,
status
);
}
}
impl<S: AsRef<str>> From<Error<S>> for $js_value<S> {
fn from(err: Error<S>) -> Self {
Self(err)
}
}
impl crate::bindgen_prelude::ToNapiValue for $js_value {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
if let Some(retained) = unsafe { val.0.referenced_value(env) } {
return Ok(retained);
}
Ok(unsafe { val.into_value(env) })
}
}
};
}
impl_object_methods!(JsError, sys::napi_create_error);
impl_object_methods!(JsTypeError, sys::napi_create_type_error);
impl_object_methods!(JsRangeError, sys::napi_create_range_error);
#[cfg(feature = "napi9")]
impl_object_methods!(JsSyntaxError, sys::node_api_create_syntax_error);
#[doc(hidden)]
#[macro_export]
macro_rules! error {
($status:expr, $($msg:tt)*) => {
$crate::Error::new($status, format!($($msg)*))
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! check_status {
($code:expr) => {{
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
_ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())),
}
}};
($code:expr, $($msg:tt)*) => {{
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
_ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))),
}
}};
($code:expr, $msg:expr, $env:expr, $val:expr) => {{
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
_ => Err($crate::Error::new($crate::Status::from(c), format!($msg, $crate::type_of!($env, $val)?))),
}
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! check_status_and_type {
($code:expr, $env:ident, $val:ident, $msg:expr) => {{
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
_ => {
use $crate::js_values::JsValue;
let value_type = $crate::type_of!($env, $val)?;
let error_msg = match value_type {
ValueType::Function => {
let function_name = unsafe {
$crate::bindgen_prelude::Function::<
$crate::bindgen_prelude::Unknown,
$crate::bindgen_prelude::Unknown,
>::from_napi_value($env, $val)?
.name()?
};
format!(
$msg,
format!(
"function {}(..) ",
if function_name.len() == 0 {
"anonymous".to_owned()
} else {
function_name
}
)
)
}
ValueType::Object => {
let env_ = $crate::Env::from($env);
let json: $crate::JSON = env_.get_global()?.get_named_property_unchecked("JSON")?;
let object = json.stringify($crate::bindgen_prelude::Object::from_raw($env, $val))?;
format!($msg, format!("Object {}", object))
}
ValueType::Boolean | ValueType::Number => {
let val = $crate::Unknown::from_raw_unchecked($env, $val);
let value = val.coerce_to_string()?.into_utf8()?;
format!($msg, format!("{} {} ", value_type, value.as_str()?))
}
#[cfg(feature = "napi6")]
ValueType::BigInt => {
let val = $crate::Unknown::from_raw_unchecked($env, $val);
let value = val.coerce_to_string()?.into_utf8()?;
format!($msg, format!("{} {} ", value_type, value.as_str()?))
}
_ => format!($msg, value_type),
};
Err($crate::Error::new($crate::Status::from(c), error_msg))
}
}
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! check_pending_exception {
($env:expr, $code:expr) => {{
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
$crate::sys::Status::napi_pending_exception => {
let mut error_result = std::ptr::null_mut();
assert_eq!(
unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) },
$crate::sys::Status::napi_ok
);
return Err($crate::Error::from(unsafe {
$crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result)
}));
}
_ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())),
}
}};
($env:expr, $code:expr, $($msg:tt)*) => {{
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
$crate::sys::Status::napi_pending_exception => {
let mut error_result = std::ptr::null_mut();
assert_eq!(
unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) },
$crate::sys::Status::napi_ok
);
return Err($crate::Error::from(unsafe {
$crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result)
}));
}
_ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))),
}
}};
}
pub(crate) fn extract_error_cause(value: Unknown<'_>) -> Result<Option<Box<Error>>> {
if value.get_type()? != ValueType::Object {
return Ok(None);
}
let env = value.0.env;
let key = c"cause";
let mut raw_cause = ptr::null_mut();
check_pending_exception!(
env,
unsafe { sys::napi_get_named_property(env, value.0.value, key.as_ptr(), &mut raw_cause) },
"get_named_property error"
)?;
let cause = unsafe { Unknown::from_raw_unchecked(env, raw_cause) };
match cause.get_type()? {
ValueType::Undefined | ValueType::Null => Ok(None),
_ => Ok(Some(Box::new(cause.into()))),
}
}