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(
self.0,
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>),
ptr::null_mut(),
ptr::null_mut(),
@ -1007,9 +1007,9 @@ impl Env {
check_status!(unsafe {
sys::napi_create_external(
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>),
Box::into_raw(Box::new(size_hint)) as *mut c_void,
Box::into_raw(Box::new(size_hint)).cast(),
&mut object_value,
)
})?;

View file

@ -15,21 +15,21 @@ 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>;
pub type Result<T, S = Status> = std::result::Result<T, Error<S>>;
/// Represent `JsError`.
/// 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)`
#[derive(Debug, Clone)]
pub struct Error {
pub status: Status,
pub struct Error<S: AsRef<str> = Status> {
pub status: S,
pub reason: String,
// Convert raw `JsError` into Error
maybe_raw: sys::napi_ref,
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> {
if val.maybe_raw.is_null() {
let err = unsafe { JsError::from(val).into_value(env) };
@ -109,17 +109,17 @@ impl fmt::Display for Error {
}
}
impl Error {
pub fn new(status: Status, reason: String) -> Self {
impl<S: AsRef<str>> Error<S> {
pub fn new<R: ToString>(status: S, reason: R) -> Self {
Error {
status,
reason,
reason: reason.to_string(),
maybe_raw: ptr::null_mut(),
maybe_env: ptr::null_mut(),
}
}
pub fn from_status(status: Status) -> Self {
pub fn from_status(status: S) -> Self {
Error {
status,
reason: "".to_owned(),
@ -127,7 +127,9 @@ impl Error {
maybe_env: ptr::null_mut(),
}
}
}
impl Error {
pub fn from_reason<T: Into<String>>(reason: T) -> Self {
Error {
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) {
#[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")]
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")]
pub struct JsSyntaxError(Error);
pub struct JsSyntaxError<S: AsRef<str> = Status>(Error<S>);
macro_rules! impl_object_methods {
($js_value:ident, $kind:expr) => {
impl $js_value {
impl<S: AsRef<str>> $js_value<S> {
/// # Safety
///
/// This function is safety if env is not null ptr.
@ -235,7 +237,7 @@ macro_rules! impl_object_methods {
return err;
}
let error_status = format!("{:?}", self.0.status);
let error_status = self.0.status.as_ref();
let status_len = error_status.len();
let error_code_string = CString::new(error_status).unwrap();
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) {
#[cfg(debug_assertions)]
let reason = self.0.reason.clone();
let status = self.0.status;
if status == Status::PendingException {
let status = self.0.status.as_ref().to_string();
if status == Status::PendingException.as_ref() {
return;
}
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: [{}]",
Status::from(throw_status),
reason,
Status::from(status)
status
);
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
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 error_code_string =
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 {
fn from(err: Error) -> Self {
impl<S: AsRef<str>> From<Error<S>> for $js_value<S> {
fn from(err: Error<S>) -> Self {
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 {
fn from(code: i32) -> Self {
match code {

View file

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

View file

@ -31,6 +31,7 @@ import {
mapOption,
readFile,
throwError,
customStatusCode,
panic,
readPackageJson,
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) => {
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 panic(): void
export function receiveString(s: string): string
export function customStatusCode(): void
export function createExternal(size: number): ExternalObject<number>
export function createExternalString(content: string): ExternalObject<string>
export function getExternal(external: ExternalObject<number>): number

View file

@ -14,3 +14,22 @@ pub fn panic() {
pub fn receive_string(s: String) -> String {
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"))
}