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
15use crate::bindgen_runtime::JsObjectValue;
16#[cfg(target_family = "wasm")]
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: Option<Box<Error>> = value
150      .coerce_to_object()
151      .and_then(|obj| obj.get_named_property::<Unknown>("cause"))
152      .map(|cause| Box::new(cause.into()))
153      .ok();
154
155    if let Ok(error_message) = maybe_error_message {
156      return Self {
157        status: Status::GenericFailure,
158        reason: error_message,
159        cause: maybe_cause,
160        maybe_raw: result,
161        maybe_env,
162      };
163    }
164
165    Self {
166      status: Status::GenericFailure,
167      reason: "".to_string(),
168      cause: maybe_cause,
169      maybe_raw: result,
170      maybe_env,
171    }
172  }
173}
174
175#[cfg(target_family = "wasm")]
176impl From<Unknown<'_>> for Error {
177  fn from(value: Unknown) -> Self {
178    let value_type = value.get_type();
179
180    let maybe_error_message;
181
182    if let Ok(vt) = value_type {
183      if vt == ValueType::Object {
184        maybe_error_message = value
185          .coerce_to_object()
186          .and_then(|obj| obj.get_named_property::<Unknown>("message"))
187          .and_then(|message| {
188            message
189              .coerce_to_string()
190              .and_then(|message| message.into_utf8().and_then(|message| message.into_owned()))
191          });
192      } else {
193        maybe_error_message = value
194          .coerce_to_string()
195          .and_then(|a| a.into_utf8().and_then(|a| a.into_owned()));
196      }
197    } else {
198      maybe_error_message = value
199        .coerce_to_string()
200        .and_then(|a| a.into_utf8().and_then(|a| a.into_owned()));
201    };
202
203    let maybe_cause: Option<Box<Error>> = if let Ok(vt) = value_type {
204      if vt == ValueType::Object {
205        value
206          .coerce_to_object()
207          .and_then(|obj| obj.get_named_property::<Unknown>("cause"))
208          .map(|cause| Box::new(cause.into()))
209          .ok()
210      } else {
211        None
212      }
213    } else {
214      None
215    };
216
217    if let Ok(error_message) = maybe_error_message {
218      return Self {
219        status: Status::GenericFailure,
220        reason: error_message,
221        cause: maybe_cause,
222        maybe_raw: ptr::null_mut(),
223        maybe_env: ptr::null_mut(),
224      };
225    }
226
227    Self {
228      status: Status::GenericFailure,
229      reason: "".to_string(),
230      cause: maybe_cause,
231      maybe_raw: ptr::null_mut(),
232      maybe_env: ptr::null_mut(),
233    }
234  }
235}
236
237#[cfg(feature = "anyhow")]
238impl From<anyhow::Error> for Error {
239  fn from(value: anyhow::Error) -> Self {
240    Error::new(Status::GenericFailure, format!("{:?}", value))
241  }
242}
243
244impl<S: AsRef<str> + std::fmt::Debug> fmt::Display for Error<S> {
245  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246    if !self.reason.is_empty() {
247      write!(f, "{:?}, {}", self.status, self.reason)
248    } else {
249      write!(f, "{:?}", self.status)
250    }
251  }
252}
253
254impl<S: AsRef<str>> Error<S> {
255  pub fn new<R: ToString>(status: S, reason: R) -> Self {
256    Error {
257      status,
258      reason: reason.to_string(),
259      cause: None,
260      maybe_raw: ptr::null_mut(),
261      maybe_env: ptr::null_mut(),
262    }
263  }
264
265  pub fn from_status(status: S) -> Self {
266    Error {
267      status,
268      reason: "".to_owned(),
269      cause: None,
270      maybe_raw: ptr::null_mut(),
271      maybe_env: ptr::null_mut(),
272    }
273  }
274}
275
276impl<S: AsRef<str> + Clone> Error<S> {
277  pub fn try_clone(&self) -> Result<Self> {
278    if !self.maybe_raw.is_null() {
279      check_status!(
280        unsafe { sys::napi_reference_ref(self.maybe_env, self.maybe_raw, &mut 0) },
281        "Failed to increase error reference count"
282      )?;
283    }
284    Ok(Self {
285      status: self.status.clone(),
286      reason: self.reason.to_string(),
287      cause: None,
288      maybe_raw: self.maybe_raw,
289      maybe_env: self.maybe_env,
290    })
291  }
292}
293
294impl Error {
295  pub fn from_reason<T: Into<String>>(reason: T) -> Self {
296    Error {
297      status: Status::GenericFailure,
298      reason: reason.into(),
299      cause: None,
300      maybe_raw: ptr::null_mut(),
301      maybe_env: ptr::null_mut(),
302    }
303  }
304}
305
306impl From<std::ffi::NulError> for Error {
307  fn from(error: std::ffi::NulError) -> Self {
308    Error {
309      status: Status::GenericFailure,
310      reason: format!("{error}"),
311      cause: None,
312      maybe_raw: ptr::null_mut(),
313      maybe_env: ptr::null_mut(),
314    }
315  }
316}
317
318impl From<std::io::Error> for Error {
319  fn from(error: std::io::Error) -> Self {
320    Error {
321      status: Status::GenericFailure,
322      reason: format!("{error}"),
323      cause: None,
324      maybe_raw: ptr::null_mut(),
325      maybe_env: ptr::null_mut(),
326    }
327  }
328}
329
330#[derive(Clone, Debug)]
331pub struct ExtendedErrorInfo {
332  pub message: String,
333  pub engine_reserved: *mut c_void,
334  pub engine_error_code: u32,
335  pub error_code: Status,
336}
337
338impl TryFrom<sys::napi_extended_error_info> for ExtendedErrorInfo {
339  type Error = Error;
340
341  fn try_from(value: sys::napi_extended_error_info) -> Result<Self> {
342    Ok(Self {
343      message: unsafe {
344        CStr::from_ptr(value.error_message.cast())
345          .to_str()
346          .map_err(|e| Error::new(Status::GenericFailure, format!("{e}")))?
347          .to_owned()
348      },
349      engine_error_code: value.engine_error_code,
350      engine_reserved: value.engine_reserved,
351      error_code: Status::from(value.error_code),
352    })
353  }
354}
355
356pub struct JsError<S: AsRef<str> = Status>(Error<S>);
357
358#[cfg(feature = "anyhow")]
359impl From<anyhow::Error> for JsError {
360  fn from(value: anyhow::Error) -> Self {
361    JsError(Error::new(Status::GenericFailure, value.to_string()))
362  }
363}
364
365pub struct JsTypeError<S: AsRef<str> = Status>(Error<S>);
366
367pub struct JsRangeError<S: AsRef<str> = Status>(Error<S>);
368
369#[cfg(feature = "napi9")]
370pub struct JsSyntaxError<S: AsRef<str> = Status>(Error<S>);
371
372pub(crate) fn get_error_message_and_stack_trace(
373  env: sys::napi_env,
374  err: sys::napi_value,
375) -> Result<String> {
376  use crate::bindgen_runtime::FromNapiValue;
377
378  let mut error_string = ptr::null_mut();
379  check_status!(
380    unsafe { sys::napi_coerce_to_string(env, err, &mut error_string) },
381    "Get error message failed"
382  )?;
383  let mut result = unsafe { String::from_napi_value(env, error_string) }?;
384
385  let mut stack_trace = ptr::null_mut();
386  check_status!(
387    unsafe { sys::napi_get_named_property(env, err, c"stack".as_ptr().cast(), &mut stack_trace) },
388    "Get stack trace failed"
389  )?;
390  let mut stack_type = -1;
391  check_status!(
392    unsafe { sys::napi_typeof(env, stack_trace, &mut stack_type) },
393    "Get stack trace type failed"
394  )?;
395  if stack_type == sys::ValueType::napi_string {
396    let stack_trace = unsafe { String::from_napi_value(env, stack_trace) }?;
397    result.push('\n');
398    result.push_str(&stack_trace);
399  }
400
401  Ok(result)
402}
403
404macro_rules! impl_object_methods {
405  ($js_value:ident, $kind:expr) => {
406    impl<S: AsRef<str>> $js_value<S> {
407      /// # Safety
408      ///
409      /// This function is safety if env is not null ptr.
410      pub unsafe fn into_value(mut self, env: sys::napi_env) -> sys::napi_value {
411        if !self.0.maybe_raw.is_null() {
412          let mut err = ptr::null_mut();
413          let get_err_status =
414            unsafe { sys::napi_get_reference_value(env, self.0.maybe_raw, &mut err) };
415          debug_assert!(
416            get_err_status == sys::Status::napi_ok,
417            "Get Error from Reference failed"
418          );
419          let mut ref_count = 0;
420          let unref_status =
421            unsafe { sys::napi_reference_unref(env, self.0.maybe_raw, &mut ref_count) };
422          debug_assert!(
423            unref_status == sys::Status::napi_ok,
424            "Unref Error Reference failed"
425          );
426          if ref_count == 0 {
427            let delete_err_status = unsafe { sys::napi_delete_reference(env, self.0.maybe_raw) };
428            debug_assert!(
429              delete_err_status == sys::Status::napi_ok,
430              "Delete Error Reference failed"
431            );
432          }
433          // already unref, skip the logic in `Drop`
434          self.0.maybe_raw = ptr::null_mut();
435          self.0.maybe_env = ptr::null_mut();
436          let mut is_error = false;
437          let is_error_status = unsafe { sys::napi_is_error(env, err, &mut is_error) };
438          debug_assert!(
439            is_error_status == sys::Status::napi_ok,
440            "Check Error failed"
441          );
442          // make sure ref_value is a valid error at first and avoid throw error failed.
443          if is_error {
444            return err;
445          }
446        }
447
448        let error_status = self.0.status.as_ref();
449        let status_len = error_status.len();
450        let reason_len = self.0.reason.len();
451        let mut error_code = ptr::null_mut();
452        let mut reason_string = ptr::null_mut();
453        let mut js_error = ptr::null_mut();
454        let create_code_status = unsafe {
455          sys::napi_create_string_utf8(
456            env,
457            error_status.as_ptr().cast(),
458            status_len as isize,
459            &mut error_code,
460          )
461        };
462        debug_assert!(create_code_status == sys::Status::napi_ok);
463        let create_reason_status = unsafe {
464          sys::napi_create_string_utf8(
465            env,
466            self.0.reason.as_ptr().cast(),
467            reason_len as isize,
468            &mut reason_string,
469          )
470        };
471        debug_assert!(create_reason_status == sys::Status::napi_ok);
472        let create_error_status = unsafe { $kind(env, error_code, reason_string, &mut js_error) };
473        debug_assert!(create_error_status == sys::Status::napi_ok);
474        if let Some(cause_error) = self.0.cause.take() {
475          let cause = ToNapiValue::to_napi_value(env, *cause_error)
476            .expect("Convert cause Error to napi_value should never error");
477          let set_cause_status =
478            unsafe { sys::napi_set_named_property(env, js_error, c"cause".as_ptr().cast(), cause) };
479          debug_assert!(
480            set_cause_status == sys::Status::napi_ok,
481            "Set cause property failed"
482          );
483        }
484        js_error
485      }
486
487      pub fn into_unknown<'env>(self, env: Env) -> Unknown<'env> {
488        let value = unsafe { self.into_value(env.raw()) };
489        unsafe { Unknown::from_raw_unchecked(env.raw(), value) }
490      }
491
492      /// # Safety
493      ///
494      /// This function is safety if env is not null ptr.
495      pub unsafe fn throw_into(self, env: sys::napi_env) {
496        #[cfg(debug_assertions)]
497        let reason = self.0.reason.clone();
498        let status = self.0.status.as_ref().to_string();
499        // just sure current error is pending_exception
500        if status == Status::PendingException.as_ref() {
501          return;
502        }
503        // make sure current env is not exception_pending status
504        let mut is_pending_exception = false;
505        assert_eq!(
506          unsafe { $crate::sys::napi_is_exception_pending(env, &mut is_pending_exception) },
507          $crate::sys::Status::napi_ok,
508          "Check exception status failed"
509        );
510        let js_error = match is_pending_exception {
511          true => {
512            let mut error_result = std::ptr::null_mut();
513            assert_eq!(
514              unsafe { $crate::sys::napi_get_and_clear_last_exception(env, &mut error_result) },
515              $crate::sys::Status::napi_ok,
516              "Get and clear last exception failed"
517            );
518            error_result
519          }
520          false => unsafe { self.into_value(env) },
521        };
522        #[cfg(debug_assertions)]
523        let throw_status = unsafe { sys::napi_throw(env, js_error) };
524        unsafe { sys::napi_throw(env, js_error) };
525        #[cfg(debug_assertions)]
526        assert!(
527          throw_status == sys::Status::napi_ok,
528          "Throw error failed, status: [{}], raw message: \"{}\", raw status: [{}]",
529          Status::from(throw_status),
530          reason,
531          status
532        );
533      }
534    }
535
536    impl<S: AsRef<str>> From<Error<S>> for $js_value<S> {
537      fn from(err: Error<S>) -> Self {
538        Self(err)
539      }
540    }
541
542    impl crate::bindgen_prelude::ToNapiValue for $js_value {
543      unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
544        unsafe { ToNapiValue::to_napi_value(env, val.0) }
545      }
546    }
547  };
548}
549
550impl_object_methods!(JsError, sys::napi_create_error);
551impl_object_methods!(JsTypeError, sys::napi_create_type_error);
552impl_object_methods!(JsRangeError, sys::napi_create_range_error);
553#[cfg(feature = "napi9")]
554impl_object_methods!(JsSyntaxError, sys::node_api_create_syntax_error);
555
556#[doc(hidden)]
557#[macro_export]
558macro_rules! error {
559  ($status:expr, $($msg:tt)*) => {
560    $crate::Error::new($status, format!($($msg)*))
561  };
562}
563
564#[doc(hidden)]
565#[macro_export]
566macro_rules! check_status {
567  ($code:expr) => {{
568    let c = $code;
569    match c {
570      $crate::sys::Status::napi_ok => Ok(()),
571      _ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())),
572    }
573  }};
574
575  ($code:expr, $($msg:tt)*) => {{
576    let c = $code;
577    match c {
578      $crate::sys::Status::napi_ok => Ok(()),
579      _ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))),
580    }
581  }};
582
583  ($code:expr, $msg:expr, $env:expr, $val:expr) => {{
584    let c = $code;
585    match c {
586      $crate::sys::Status::napi_ok => Ok(()),
587      _ => Err($crate::Error::new($crate::Status::from(c), format!($msg, $crate::type_of!($env, $val)?))),
588    }
589  }};
590}
591
592#[doc(hidden)]
593#[macro_export]
594macro_rules! check_status_and_type {
595  ($code:expr, $env:ident, $val:ident, $msg:expr) => {{
596    let c = $code;
597    match c {
598      $crate::sys::Status::napi_ok => Ok(()),
599      _ => {
600        use $crate::js_values::JsValue;
601        let value_type = $crate::type_of!($env, $val)?;
602        let error_msg = match value_type {
603          ValueType::Function => {
604            let function_name = unsafe {
605              $crate::bindgen_prelude::Function::<
606                $crate::bindgen_prelude::Unknown,
607                $crate::bindgen_prelude::Unknown,
608              >::from_napi_value($env, $val)?
609              .name()?
610            };
611            format!(
612              $msg,
613              format!(
614                "function {}(..) ",
615                if function_name.len() == 0 {
616                  "anonymous".to_owned()
617                } else {
618                  function_name
619                }
620              )
621            )
622          }
623          ValueType::Object => {
624            let env_ = $crate::Env::from($env);
625            let json: $crate::JSON = env_.get_global()?.get_named_property_unchecked("JSON")?;
626            let object = json.stringify($crate::bindgen_prelude::Object::from_raw($env, $val))?;
627            format!($msg, format!("Object {}", object))
628          }
629          ValueType::Boolean | ValueType::Number => {
630            let val = $crate::Unknown::from_raw_unchecked($env, $val);
631            let value = val.coerce_to_string()?.into_utf8()?;
632            format!($msg, format!("{} {} ", value_type, value.as_str()?))
633          }
634          #[cfg(feature = "napi6")]
635          ValueType::BigInt => {
636            let val = $crate::Unknown::from_raw_unchecked($env, $val);
637            let value = val.coerce_to_string()?.into_utf8()?;
638            format!($msg, format!("{} {} ", value_type, value.as_str()?))
639          }
640          _ => format!($msg, value_type),
641        };
642        Err($crate::Error::new($crate::Status::from(c), error_msg))
643      }
644    }
645  }};
646}
647
648#[doc(hidden)]
649#[macro_export]
650macro_rules! check_pending_exception {
651  ($env:expr, $code:expr) => {{
652    let c = $code;
653    match c {
654      $crate::sys::Status::napi_ok => Ok(()),
655      $crate::sys::Status::napi_pending_exception => {
656        let mut error_result = std::ptr::null_mut();
657        assert_eq!(
658          unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) },
659          $crate::sys::Status::napi_ok
660        );
661        return Err($crate::Error::from(unsafe {
662          $crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result)
663        }));
664      }
665      _ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())),
666    }
667  }};
668
669  ($env:expr, $code:expr, $($msg:tt)*) => {{
670    let c = $code;
671    match c {
672      $crate::sys::Status::napi_ok => Ok(()),
673      $crate::sys::Status::napi_pending_exception => {
674        let mut error_result = std::ptr::null_mut();
675        assert_eq!(
676          unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) },
677          $crate::sys::Status::napi_ok
678        );
679        return Err($crate::Error::from(unsafe {
680          $crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result)
681        }));
682      }
683      _ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))),
684    }
685  }};
686}