From 31fe10fa9d3a5f7ffbd34e8ecf7f34b65f8aa698 Mon Sep 17 00:00:00 2001 From: Semyon Date: Tue, 20 Sep 2022 18:52:08 +0300 Subject: [PATCH 1/3] test: add expect-type package and utilize it --- package.json | 1 + src/index.spec.ts | 22 ++++++++++++++++++---- yarn.lock | 5 +++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9d2150f..8f5cae8 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@types/node": "^12.6.2", "codecov": "^3.5.0", "cz-conventional-changelog": "^2.1.0", + "expect-type": "^0.14.2", "gh-pages": "^2.0.1", "mocha": "^6.1.4", "npm-run-all": "^4.1.5", diff --git a/src/index.spec.ts b/src/index.spec.ts index d8a18ab..f8cfff2 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -1,4 +1,5 @@ import assert from 'assert'; +import { expectTypeOf } from 'expect-type'; import { getValid, NumberParser, @@ -40,9 +41,9 @@ describe('index', () => { } // Check schema conformance - const answer: Schema = result; + expectTypeOf(result).toMatchTypeOf(); - deepEqual(answer, { a: 1, b: null, c: 'foo' }); + deepEqual(result, { a: 1, b: null, c: 'foo' }); deepEqual(errors, []); }); @@ -125,9 +126,22 @@ describe('index', () => { } // Check schema conformance - const answer: Schema = result; + expectTypeOf(result).toMatchTypeOf(); + expectTypeOf(result).not.toMatchTypeOf< + Schema & { + readonly tuple: readonly [unknown, unknown]; + } + >(); + expectTypeOf(result).not.toMatchTypeOf< + Schema & { + readonly objectArray: readonly { + readonly a: number; + readonly b: boolean; + }[]; + } + >(); - deepEqual(answer, { + deepEqual(result, { num: 1, tuple: [1, 'thing', undefined], object: { a: 1, b: 'foo' }, diff --git a/yarn.lock b/yarn.lock index 052b31a..d5b1e86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -957,6 +957,11 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +expect-type@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-0.14.2.tgz#3924d0e596455a9b27af48e8a99c582cdd4506eb" + integrity sha512-ed3+tr5ujbIYXZ8Pl/VgIphwJQ0q5tBLGGdn7Zvwt1WyPBRX83xjT5pT77P/GkuQbctx0K2ZNSSan7eruJqTCQ== + figures@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.0.0.tgz#756275c964646163cc6f9197c7a0295dbfd04de9" From 8fb47a514dc0658c22b2fefc725de7d3083c7619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semy=C3=B3n?= Date: Mon, 13 Feb 2023 02:04:14 +0100 Subject: [PATCH 2/3] fix: add missing implementation for StringParser's "allowNumeric" option --- src/parsers/string.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/parsers/string.ts b/src/parsers/string.ts index 5a9c54e..08d6538 100644 --- a/src/parsers/string.ts +++ b/src/parsers/string.ts @@ -27,15 +27,20 @@ export const StringParser = ( } if (typeof inp.value !== 'string') { - return { - value: ValidationFail, - errors: [ - { - path: inp.path, - message: `Value "${inp.value}" is not a string` - } - ] - }; + if (typeof inp.value === 'number' && options && options.allowNumeric) { + // tslint:disable-next-line: no-parameter-reassignment + inp = { value: inp.value.toString(), path: inp.path }; + } else { + return { + value: ValidationFail, + errors: [ + { + path: inp.path, + message: `Value "${inp.value}" is not a string` + } + ] + }; + } } const errors: ValidationError[] = []; From 41c396785c5eac054e69b0d3cc2128aea4379baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Semy=C3=B3n?= Date: Mon, 13 Feb 2023 02:04:14 +0100 Subject: [PATCH 3/3] feat: add flexibility to NumberParser's logic --- src/parsers/number.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/parsers/number.ts b/src/parsers/number.ts index cd8397b..587de70 100644 --- a/src/parsers/number.ts +++ b/src/parsers/number.ts @@ -9,13 +9,20 @@ import { interface NumberOptions extends StandardOptions { readonly allowNumeric?: boolean; + readonly allowTypeCoercion?: boolean; } export const NumberParser = ( - options?: TOptions + optionsParameter?: TOptions ) => ( inp: ParserInput ): ParserResult> => { + const options = { + allowTypeCoercion: true, + ...optionsParameter + // tslint:disable-next-line: no-object-literal-type-assertion + } as TOptions; + const emptyResult = checkEmpty(inp, options); if (emptyResult) { @@ -51,14 +58,14 @@ const handleNonNumber = ( options?: NumberOptions ): ParserResult | null => { if (typeof inp.value === 'string' && options && options.allowNumeric) { - if (inp.value === '' && options.optional) { + if (inp.value === '' && options.optional && options.allowTypeCoercion) { return { value: undefined, errors: [] }; } - if (inp.value === 'null' && options.nullable) { + if (inp.value === 'null' && options.nullable && options.allowTypeCoercion) { return { value: null, errors: []