Send Kind To TypeRegistry Function (#522)

This commit is contained in:
sinclairzx81
2023-08-05 17:31:12 +09:00
committed by GitHub
parent 93547e50de
commit 934dc9a4ae
19 changed files with 244 additions and 115 deletions

4
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -375,7 +375,9 @@ export namespace TypeCompiler {
yield IsVoidCheck(value)
}
function* TKind(schema: Types.TSchema, references: Types.TSchema[], value: string): IterableIterator<string> {
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<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: string, useHoisting: boolean = true): IterableIterator<string> {
const references_ = ValueGuard.IsString(schema.$id) ? [...references, schema] : references
@@ -470,6 +472,7 @@ export namespace TypeCompiler {
language: 'javascript', // target language
functions: new Map<string, string>(), // local functions
variables: new Map<string, string>(), // local variables
instances: new Map<number, Types.TKind>() // 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<T extends Types.TSchema>(schema: T, references: Types.TSchema[] = []): TypeCheck<T> {
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)
}

View File

@@ -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 })
})
})

View File

@@ -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'

View File

@@ -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')
})
})

View File

@@ -16,6 +16,7 @@ import './integer'
import './intersect'
import './iterator'
import './keyof'
import './kind'
import './literal'
import './lowercase'
import './not'

View File

@@ -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))
})
})

View File

@@ -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'

View File

@@ -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'

View File

@@ -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)
})
})

View File

@@ -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'

View File

@@ -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')
})
})

View File

@@ -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')
})
})

View File

@@ -5,7 +5,7 @@ import './bigint'
import './boolean'
import './composite'
import './constructor'
import './custom'
import './kind'
import './date'
import './enum'
import './function'

View File

@@ -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])
})
})

View File

@@ -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))
})
})

View File

@@ -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'

View File

@@ -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))
})
})