Skip to content

Commit

Permalink
Send Kind To TypeRegistry Function (#522)
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 authored Aug 5, 2023
1 parent 93547e5 commit 934dc9a
Show file tree
Hide file tree
Showing 19 changed files with 244 additions and 115 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
11 changes: 8 additions & 3 deletions src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
26 changes: 0 additions & 26 deletions test/runtime/compiler/custom.ts

This file was deleted.

2 changes: 1 addition & 1 deletion test/runtime/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import './async-iterator'
import './bigint'
import './boolean'
import './composite'
import './custom'
import './date'
import './unicode'
import './enum'
import './integer'
import './intersect'
import './iterator'
import './keyof'
import './kind'
import './literal'
import './modifier'
import './never'
Expand Down
72 changes: 72 additions & 0 deletions test/runtime/compiler/kind.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})
1 change: 1 addition & 0 deletions test/runtime/type/guard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import './integer'
import './intersect'
import './iterator'
import './keyof'
import './kind'
import './literal'
import './lowercase'
import './not'
Expand Down
13 changes: 13 additions & 0 deletions test/runtime/type/guard/kind.ts
Original file line number Diff line number Diff line change
@@ -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))
})
})
2 changes: 1 addition & 1 deletion test/runtime/value/cast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
29 changes: 0 additions & 29 deletions test/runtime/value/check/custom.ts

This file was deleted.

2 changes: 1 addition & 1 deletion test/runtime/value/check/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
70 changes: 70 additions & 0 deletions test/runtime/value/check/kind.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})
24 changes: 0 additions & 24 deletions test/runtime/value/convert/custom.ts

This file was deleted.

2 changes: 1 addition & 1 deletion test/runtime/value/convert/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import './bigint'
import './boolean'
import './composite'
import './constructor'
import './custom'
import './kind'
import './date'
import './enum'
import './function'
Expand Down
Loading

0 comments on commit 934dc9a

Please sign in to comment.