From 934dc9a4ae3bde14825465b30e3cc4db1e6de754 Mon Sep 17 00:00:00 2001 From: sinclairzx81 Date: Sat, 5 Aug 2023 17:31:12 +0900 Subject: [PATCH] Send Kind To TypeRegistry Function (#522) --- package-lock.json | 4 +- package.json | 2 +- src/compiler/compiler.ts | 11 ++- test/runtime/compiler/custom.ts | 26 ------- test/runtime/compiler/index.ts | 2 +- test/runtime/compiler/kind.ts | 72 +++++++++++++++++++ test/runtime/type/guard/index.ts | 1 + test/runtime/type/guard/kind.ts | 13 ++++ test/runtime/value/cast/index.ts | 2 +- .../runtime/value/cast/{custom.ts => kind.ts} | 18 ++--- test/runtime/value/check/custom.ts | 29 -------- test/runtime/value/check/index.ts | 2 +- test/runtime/value/check/kind.ts | 70 ++++++++++++++++++ test/runtime/value/convert/custom.ts | 24 ------- test/runtime/value/convert/index.ts | 2 +- test/runtime/value/convert/kind.ts | 39 ++++++++++ test/runtime/value/create/custom.ts | 17 ----- test/runtime/value/create/index.ts | 2 +- test/runtime/value/create/kind.ts | 23 ++++++ 19 files changed, 244 insertions(+), 115 deletions(-) delete mode 100644 test/runtime/compiler/custom.ts create mode 100644 test/runtime/compiler/kind.ts create mode 100644 test/runtime/type/guard/kind.ts rename test/runtime/value/cast/{custom.ts => kind.ts} (73%) delete mode 100644 test/runtime/value/check/custom.ts create mode 100644 test/runtime/value/check/kind.ts delete mode 100644 test/runtime/value/convert/custom.ts create mode 100644 test/runtime/value/convert/kind.ts delete mode 100644 test/runtime/value/create/custom.ts create mode 100644 test/runtime/value/create/kind.ts diff --git a/package-lock.json b/package-lock.json index 3c774d74c..e71d5adf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.30.2", + "version": "0.30.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.30.2", + "version": "0.30.3", "license": "MIT", "devDependencies": { "@sinclair/hammer": "^0.17.1", diff --git a/package.json b/package.json index e2e0cb959..bee3ce854 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.30.2", + "version": "0.30.3", "description": "JSONSchema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 9509e0d29..c0bac349e 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -375,7 +375,9 @@ export namespace TypeCompiler { yield IsVoidCheck(value) } function* TKind(schema: Types.TSchema, references: Types.TSchema[], value: string): IterableIterator { - yield `kind('${schema[Types.Kind]}', ${value})` + const instance = state.instances.size + state.instances.set(instance, schema) + yield `kind('${schema[Types.Kind]}', ${instance}, ${value})` } function* Visit(schema: T, references: Types.TSchema[], value: string, useHoisting: boolean = true): IterableIterator { const references_ = ValueGuard.IsString(schema.$id) ? [...references, schema] : references @@ -470,6 +472,7 @@ export namespace TypeCompiler { language: 'javascript', // target language functions: new Map(), // local functions variables: new Map(), // local variables + instances: new Map() // exterior kind instances } // ------------------------------------------------------------------- // Compiler Factory @@ -533,6 +536,7 @@ export namespace TypeCompiler { state.language = options.language state.variables.clear() state.functions.clear() + state.instances.clear() if (!Types.TypeGuard.TSchema(schema)) throw new TypeCompilerTypeGuardError(schema) for (const schema of references) if (!Types.TypeGuard.TSchema(schema)) throw new TypeCompilerTypeGuardError(schema) return Build(schema, references, options) @@ -541,8 +545,9 @@ export namespace TypeCompiler { export function Compile(schema: T, references: Types.TSchema[] = []): TypeCheck { const generatedCode = Code(schema, references, { language: 'javascript' }) const compiledFunction = globalThis.Function('kind', 'format', 'hash', generatedCode) - function typeRegistryFunction(kind: string, value: unknown) { - if (!Types.TypeRegistry.Has(kind)) return false + function typeRegistryFunction(kind: string, instance: number, value: unknown) { + if (!Types.TypeRegistry.Has(kind) || !state.instances.has(instance)) return false + const schema = state.instances.get(instance) const checkFunc = Types.TypeRegistry.Get(kind)! return checkFunc(schema, value) } diff --git a/test/runtime/compiler/custom.ts b/test/runtime/compiler/custom.ts deleted file mode 100644 index 4380a9e82..000000000 --- a/test/runtime/compiler/custom.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Type, Kind, TypeRegistry } from '@sinclair/typebox' -import { Ok, Fail } from './validate' - -describe('type/compiler/Custom', () => { - TypeRegistry.Set('BigInt', (schema, value) => typeof value === 'bigint') - it('Should validate bigint', () => { - const T = Type.Unsafe({ [Kind]: 'BigInt' }) - Ok(T, 1n) - }) - it('Should not validate bigint', () => { - const T = Type.Unsafe({ [Kind]: 'BigInt' }) - Fail(T, 1) - }) - it('Should validate bigint nested', () => { - const T = Type.Object({ - x: Type.Unsafe({ [Kind]: 'BigInt' }), - }) - Ok(T, { x: 1n }) - }) - it('Should not validate bigint nested', () => { - const T = Type.Object({ - x: Type.Unsafe({ [Kind]: 'BigInt' }), - }) - Fail(T, { x: 1 }) - }) -}) diff --git a/test/runtime/compiler/index.ts b/test/runtime/compiler/index.ts index d0120684b..e47c1ce9b 100644 --- a/test/runtime/compiler/index.ts +++ b/test/runtime/compiler/index.ts @@ -4,7 +4,6 @@ import './async-iterator' import './bigint' import './boolean' import './composite' -import './custom' import './date' import './unicode' import './enum' @@ -12,6 +11,7 @@ import './integer' import './intersect' import './iterator' import './keyof' +import './kind' import './literal' import './modifier' import './never' diff --git a/test/runtime/compiler/kind.ts b/test/runtime/compiler/kind.ts new file mode 100644 index 000000000..972c5def2 --- /dev/null +++ b/test/runtime/compiler/kind.ts @@ -0,0 +1,72 @@ +import { TypeRegistry, Type, Kind, TSchema } from '@sinclair/typebox' +import { TypeCompiler } from '@sinclair/typebox/compiler' +import { Ok, Fail } from './validate' +import { Assert } from '../assert' + +describe('type/compiler/Kind', () => { + // ------------------------------------------------------------ + // Fixtures + // ------------------------------------------------------------ + beforeEach(() => TypeRegistry.Set('PI', (_, value) => value === Math.PI)) + afterEach(() => TypeRegistry.Delete('PI')) + // ------------------------------------------------------------ + // Tests + // ------------------------------------------------------------ + it('Should validate', () => { + const T = Type.Unsafe({ [Kind]: 'PI' }) + Ok(T, Math.PI) + }) + it('Should not validate', () => { + const T = Type.Unsafe({ [Kind]: 'PI' }) + Fail(T, Math.PI * 2) + }) + it('Should validate in object', () => { + const T = Type.Object({ + x: Type.Unsafe({ [Kind]: 'PI' }), + }) + Ok(T, { x: Math.PI }) + }) + it('Should not validate in object', () => { + const T = Type.Object({ + x: Type.Unsafe({ [Kind]: 'PI' }), + }) + Fail(T, { x: Math.PI * 2 }) + }) + it('Should validate in array', () => { + const T = Type.Array(Type.Unsafe({ [Kind]: 'PI' })) + Ok(T, [Math.PI]) + }) + it('Should not validate in array', () => { + const T = Type.Array(Type.Unsafe({ [Kind]: 'PI' })) + Fail(T, [Math.PI * 2]) + }) + it('Should validate in tuple', () => { + const T = Type.Tuple([Type.Unsafe({ [Kind]: 'PI' })]) + Ok(T, [Math.PI]) + }) + it('Should not validate in tuple', () => { + const T = Type.Tuple([Type.Unsafe({ [Kind]: 'PI' })]) + Fail(T, [Math.PI * 2]) + }) + // ------------------------------------------------------------ + // Instances + // ------------------------------------------------------------ + it('Should receive kind instance on registry callback', () => { + const stack: string[] = [] + TypeRegistry.Set('Kind', (schema: unknown) => { + // prettier-ignore + return (typeof schema === 'object' && schema !== null && Kind in schema && schema[Kind] === 'Kind' && '$id' in schema && typeof schema.$id === 'string') + ? (() => { stack.push(schema.$id); return true })() + : false + }) + const A = { [Kind]: 'Kind', $id: 'A' } as TSchema + const B = { [Kind]: 'Kind', $id: 'B' } as TSchema + const T = Type.Object({ a: A, b: B }) + const C = TypeCompiler.Compile(T) + const R = C.Check({ a: null, b: null }) + Assert.IsTrue(R) + Assert.IsEqual(stack[0], 'A') + Assert.IsEqual(stack[1], 'B') + TypeRegistry.Delete('Kind') + }) +}) diff --git a/test/runtime/type/guard/index.ts b/test/runtime/type/guard/index.ts index 33ee2fb61..c1cb4c70d 100644 --- a/test/runtime/type/guard/index.ts +++ b/test/runtime/type/guard/index.ts @@ -16,6 +16,7 @@ import './integer' import './intersect' import './iterator' import './keyof' +import './kind' import './literal' import './lowercase' import './not' diff --git a/test/runtime/type/guard/kind.ts b/test/runtime/type/guard/kind.ts new file mode 100644 index 000000000..52c87f883 --- /dev/null +++ b/test/runtime/type/guard/kind.ts @@ -0,0 +1,13 @@ +import { TypeGuard, Kind } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/guard/TKind', () => { + it('Should guard 1', () => { + const T = { [Kind]: 'Kind' } + Assert.IsTrue(TypeGuard.TKind(T)) + }) + it('Should guard 2', () => { + const T = {} + Assert.IsFalse(TypeGuard.TKind(T)) + }) +}) diff --git a/test/runtime/value/cast/index.ts b/test/runtime/value/cast/index.ts index be2225807..911bd9f17 100644 --- a/test/runtime/value/cast/index.ts +++ b/test/runtime/value/cast/index.ts @@ -4,13 +4,13 @@ import './async-iterator' import './bigint' import './boolean' import './composite' -import './custom' import './date' import './enum' import './integer' import './intersect' import './iterator' import './keyof' +import './kind' import './literal' import './never' import './not' diff --git a/test/runtime/value/cast/custom.ts b/test/runtime/value/cast/kind.ts similarity index 73% rename from test/runtime/value/cast/custom.ts rename to test/runtime/value/cast/kind.ts index 3bfb8959f..42cffc618 100644 --- a/test/runtime/value/cast/custom.ts +++ b/test/runtime/value/cast/kind.ts @@ -2,14 +2,16 @@ import { Value } from '@sinclair/typebox/value' import { Type, Kind, TypeRegistry } from '@sinclair/typebox' import { Assert } from '../../assert/index' -describe('value/cast/Custom', () => { - before(() => { - TypeRegistry.Set('CustomCast', (schema, value) => value === 'hello' || value === 'world') - }) - after(() => { - TypeRegistry.Clear() - }) - const T = Type.Unsafe({ [Kind]: 'CustomCast', default: 'hello' }) +describe('value/cast/Kind', () => { + // --------------------------------------------------------- + // Fixtures + // --------------------------------------------------------- + before(() => TypeRegistry.Set('Kind', (schema, value) => value === 'hello' || value === 'world')) + after(() => TypeRegistry.Clear()) + // --------------------------------------------------------- + // Tests + // --------------------------------------------------------- + const T = Type.Unsafe({ [Kind]: 'Kind', default: 'hello' }) const E = 'hello' it('Should upcast from string', () => { const value = 'hello' diff --git a/test/runtime/value/check/custom.ts b/test/runtime/value/check/custom.ts deleted file mode 100644 index 3072822f9..000000000 --- a/test/runtime/value/check/custom.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Value } from '@sinclair/typebox/value' -import { Type, Kind, TypeRegistry } from '@sinclair/typebox' -import { Assert } from '../../assert/index' - -describe('type/check/Custom', () => { - const FooBar = Type.Unsafe({ [Kind]: 'FooBar' }) - before(() => { - TypeRegistry.Set('FooBar', (schema, value) => value === 'foobar') - }) - after(() => { - TypeRegistry.Delete('FooBar') - }) - it('Should validate foobar', () => { - Assert.IsEqual(Value.Check(FooBar, 'foobar'), true) - }) - it('Should not validate foobar', () => { - Assert.IsEqual(Value.Check(FooBar, 1), false) - }) - it('Should validate foobar nested', () => { - // prettier-ignore - const T = Type.Object({ x: FooBar }) - Assert.IsEqual(Value.Check(T, { x: 'foobar' }), true) - }) - it('Should not validate foobar nested', () => { - // prettier-ignore - const T = Type.Object({ x: FooBar }) - Assert.IsEqual(Value.Check(T, { x: 1 }), false) - }) -}) diff --git a/test/runtime/value/check/index.ts b/test/runtime/value/check/index.ts index 9dac82003..a55d53a0b 100644 --- a/test/runtime/value/check/index.ts +++ b/test/runtime/value/check/index.ts @@ -4,13 +4,13 @@ import './async-iterator' import './bigint' import './boolean' import './composite' -import './custom' import './date' import './enum' import './integer' import './intersect' import './iterator' import './keyof' +import './kind' import './literal' import './never' import './not' diff --git a/test/runtime/value/check/kind.ts b/test/runtime/value/check/kind.ts new file mode 100644 index 000000000..91ad48a15 --- /dev/null +++ b/test/runtime/value/check/kind.ts @@ -0,0 +1,70 @@ +import { Value } from '@sinclair/typebox/value' +import { TypeRegistry, Type, Kind, TSchema } from '@sinclair/typebox' +import { Assert } from '../../assert' + +describe('value/check/Kind', () => { + // ------------------------------------------------------------ + // Fixtures + // ------------------------------------------------------------ + beforeEach(() => TypeRegistry.Set('PI', (_, value) => value === Math.PI)) + afterEach(() => TypeRegistry.Delete('PI')) + // ------------------------------------------------------------ + // Tests + // ------------------------------------------------------------ + it('Should validate', () => { + const T = Type.Unsafe({ [Kind]: 'PI' }) + Assert.IsTrue(Value.Check(T, Math.PI)) + }) + it('Should not validate', () => { + const T = Type.Unsafe({ [Kind]: 'PI' }) + Assert.IsFalse(Value.Check(T, Math.PI * 2)) + }) + it('Should validate in object', () => { + const T = Type.Object({ + x: Type.Unsafe({ [Kind]: 'PI' }), + }) + Assert.IsTrue(Value.Check(T, { x: Math.PI })) + }) + it('Should not validate in object', () => { + const T = Type.Object({ + x: Type.Unsafe({ [Kind]: 'PI' }), + }) + Assert.IsFalse(Value.Check(T, { x: Math.PI * 2 })) + }) + it('Should validate in array', () => { + const T = Type.Array(Type.Unsafe({ [Kind]: 'PI' })) + Assert.IsTrue(Value.Check(T, [Math.PI])) + }) + it('Should not validate in array', () => { + const T = Type.Array(Type.Unsafe({ [Kind]: 'PI' })) + Assert.IsFalse(Value.Check(T, [Math.PI * 2])) + }) + it('Should validate in tuple', () => { + const T = Type.Tuple([Type.Unsafe({ [Kind]: 'PI' })]) + Assert.IsTrue(Value.Check(T, [Math.PI])) + }) + it('Should not validate in tuple', () => { + const T = Type.Tuple([Type.Unsafe({ [Kind]: 'PI' })]) + Assert.IsFalse(Value.Check(T, [Math.PI * 2])) + }) + // ------------------------------------------------------------ + // Instances + // ------------------------------------------------------------ + it('Should receive kind instance on registry callback', () => { + const stack: string[] = [] + TypeRegistry.Set('Kind', (schema: unknown) => { + // prettier-ignore + return (typeof schema === 'object' && schema !== null && Kind in schema && schema[Kind] === 'Kind' && '$id' in schema && typeof schema.$id === 'string') + ? (() => { stack.push(schema.$id); return true })() + : false + }) + const A = { [Kind]: 'Kind', $id: 'A' } as TSchema + const B = { [Kind]: 'Kind', $id: 'B' } as TSchema + const T = Type.Object({ a: A, b: B }) + const R = Value.Check(T, { a: null, b: null }) + Assert.IsTrue(R) + Assert.IsEqual(stack[0], 'A') + Assert.IsEqual(stack[1], 'B') + TypeRegistry.Delete('Kind') + }) +}) diff --git a/test/runtime/value/convert/custom.ts b/test/runtime/value/convert/custom.ts deleted file mode 100644 index e284fbe82..000000000 --- a/test/runtime/value/convert/custom.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Value } from '@sinclair/typebox/value' -import { TypeSystem } from '@sinclair/typebox/system' -import { Assert } from '../../assert/index' - -describe('value/convert/Custom', () => { - it('Should not convert 1', () => { - const Custom = TypeSystem.Type('type/convert/Custom/1', () => true) - const T = Custom() - const R = Value.Convert(T, true) - Assert.IsEqual(R, true) - }) - it('Should not convert 2', () => { - const Custom = TypeSystem.Type('type/convert/Custom/2', () => true) - const T = Custom() - const R = Value.Convert(T, 42) - Assert.IsEqual(R, 42) - }) - it('Should not convert 3', () => { - const Custom = TypeSystem.Type('type/convert/Custom/3', () => true) - const T = Custom() - const R = Value.Convert(T, 'hello') - Assert.IsEqual(R, 'hello') - }) -}) diff --git a/test/runtime/value/convert/index.ts b/test/runtime/value/convert/index.ts index 8514f77cc..09f006f09 100644 --- a/test/runtime/value/convert/index.ts +++ b/test/runtime/value/convert/index.ts @@ -5,7 +5,7 @@ import './bigint' import './boolean' import './composite' import './constructor' -import './custom' +import './kind' import './date' import './enum' import './function' diff --git a/test/runtime/value/convert/kind.ts b/test/runtime/value/convert/kind.ts new file mode 100644 index 000000000..116178c9b --- /dev/null +++ b/test/runtime/value/convert/kind.ts @@ -0,0 +1,39 @@ +import { Value } from '@sinclair/typebox/value' +import { TypeRegistry, Kind, TSchema } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Kind', () => { + // --------------------------------------------------------- + // Fixtures + // --------------------------------------------------------- + beforeEach(() => TypeRegistry.Set('Kind', () => true)) + afterEach(() => TypeRegistry.Delete('Kind')) + // --------------------------------------------------------- + // Test + // --------------------------------------------------------- + it('Should not convert value 1', () => { + const T = { [Kind]: 'Kind' } as TSchema + const R = Value.Convert(T, true) + Assert.IsEqual(R, true) + }) + it('Should not convert value 2', () => { + const T = { [Kind]: 'Kind' } as TSchema + const R = Value.Convert(T, 42) + Assert.IsEqual(R, 42) + }) + it('Should not convert value 3', () => { + const T = { [Kind]: 'Kind' } as TSchema + const R = Value.Convert(T, 'hello') + Assert.IsEqual(R, 'hello') + }) + it('Should not convert value 4', () => { + const T = { [Kind]: 'Kind' } as TSchema + const R = Value.Convert(T, { x: 1 }) + Assert.IsEqual(R, { x: 1 }) + }) + it('Should not convert value 5', () => { + const T = { [Kind]: 'Kind' } as TSchema + const R = Value.Convert(T, [0, 1]) + Assert.IsEqual(R, [0, 1]) + }) +}) diff --git a/test/runtime/value/create/custom.ts b/test/runtime/value/create/custom.ts deleted file mode 100644 index 8756a9c0a..000000000 --- a/test/runtime/value/create/custom.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Value } from '@sinclair/typebox/value' -import { Type, Kind, TypeRegistry } from '@sinclair/typebox' -import { Assert } from '../../assert/index' - -describe('value/create/Custom', () => { - it('Should create custom value with default', () => { - TypeRegistry.Set('CustomCreate1', () => true) - const T = Type.Unsafe({ [Kind]: 'CustomCreate1', default: 'hello' }) - Assert.IsEqual(Value.Create(T), 'hello') - }) - - it('Should throw when no default value is specified', () => { - TypeRegistry.Set('CustomCreate2', () => true) - const T = Type.Unsafe({ [Kind]: 'CustomCreate2' }) - Assert.Throws(() => Value.Create(T)) - }) -}) diff --git a/test/runtime/value/create/index.ts b/test/runtime/value/create/index.ts index 385f53f1e..9bb482979 100644 --- a/test/runtime/value/create/index.ts +++ b/test/runtime/value/create/index.ts @@ -4,7 +4,6 @@ import './async-iterator' import './bigint' import './boolean' import './composite' -import './custom' import './constructor' import './enum' import './function' @@ -12,6 +11,7 @@ import './integer' import './intersect' import './iterator' import './keyof' +import './kind' import './literal' import './never' import './not' diff --git a/test/runtime/value/create/kind.ts b/test/runtime/value/create/kind.ts new file mode 100644 index 000000000..3bb42d242 --- /dev/null +++ b/test/runtime/value/create/kind.ts @@ -0,0 +1,23 @@ +import { Value } from '@sinclair/typebox/value' +import { Type, Kind, TypeRegistry } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/create/Kind', () => { + // --------------------------------------------------------- + // Fixtures + // --------------------------------------------------------- + beforeEach(() => TypeRegistry.Set('Kind', () => true)) + afterEach(() => TypeRegistry.Delete('Kind')) + // --------------------------------------------------------- + // Tests + // --------------------------------------------------------- + it('Should create custom value with default', () => { + const T = Type.Unsafe({ [Kind]: 'Kind', default: 'hello' }) + Assert.IsEqual(Value.Create(T), 'hello') + }) + it('Should throw when no default value is specified', () => { + TypeRegistry.Set('Kind', () => true) + const T = Type.Unsafe({ [Kind]: 'Kind' }) + Assert.Throws(() => Value.Create(T)) + }) +})