diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 7fbb03444a..43d7e274e0 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -343,7 +343,8 @@ impl NapiFn { let mut ret_ptr = std::ptr::null_mut(); - napi::bindgen_prelude::check_status!( + napi::bindgen_prelude::check_pending_exception!( + env, napi::bindgen_prelude::sys::napi_call_function( env, cb.this(), @@ -351,8 +352,7 @@ impl NapiFn { args.len(), args.as_ptr(), &mut ret_ptr - ), - "Failed to call napi callback", + ) )?; #ret diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index ead99fac7b..3ca88a0682 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -148,6 +148,10 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("Buffer", "Buffer"), ("Vec", "Array<{}>"), ("Result", "Error | {}"), + ("Error", "Error"), + ("JsError", "Error"), + ("JsTypeError", "TypeError"), + ("JsRangeError", "RangeError"), ("ClassInstance", "{}"), ("Either", "{} | {}"), ("Either3", "{} | {} | {}"), diff --git a/crates/napi/src/error.rs b/crates/napi/src/error.rs index de37b6efd3..019e3822e9 100644 --- a/crates/napi/src/error.rs +++ b/crates/napi/src/error.rs @@ -12,6 +12,7 @@ use serde::{de, ser}; #[cfg(feature = "serde-json")] use serde_json::Error as SerdeJSONError; +use crate::bindgen_runtime::ToNapiValue; use crate::{check_status, sys, Env, JsUnknown, NapiValue, Status}; pub type Result = std::result::Result; @@ -28,6 +29,21 @@ pub struct Error { maybe_env: sys::napi_env, } +impl ToNapiValue for Error { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + if val.maybe_raw.is_null() { + let err = unsafe { JsError::from(val).into_value(env) }; + Ok(err) + } else { + let mut value = std::ptr::null_mut(); + check_status!(unsafe { + sys::napi_get_reference_value(val.maybe_env, val.maybe_raw, &mut value) + })?; + Ok(value) + } + } +} + unsafe impl Send for Error {} unsafe impl Sync for Error {} @@ -294,6 +310,12 @@ macro_rules! impl_object_methods { Self(err) } } + + impl crate::bindgen_prelude::ToNapiValue for $js_value { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + unsafe { ToNapiValue::to_napi_value(env, val.0) } + } + } }; } @@ -334,7 +356,8 @@ macro_rules! check_status { #[doc(hidden)] #[macro_export] macro_rules! check_pending_exception { - ($env: expr, $code:expr) => {{ + ($env:expr, $code:expr) => {{ + use $crate::NapiValue; let c = $code; match c { $crate::sys::Status::napi_ok => Ok(()), @@ -344,11 +367,30 @@ macro_rules! check_pending_exception { unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) }, $crate::sys::Status::napi_ok ); - return Err(Error::from(unsafe { - JsUnknown::from_raw_unchecked($env, error_result) + return Err($crate::Error::from(unsafe { + $crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result) })); } _ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())), } }}; + + ($env:expr, $code:expr, $($msg:tt)*) => {{ + use $crate::NapiValue; + let c = $code; + match c { + $crate::sys::Status::napi_ok => Ok(()), + $crate::sys::Status::napi_pending_exception => { + let mut error_result = std::ptr::null_mut(); + assert_eq!( + unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) }, + $crate::sys::Status::napi_ok + ); + return Err($crate::Error::from(unsafe { + $crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result) + })); + } + _ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))), + } + }}; } diff --git a/crates/napi/src/lib.rs b/crates/napi/src/lib.rs index 642d504092..002e6d5a77 100644 --- a/crates/napi/src/lib.rs +++ b/crates/napi/src/lib.rs @@ -160,8 +160,9 @@ pub mod bindgen_prelude { #[cfg(feature = "tokio_rt")] pub use crate::tokio_runtime::*; pub use crate::{ - assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys, - type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType, + assert_type_of, bindgen_runtime::*, check_pending_exception, check_status, + check_status_or_throw, error, error::*, sys, type_of, JsError, Property, PropertyAttributes, + Result, Status, Task, ValueType, }; // This function's signature must be kept in sync with the one in tokio_runtime.rs, otherwise napi diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index bcf1735e9a..ce2521b9ea 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -43,6 +43,7 @@ Generated by [AVA](https://avajs.dev). export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊ export function returnJsFunction(): (...args: any[]) => any␊ export function callbackReturnPromise(functionInput: () => T | Promise, callback: (err: Error | null, result: T) => void): T | Promise␊ + export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void␊ export interface ObjectFieldClassInstance {␊ bird: Bird␊ }␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 6091812b16..88f7a84c7d 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index bf351b0b94..d4f7fe514a 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -112,6 +112,7 @@ import { CustomFinalize, plusOne, Width, + captureErrorInCallback, } from '../' test('export const', (t) => { @@ -290,6 +291,15 @@ test('callback', (t) => { t.is(err, undefined) t.is(content, 'hello world') }) + + captureErrorInCallback( + () => { + throw new Error('Testing') + }, + (err) => { + t.is((err as Error).message, 'Testing') + }, + ) }) test('return function', (t) => { diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index e7c74e2bb0..67d8b6c70b 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -33,6 +33,7 @@ export function optionOnly(callback: (arg0?: string | undefined | null) => void) export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void export function returnJsFunction(): (...args: any[]) => any export function callbackReturnPromise(functionInput: () => T | Promise, callback: (err: Error | null, result: T) => void): T | Promise +export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void export interface ObjectFieldClassInstance { bird: Bird } diff --git a/examples/napi/src/callback.rs b/examples/napi/src/callback.rs index f92e01ef93..069ceb2a79 100644 --- a/examples/napi/src/callback.rs +++ b/examples/napi/src/callback.rs @@ -80,3 +80,15 @@ fn callback_return_promise Result>( Ok(ret) } } + +#[napi] +pub fn capture_error_in_callback Result<()>, E: Fn(Error) -> Result<()>>( + cb1: C, + cb2: E, +) -> Result<()> { + if let Err(e) = cb1() { + cb2(e) + } else { + Ok(()) + } +}