diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 50f1e74a..25ea9d98 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -74,6 +74,8 @@ impl TypeName for JsUnknown { } } +impl ValidateNapiValue for JsUnknown {} + impl ToNapiValue for T { unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result { Ok(unsafe { NapiRaw::raw(&val) }) @@ -158,6 +160,32 @@ impl TypeName for Option { } } +impl ValidateNapiValue for Option { + unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut result = -1; + check_status!( + unsafe { sys::napi_typeof(env, napi_val, &mut result) }, + "Failed to detect napi value type", + )?; + + let received_type = ValueType::from(result); + if received_type == ValueType::Null { + Ok(ptr::null_mut()) + } else if let Ok(validate_ret) = unsafe { T::validate(env, napi_val) } { + Ok(validate_ret) + } else { + Err(Error::new( + Status::InvalidArg, + format!( + "Expect value to be Option<{}>, but received {}", + T::value_type(), + received_type + ), + )) + } + } +} + impl FromNapiValue for Option where T: FromNapiValue, diff --git a/crates/napi/src/bindgen_runtime/js_values/nil.rs b/crates/napi/src/bindgen_runtime/js_values/nil.rs index 91b5a736..325cd7a2 100644 --- a/crates/napi/src/bindgen_runtime/js_values/nil.rs +++ b/crates/napi/src/bindgen_runtime/js_values/nil.rs @@ -56,7 +56,6 @@ impl ValidateNapiValue for Undefined {} impl FromNapiValue for Undefined { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { - // TODO: with typecheck match type_of!(env, napi_val) { Ok(ValueType::Undefined) => Ok(()), _ => Err(Error::new( @@ -73,7 +72,7 @@ impl ToNapiValue for Undefined { check_status!( unsafe { sys::napi_get_undefined(env, &mut ret) }, - "Failed to create napi null value" + "Failed to create napi undefined value" )?; Ok(ret) diff --git a/examples/napi/__test__/strict.spec.ts b/examples/napi/__test__/strict.spec.ts index 62b98e86..4b03aa95 100644 --- a/examples/napi/__test__/strict.spec.ts +++ b/examples/napi/__test__/strict.spec.ts @@ -19,6 +19,7 @@ import { validateUndefined, returnUndefinedIfInvalid, returnUndefinedIfInvalidPromise, + validateOptional, } from '../index' test('should validate array', (t) => { @@ -180,3 +181,16 @@ test('should return Promise.reject() if arg is not Promise', async (t) => { // @ts-expect-error await t.throwsAsync(() => returnUndefinedIfInvalidPromise(1)) }) + +test('should validate Option', (t) => { + t.is(validateOptional(null, null), false) + t.is(validateOptional(null, false), false) + t.is(validateOptional('1', false), true) + t.is(validateOptional(null, true), true) + // @ts-expect-error + t.throws(() => validateOptional(1, null)) + // @ts-expect-error + t.throws(() => validateOptional(null, 2)) + // @ts-expect-error + t.throws(() => validateOptional(1, 2)) +}) diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 91b27604..c75e36cf 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 validatePromise(p: Promise): Promise␊ export function validateString(s: string): string␊ export function validateSymbol(s: symbol): boolean␊ + export function validateOptional(input1?: string | undefined | null, input2?: boolean | undefined | null): boolean␊ export function returnUndefinedIfInvalid(input: boolean): boolean␊ export function returnUndefinedIfInvalidPromise(input: Promise): Promise␊ export function tsRename(a: { foo: number }): string[]␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index f4790735..ac458c4c 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index c6a3ae9d..49f05a7d 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -100,6 +100,7 @@ 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 validateOptional(input1?: string | undefined | null, input2?: boolean | undefined | null): boolean export function returnUndefinedIfInvalid(input: boolean): boolean export function returnUndefinedIfInvalidPromise(input: Promise): Promise export function tsRename(a: { foo: number }): string[] diff --git a/examples/napi/src/fn_strict.rs b/examples/napi/src/fn_strict.rs index 5ce758a4..0d4de886 100644 --- a/examples/napi/src/fn_strict.rs +++ b/examples/napi/src/fn_strict.rs @@ -88,6 +88,11 @@ fn validate_symbol(_s: JsSymbol) -> bool { true } +#[napi(strict)] +fn validate_optional(input1: Option, input2: Option) -> bool { + input1.is_some() || input2.unwrap_or(false) +} + #[napi(return_if_invalid)] fn return_undefined_if_invalid(input: bool) -> bool { !input