From 18afd86a2e05b2481250710130948270d4e8f5f0 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Sat, 5 Mar 2022 23:19:51 +0800 Subject: [PATCH] fix(napi): missing ValidateNapiValue trait for types --- crates/backend/src/codegen/enum.rs | 5 +- crates/backend/src/codegen/fn.rs | 5 +- crates/napi/src/bindgen_runtime/js_values.rs | 9 +- .../src/bindgen_runtime/js_values/array.rs | 18 +- .../bindgen_runtime/js_values/arraybuffer.rs | 24 ++- .../src/bindgen_runtime/js_values/bigint.rs | 8 +- .../src/bindgen_runtime/js_values/buffer.rs | 15 +- .../src/bindgen_runtime/js_values/date.rs | 21 ++- .../src/bindgen_runtime/js_values/external.rs | 18 +- .../src/bindgen_runtime/js_values/function.rs | 8 + .../napi/src/bindgen_runtime/js_values/map.rs | 6 + .../src/bindgen_runtime/js_values/object.rs | 6 + .../src/bindgen_runtime/js_values/promise.rs | 69 ++++++- .../src/bindgen_runtime/js_values/string.rs | 24 +++ .../src/bindgen_runtime/js_values/symbol.rs | 27 ++- crates/napi/src/js_values/date.rs | 22 ++- crates/napi/src/js_values/mod.rs | 9 +- examples/napi/__test__/strict.spect.ts | 168 ++++++++++++++++++ examples/napi/__test__/typegen.spec.ts.md | 16 ++ examples/napi/__test__/typegen.spec.ts.snap | Bin 2702 -> 2841 bytes examples/napi/index.d.ts | 16 ++ examples/napi/src/fn_strict.rs | 89 ++++++++++ examples/napi/src/lib.rs | 1 + 23 files changed, 563 insertions(+), 21 deletions(-) create mode 100644 examples/napi/__test__/strict.spect.ts create mode 100644 examples/napi/src/fn_strict.rs diff --git a/crates/backend/src/codegen/enum.rs b/crates/backend/src/codegen/enum.rs index 57aa2515..a69e299a 100644 --- a/crates/backend/src/codegen/enum.rs +++ b/crates/backend/src/codegen/enum.rs @@ -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::bindgen_prelude::assert_type_of!(env, napi_val, napi::bindgen_prelude::ValueType::Number)?; + Ok(std::ptr::null_mut()) } } diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 1a58b472..30d4178b 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -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! {} diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index a835621b..579e12bf 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -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 { 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, diff --git a/crates/napi/src/bindgen_runtime/js_values/array.rs b/crates/napi/src/bindgen_runtime/js_values/array.rs index 2b6c2137..30d19d44 100644 --- a/crates/napi/src/bindgen_runtime/js_values/array.rs +++ b/crates/napi/src/bindgen_runtime/js_values/array.rs @@ -257,7 +257,21 @@ impl ValidateNapiValue for Vec where T: FromNapiValue, { - fn type_of() -> Vec { - vec![ValueType::Object] + unsafe fn validate( + env: sys::napi_env, + napi_val: sys::napi_value, + ) -> Result { + 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()) } } diff --git a/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs b/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs index 4f8eb901..ce092d31 100644 --- a/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs @@ -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, diff --git a/crates/napi/src/bindgen_runtime/js_values/bigint.rs b/crates/napi/src/bindgen_runtime/js_values/bigint.rs index 8a87764e..3149c2e9 100644 --- a/crates/napi/src/bindgen_runtime/js_values/bigint.rs +++ b/crates/napi/src/bindgen_runtime/js_values/bigint.rs @@ -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 { + vec![crate::ValueType::BigInt] + } +} + impl FromNapiValue for BigInt { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result { let mut word_count = 0usize; diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs index db5233cd..c54347b3 100644 --- a/crates/napi/src/bindgen_runtime/js_values/buffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -140,7 +140,18 @@ impl ToNapiValue for Buffer { } impl ValidateNapiValue for Buffer { - fn type_of() -> Vec { - vec![ValueType::Object] + unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + 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()) } } diff --git a/crates/napi/src/bindgen_runtime/js_values/date.rs b/crates/napi/src/bindgen_runtime/js_values/date.rs index 8188fcb6..a8b77994 100644 --- a/crates/napi/src/bindgen_runtime/js_values/date.rs +++ b/crates/napi/src/bindgen_runtime/js_values/date.rs @@ -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 { fn type_name() -> &'static str { "DateTime" @@ -12,8 +15,20 @@ impl TypeName for DateTime { } impl ValidateNapiValue for DateTime { - fn type_of() -> Vec { - vec![ValueType::Object] + unsafe fn validate( + env: sys::napi_env, + napi_val: sys::napi_value, + ) -> Result { + 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()) } } diff --git a/crates/napi/src/bindgen_runtime/js_values/external.rs b/crates/napi/src/bindgen_runtime/js_values/external.rs index 4848cd91..eb0fb33e 100644 --- a/crates/napi/src/bindgen_runtime/js_values/external.rs +++ b/crates/napi/src/bindgen_runtime/js_values/external.rs @@ -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 { obj: *mut TaggedObject, @@ -13,6 +13,22 @@ pub struct External { pub adjusted_size: i64, } +impl TypeName for External { + fn type_name() -> &'static str { + "External" + } + + fn value_type() -> crate::ValueType { + crate::ValueType::External + } +} + +impl ValidateNapiValue for External { + fn type_of() -> Vec { + vec![crate::ValueType::External] + } +} + impl External { pub fn new(value: T) -> Self { Self { diff --git a/crates/napi/src/bindgen_runtime/js_values/function.rs b/crates/napi/src/bindgen_runtime/js_values/function.rs index 17f75ff0..208251d5 100644 --- a/crates/napi/src/bindgen_runtime/js_values/function.rs +++ b/crates/napi/src/bindgen_runtime/js_values/function.rs @@ -1 +1,9 @@ +use super::ValidateNapiValue; + pub use crate::JsFunction; + +impl ValidateNapiValue for JsFunction { + fn type_of() -> Vec { + vec![crate::ValueType::Function] + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/map.rs b/crates/napi/src/bindgen_runtime/js_values/map.rs index c78e361c..91f6e8ea 100644 --- a/crates/napi/src/bindgen_runtime/js_values/map.rs +++ b/crates/napi/src/bindgen_runtime/js_values/map.rs @@ -13,6 +13,12 @@ impl TypeName for HashMap { } } +impl + Eq + Hash, V: FromNapiValue> ValidateNapiValue for HashMap { + fn type_of() -> Vec { + vec![crate::ValueType::Object] + } +} + impl ToNapiValue for HashMap where K: AsRef, diff --git a/crates/napi/src/bindgen_runtime/js_values/object.rs b/crates/napi/src/bindgen_runtime/js_values/object.rs index 04870e59..512ff5ae 100644 --- a/crates/napi/src/bindgen_runtime/js_values/object.rs +++ b/crates/napi/src/bindgen_runtime/js_values/object.rs @@ -88,3 +88,9 @@ impl TypeName for Object { ValueType::Object } } + +impl ValidateNapiValue for Object { + fn type_of() -> Vec { + vec![ValueType::Object] + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/promise.rs b/crates/napi/src/bindgen_runtime/js_values/promise.rs index 17bfcc4b..a843a536 100644 --- a/crates/napi/src/bindgen_runtime/js_values/promise.rs +++ b/crates/napi/src/bindgen_runtime/js_values/promise.rs @@ -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 { value: Pin>>>, } +impl TypeName for Promise { + fn type_name() -> &'static str { + "Promise" + } + + fn value_type() -> crate::ValueType { + crate::ValueType::Object + } +} + +impl ValidateNapiValue for Promise { + fn type_of() -> Vec { + vec![crate::ValueType::Object] + } + + unsafe fn validate( + env: crate::sys::napi_env, + napi_val: crate::sys::napi_value, + ) -> Result { + 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 Send for Promise {} impl FromNapiValue for Promise { diff --git a/crates/napi/src/bindgen_runtime/js_values/string.rs b/crates/napi/src/bindgen_runtime/js_values/string.rs index 2af44beb..4ab329ac 100644 --- a/crates/napi/src/bindgen_runtime/js_values/string.rs +++ b/crates/napi/src/bindgen_runtime/js_values/string.rs @@ -16,6 +16,12 @@ impl TypeName for String { } } +impl ValidateNapiValue for String { + fn type_of() -> Vec { + vec![ValueType::String] + } +} + impl ToNapiValue for String { unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { let mut ptr = ptr::null_mut(); @@ -75,6 +81,12 @@ impl TypeName for &str { } } +impl ValidateNapiValue for &str { + fn type_of() -> Vec { + vec![ValueType::String] + } +} + impl FromNapiValue for &str { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { 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 { + vec![ValueType::String] + } +} + impl From 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 { + vec![ValueType::String] + } + } + impl From for Latin1String { fn from(s: String) -> Self { Latin1String(s) diff --git a/crates/napi/src/bindgen_runtime/js_values/symbol.rs b/crates/napi/src/bindgen_runtime/js_values/symbol.rs index 181f6f9c..9cba30ae 100644 --- a/crates/napi/src/bindgen_runtime/js_values/symbol.rs +++ b/crates/napi/src/bindgen_runtime/js_values/symbol.rs @@ -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, } +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 { + 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 { + Ok(Self { desc: None }) + } +} diff --git a/crates/napi/src/js_values/date.rs b/crates/napi/src/js_values/date.rs index 2b6946c8..6856b1af 100644 --- a/crates/napi/src/js_values/date.rs +++ b/crates/napi/src/js_values/date.rs @@ -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 { + 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 { let mut timestamp: f64 = 0.0; diff --git a/crates/napi/src/js_values/mod.rs b/crates/napi/src/js_values/mod.rs index 0cb7ae21..0787e541 100644 --- a/crates/napi/src/js_values/mod.rs +++ b/crates/napi/src/js_values/mod.rs @@ -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 { + vec![ValueType::Symbol] + } +} + pub struct JsExternal(pub(crate) Value); impl TypeName for JsExternal { diff --git a/examples/napi/__test__/strict.spect.ts b/examples/napi/__test__/strict.spect.ts new file mode 100644 index 00000000..d469ae2a --- /dev/null +++ b/examples/napi/__test__/strict.spect.ts @@ -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', + }) +}) diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index f7f6738f..922e7ee0 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -82,6 +82,22 @@ Generated by [AVA](https://avajs.dev). export function createExternalString(content: string): ExternalObject␊ export function getExternal(external: ExternalObject): number␊ export function mutateExternal(external: ExternalObject, newVal: number): void␊ + export function validateArray(arr: Array): 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␊ + export function validateFunction(cb: () => number): number␊ + export function validateHashMap(input: Record): number␊ + export function validateNull(i: null): boolean␊ + export function validateUndefined(i: undefined): boolean␊ + export function validateNumber(i: number): number␊ + export function validatePromise(p: Promise): Promise␊ + 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␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 39d0b36c51ab03d6d4ad56ba94c85075245ed116..d1341ed1309a1b13d53cb27c92fe7ee86f8fe4f0 100644 GIT binary patch literal 2841 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 IuMr~v0B0RJYXATM diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 9a636c1b..b17d1384 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -72,6 +72,22 @@ export function createExternal(size: number): ExternalObject export function createExternalString(content: string): ExternalObject export function getExternal(external: ExternalObject): number export function mutateExternal(external: ExternalObject, newVal: number): void +export function validateArray(arr: Array): 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 +export function validateFunction(cb: () => number): number +export function validateHashMap(input: Record): number +export function validateNull(i: null): boolean +export function validateUndefined(i: undefined): boolean +export function validateNumber(i: number): number +export function validatePromise(p: Promise): Promise +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 diff --git a/examples/napi/src/fn_strict.rs b/examples/napi/src/fn_strict.rs new file mode 100644 index 00000000..2b2a9cf0 --- /dev/null +++ b/examples/napi/src/fn_strict.rs @@ -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 { + 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 { + d.value_of() +} + +#[napi(strict)] +fn validate_date_time(_d: DateTime) -> i64 { + 1 +} + +#[napi(strict)] +fn validate_external(e: External) -> u32 { + *e +} + +#[napi(strict, ts_args_type = "cb: () => number")] +fn validate_function(cb: JsFunction) -> Result { + Ok( + cb.call::(None, &[])? + .coerce_to_number()? + .get_uint32()? + + 3, + ) +} + +#[napi(strict)] +fn validate_hash_map(input: HashMap) -> 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) -> Result { + Ok(p.await? + 1) +} + +#[napi(strict)] +fn validate_string(s: String) -> String { + s + "!" +} + +#[napi(strict)] +fn validate_symbol(_s: JsSymbol) -> bool { + true +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index 98005952..b6baf3e7 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -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;