rspack_napi/
errors.rs

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