chore(napi): enhance the error messages while converting types failed (#1473)

This commit is contained in:
LongYinan 2023-02-06 00:52:59 +08:00 committed by GitHub
parent f1838a7b32
commit 7613d669fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 161 additions and 15 deletions

View file

@ -39,7 +39,7 @@ impl FromNapiValue for String {
unsafe { sys::napi_get_value_string_utf8(env, napi_val, ptr::null_mut(), 0, &mut len) },
env,
napi_val,
"Failed to convert napi `{}` into rust type `String`"
"Failed to convert JavaScript value `{}` into rust type `String`"
)?;
// end char len in C
@ -106,7 +106,7 @@ impl FromNapiValue for &str {
},
env,
napi_val,
"Failed to convert napi `{}` into rust type `String`"
"Failed to convert JavaScript value `{}` into rust type `String`"
)?;
// The `&str` should only be accepted from function arguments.

View file

@ -369,11 +369,49 @@ macro_rules! check_status_and_type {
match c {
$crate::sys::Status::napi_ok => Ok(()),
_ => {
use $crate::js_values::NapiValue;
let value_type = $crate::type_of!($env, $val)?;
Err($crate::Error::new(
$crate::Status::from(c),
format!($msg, value_type),
))
let error_msg = match value_type {
ValueType::Function => {
let function_name = unsafe { JsFunction::from_raw_unchecked($env, $val).name()? };
format!(
$msg,
format!(
"function {}(..) ",
if function_name.len() == 0 {
"anonymous".to_owned()
} else {
function_name
}
)
)
}
ValueType::Object => {
let env_ = $crate::Env::from($env);
let json: $crate::JSON = env_.get_global()?.get_named_property_unchecked("JSON")?;
let object = json.stringify($crate::JsObject($crate::Value {
value: $val,
env: $env,
value_type: ValueType::Object,
}))?;
format!($msg, format!("Object {}", object))
}
ValueType::Boolean | ValueType::Number => {
let value =
unsafe { $crate::JsUnknown::from_raw_unchecked($env, $val).coerce_to_string()? }
.into_utf8()?;
format!($msg, format!("{} {} ", value_type, value.as_str()?))
}
#[cfg(feature = "napi6")]
ValueType::BigInt => {
let value =
unsafe { $crate::JsUnknown::from_raw_unchecked($env, $val).coerce_to_string()? }
.into_utf8()?;
format!($msg, format!("{} {} ", value_type, value.as_str()?))
}
_ => format!($msg, value_type),
};
Err($crate::Error::new($crate::Status::from(c), error_msg))
}
}
}};

View file

@ -1,12 +1,12 @@
use std::ptr;
use super::Value;
use crate::bindgen_runtime::TypeName;
#[cfg(feature = "napi4")]
use crate::{
bindgen_runtime::ToNapiValue,
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction},
};
use crate::{bindgen_runtime::TypeName, JsString};
use crate::{check_pending_exception, ValueType};
use crate::{sys, Env, Error, JsObject, JsUnknown, NapiRaw, NapiValue, Result, Status};
@ -122,6 +122,21 @@ impl JsFunction {
Ok(unsafe { JsObject::from_raw_unchecked(self.0.env, js_instance) })
}
/// function name
pub fn name(&self) -> Result<String> {
let mut name = ptr::null_mut();
check_pending_exception!(self.0.env, unsafe {
sys::napi_get_named_property(
self.0.env,
self.0.value,
"name\0".as_ptr().cast(),
&mut name,
)
})?;
let name_value = unsafe { JsString::from_raw_unchecked(self.0.env, name) };
Ok(name_value.into_utf8()?.as_str()?.to_owned())
}
#[cfg(feature = "napi4")]
pub fn create_threadsafe_function<T, V, F, ES>(
&self,

View file

@ -1,12 +1,34 @@
use std::convert::TryInto;
use super::*;
use crate::Env;
use crate::{bindgen_runtime::FromNapiValue, Env};
pub struct JsGlobal(pub(crate) Value);
pub struct JsTimeout(pub(crate) Value);
pub struct JSON(pub(crate) Value);
impl FromNapiValue for JSON {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
Ok(JSON(Value {
env,
value: napi_val,
value_type: ValueType::Object,
}))
}
}
impl JSON {
pub fn stringify<V: NapiRaw>(&self, value: V) -> Result<std::string::String> {
let func: JsFunction = self.get_named_property_unchecked("stringify")?;
let result = func
.call(None, &[value])
.map(|ret| unsafe { ret.cast::<JsString>() })?;
result.into_utf8()?.as_str().map(|s| s.to_owned())
}
}
impl JsGlobal {
pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
let func: JsFunction = self.get_named_property_unchecked("setInterval")?;

View file

@ -3,7 +3,7 @@ use std::ffi::CString;
use std::ptr;
use crate::{
bindgen_runtime::{TypeName, ValidateNapiValue},
bindgen_runtime::{FromNapiValue, TypeName, ValidateNapiValue},
check_status, sys, type_of, Callback, Error, Result, Status, ValueType,
};
@ -337,26 +337,26 @@ macro_rules! impl_object_methods {
pub fn get_named_property<T>(&self, name: &str) -> Result<T>
where
T: NapiValue,
T: FromNapiValue,
{
let key = CString::new(name)?;
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_named_property(self.0.env, self.0.value, key.as_ptr(), &mut raw_value)
})?;
unsafe { T::from_raw(self.0.env, raw_value) }
unsafe { <T as FromNapiValue>::from_napi_value(self.0.env, raw_value) }
}
pub fn get_named_property_unchecked<T>(&self, name: &str) -> Result<T>
where
T: NapiValue,
T: FromNapiValue,
{
let key = CString::new(name)?;
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_named_property(self.0.env, self.0.value, key.as_ptr(), &mut raw_value)
})?;
Ok(unsafe { T::from_raw_unchecked(self.0.env, raw_value) })
unsafe { <T as FromNapiValue>::from_napi_value(self.0.env, raw_value) }
}
pub fn has_named_property(&self, name: &str) -> Result<bool> {
@ -618,6 +618,7 @@ impl_js_value_methods!(JsFunction);
impl_js_value_methods!(JsExternal);
impl_js_value_methods!(JsSymbol);
impl_js_value_methods!(JsTimeout);
impl_js_value_methods!(JSON);
impl_object_methods!(JsObject);
impl_object_methods!(JsBuffer);
@ -625,6 +626,7 @@ impl_object_methods!(JsArrayBuffer);
impl_object_methods!(JsTypedArray);
impl_object_methods!(JsDataView);
impl_object_methods!(JsGlobal);
impl_object_methods!(JSON);
use ValueType::*;

View file

@ -0,0 +1,59 @@
import test from 'ava'
import { receiveString } from '../index'
test('Function message', (t) => {
// @ts-expect-error
t.throws(() => receiveString(function a() {}), {
message:
'Failed to convert JavaScript value `function a(..) ` into rust type `String`',
})
// @ts-expect-error
t.throws(() => receiveString(() => {}), {
message:
'Failed to convert JavaScript value `function anonymous(..) ` into rust type `String`',
})
// @ts-expect-error
t.throws(() => receiveString(1), {
message:
'Failed to convert JavaScript value `Number 1 ` into rust type `String`',
})
t.throws(
() =>
// @ts-expect-error
receiveString({
a: 1,
b: {
foo: 'bar',
s: false,
},
}),
{
message:
'Failed to convert JavaScript value `Object {"a":1,"b":{"foo":"bar","s":false}}` into rust type `String`',
},
)
// @ts-expect-error
t.throws(() => receiveString(Symbol('1')), {
message:
'Failed to convert JavaScript value `Symbol` into rust type `String`',
})
// @ts-expect-error
t.throws(() => receiveString(), {
message:
'Failed to convert JavaScript value `Undefined` into rust type `String`',
})
// @ts-expect-error
t.throws(() => receiveString(null), {
message:
'Failed to convert JavaScript value `Null` into rust type `String`',
})
// @ts-expect-error
t.throws(() => receiveString(100n), {
message:
'Failed to convert JavaScript value `BigInt 100 ` into rust type `String`',
})
})

View file

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

View file

@ -427,7 +427,10 @@ test('option object', (t) => {
test('should throw if object type is not matched', (t) => {
// @ts-expect-error
const err1 = t.throws(() => receiveStrictObject({ name: 1 }))
t.is(err1!.message, 'Failed to convert napi `Number` into rust type `String`')
t.is(
err1!.message,
'Failed to convert JavaScript value `Number 1 ` into rust type `String`',
)
// @ts-expect-error
const err2 = t.throws(() => receiveStrictObject({ bar: 1 }))
t.is(err2!.message, 'Missing field `name`')

View file

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

View file

@ -9,3 +9,8 @@ pub fn throw_error() -> Result<()> {
pub fn panic() {
panic!("Don't panic");
}
#[napi]
pub fn receive_string(s: String) -> String {
s
}

View file

@ -51,5 +51,5 @@ mod typed_array;
#[napi]
pub fn run_script(env: Env, script: String) -> napi::Result<JsUnknown> {
env.run_script(&script)
env.run_script(script)
}