fix(napi): support custom status in Error (#1486)

This commit is contained in:
LongYinan 2023-02-09 23:18:57 +08:00 committed by GitHub
parent 8e5ed4c7a0
commit 8e3eb6204b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 85 additions and 24 deletions

View file

@ -834,7 +834,7 @@ impl Env {
sys::napi_wrap( sys::napi_wrap(
self.0, self.0,
js_object.0.value, js_object.0.value,
Box::into_raw(Box::new(TaggedObject::new(native_object))) as *mut c_void, Box::into_raw(Box::new(TaggedObject::new(native_object))).cast(),
Some(raw_finalize::<T>), Some(raw_finalize::<T>),
ptr::null_mut(), ptr::null_mut(),
ptr::null_mut(), ptr::null_mut(),
@ -1007,9 +1007,9 @@ impl Env {
check_status!(unsafe { check_status!(unsafe {
sys::napi_create_external( sys::napi_create_external(
self.0, self.0,
Box::into_raw(Box::new(TaggedObject::new(native_object))) as *mut c_void, Box::into_raw(Box::new(TaggedObject::new(native_object))).cast(),
Some(raw_finalize::<T>), Some(raw_finalize::<T>),
Box::into_raw(Box::new(size_hint)) as *mut c_void, Box::into_raw(Box::new(size_hint)).cast(),
&mut object_value, &mut object_value,
) )
})?; })?;

View file

@ -15,21 +15,21 @@ use serde_json::Error as SerdeJSONError;
use crate::bindgen_runtime::ToNapiValue; use crate::bindgen_runtime::ToNapiValue;
use crate::{check_status, sys, Env, JsUnknown, NapiValue, Status}; use crate::{check_status, sys, Env, JsUnknown, NapiValue, Status};
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T, S = Status> = std::result::Result<T, Error<S>>;
/// Represent `JsError`. /// Represent `JsError`.
/// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you. /// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you.
/// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)` /// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Error { pub struct Error<S: AsRef<str> = Status> {
pub status: Status, pub status: S,
pub reason: String, pub reason: String,
// Convert raw `JsError` into Error // Convert raw `JsError` into Error
maybe_raw: sys::napi_ref, maybe_raw: sys::napi_ref,
maybe_env: sys::napi_env, maybe_env: sys::napi_env,
} }
impl ToNapiValue for Error { impl<S: AsRef<str>> ToNapiValue for Error<S> {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> { unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
if val.maybe_raw.is_null() { if val.maybe_raw.is_null() {
let err = unsafe { JsError::from(val).into_value(env) }; let err = unsafe { JsError::from(val).into_value(env) };
@ -109,17 +109,17 @@ impl fmt::Display for Error {
} }
} }
impl Error { impl<S: AsRef<str>> Error<S> {
pub fn new(status: Status, reason: String) -> Self { pub fn new<R: ToString>(status: S, reason: R) -> Self {
Error { Error {
status, status,
reason, reason: reason.to_string(),
maybe_raw: ptr::null_mut(), maybe_raw: ptr::null_mut(),
maybe_env: ptr::null_mut(), maybe_env: ptr::null_mut(),
} }
} }
pub fn from_status(status: Status) -> Self { pub fn from_status(status: S) -> Self {
Error { Error {
status, status,
reason: "".to_owned(), reason: "".to_owned(),
@ -127,7 +127,9 @@ impl Error {
maybe_env: ptr::null_mut(), maybe_env: ptr::null_mut(),
} }
} }
}
impl Error {
pub fn from_reason<T: Into<String>>(reason: T) -> Self { pub fn from_reason<T: Into<String>>(reason: T) -> Self {
Error { Error {
status: Status::GenericFailure, status: Status::GenericFailure,
@ -160,7 +162,7 @@ impl From<std::io::Error> for Error {
} }
} }
impl Drop for Error { impl<S: AsRef<str>> Drop for Error<S> {
fn drop(&mut self) { fn drop(&mut self) {
#[cfg(not(feature = "noop"))] #[cfg(not(feature = "noop"))]
{ {
@ -201,7 +203,7 @@ impl TryFrom<sys::napi_extended_error_info> for ExtendedErrorInfo {
} }
} }
pub struct JsError(Error); pub struct JsError<S: AsRef<str> = Status>(Error<S>);
#[cfg(feature = "anyhow")] #[cfg(feature = "anyhow")]
impl From<anyhow::Error> for JsError { impl From<anyhow::Error> for JsError {
@ -210,16 +212,16 @@ impl From<anyhow::Error> for JsError {
} }
} }
pub struct JsTypeError(Error); pub struct JsTypeError<S: AsRef<str> = Status>(Error<S>);
pub struct JsRangeError(Error); pub struct JsRangeError<S: AsRef<str> = Status>(Error<S>);
#[cfg(feature = "experimental")] #[cfg(feature = "experimental")]
pub struct JsSyntaxError(Error); pub struct JsSyntaxError<S: AsRef<str> = Status>(Error<S>);
macro_rules! impl_object_methods { macro_rules! impl_object_methods {
($js_value:ident, $kind:expr) => { ($js_value:ident, $kind:expr) => {
impl $js_value { impl<S: AsRef<str>> $js_value<S> {
/// # Safety /// # Safety
/// ///
/// This function is safety if env is not null ptr. /// This function is safety if env is not null ptr.
@ -235,7 +237,7 @@ macro_rules! impl_object_methods {
return err; return err;
} }
let error_status = format!("{:?}", self.0.status); let error_status = self.0.status.as_ref();
let status_len = error_status.len(); let status_len = error_status.len();
let error_code_string = CString::new(error_status).unwrap(); let error_code_string = CString::new(error_status).unwrap();
let reason_len = self.0.reason.len(); let reason_len = self.0.reason.len();
@ -267,8 +269,8 @@ macro_rules! impl_object_methods {
pub unsafe fn throw_into(self, env: sys::napi_env) { pub unsafe fn throw_into(self, env: sys::napi_env) {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let reason = self.0.reason.clone(); let reason = self.0.reason.clone();
let status = self.0.status; let status = self.0.status.as_ref().to_string();
if status == Status::PendingException { if status == Status::PendingException.as_ref() {
return; return;
} }
let js_error = unsafe { self.into_value(env) }; let js_error = unsafe { self.into_value(env) };
@ -281,13 +283,13 @@ macro_rules! impl_object_methods {
"Throw error failed, status: [{}], raw message: \"{}\", raw status: [{}]", "Throw error failed, status: [{}], raw message: \"{}\", raw status: [{}]",
Status::from(throw_status), Status::from(throw_status),
reason, reason,
Status::from(status) status
); );
} }
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn throw(&self, env: sys::napi_env) -> Result<()> { pub fn throw(&self, env: sys::napi_env) -> Result<()> {
let error_status = format!("{:?}\0", self.0.status); let error_status = format!("{:?}\0", self.0.status.as_ref());
let status_len = error_status.len(); let status_len = error_status.len();
let error_code_string = let error_code_string =
unsafe { CStr::from_bytes_with_nul_unchecked(error_status.as_bytes()) }; unsafe { CStr::from_bytes_with_nul_unchecked(error_status.as_bytes()) };
@ -308,8 +310,8 @@ macro_rules! impl_object_methods {
} }
} }
impl From<Error> for $js_value { impl<S: AsRef<str>> From<Error<S>> for $js_value<S> {
fn from(err: Error) -> Self { fn from(err: Error<S>) -> Self {
Self(err) Self(err)
} }
} }

View file

@ -40,6 +40,37 @@ impl Display for Status {
} }
} }
impl AsRef<str> for Status {
fn as_ref(&self) -> &str {
match self {
Status::Ok => "Ok",
Status::InvalidArg => "InvalidArg",
Status::ObjectExpected => "ObjectExpected",
Status::StringExpected => "StringExpected",
Status::NameExpected => "NameExpected",
Status::FunctionExpected => "FunctionExpected",
Status::NumberExpected => "NumberExpected",
Status::BooleanExpected => "BooleanExpected",
Status::ArrayExpected => "ArrayExpected",
Status::GenericFailure => "GenericFailure",
Status::PendingException => "PendingException",
Status::Cancelled => "Cancelled",
Status::EscapeCalledTwice => "EscapeCalledTwice",
Status::HandleScopeMismatch => "HandleScopeMismatch",
Status::CallbackScopeMismatch => "CallbackScopeMismatch",
Status::QueueFull => "QueueFull",
Status::Closing => "Closing",
Status::BigintExpected => "BigintExpected",
Status::DateExpected => "DateExpected",
Status::ArrayBufferExpected => "ArrayBufferExpected",
Status::DetachableArraybufferExpected => "DetachableArraybufferExpected",
Status::WouldDeadlock => "WouldDeadlock",
Status::NoExternalBuffersAllowed => "NoExternalBuffersAllowed",
_ => "Unknown",
}
}
}
impl From<i32> for Status { impl From<i32> for Status {
fn from(code: i32) -> Self { fn from(code: i32) -> Self {
match code { match code {

View file

@ -110,6 +110,7 @@ Generated by [AVA](https://avajs.dev).
export function throwError(): void␊ export function throwError(): void␊
export function panic(): void␊ export function panic(): void␊
export function receiveString(s: string): string␊ export function receiveString(s: string): string␊
export function customStatusCode(): void␊
export function createExternal(size: number): ExternalObject<number> export function createExternal(size: number): ExternalObject<number>
export function createExternalString(content: string): ExternalObject<string> export function createExternalString(content: string): ExternalObject<string>
export function getExternal(external: ExternalObject<number>): number␊ export function getExternal(external: ExternalObject<number>): number␊

View file

@ -31,6 +31,7 @@ import {
mapOption, mapOption,
readFile, readFile,
throwError, throwError,
customStatusCode,
panic, panic,
readPackageJson, readPackageJson,
getPackageJsonName, getPackageJsonName,
@ -402,6 +403,12 @@ test('Result', (t) => {
} }
}) })
test('custom status code in Error', (t) => {
t.throws(() => customStatusCode(), {
code: 'Panic',
})
})
test('function ts type override', (t) => { test('function ts type override', (t) => {
t.deepEqual(tsRename({ foo: 1, bar: 2, baz: 2 }), ['foo', 'bar', 'baz']) t.deepEqual(tsRename({ foo: 1, bar: 2, baz: 2 }), ['foo', 'bar', 'baz'])
}) })

View file

@ -100,6 +100,7 @@ export function enumToI32(e: CustomNumEnum): number
export function throwError(): void export function throwError(): void
export function panic(): void export function panic(): void
export function receiveString(s: string): string export function receiveString(s: string): string
export function customStatusCode(): void
export function createExternal(size: number): ExternalObject<number> export function createExternal(size: number): ExternalObject<number>
export function createExternalString(content: string): ExternalObject<string> export function createExternalString(content: string): ExternalObject<string>
export function getExternal(external: ExternalObject<number>): number export function getExternal(external: ExternalObject<number>): number

View file

@ -14,3 +14,22 @@ pub fn panic() {
pub fn receive_string(s: String) -> String { pub fn receive_string(s: String) -> String {
s s
} }
pub enum CustomError {
NapiError(Error<Status>),
Panic,
}
impl AsRef<str> for CustomError {
fn as_ref(&self) -> &str {
match self {
CustomError::Panic => "Panic",
CustomError::NapiError(e) => e.status.as_ref(),
}
}
}
#[napi]
pub fn custom_status_code() -> Result<(), CustomError> {
Err(Error::new(CustomError::Panic, "don't panic"))
}