rspack_napi/
errors.rs

1use std::{ffi::CString, ptr};
2
3use napi::{Env, Error, bindgen_prelude::*, sys::napi_value};
4use rspack_error::Error as RspackError;
5
6pub trait NapiErrorToRspackErrorExt {
7  fn to_rspack_error(self, env: &Env) -> RspackError;
8}
9
10impl NapiErrorToRspackErrorExt for Error {
11  fn to_rspack_error(self, env: &Env) -> RspackError {
12    let (reason, stack, backtrace, hide_stack) =
13      extract_stack_or_message_from_napi_error(env, self);
14    let mut err = RspackError::error(format!("{reason}\n{}", backtrace.unwrap_or_default()));
15    err.stack = stack;
16    err.hide_stack = hide_stack;
17    err
18  }
19}
20
21const fn get_backtrace() -> Option<String> {
22  None
23}
24
25/// Extract stack or message from a native Node error object,
26/// otherwise we try to format the error from the given `Error` object that indicates which was created on the Rust side.
27#[inline(always)]
28fn extract_stack_or_message_from_napi_error(
29  env: &Env,
30  err: Error,
31) -> (String, Option<String>, Option<String>, Option<bool>) {
32  let maybe_reason = err.reason.clone();
33  match unsafe { ToNapiValue::to_napi_value(env.raw(), err) } {
34    Ok(napi_error) => {
35      let hide_stack = try_extract_string_value_from_property(env, napi_error, "hideStack")
36        .ok()
37        .map(|r| r == "true");
38      let message = match try_extract_string_value_from_property(env, napi_error, "message") {
39        Err(e) => format!("Unknown NAPI error {e}"),
40        Ok(message) => message,
41      };
42      let stack = try_extract_string_value_from_property(env, napi_error, "stack").ok();
43      (
44        if hide_stack.unwrap_or_default() {
45          message
46        } else {
47          stack.clone().unwrap_or(message)
48        },
49        stack,
50        get_backtrace(),
51        hide_stack,
52      )
53    }
54    Err(e) if maybe_reason.is_empty() => (
55      format!("Failed to extract NAPI error stack or message: {e}"),
56      None,
57      get_backtrace(),
58      None,
59    ),
60    Err(_) => (maybe_reason, None, None, None),
61  }
62}
63
64fn try_extract_string_value_from_property<S: AsRef<str>>(
65  env: &Env,
66  napi_object: napi_value,
67  property: S,
68) -> napi::Result<String> {
69  let property = CString::new(property.as_ref())?;
70
71  let mut value_ptr = ptr::null_mut();
72
73  check_status!(
74    unsafe {
75      sys::napi_get_named_property(env.raw(), napi_object, property.as_ptr(), &mut value_ptr)
76    },
77    "Failed to get the named property from object (property: {property:?})"
78  )?;
79
80  let mut str_len = 0;
81  check_status!(
82    unsafe {
83      sys::napi_get_value_string_utf8(env.raw(), value_ptr, ptr::null_mut(), 0, &mut str_len)
84    },
85    "Failed to get the length of the underlying property (property: {property:?})"
86  )?;
87
88  str_len += 1;
89  let mut buf = Vec::with_capacity(str_len);
90  let mut copied_len = 0;
91
92  check_status!(
93    unsafe {
94      sys::napi_get_value_string_utf8(
95        env.raw(),
96        value_ptr,
97        buf.as_mut_ptr(),
98        str_len,
99        &mut copied_len,
100      )
101    },
102    "Failed to get value of the property (property: {property:?})"
103  )?;
104
105  let mut buf = std::mem::ManuallyDrop::new(buf);
106
107  let buf = unsafe { Vec::from_raw_parts(buf.as_mut_ptr() as *mut u8, copied_len, copied_len) };
108
109  Ok(String::from_utf8_lossy(&buf).into_owned())
110}