napi/
error.rs

1use std::convert::{From, TryFrom};
2use std::error;
3use std::ffi::CString;
4use std::fmt;
5#[cfg(feature = "serde-json")]
6use std::fmt::Display;
7use std::os::raw::{c_char, c_void};
8use std::ptr;
9
10#[cfg(feature = "serde-json")]
11use serde::{de, ser};
12#[cfg(feature = "serde-json")]
13use serde_json::Error as SerdeJSONError;
14
15use crate::bindgen_runtime::JsObjectValue;
16use crate::{bindgen_runtime::ToNapiValue, check_status, sys, Env, JsValue, Status, Unknown};
17
18pub type Result<T, S = Status> = std::result::Result<T, Error<S>>;
19
20/// Represent `JsError`.
21/// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you.
22/// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)`
23pub struct Error<S: AsRef<str> = Status> {
24  pub status: S,
25  pub reason: String,
26  pub cause: Option<Box<Error>>,
27  // Convert raw `JsError` into Error
28  pub(crate) maybe_raw: sys::napi_ref,
29  pub(crate) maybe_env: sys::napi_env,
30}
31
32impl<S: AsRef<str>> Drop for Error<S> {
33  fn drop(&mut self) {
34    // @TODO: deal with Error created with reference and leave it to drop in `async fn`
35    if !self.maybe_raw.is_null() {
36      let mut ref_count = 0;
37      let status =
38        unsafe { sys::napi_reference_unref(self.maybe_env, self.maybe_raw, &mut ref_count) };
39      if status != sys::Status::napi_ok {
40        eprintln!("unref error reference failed: {}", Status::from(status));
41      }
42      if ref_count == 0 {
43        let status = unsafe { sys::napi_delete_reference(self.maybe_env, self.maybe_raw) };
44        if status != sys::Status::napi_ok {
45          eprintln!("delete error reference failed: {}", Status::from(status));
46        }
47      }
48    }
49  }
50}
51
52impl<S: AsRef<str>> Error<S> {
53  pub fn set_cause(&mut self, cause: Error) {
54    self.cause = Some(Box::new(cause));
55  }
56}
57
58impl<S: AsRef<str>> std::fmt::Debug for Error<S> {
59  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60    write!(
61      f,
62      "Error {{ status: {:?}, reason: {:?} }}",
63      self.status.as_ref(),
64      self.reason
65    )
66  }
67}
68
69impl<S: AsRef<str>> ToNapiValue for Error<S> {
70  unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result<sys::napi_value> {
71    if val.maybe_raw.is_null() {
72      let err = unsafe { JsError::from(val).into_value(env) };
73      Ok(err)
74    } else {
75      let mut value = std::ptr::null_mut();
76      check_status!(
77        unsafe { sys::napi_get_reference_value(env, val.maybe_raw, &mut value) },
78        "Get error reference in `to_napi_value` failed"
79      )?;
80      let mut ref_count = 0;
81      check_status!(
82        unsafe { sys::napi_reference_unref(env, val.maybe_raw, &mut ref_count) },
83        "Unref error reference in `to_napi_value` failed"
84      )?;
85      if ref_count == 0 {
86        check_status!(
87          unsafe { sys::napi_delete_reference(env, val.maybe_raw) },
88          "Delete error reference in `to_napi_value` failed"
89        )?;
90      }
91      // already unref, skip the logic in `Drop`
92      val.maybe_raw = ptr::null_mut();
93      val.maybe_env = ptr::null_mut();
94      Ok(value)
95    }
96  }
97}
98
99unsafe impl<S> Send for Error<S> where S: Send + AsRef<str> {}
100unsafe impl<S> Sync for Error<S> where S: Sync + AsRef<str> {}
101
102impl<S: AsRef<str> + std::fmt::Debug> error::Error for Error<S> {}
103
104impl<S: AsRef<str>> From<std::convert::Infallible> for Error<S> {
105  fn from(_: std::convert::Infallible) -> Self {
106    unreachable!()
107  }
108}
109
110#[cfg(feature = "serde-json")]
111impl ser::Error for Error {
112  fn custom<T: Display>(msg: T) -> Self {
113    Error::new(Status::InvalidArg, msg.to_string())
114  }
115}
116
117#[cfg(feature = "serde-json")]
118impl de::Error for Error {
119  fn custom<T: Display>(msg: T) -> Self {
120    Error::new(Status::InvalidArg, msg.to_string())
121  }
122}
123
124#[cfg(feature = "serde-json")]
125impl From<SerdeJSONError> for Error {
126  fn from(value: SerdeJSONError) -> Self {
127    Error::new(Status::InvalidArg, format!("{value}"))
128  }
129}
130
131impl From<Unknown<'_>> for Error {
132  fn from(value: Unknown) -> Self {
133    let mut result = std::ptr::null_mut();
134    let status = unsafe { sys::napi_create_reference(value.0.env, value.0.value, 1, &mut result) };
135    if status != sys::Status::napi_ok {
136      return Error::new(
137        Status::from(status),
138        "Create Error reference failed".to_owned(),
139      );
140    }
141    let maybe_env = value.0.env;
142    let maybe_error_message = value
143      .coerce_to_string()
144      .and_then(|a| a.into_utf8().and_then(|a| a.into_owned()));
145    let maybe_cause: Option<Box<Error>> = value
146      .coerce_to_object()
147      .and_then(|obj| obj.get_named_property::<Unknown>("cause"))
148      .map(|cause| Box::new(cause.into()))
149      .ok();
150
151    if let Ok(error_message) = maybe_error_message {
152      return Self {
153        status: Status::GenericFailure,
154        reason: error_message,
155        cause: maybe_cause,
156        maybe_raw: result,
157        maybe_env,
158      };
159    }
160
161    Self {
162      status: Status::GenericFailure,
163      reason: "".to_string(),
164      cause: maybe_cause,
165      maybe_raw: result,
166      maybe_env,
167    }
168  }
169}
170
171#[cfg(feature = "anyhow")]
172impl From<anyhow::Error> for Error {
173  fn from(value: anyhow::Error) -> Self {
174    Error::new(Status::GenericFailure, format!("{:?}", value))
175  }
176}
177
178impl<S: AsRef<str> + std::fmt::Debug> fmt::Display for Error<S> {
179  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180    if !self.reason.is_empty() {
181      write!(f, "{:?}, {}", self.status, self.reason)
182    } else {
183      write!(f, "{:?}", self.status)
184    }
185  }
186}
187
188impl<S: AsRef<str>> Error<S> {
189  pub fn new<R: ToString>(status: S, reason: R) -> Self {
190    Error {
191      status,
192      reason: reason.to_string(),
193      cause: None,
194      maybe_raw: ptr::null_mut(),
195      maybe_env: ptr::null_mut(),
196    }
197  }
198
199  pub fn from_status(status: S) -> Self {
200    Error {
201      status,
202      reason: "".to_owned(),
203      cause: None,
204      maybe_raw: ptr::null_mut(),
205      maybe_env: ptr::null_mut(),
206    }
207  }
208}
209
210impl<S: AsRef<str> + Clone> Error<S> {
211  pub fn try_clone(&self) -> Result<Self> {
212    if !self.maybe_raw.is_null() {
213      check_status!(
214        unsafe { sys::napi_reference_ref(self.maybe_env, self.maybe_raw, &mut 0) },
215        "Failed to increase error reference count"
216      )?;
217    }
218    Ok(Self {
219      status: self.status.clone(),
220      reason: self.reason.to_string(),
221      cause: None,
222      maybe_raw: self.maybe_raw,
223      maybe_env: self.maybe_env,
224    })
225  }
226}
227
228impl Error {
229  pub fn from_reason<T: Into<String>>(reason: T) -> Self {
230    Error {
231      status: Status::GenericFailure,
232      reason: reason.into(),
233      cause: None,
234      maybe_raw: ptr::null_mut(),
235      maybe_env: ptr::null_mut(),
236    }
237  }
238}
239
240impl From<std::ffi::NulError> for Error {
241  fn from(error: std::ffi::NulError) -> Self {
242    Error {
243      status: Status::GenericFailure,
244      reason: format!("{error}"),
245      cause: None,
246      maybe_raw: ptr::null_mut(),
247      maybe_env: ptr::null_mut(),
248    }
249  }
250}
251
252impl From<std::io::Error> for Error {
253  fn from(error: std::io::Error) -> Self {
254    Error {
255      status: Status::GenericFailure,
256      reason: format!("{error}"),
257      cause: None,
258      maybe_raw: ptr::null_mut(),
259      maybe_env: ptr::null_mut(),
260    }
261  }
262}
263
264#[derive(Clone, Debug)]
265pub struct ExtendedErrorInfo {
266  pub message: String,
267  pub engine_reserved: *mut c_void,
268  pub engine_error_code: u32,
269  pub error_code: Status,
270}
271
272impl TryFrom<sys::napi_extended_error_info> for ExtendedErrorInfo {
273  type Error = Error;
274
275  fn try_from(value: sys::napi_extended_error_info) -> Result<Self> {
276    Ok(Self {
277      message: unsafe {
278        CString::from_raw(value.error_message as *mut c_char)
279          .into_string()
280          .map_err(|e| Error::new(Status::GenericFailure, format!("{e}")))?
281      },
282      engine_error_code: value.engine_error_code,
283      engine_reserved: value.engine_reserved,
284      error_code: Status::from(value.error_code),
285    })
286  }
287}
288
289pub struct JsError<S: AsRef<str> = Status>(Error<S>);
290
291#[cfg(feature = "anyhow")]
292impl From<anyhow::Error> for JsError {
293  fn from(value: anyhow::Error) -> Self {
294    JsError(Error::new(Status::GenericFailure, value.to_string()))
295  }
296}
297
298pub struct JsTypeError<S: AsRef<str> = Status>(Error<S>);
299
300pub struct JsRangeError<S: AsRef<str> = Status>(Error<S>);
301
302#[cfg(feature = "napi9")]
303pub struct JsSyntaxError<S: AsRef<str> = Status>(Error<S>);
304
305pub(crate) fn get_error_message_and_stack_trace(
306  env: sys::napi_env,
307  err: sys::napi_value,
308) -> Result<String> {
309  use crate::bindgen_runtime::FromNapiValue;
310
311  let mut error_string = ptr::null_mut();
312  check_status!(
313    unsafe { sys::napi_coerce_to_string(env, err, &mut error_string) },
314    "Get error message failed"
315  )?;
316  let mut result = unsafe { String::from_napi_value(env, error_string) }?;
317
318  let mut stack_trace = ptr::null_mut();
319  check_status!(
320    unsafe { sys::napi_get_named_property(env, err, c"stack".as_ptr().cast(), &mut stack_trace) },
321    "Get stack trace failed"
322  )?;
323  let mut stack_type = -1;
324  check_status!(
325    unsafe { sys::napi_typeof(env, stack_trace, &mut stack_type) },
326    "Get stack trace type failed"
327  )?;
328  if stack_type == sys::ValueType::napi_string {
329    let stack_trace = unsafe { String::from_napi_value(env, stack_trace) }?;
330    result.push('\n');
331    result.push_str(&stack_trace);
332  }
333
334  Ok(result)
335}
336
337macro_rules! impl_object_methods {
338  ($js_value:ident, $kind:expr) => {
339    impl<S: AsRef<str>> $js_value<S> {
340      /// # Safety
341      ///
342      /// This function is safety if env is not null ptr.
343      pub unsafe fn into_value(mut self, env: sys::napi_env) -> sys::napi_value {
344        if !self.0.maybe_raw.is_null() {
345          let mut err = ptr::null_mut();
346          let get_err_status =
347            unsafe { sys::napi_get_reference_value(env, self.0.maybe_raw, &mut err) };
348          debug_assert!(
349            get_err_status == sys::Status::napi_ok,
350            "Get Error from Reference failed"
351          );
352          let mut ref_count = 0;
353          let unref_status =
354            unsafe { sys::napi_reference_unref(env, self.0.maybe_raw, &mut ref_count) };
355          debug_assert!(
356            unref_status == sys::Status::napi_ok,
357            "Unref Error Reference failed"
358          );
359          if ref_count == 0 {
360            let delete_err_status = unsafe { sys::napi_delete_reference(env, self.0.maybe_raw) };
361            debug_assert!(
362              delete_err_status == sys::Status::napi_ok,
363              "Delete Error Reference failed"
364            );
365          }
366          // already unref, skip the logic in `Drop`
367          self.0.maybe_raw = ptr::null_mut();
368          self.0.maybe_env = ptr::null_mut();
369          let mut is_error = false;
370          let is_error_status = unsafe { sys::napi_is_error(env, err, &mut is_error) };
371          debug_assert!(
372            is_error_status == sys::Status::napi_ok,
373            "Check Error failed"
374          );
375          // make sure ref_value is a valid error at first and avoid throw error failed.
376          if is_error {
377            return err;
378          }
379        }
380
381        let error_status = self.0.status.as_ref();
382        let status_len = error_status.len();
383        let reason_len = self.0.reason.len();
384        let mut error_code = ptr::null_mut();
385        let mut reason_string = ptr::null_mut();
386        let mut js_error = ptr::null_mut();
387        let create_code_status = unsafe {
388          sys::napi_create_string_utf8(
389            env,
390            error_status.as_ptr().cast(),
391            status_len as isize,
392            &mut error_code,
393          )
394        };
395        debug_assert!(create_code_status == sys::Status::napi_ok);
396        let create_reason_status = unsafe {
397          sys::napi_create_string_utf8(
398            env,
399            self.0.reason.as_ptr().cast(),
400            reason_len as isize,
401            &mut reason_string,
402          )
403        };
404        debug_assert!(create_reason_status == sys::Status::napi_ok);
405        let create_error_status = unsafe { $kind(env, error_code, reason_string, &mut js_error) };
406        debug_assert!(create_error_status == sys::Status::napi_ok);
407        if let Some(cause_error) = self.0.cause.take() {
408          let cause = ToNapiValue::to_napi_value(env, *cause_error)
409            .expect("Convert cause Error to napi_value should never error");
410          let set_cause_status =
411            unsafe { sys::napi_set_named_property(env, js_error, c"cause".as_ptr().cast(), cause) };
412          debug_assert!(
413            set_cause_status == sys::Status::napi_ok,
414            "Set cause property failed"
415          );
416        }
417        js_error
418      }
419
420      pub fn into_unknown<'env>(self, env: Env) -> Unknown<'env> {
421        let value = unsafe { self.into_value(env.raw()) };
422        unsafe { Unknown::from_raw_unchecked(env.raw(), value) }
423      }
424
425      /// # Safety
426      ///
427      /// This function is safety if env is not null ptr.
428      pub unsafe fn throw_into(self, env: sys::napi_env) {
429        #[cfg(debug_assertions)]
430        let reason = self.0.reason.clone();
431        let status = self.0.status.as_ref().to_string();
432        // just sure current error is pending_exception
433        if status == Status::PendingException.as_ref() {
434          return;
435        }
436        // make sure current env is not exception_pending status
437        let mut is_pending_exception = false;
438        assert_eq!(
439          unsafe { $crate::sys::napi_is_exception_pending(env, &mut is_pending_exception) },
440          $crate::sys::Status::napi_ok,
441          "Check exception status failed"
442        );
443        let js_error = match is_pending_exception {
444          true => {
445            let mut error_result = std::ptr::null_mut();
446            assert_eq!(
447              unsafe { $crate::sys::napi_get_and_clear_last_exception(env, &mut error_result) },
448              $crate::sys::Status::napi_ok,
449              "Get and clear last exception failed"
450            );
451            error_result
452          }
453          false => unsafe { self.into_value(env) },
454        };
455        #[cfg(debug_assertions)]
456        let throw_status = unsafe { sys::napi_throw(env, js_error) };
457        unsafe { sys::napi_throw(env, js_error) };
458        #[cfg(debug_assertions)]
459        assert!(
460          throw_status == sys::Status::napi_ok,
461          "Throw error failed, status: [{}], raw message: \"{}\", raw status: [{}]",
462          Status::from(throw_status),
463          reason,
464          status
465        );
466      }
467    }
468
469    impl<S: AsRef<str>> From<Error<S>> for $js_value<S> {
470      fn from(err: Error<S>) -> Self {
471        Self(err)
472      }
473    }
474
475    impl crate::bindgen_prelude::ToNapiValue for $js_value {
476      unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
477        unsafe { ToNapiValue::to_napi_value(env, val.0) }
478      }
479    }
480  };
481}
482
483impl_object_methods!(JsError, sys::napi_create_error);
484impl_object_methods!(JsTypeError, sys::napi_create_type_error);
485impl_object_methods!(JsRangeError, sys::napi_create_range_error);
486#[cfg(feature = "napi9")]
487impl_object_methods!(JsSyntaxError, sys::node_api_create_syntax_error);
488
489#[doc(hidden)]
490#[macro_export]
491macro_rules! error {
492  ($status:expr, $($msg:tt)*) => {
493    $crate::Error::new($status, format!($($msg)*))
494  };
495}
496
497#[doc(hidden)]
498#[macro_export]
499macro_rules! check_status {
500  ($code:expr) => {{
501    let c = $code;
502    match c {
503      $crate::sys::Status::napi_ok => Ok(()),
504      _ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())),
505    }
506  }};
507
508  ($code:expr, $($msg:tt)*) => {{
509    let c = $code;
510    match c {
511      $crate::sys::Status::napi_ok => Ok(()),
512      _ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))),
513    }
514  }};
515
516  ($code:expr, $msg:expr, $env:expr, $val:expr) => {{
517    let c = $code;
518    match c {
519      $crate::sys::Status::napi_ok => Ok(()),
520      _ => Err($crate::Error::new($crate::Status::from(c), format!($msg, $crate::type_of!($env, $val)?))),
521    }
522  }};
523}
524
525#[doc(hidden)]
526#[macro_export]
527macro_rules! check_status_and_type {
528  ($code:expr, $env:ident, $val:ident, $msg:expr) => {{
529    let c = $code;
530    match c {
531      $crate::sys::Status::napi_ok => Ok(()),
532      _ => {
533        use $crate::js_values::JsValue;
534        let value_type = $crate::type_of!($env, $val)?;
535        let error_msg = match value_type {
536          ValueType::Function => {
537            let function_name = unsafe {
538              $crate::bindgen_prelude::Function::<
539                $crate::bindgen_prelude::Unknown,
540                $crate::bindgen_prelude::Unknown,
541              >::from_napi_value($env, $val)?
542              .name()?
543            };
544            format!(
545              $msg,
546              format!(
547                "function {}(..) ",
548                if function_name.len() == 0 {
549                  "anonymous".to_owned()
550                } else {
551                  function_name
552                }
553              )
554            )
555          }
556          ValueType::Object => {
557            let env_ = $crate::Env::from($env);
558            let json: $crate::JSON = env_.get_global()?.get_named_property_unchecked("JSON")?;
559            let object = json.stringify($crate::bindgen_prelude::Object::from_raw($env, $val))?;
560            format!($msg, format!("Object {}", object))
561          }
562          ValueType::Boolean | ValueType::Number => {
563            let val = $crate::Unknown::from_raw_unchecked($env, $val);
564            let value = val.coerce_to_string()?.into_utf8()?;
565            format!($msg, format!("{} {} ", value_type, value.as_str()?))
566          }
567          #[cfg(feature = "napi6")]
568          ValueType::BigInt => {
569            let val = $crate::Unknown::from_raw_unchecked($env, $val);
570            let value = val.coerce_to_string()?.into_utf8()?;
571            format!($msg, format!("{} {} ", value_type, value.as_str()?))
572          }
573          _ => format!($msg, value_type),
574        };
575        Err($crate::Error::new($crate::Status::from(c), error_msg))
576      }
577    }
578  }};
579}
580
581#[doc(hidden)]
582#[macro_export]
583macro_rules! check_pending_exception {
584  ($env:expr, $code:expr) => {{
585    let c = $code;
586    match c {
587      $crate::sys::Status::napi_ok => Ok(()),
588      $crate::sys::Status::napi_pending_exception => {
589        let mut error_result = std::ptr::null_mut();
590        assert_eq!(
591          unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) },
592          $crate::sys::Status::napi_ok
593        );
594        return Err($crate::Error::from(unsafe {
595          $crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result)
596        }));
597      }
598      _ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())),
599    }
600  }};
601
602  ($env:expr, $code:expr, $($msg:tt)*) => {{
603    let c = $code;
604    match c {
605      $crate::sys::Status::napi_ok => Ok(()),
606      $crate::sys::Status::napi_pending_exception => {
607        let mut error_result = std::ptr::null_mut();
608        assert_eq!(
609          unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) },
610          $crate::sys::Status::napi_ok
611        );
612        return Err($crate::Error::from(unsafe {
613          $crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result)
614        }));
615      }
616      _ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))),
617    }
618  }};
619}