From 8e3eb6204b0233f3e95125f9c611244c5081c5c1 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 9 Feb 2023 23:18:57 +0800 Subject: [PATCH] fix(napi): support custom status in Error (#1486) --- crates/napi/src/env.rs | 6 +-- crates/napi/src/error.rs | 44 ++++++++++---------- crates/napi/src/status.rs | 31 ++++++++++++++ examples/napi/__test__/typegen.spec.ts.md | 1 + examples/napi/__test__/typegen.spec.ts.snap | Bin 3795 -> 3810 bytes examples/napi/__test__/values.spec.ts | 7 ++++ examples/napi/index.d.ts | 1 + examples/napi/src/error.rs | 19 +++++++++ 8 files changed, 85 insertions(+), 24 deletions(-) diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index 95eae3f5..71828f7b 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -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::), 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::), - Box::into_raw(Box::new(size_hint)) as *mut c_void, + Box::into_raw(Box::new(size_hint)).cast(), &mut object_value, ) })?; diff --git a/crates/napi/src/error.rs b/crates/napi/src/error.rs index 90f96e03..db9543e9 100644 --- a/crates/napi/src/error.rs +++ b/crates/napi/src/error.rs @@ -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 = std::result::Result; +pub type Result = std::result::Result>; /// 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 = 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> 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) }; @@ -109,17 +109,17 @@ impl fmt::Display for Error { } } -impl Error { - pub fn new(status: Status, reason: String) -> Self { +impl> Error { + pub fn new(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>(reason: T) -> Self { Error { status: Status::GenericFailure, @@ -160,7 +162,7 @@ impl From for Error { } } -impl Drop for Error { +impl> Drop for Error { fn drop(&mut self) { #[cfg(not(feature = "noop"))] { @@ -201,7 +203,7 @@ impl TryFrom for ExtendedErrorInfo { } } -pub struct JsError(Error); +pub struct JsError = Status>(Error); #[cfg(feature = "anyhow")] impl From for JsError { @@ -210,16 +212,16 @@ impl From for JsError { } } -pub struct JsTypeError(Error); +pub struct JsTypeError = Status>(Error); -pub struct JsRangeError(Error); +pub struct JsRangeError = Status>(Error); #[cfg(feature = "experimental")] -pub struct JsSyntaxError(Error); +pub struct JsSyntaxError = Status>(Error); macro_rules! impl_object_methods { ($js_value:ident, $kind:expr) => { - impl $js_value { + impl> $js_value { /// # 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 for $js_value { - fn from(err: Error) -> Self { + impl> From> for $js_value { + fn from(err: Error) -> Self { Self(err) } } diff --git a/crates/napi/src/status.rs b/crates/napi/src/status.rs index 0b20bcc3..f462b964 100644 --- a/crates/napi/src/status.rs +++ b/crates/napi/src/status.rs @@ -40,6 +40,37 @@ impl Display for Status { } } +impl AsRef 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 for Status { fn from(code: i32) -> Self { match code { diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 7c627270..4bc5c775 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -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␊ export function createExternalString(content: string): ExternalObject␊ export function getExternal(external: ExternalObject): number␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index c299f3468f63e1c2ffa082879277876c5438c8b2..8bcb74de66edee8b56455c8ebab37d6f241c9cca 100644 GIT binary patch literal 3810 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%0D;ZxD4S z{SB2f#||`>%vRFe?O5GkHGx3nSih?B=09U-uep0&)j}*ilWLB<920r9u$#BSt!A|R zf~s$LbtUz7LTPTARKCl(#9RN$U)|Q$3!}xg_NGlVHch#~eGaVhA6QFkF7a|5ho9rV5lxBONE4P)0ln-G49)VHu^pmlevy^~s&wd3;H z>TdgdtccEZvFHJp=q1zqJ#Nhd^YYY+USEN=q5f*1ucQpXbb~;AClmt538BE&ZU0OMg^djJ3c literal 3795 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 J^t+ok008yQP`>~G diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 53624a31..474f2742 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -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']) }) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index b76c0778..3f63dfb5 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -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 export function createExternalString(content: string): ExternalObject export function getExternal(external: ExternalObject): number diff --git a/examples/napi/src/error.rs b/examples/napi/src/error.rs index dbe41276..344a8ed0 100644 --- a/examples/napi/src/error.rs +++ b/examples/napi/src/error.rs @@ -14,3 +14,22 @@ pub fn panic() { pub fn receive_string(s: String) -> String { s } + +pub enum CustomError { + NapiError(Error), + Panic, +} + +impl AsRef 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")) +}