Skip to main content

napi/
error.rs

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