use std::{ffi::CString, ptr};
use napi::{Env, Error, bindgen_prelude::*, sys::napi_value};
use rspack_error::Error as RspackError;
pub trait NapiErrorToRspackErrorExt {
fn to_rspack_error(self, env: &Env) -> RspackError;
}
impl NapiErrorToRspackErrorExt for Error {
fn to_rspack_error(self, env: &Env) -> RspackError {
let (reason, stack, backtrace, hide_stack) =
extract_stack_or_message_from_napi_error(env, self);
let mut err = RspackError::error(format!("{reason}\n{}", backtrace.unwrap_or_default()));
err.stack = stack;
err.hide_stack = hide_stack;
err
}
}
const fn get_backtrace() -> Option<String> {
None
}
#[inline(always)]
fn extract_stack_or_message_from_napi_error(
env: &Env,
err: Error,
) -> (String, Option<String>, Option<String>, Option<bool>) {
let maybe_reason = err.reason.clone();
match unsafe { ToNapiValue::to_napi_value(env.raw(), err) } {
Ok(napi_error) => {
let hide_stack = try_extract_string_value_from_property(env, napi_error, "hideStack")
.ok()
.map(|r| r == "true");
let message = match try_extract_string_value_from_property(env, napi_error, "message") {
Err(e) => format!("Unknown NAPI error {e}"),
Ok(message) => message,
};
let stack = try_extract_string_value_from_property(env, napi_error, "stack").ok();
(
if hide_stack.unwrap_or_default() {
message
} else {
stack.clone().unwrap_or(message)
},
stack,
get_backtrace(),
hide_stack,
)
}
Err(e) if maybe_reason.is_empty() => (
format!("Failed to extract NAPI error stack or message: {e}"),
None,
get_backtrace(),
None,
),
Err(_) => (maybe_reason, None, None, None),
}
}
fn try_extract_string_value_from_property<S: AsRef<str>>(
env: &Env,
napi_object: napi_value,
property: S,
) -> napi::Result<String> {
let property = CString::new(property.as_ref())?;
let mut value_ptr = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(env.raw(), napi_object, property.as_ptr(), &mut value_ptr)
},
"Failed to get the named property from object (property: {property:?})"
)?;
let mut str_len = 0;
check_status!(
unsafe {
sys::napi_get_value_string_utf8(env.raw(), value_ptr, ptr::null_mut(), 0, &mut str_len)
},
"Failed to get the length of the underlying property (property: {property:?})"
)?;
str_len += 1;
let mut buf = Vec::with_capacity(str_len);
let mut copied_len = 0;
check_status!(
unsafe {
sys::napi_get_value_string_utf8(
env.raw(),
value_ptr,
buf.as_mut_ptr(),
str_len,
&mut copied_len,
)
},
"Failed to get value of the property (property: {property:?})"
)?;
let mut buf = std::mem::ManuallyDrop::new(buf);
let buf = unsafe { Vec::from_raw_parts(buf.as_mut_ptr() as *mut u8, copied_len, copied_len) };
Ok(String::from_utf8_lossy(&buf).into_owned())
}