Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(napi): propagation error in function call #1315

Merged
merged 1 commit into from Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/backend/src/codegen/fn.rs
Expand Up @@ -343,16 +343,16 @@ 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(),
cb.get_arg(#index),
args.len(),
args.as_ptr(),
&mut ret_ptr
),
"Failed to call napi callback",
)
)?;

#ret
Expand Down
4 changes: 4 additions & 0 deletions crates/backend/src/typegen.rs
Expand Up @@ -148,6 +148,10 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("Buffer", "Buffer"),
("Vec", "Array<{}>"),
("Result", "Error | {}"),
("Error", "Error"),
("JsError", "Error"),
("JsTypeError", "TypeError"),
("JsRangeError", "RangeError"),
("ClassInstance", "{}"),
("Either", "{} | {}"),
("Either3", "{} | {} | {}"),
Expand Down
48 changes: 45 additions & 3 deletions crates/napi/src/error.rs
Expand Up @@ -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<T> = std::result::Result<T, Error>;
Expand All @@ -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<sys::napi_value> {
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 {}

Expand Down Expand Up @@ -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<sys::napi_value> {
unsafe { ToNapiValue::to_napi_value(env, val.0) }
}
}
};
}

Expand Down Expand Up @@ -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(()),
Expand All @@ -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)*))),
}
}};
}
5 changes: 3 additions & 2 deletions crates/napi/src/lib.rs
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions examples/napi/__test__/typegen.spec.ts.md
Expand Up @@ -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<T>(functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void): T | Promise<T>␊
export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void␊
export interface ObjectFieldClassInstance {␊
bird: Bird␊
}␊
Expand Down
Binary file modified examples/napi/__test__/typegen.spec.ts.snap
Binary file not shown.
10 changes: 10 additions & 0 deletions examples/napi/__test__/values.spec.ts
Expand Up @@ -112,6 +112,7 @@ import {
CustomFinalize,
plusOne,
Width,
captureErrorInCallback,
} from '../'

test('export const', (t) => {
Expand Down Expand Up @@ -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) => {
Expand Down
1 change: 1 addition & 0 deletions examples/napi/index.d.ts
Expand Up @@ -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<T>(functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void): T | Promise<T>
export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void
export interface ObjectFieldClassInstance {
bird: Bird
}
Expand Down
12 changes: 12 additions & 0 deletions examples/napi/src/callback.rs
Expand Up @@ -80,3 +80,15 @@ fn callback_return_promise<T: Fn() -> Result<JsUnknown>>(
Ok(ret)
}
}

#[napi]
pub fn capture_error_in_callback<C: Fn() -> Result<()>, E: Fn(Error) -> Result<()>>(
cb1: C,
cb2: E,
) -> Result<()> {
if let Err(e) = cb1() {
cb2(e)
} else {
Ok(())
}
}