fix(napi): missing ValidateNapiValue trait for types

This commit is contained in:
LongYinan 2022-03-05 23:19:51 +08:00
parent b48a757837
commit 18afd86a2e
23 changed files with 563 additions and 21 deletions

View file

@ -51,8 +51,9 @@ impl NapiEnum {
unsafe fn validate(
env: napi::bindgen_prelude::sys::napi_env,
napi_val: napi::bindgen_prelude::sys::napi_value
) -> napi::bindgen_prelude::Result<()> {
napi::bindgen_prelude::assert_type_of!(env, napi_val, napi::bindgen_prelude::ValueType::Number)
) -> napi::bindgen_prelude::Result<napi::sys::napi_value> {
napi::bindgen_prelude::assert_type_of!(env, napi_val, napi::bindgen_prelude::ValueType::Number)?;
Ok(std::ptr::null_mut())
}
}

View file

@ -159,7 +159,10 @@ impl NapiFn {
_ => {
let type_check = if self.strict {
quote! {
<#ty as napi::bindgen_prelude::ValidateNapiValue>::validate(env, cb.get_arg(#index))?;
let maybe_promise = <#ty as napi::bindgen_prelude::ValidateNapiValue>::validate(env, cb.get_arg(#index))?;
if !maybe_promise.is_null() {
return Ok(maybe_promise);
}
}
} else {
quote! {}

View file

@ -114,10 +114,13 @@ pub trait ValidateNapiValue: FromNapiValue + TypeName {
/// # Safety
///
/// this function called to validate whether napi value passed to rust is valid type
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<()> {
unsafe fn validate(
env: sys::napi_env,
napi_val: sys::napi_value,
) -> Result<napi_sys::napi_value> {
let available_types = Self::type_of();
if available_types.is_empty() {
return Ok(());
return Ok(ptr::null_mut());
}
let mut result = -1;
@ -128,7 +131,7 @@ pub trait ValidateNapiValue: FromNapiValue + TypeName {
let received_type = ValueType::from(result);
if available_types.contains(&received_type) {
Ok(())
Ok(ptr::null_mut())
} else {
Err(Error::new(
Status::InvalidArg,

View file

@ -257,7 +257,21 @@ impl<T> ValidateNapiValue for Vec<T>
where
T: FromNapiValue,
{
fn type_of() -> Vec<ValueType> {
vec![ValueType::Object]
unsafe fn validate(
env: sys::napi_env,
napi_val: sys::napi_value,
) -> Result<napi_sys::napi_value> {
let mut is_array = false;
check_status!(
unsafe { sys::napi_is_array(env, napi_val, &mut is_array) },
"Failed to check given napi value is array"
)?;
if !is_array {
return Err(Error::new(
Status::InvalidArg,
"Expected an array".to_owned(),
));
}
Ok(ptr::null_mut())
}
}

View file

@ -6,7 +6,7 @@ use std::ptr;
pub use crate::js_values::TypedArrayType;
use crate::{check_status, sys, Error, Result, Status};
use super::{FromNapiValue, ToNapiValue, TypeName};
use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue};
macro_rules! impl_typed_array {
($name:ident, $rust_type:ident, $typed_array_type:expr) => {
@ -98,7 +98,7 @@ macro_rules! impl_typed_array {
impl TypeName for $name {
fn type_name() -> &'static str {
"TypedArray"
concat!("TypedArray<", stringify!($rust_type), ">")
}
fn value_type() -> crate::ValueType {
@ -106,6 +106,26 @@ macro_rules! impl_typed_array {
}
}
impl ValidateNapiValue for $name {
unsafe fn validate(
env: sys::napi_env,
napi_val: sys::napi_value,
) -> Result<$crate::sys::napi_value> {
let mut is_typed_array = false;
check_status!(
unsafe { sys::napi_is_typedarray(env, napi_val, &mut is_typed_array) },
"Failed to check if value is typed array"
)?;
if !is_typed_array {
return Err(Error::new(
Status::InvalidArg,
"Expected a TypedArray value".to_owned(),
));
}
Ok(ptr::null_mut())
}
}
impl FromNapiValue for $name {
unsafe fn from_napi_value(
env: napi_sys::napi_env,

View file

@ -12,7 +12,7 @@ use std::ptr;
use crate::{check_status, sys};
use super::{FromNapiValue, ToNapiValue, TypeName};
use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue};
/// i64 is converted to `Number`
#[repr(transparent)]
@ -38,6 +38,12 @@ impl TypeName for BigInt {
}
}
impl ValidateNapiValue for BigInt {
fn type_of() -> Vec<crate::ValueType> {
vec![crate::ValueType::BigInt]
}
}
impl FromNapiValue for BigInt {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
let mut word_count = 0usize;

View file

@ -140,7 +140,18 @@ impl ToNapiValue for Buffer {
}
impl ValidateNapiValue for Buffer {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Object]
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
let mut is_buffer = false;
check_status!(
unsafe { sys::napi_is_buffer(env, napi_val, &mut is_buffer) },
"Failed to validate napi buffer"
)?;
if !is_buffer {
return Err(Error::new(
Status::InvalidArg,
"Expected a Buffer value".to_owned(),
));
}
Ok(ptr::null_mut())
}
}

View file

@ -1,6 +1,9 @@
use crate::{bindgen_prelude::*, check_status, sys, ValueType};
use std::ptr;
use chrono::{DateTime, NaiveDateTime, Utc};
use crate::{bindgen_prelude::*, check_status, sys, ValueType};
impl TypeName for DateTime<Utc> {
fn type_name() -> &'static str {
"DateTime"
@ -12,8 +15,20 @@ impl TypeName for DateTime<Utc> {
}
impl ValidateNapiValue for DateTime<Utc> {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Object]
unsafe fn validate(
env: sys::napi_env,
napi_val: sys::napi_value,
) -> Result<napi_sys::napi_value> {
let mut is_date = false;
check_status!(unsafe { napi_sys::napi_is_date(env, napi_val, &mut is_date) })?;
if !is_date {
return Err(Error::new(
Status::InvalidArg,
"Expected a Date object".to_owned(),
));
}
Ok(ptr::null_mut())
}
}

View file

@ -5,7 +5,7 @@ use std::{
use crate::{check_status, Error, Status, TaggedObject};
use super::{FromNapiValue, ToNapiValue};
use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue};
pub struct External<T: 'static> {
obj: *mut TaggedObject<T>,
@ -13,6 +13,22 @@ pub struct External<T: 'static> {
pub adjusted_size: i64,
}
impl<T: 'static> TypeName for External<T> {
fn type_name() -> &'static str {
"External"
}
fn value_type() -> crate::ValueType {
crate::ValueType::External
}
}
impl<T: 'static> ValidateNapiValue for External<T> {
fn type_of() -> Vec<crate::ValueType> {
vec![crate::ValueType::External]
}
}
impl<T: 'static> External<T> {
pub fn new(value: T) -> Self {
Self {

View file

@ -1 +1,9 @@
use super::ValidateNapiValue;
pub use crate::JsFunction;
impl ValidateNapiValue for JsFunction {
fn type_of() -> Vec<crate::ValueType> {
vec![crate::ValueType::Function]
}
}

View file

@ -13,6 +13,12 @@ impl<K, V, S> TypeName for HashMap<K, V, S> {
}
}
impl<K: From<String> + Eq + Hash, V: FromNapiValue> ValidateNapiValue for HashMap<K, V> {
fn type_of() -> Vec<crate::ValueType> {
vec![crate::ValueType::Object]
}
}
impl<K, V, S> ToNapiValue for HashMap<K, V, S>
where
K: AsRef<str>,

View file

@ -88,3 +88,9 @@ impl TypeName for Object {
ValueType::Object
}
}
impl ValidateNapiValue for Object {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Object]
}
}

View file

@ -8,12 +8,79 @@ use tokio::sync::oneshot::{channel, Receiver, Sender};
use crate::{check_status, Error, Result, Status};
use super::FromNapiValue;
use super::{FromNapiValue, TypeName, ValidateNapiValue};
pub struct Promise<T: FromNapiValue> {
value: Pin<Box<Receiver<*mut Result<T>>>>,
}
impl<T: FromNapiValue> TypeName for Promise<T> {
fn type_name() -> &'static str {
"Promise"
}
fn value_type() -> crate::ValueType {
crate::ValueType::Object
}
}
impl<T: FromNapiValue> ValidateNapiValue for Promise<T> {
fn type_of() -> Vec<crate::ValueType> {
vec![crate::ValueType::Object]
}
unsafe fn validate(
env: crate::sys::napi_env,
napi_val: crate::sys::napi_value,
) -> Result<napi_sys::napi_value> {
let mut is_promise = false;
check_status!(
unsafe { crate::sys::napi_is_promise(env, napi_val, &mut is_promise) },
"Failed to check if value is promise"
)?;
if !is_promise {
let mut deferred = ptr::null_mut();
let mut promise = ptr::null_mut();
check_status!(
unsafe { crate::sys::napi_create_promise(env, &mut deferred, &mut promise) },
"Failed to create promise"
)?;
let mut err = ptr::null_mut();
let mut code = ptr::null_mut();
let mut message = ptr::null_mut();
check_status!(
unsafe {
crate::sys::napi_create_string_utf8(
env,
CStr::from_bytes_with_nul_unchecked(b"InvalidArg\0").as_ptr(),
10,
&mut code,
)
},
"Failed to create error message"
)?;
check_status!(
unsafe {
crate::sys::napi_create_string_utf8(
env,
CStr::from_bytes_with_nul_unchecked(b"Expected Promise object\0").as_ptr(),
23,
&mut message,
)
},
"Failed to create error message"
)?;
check_status!(
unsafe { crate::sys::napi_create_error(env, code, message, &mut err) },
"Failed to create rejected error"
)?;
check_status!(unsafe { crate::sys::napi_reject_deferred(env, deferred, err) })?;
return Ok(promise);
}
Ok(ptr::null_mut())
}
}
unsafe impl<T: FromNapiValue> Send for Promise<T> {}
impl<T: FromNapiValue> FromNapiValue for Promise<T> {

View file

@ -16,6 +16,12 @@ impl TypeName for String {
}
}
impl ValidateNapiValue for String {
fn type_of() -> Vec<ValueType> {
vec![ValueType::String]
}
}
impl ToNapiValue for String {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
let mut ptr = ptr::null_mut();
@ -75,6 +81,12 @@ impl TypeName for &str {
}
}
impl ValidateNapiValue for &str {
fn type_of() -> Vec<ValueType> {
vec![ValueType::String]
}
}
impl FromNapiValue for &str {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut len = 0;
@ -134,6 +146,12 @@ impl ToNapiValue for &str {
#[derive(Debug)]
pub struct Utf16String(String);
impl ValidateNapiValue for Utf16String {
fn type_of() -> Vec<ValueType> {
vec![ValueType::String]
}
}
impl From<String> for Utf16String {
fn from(s: String) -> Self {
Utf16String(s)
@ -227,6 +245,12 @@ pub mod latin1_string {
#[derive(Debug)]
pub struct Latin1String(String);
impl ValidateNapiValue for Latin1String {
fn type_of() -> Vec<ValueType> {
vec![ValueType::String]
}
}
impl From<String> for Latin1String {
fn from(s: String) -> Self {
Latin1String(s)

View file

@ -2,12 +2,28 @@ use std::{ffi::CString, ptr};
use crate::check_status;
use super::ToNapiValue;
use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue};
pub struct Symbol {
desc: Option<String>,
}
impl TypeName for Symbol {
fn type_name() -> &'static str {
"Symbol"
}
fn value_type() -> crate::ValueType {
crate::ValueType::Object
}
}
impl ValidateNapiValue for Symbol {
fn type_of() -> Vec<crate::ValueType> {
vec![crate::ValueType::Symbol]
}
}
impl Symbol {
pub fn new(desc: String) -> Self {
Self { desc: Some(desc) }
@ -48,3 +64,12 @@ impl ToNapiValue for Symbol {
Ok(symbol_value)
}
}
impl FromNapiValue for Symbol {
unsafe fn from_napi_value(
_env: napi_sys::napi_env,
_napi_val: napi_sys::napi_value,
) -> crate::Result<Self> {
Ok(Self { desc: None })
}
}

View file

@ -1,5 +1,10 @@
use std::ptr;
use super::check_status;
use crate::{bindgen_runtime::TypeName, sys, Result, Value, ValueType};
use crate::{
bindgen_runtime::{TypeName, ValidateNapiValue},
sys, Error, Result, Status, Value, ValueType,
};
pub struct JsDate(pub(crate) Value);
@ -13,6 +18,21 @@ impl TypeName for JsDate {
}
}
impl ValidateNapiValue for JsDate {
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
let mut is_date = false;
check_status!(unsafe { napi_sys::napi_is_date(env, napi_val, &mut is_date) })?;
if !is_date {
return Err(Error::new(
Status::InvalidArg,
"Expected a Date object".to_owned(),
));
}
Ok(ptr::null_mut())
}
}
impl JsDate {
pub fn value_of(&self) -> Result<f64> {
let mut timestamp: f64 = 0.0;

View file

@ -3,7 +3,8 @@ use std::ffi::CString;
use std::ptr;
use crate::{
bindgen_runtime::TypeName, check_status, sys, type_of, Callback, Error, Result, Status, ValueType,
bindgen_runtime::{TypeName, ValidateNapiValue},
check_status, sys, type_of, Callback, Error, Result, Status, ValueType,
};
#[cfg(feature = "serde-json")]
@ -85,6 +86,12 @@ impl TypeName for JsSymbol {
}
}
impl ValidateNapiValue for JsSymbol {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Symbol]
}
}
pub struct JsExternal(pub(crate) Value);
impl TypeName for JsExternal {

View file

@ -0,0 +1,168 @@
import test from 'ava'
import {
validateArray,
validateTypedArray,
validateBigint,
validateBuffer,
validateBoolean,
validateDate,
validateDateTime,
createExternal,
validateExternal,
validateFunction,
validateHashMap,
validatePromise,
validateString,
validateSymbol,
validateNull,
validateUndefined,
} from '../index'
test('should validate array', (t) => {
t.is(validateArray([1, 2, 3]), 3)
// @ts-expect-error
t.throws(() => validateArray(1), {
message: 'Expected an array',
code: 'InvalidArg',
})
})
test('should validate arraybuffer', (t) => {
t.is(validateTypedArray(new Uint8Array([1, 2, 3])), 3)
// @ts-expect-error
t.throws(() => validateTypedArray(1), {
code: 'InvalidArg',
message: 'Expected a TypedArray value',
})
})
test('should validate BigInt', (t) => {
if (typeof BigInt === 'undefined') {
t.pass('BigInt is not supported')
} else {
const fx = BigInt(1024 * 1024 * 1024 * 1024)
t.is(validateBigint(fx), fx)
// @ts-expect-error
t.throws(() => validateBigint(1), {
code: 'InvalidArg',
message: 'Expect value to be BigInt, but received Number',
})
}
})
test('should validate buffer', (t) => {
t.is(validateBuffer(Buffer.from('hello')), 5)
// @ts-expect-error
t.throws(() => validateBuffer(2), {
code: 'InvalidArg',
message: 'Expected a Buffer value',
})
})
test('should validate boolean value', (t) => {
t.is(validateBoolean(true), false)
t.is(validateBoolean(false), true)
// @ts-expect-error
t.throws(() => validateBoolean(1), {
code: 'InvalidArg',
message: 'Expect value to be Boolean, but received Number',
})
})
test('should validate date', (t) => {
if (Number(process.versions.napi) >= 5) {
return t.pass()
}
const fx = new Date('2016-12-24')
t.is(validateDate(new Date()), fx.valueOf())
t.is(validateDateTime(fx), 1)
// @ts-expect-error
t.throws(() => validateDate(1), {
code: 'InvalidArg',
message: 'Expected a Date value',
})
// @ts-expect-error
t.throws(() => validateDateTime(2), {
code: 'InvalidArg',
message: 'Expected a Date value',
})
})
test('should validate External', (t) => {
const fx = createExternal(1)
t.is(validateExternal(fx), 1)
// @ts-expect-error
t.throws(() => validateExternal(1), {
code: 'InvalidArg',
message: 'Expect value to be External, but received Number',
})
})
test('should validate function', (t) => {
t.is(
validateFunction(() => 1),
4,
)
// @ts-expect-error
t.throws(() => validateFunction(2), {
code: 'InvalidArg',
message: 'Expect value to be Function, but received Number',
})
})
test('should validate Map', (t) => {
t.is(validateHashMap({ a: 1, b: 2 }), 2)
// @ts-expect-error
t.throws(() => validateHashMap(), {
code: 'InvalidArg',
message: 'Expect value to be Object, but received Undefined',
})
})
test('should validate promise', async (t) => {
t.is(await validatePromise(Promise.resolve(1)), 2)
// @ts-expect-error
await t.throwsAsync(() => validatePromise(1), {
code: 'InvalidArg',
message: 'Expected Promise object',
})
})
test('should validate string', (t) => {
t.is(validateString('hello'), 'hello!')
// @ts-expect-error
t.throws(() => validateString(1), {
code: 'InvalidArg',
message: 'Expect value to be String, but received Number',
})
})
test('should validate symbol', (t) => {
t.notThrows(() => validateSymbol(Symbol()))
// @ts-expect-error
t.throws(() => validateSymbol(1), {
code: 'InvalidArg',
message: 'Expect value to be Symbol, but received Number',
})
})
test('should validate null', (t) => {
t.notThrows(() => validateNull(null))
// @ts-expect-error
t.throws(() => validateNull(1), {
code: 'InvalidArg',
message: 'Expect value to be Null, but received Number',
})
})
test('should validate undefined', (t) => {
t.notThrows(() => validateUndefined(void 0))
// @ts-expect-error
t.notThrows(() => validateUndefined())
// @ts-expect-error
t.throws(() => validateUndefined(1), {
code: 'InvalidArg',
message: 'Expect value to be Undefined, but received Number',
})
})

View file

@ -82,6 +82,22 @@ Generated by [AVA](https://avajs.dev).
export function createExternalString(content: string): ExternalObject<string>
export function getExternal(external: ExternalObject<number>): number␊
export function mutateExternal(external: ExternalObject<number>, newVal: number): void␊
export function validateArray(arr: Array<number>): number␊
export function validateBuffer(b: Buffer): number␊
export function validateTypedArray(input: Uint8Array): number␊
export function validateBigint(input: bigint): bigint␊
export function validateBoolean(i: boolean): boolean␊
export function validateDate(d: Date): number␊
export function validateDateTime(d: Date): number␊
export function validateExternal(e: ExternalObject<number>): number␊
export function validateFunction(cb: () => number): number␊
export function validateHashMap(input: Record<string, number>): number␊
export function validateNull(i: null): boolean␊
export function validateUndefined(i: undefined): boolean␊
export function validateNumber(i: number): number␊
export function validatePromise(p: Promise<number>): Promise<number>
export function validateString(s: string): string␊
export function validateSymbol(s: symbol): boolean␊
export function tsRename(a: { foo: number }): string[]␊
export function xxh64Alias(input: Buffer): bigint␊
export function getMapping(): Record<string, number>

View file

@ -72,6 +72,22 @@ export function createExternal(size: number): ExternalObject<number>
export function createExternalString(content: string): ExternalObject<string>
export function getExternal(external: ExternalObject<number>): number
export function mutateExternal(external: ExternalObject<number>, newVal: number): void
export function validateArray(arr: Array<number>): number
export function validateBuffer(b: Buffer): number
export function validateTypedArray(input: Uint8Array): number
export function validateBigint(input: bigint): bigint
export function validateBoolean(i: boolean): boolean
export function validateDate(d: Date): number
export function validateDateTime(d: Date): number
export function validateExternal(e: ExternalObject<number>): number
export function validateFunction(cb: () => number): number
export function validateHashMap(input: Record<string, number>): number
export function validateNull(i: null): boolean
export function validateUndefined(i: undefined): boolean
export function validateNumber(i: number): number
export function validatePromise(p: Promise<number>): Promise<number>
export function validateString(s: string): string
export function validateSymbol(s: symbol): boolean
export function tsRename(a: { foo: number }): string[]
export function xxh64Alias(input: Buffer): bigint
export function getMapping(): Record<string, number>

View file

@ -0,0 +1,89 @@
use std::collections::HashMap;
use chrono::{DateTime, Utc};
use napi::{bindgen_prelude::*, JsSymbol, JsUnknown};
#[napi(strict)]
fn validate_array(arr: Vec<u32>) -> u32 {
arr.len() as u32
}
#[napi(strict)]
fn validate_buffer(b: Buffer) -> u32 {
b.len() as u32
}
#[napi(strict)]
fn validate_typed_array(input: Uint8Array) -> u32 {
input.len() as u32
}
#[napi(strict)]
fn validate_bigint(input: BigInt) -> i128 {
input.get_i128().0
}
#[napi(strict)]
fn validate_boolean(i: bool) -> bool {
!i
}
#[napi(strict)]
fn validate_date(d: Date) -> Result<f64> {
d.value_of()
}
#[napi(strict)]
fn validate_date_time(_d: DateTime<Utc>) -> i64 {
1
}
#[napi(strict)]
fn validate_external(e: External<u32>) -> u32 {
*e
}
#[napi(strict, ts_args_type = "cb: () => number")]
fn validate_function(cb: JsFunction) -> Result<u32> {
Ok(
cb.call::<JsUnknown>(None, &[])?
.coerce_to_number()?
.get_uint32()?
+ 3,
)
}
#[napi(strict)]
fn validate_hash_map(input: HashMap<String, u32>) -> u32 {
input.len() as u32
}
#[napi(strict)]
fn validate_null(_i: Null) -> bool {
true
}
#[napi(strict)]
fn validate_undefined(_i: Undefined) -> bool {
true
}
#[napi(strict)]
fn validate_number(i: f64) -> f64 {
i + 1.0
}
#[napi(strict)]
async fn validate_promise(p: Promise<u32>) -> Result<u32> {
Ok(p.await? + 1)
}
#[napi(strict)]
fn validate_string(s: String) -> String {
s + "!"
}
#[napi(strict)]
fn validate_symbol(_s: JsSymbol) -> bool {
true
}

View file

@ -23,6 +23,7 @@ mod either;
mod r#enum;
mod error;
mod external;
mod fn_strict;
mod fn_ts_override;
mod js_mod;
mod map;