From cc3086d804bdd8759349f6f61c9421272ce55323 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 6 Jul 2022 14:01:32 +0800 Subject: [PATCH] fix(napi): validate fn for Option --- crates/napi/src/bindgen_runtime/js_values.rs | 28 ++++++++++++++++++ .../napi/src/bindgen_runtime/js_values/nil.rs | 3 +- examples/napi/__test__/strict.spec.ts | 14 +++++++++ examples/napi/__test__/typegen.spec.ts.md | 1 + examples/napi/__test__/typegen.spec.ts.snap | Bin 3357 -> 3376 bytes examples/napi/index.d.ts | 1 + examples/napi/src/fn_strict.rs | 5 ++++ 7 files changed, 50 insertions(+), 2 deletions(-) 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 f47907357b3740f2a741f87d9c616383348a272b..ac458c4c9c95dad995d819688ce7c710894c1be0 100644 GIT binary patch delta 3339 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 V%k- delta 3320 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: 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