This commit is contained in:
sinclair
2025-12-24 15:44:34 +09:00
commit 13d553220c
1047 changed files with 80931 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Type, TypeGuard, ValueGuard } from '@sinclair/typebox'
import { Assert } from '../assert/index'
describe('compiler/TypeCheckMembers', () => {
it('Should return Schema', () => {
const A = TypeCompiler.Compile(Type.Number(), [Type.String(), Type.Boolean()])
Assert.IsTrue(TypeGuard.IsNumber(A.Schema()))
})
it('Should return References', () => {
const A = TypeCompiler.Compile(Type.Number(), [Type.String(), Type.Boolean()])
Assert.IsTrue(TypeGuard.IsNumber(A.Schema()))
Assert.IsTrue(TypeGuard.IsString(A.References()[0]))
Assert.IsTrue(TypeGuard.IsBoolean(A.References()[1]))
})
it('Should return Code', () => {
const A = TypeCompiler.Compile(Type.Number(), [Type.String(), Type.Boolean()])
Assert.IsTrue(ValueGuard.IsString(A.Code()))
})
})

View File

@@ -0,0 +1,41 @@
import { Type } from '@sinclair/typebox'
import { Ok } from './validate'
describe('compiler/Any', () => {
it('Should validate number', () => {
const T = Type.Any()
Ok(T, 1)
})
it('Should validate string', () => {
const T = Type.Any()
Ok(T, 'hello')
})
it('Should validate boolean', () => {
const T = Type.Any()
Ok(T, true)
})
it('Should validate array', () => {
const T = Type.Any()
Ok(T, [1, 2, 3])
})
it('Should validate object', () => {
const T = Type.Any()
Ok(T, { a: 1, b: 2 })
})
it('Should validate null', () => {
const T = Type.Any()
Ok(T, null)
})
it('Should validate undefined', () => {
const T = Type.Any()
Ok(T, undefined)
})
it('Should validate bigint', () => {
const T = Type.Any()
Ok(T, BigInt(1))
})
it('Should validate symbol', () => {
const T = Type.Any()
Ok(T, Symbol(1))
})
})

View File

@@ -0,0 +1,41 @@
import { Type } from '@sinclair/typebox'
import { Ok } from './validate'
describe('compiler/Argument', () => {
it('Should validate number', () => {
const T = Type.Argument(0)
Ok(T, 1)
})
it('Should validate string', () => {
const T = Type.Argument(0)
Ok(T, 'hello')
})
it('Should validate boolean', () => {
const T = Type.Argument(0)
Ok(T, true)
})
it('Should validate array', () => {
const T = Type.Argument(0)
Ok(T, [1, 2, 3])
})
it('Should validate object', () => {
const T = Type.Argument(0)
Ok(T, { a: 1, b: 2 })
})
it('Should validate null', () => {
const T = Type.Argument(0)
Ok(T, null)
})
it('Should validate undefined', () => {
const T = Type.Argument(0)
Ok(T, undefined)
})
it('Should validate bigint', () => {
const T = Type.Argument(0)
Ok(T, BigInt(1))
})
it('Should validate symbol', () => {
const T = Type.Argument(0)
Ok(T, Symbol(1))
})
})

View File

@@ -0,0 +1,186 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Array', () => {
it('Should validate an array of any', () => {
const T = Type.Array(Type.Any())
Ok(T, [0, true, 'hello', {}])
})
it('Should not validate varying array when item is number', () => {
const T = Type.Array(Type.Number())
Fail(T, [1, 2, 3, 'hello'])
})
it('Should validate for an array of unions', () => {
const T = Type.Array(Type.Union([Type.Number(), Type.String()]))
Ok(T, [1, 'hello', 3, 'world'])
})
it('Should not validate for an array of unions where item is not in union.', () => {
const T = Type.Array(Type.Union([Type.Number(), Type.String()]))
Fail(T, [1, 'hello', 3, 'world', true])
})
it('Should validate for an empty array', () => {
const T = Type.Array(Type.Union([Type.Number(), Type.String()]))
Ok(T, [])
})
it('Should validate for an array of intersection types', () => {
const A = Type.Object({ a: Type.String() })
const B = Type.Object({ b: Type.String() })
const C = Type.Intersect([A, B])
const T = Type.Array(C)
Ok(T, [
{ a: 'hello', b: 'hello' },
{ a: 'hello', b: 'hello' },
{ a: 'hello', b: 'hello' },
])
})
it('Should not validate for an array of composite types when passing additionalProperties false', () => {
const A = Type.Object({ a: Type.String() })
const B = Type.Object({ b: Type.String() })
const C = Type.Composite([A, B], { additionalProperties: false })
const T = Type.Array(C)
Fail(T, [
{ a: 'hello', b: 'hello' },
{ a: 'hello', b: 'hello' },
{ a: 'hello', b: 'hello', c: 'additional' },
])
})
it('Should validate an array of tuples', () => {
const A = Type.String()
const B = Type.Number()
const C = Type.Tuple([A, B])
const T = Type.Array(C)
Ok(T, [
['hello', 1],
['hello', 1],
['hello', 1],
])
})
it('Should not validate an array of tuples when tuple values are incorrect', () => {
const A = Type.String()
const B = Type.Number()
const C = Type.Tuple([A, B])
const T = Type.Array(C)
Fail(T, [
[1, 'hello'],
[1, 'hello'],
[1, 'hello'],
])
})
it('Should not validate array with failed minItems', () => {
const T = Type.Array(Type.Number(), { minItems: 3 })
Fail(T, [0, 1])
})
it('Should not validate array with failed maxItems', () => {
const T = Type.Array(Type.Number(), { maxItems: 3 })
Fail(T, [0, 1, 2, 3])
})
// ---------------------------------------------------------
// Unique Items
// ---------------------------------------------------------
it('Should validate array with uniqueItems when items are distinct objects', () => {
const T = Type.Array(Type.Object({ x: Type.Number(), y: Type.Number() }), { uniqueItems: true })
Ok(T, [
{ x: 0, y: 1 },
{ x: 1, y: 0 },
])
})
it('Should not validate array with uniqueItems when items are not distinct objects', () => {
const T = Type.Array(Type.Object({ x: Type.Number(), y: Type.Number() }), { uniqueItems: true })
Fail(T, [
{ x: 1, y: 0 },
{ x: 1, y: 0 },
])
})
it('Should not validate array with non uniqueItems', () => {
const T = Type.Array(Type.Number(), { uniqueItems: true })
Fail(T, [0, 0])
})
// ---------------------------------------------------------
// Contains
// ---------------------------------------------------------
it('Should validate for contains', () => {
const T = Type.Array(Type.Number(), { contains: Type.Literal(1) })
Ok(T, [1])
Ok(T, [1, 2])
Fail(T, [])
Fail(T, [2])
})
it('Should validate for minContains', () => {
const T = Type.Array(Type.Number(), { contains: Type.Literal(1), minContains: 3 })
Ok(T, [1, 1, 1, 2])
Ok(T, [2, 1, 1, 1, 2])
Ok(T, [1, 1, 1])
Fail(T, [])
Fail(T, [1, 1])
Fail(T, [2])
})
it('Should validate for maxContains', () => {
const T = Type.Array(Type.Number(), { contains: Type.Literal(1), maxContains: 3 })
Ok(T, [1, 1, 1])
Ok(T, [1, 1])
Ok(T, [2, 2, 2, 2, 1, 1, 1])
Fail(T, [1, 1, 1, 1])
})
it('Should validate for minContains and maxContains', () => {
const T = Type.Array(Type.Number(), { contains: Type.Literal(1), minContains: 3, maxContains: 5 })
Fail(T, [1, 1])
Ok(T, [1, 1, 1])
Ok(T, [1, 1, 1, 1])
Ok(T, [1, 1, 1, 1, 1])
Fail(T, [1, 1, 1, 1, 1, 1])
})
it('Should not validate minContains or maxContains when contains is unspecified', () => {
const T = Type.Array(Type.Number(), { minContains: 3, maxContains: 5 })
Fail(T, [1, 1])
Fail(T, [1, 1, 1])
Fail(T, [1, 1, 1, 1])
Fail(T, [1, 1, 1, 1, 1])
Fail(T, [1, 1, 1, 1, 1, 1])
})
it('Should produce illogical schema when contains is not sub type of items', () => {
const T = Type.Array(Type.Number(), { contains: Type.String(), minContains: 3, maxContains: 5 })
Fail(T, [1, 1])
Fail(T, [1, 1, 1])
Fail(T, [1, 1, 1, 1])
Fail(T, [1, 1, 1, 1, 1])
Fail(T, [1, 1, 1, 1, 1, 1])
})
// ----------------------------------------------------------------
// Issue: https://github.com/sinclairzx81/typebox/discussions/607
// ----------------------------------------------------------------
it('Should correctly handle undefined array properties', () => {
const Answer = Type.Object({
text: Type.String(),
isCorrect: Type.Boolean(),
})
const Question = Type.Object({
text: Type.String(),
options: Type.Array(Answer, {
minContains: 1,
maxContains: 1,
contains: Type.Object({
text: Type.String(),
isCorrect: Type.Literal(true),
}),
}),
})
Fail(Question, { text: 'A' })
Fail(Question, { text: 'A', options: [] })
Ok(Question, { text: 'A', options: [{ text: 'A', isCorrect: true }] })
Ok(Question, {
text: 'A',
options: [
{ text: 'A', isCorrect: true },
{ text: 'B', isCorrect: false },
],
})
Fail(Question, { text: 'A', options: [{ text: 'A', isCorrect: false }] })
Fail(Question, {
text: 'A',
options: [
{ text: 'A', isCorrect: true },
{ text: 'B', isCorrect: true },
],
})
})
})

View File

@@ -0,0 +1,20 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/AsyncIterator', () => {
it('Should validate a async iterator 1', () => {
async function* f() {}
const T = Type.AsyncIterator(Type.Any())
Ok(T, f())
})
it('Should validate a async iterator 2', () => {
const T = Type.AsyncIterator(Type.Any())
Ok(T, {
[Symbol.asyncIterator]: () => {},
})
})
it('Should not validate a async iterator', () => {
const T = Type.AsyncIterator(Type.Any())
Fail(T, {})
})
})

View File

@@ -0,0 +1,80 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/BigInt', () => {
it('Should not validate number', () => {
const T = Type.BigInt()
Fail(T, 3.14)
})
it('Should not validate NaN', () => {
const T = Type.BigInt()
Fail(T, NaN)
})
it('Should not validate +Infinity', () => {
const T = Type.BigInt()
Fail(T, Infinity)
})
it('Should not validate -Infinity', () => {
const T = Type.BigInt()
Fail(T, -Infinity)
})
it('Should not validate integer', () => {
const T = Type.BigInt()
Fail(T, 1)
})
it('Should not validate string', () => {
const T = Type.BigInt()
Fail(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.BigInt()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.BigInt()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.BigInt()
Fail(T, { a: 1, b: 2 })
})
it('Should not validate null', () => {
const T = Type.BigInt()
Fail(T, null)
})
it('Should not validate undefined', () => {
const T = Type.BigInt()
Fail(T, undefined)
})
it('Should not validate symbol', () => {
const T = Type.BigInt()
Fail(T, Symbol())
})
it('Should validate bigint', () => {
const T = Type.BigInt()
Ok(T, BigInt(1))
})
it('Should validate minimum', () => {
const T = Type.BigInt({ minimum: BigInt(10) })
Fail(T, BigInt(9))
Ok(T, BigInt(10))
})
it('Should validate maximum', () => {
const T = Type.BigInt({ maximum: BigInt(10) })
Ok(T, BigInt(10))
Fail(T, BigInt(11))
})
it('Should validate Date exclusiveMinimum', () => {
const T = Type.BigInt({ exclusiveMinimum: BigInt(10) })
Fail(T, BigInt(10))
Ok(T, BigInt(11))
})
it('Should validate Date exclusiveMaximum', () => {
const T = Type.BigInt({ exclusiveMaximum: BigInt(10) })
Ok(T, BigInt(9))
Fail(T, BigInt(10))
})
it('Should not validate NaN', () => {
Fail(Type.Number(), NaN)
})
})

View File

@@ -0,0 +1,42 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Boolean', () => {
it('Should validate a boolean', () => {
const T = Type.Boolean()
Ok(T, true)
Ok(T, false)
})
it('Should not validate a number', () => {
const T = Type.Boolean()
Fail(T, 1)
})
it('Should not validate a string', () => {
const T = Type.Boolean()
Fail(T, 'true')
})
it('Should not validate an array', () => {
const T = Type.Boolean()
Fail(T, [true])
})
it('Should not validate an object', () => {
const T = Type.Boolean()
Fail(T, {})
})
it('Should not validate an null', () => {
const T = Type.Boolean()
Fail(T, null)
})
it('Should not validate an undefined', () => {
const T = Type.Boolean()
Fail(T, undefined)
})
it('Should not validate bigint', () => {
const T = Type.Boolean()
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.Boolean()
Fail(T, Symbol(1))
})
})

View File

@@ -0,0 +1,101 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Composite', () => {
it('Should compose two objects', () => {
const A = Type.Object({ a: Type.String() })
const B = Type.Object({ b: Type.Number() })
const T = Type.Composite([A, B], { additionalProperties: false })
Ok(T, { a: 'hello', b: 42 })
})
it('Should compose with partial', () => {
const A = Type.Partial(Type.Object({ a: Type.Number() }))
const B = Type.Partial(Type.Object({ b: Type.Number() }))
const P = Type.Composite([A, B], { additionalProperties: false })
Ok(P, { a: 1, b: 2 })
Ok(P, { a: 1 })
Ok(P, { b: 1 })
Ok(P, {})
Fail(P, { a: 1, b: 2, c: 3 })
Fail(P, { c: 1 })
})
it('Should compose with overlapping same type', () => {
const A = Type.Object({ a: Type.Number() })
const B = Type.Object({ a: Type.Number() })
const P = Type.Composite([A, B])
Ok(P, { a: 1 })
Fail(P, { a: '1' })
})
it('Should not compose with overlapping varying type', () => {
const A = Type.Object({ a: Type.Number() })
const B = Type.Object({ a: Type.String() })
const T = Type.Composite([A, B])
Fail(T, { a: 1 })
Fail(T, { a: 'hello' })
Fail(T, {})
})
it('Should compose with deeply nest overlapping varying type', () => {
const A = Type.Object({ a: Type.Number() })
const B = Type.Object({ b: Type.String() })
const C = Type.Object({ c: Type.Boolean() })
const D = Type.Object({ d: Type.Null() })
const T = Type.Composite([A, B, C, D])
Ok(T, { a: 1, b: 'hello', c: true, d: null })
})
it('Should not compose with deeply nest overlapping varying type', () => {
const A = Type.Object({ a: Type.Number() })
const B = Type.Object({ a: Type.String() })
const C = Type.Object({ a: Type.Boolean() })
const D = Type.Object({ a: Type.Null() })
const T = Type.Composite([A, B, C, D])
Fail(T, { a: 1 })
Fail(T, { a: 'hello' })
Fail(T, { a: false })
Fail(T, { a: null })
Fail(T, { a: [] })
Fail(T, {})
})
it('Should pick from composited type', () => {
const A = Type.Object({ x: Type.Number() })
const B = Type.Object({ y: Type.Number() })
const C = Type.Object({ z: Type.Number() })
const T = Type.Composite([A, B, C])
const P = Type.Pick(T, ['x', 'y'], { additionalProperties: false })
Ok(P, { x: 1, y: 1 })
Fail(P, { x: 1, y: 1, z: 1 })
})
it('Should omit from composited type', () => {
const A = Type.Object({ x: Type.Number() })
const B = Type.Object({ y: Type.Number() })
const C = Type.Object({ z: Type.Number() })
const T = Type.Composite([A, B, C])
const P = Type.Omit(T, ['z'], { additionalProperties: false })
Ok(P, { x: 1, y: 1 })
Fail(P, { x: 1, y: 1, z: 1 })
})
it('Should compose nested object properties', () => {
const A = Type.Object({ x: Type.Object({ x: Type.Number() }) })
const B = Type.Object({ y: Type.Object({ x: Type.String() }) })
const T = Type.Composite([A, B])
Ok(T, { x: { x: 1 }, y: { x: '' } })
Fail(T, { x: { x: '1' }, y: { x: '' } })
Fail(T, { x: { x: 1 }, y: { x: 1 } })
})
// prettier-ignore
it('Should composite intersection', () => {
const T = Type.Composite([
Type.Intersect([
Type.Object({ x: Type.Number() })
]),
Type.Intersect([
Type.Object({ y: Type.Number() })
]),
Type.Intersect([
Type.Object({ z: Type.Number() })
]),
])
Ok(T, { x: 1, y: 2, z: 3 })
Fail(T, { x: 1, y: 2, z: '3' })
Fail(T, { x: 1, y: 2 })
})
})

View File

@@ -0,0 +1,39 @@
import { Type } from '@sinclair/typebox'
import { Ok } from './validate'
describe('compiler/Const', () => {
it('Should validate 1', () => {
const T = Type.Const(1)
Ok(T, 1)
})
it('Should validate 2', () => {
const T = Type.Const('hello')
Ok(T, 'hello')
})
it('Should validate 3', () => {
const T = Type.Const(true)
Ok(T, true)
})
it('Should validate 4', () => {
const T = Type.Const({ x: 1, y: 2 })
Ok(T, { x: 1, y: 2 })
})
it('Should validate 5', () => {
const T = Type.Const([1, 2, 3])
Ok(T, [1, 2, 3])
})
it('Should validate 6', () => {
const T = Type.Const([1, true, 'hello'])
Ok(T, [1, true, 'hello'])
})
it('Should validate 7', () => {
const T = Type.Const({
x: [1, 2, 3, 4],
y: { x: 1, y: 2, z: 3 },
})
Ok(T, {
x: [1, 2, 3, 4],
y: { x: 1, y: 2, z: 3 },
})
})
})

View File

@@ -0,0 +1,80 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Constructor', () => {
it('Should validate constructor 1', () => {
const T = Type.Constructor([], Type.Object({}))
Ok(T, class {})
})
it('Should validate constructor 2', () => {
const T = Type.Constructor([Type.Number()], Type.Object({}))
// note: constructor arguments are non-checkable
Ok(T, class {})
})
it('Should validate constructor 3', () => {
const T = Type.Constructor(
[Type.Number()],
Type.Object({
method: Type.Function([], Type.Void()),
}),
)
Ok(
T,
class {
method() {}
},
)
})
it('Should validate constructor 4', () => {
const T = Type.Constructor(
[Type.Number()],
Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}),
)
Ok(
T,
class {
get x() {
return 1
}
get y() {
return 1
}
get z() {
return 1
}
},
)
})
it('Should not validate constructor 1', () => {
const T = Type.Constructor([Type.Number()], Type.Object({}))
Fail(T, 1)
})
it('Should not validate constructor 2', () => {
const T = Type.Constructor(
[Type.Number()],
Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}),
)
Fail(
T,
class {
get x() {
return null
}
get y() {
return null
}
get z() {
return null
}
},
)
})
})

View File

@@ -0,0 +1,74 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Date', () => {
it('Should not validate number', () => {
const T = Type.Date()
Fail(T, 1)
})
it('Should not validate string', () => {
const T = Type.Date()
Fail(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.Date()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.Date()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.Date()
Fail(T, { a: 1, b: 2 })
})
it('Should not validate null', () => {
const T = Type.Date()
Fail(T, null)
})
it('Should not validate undefined', () => {
const T = Type.Date()
Fail(T, undefined)
})
it('Should validate Date', () => {
const T = Type.Date()
Ok(T, new Date())
})
it('Should not validate bigint', () => {
const T = Type.Date()
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.Date()
Fail(T, Symbol(1))
})
it('Should not validate Date if is invalid', () => {
const T = Type.Date()
Fail(T, new Date('not-a-valid-date'))
})
it('Should validate Date minimumTimestamp', () => {
const T = Type.Date({ minimumTimestamp: 10 })
Fail(T, new Date(9))
Ok(T, new Date(10))
})
it('Should validate Date maximumTimestamp', () => {
const T = Type.Date({ maximumTimestamp: 10 })
Fail(T, new Date(11))
Ok(T, new Date(10))
})
it('Should validate Date exclusiveMinimumTimestamp', () => {
const T = Type.Date({ exclusiveMinimumTimestamp: 10 })
Fail(T, new Date(10))
Ok(T, new Date(11))
})
it('Should validate Date exclusiveMaximumTimestamp', () => {
const T = Type.Date({ exclusiveMaximumTimestamp: 10 })
Fail(T, new Date(10))
Ok(T, new Date(9))
})
it('Should validate Date multipleOfTimestamp', () => {
const T = Type.Date({ multipleOfTimestamp: 2 })
Fail(T, new Date(1))
Ok(T, new Date(2))
})
})

View File

@@ -0,0 +1,53 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Enum', () => {
it('Should validate when emum uses default numeric values', () => {
enum Kind {
Foo, // = 0
Bar, // = 1
}
const T = Type.Enum(Kind)
Ok(T, 0)
Ok(T, 1)
})
it('Should not validate when given enum values are not numeric', () => {
enum Kind {
Foo, // = 0
Bar, // = 1
}
const T = Type.Enum(Kind)
Fail(T, 'Foo')
Fail(T, 'Bar')
})
it('Should validate when emum has defined string values', () => {
enum Kind {
Foo = 'foo',
Bar = 'bar',
}
const T = Type.Enum(Kind)
Ok(T, 'foo')
Ok(T, 'bar')
})
it('Should not validate when emum has defined string values and user passes numeric', () => {
enum Kind {
Foo = 'foo',
Bar = 'bar',
}
const T = Type.Enum(Kind)
Fail(T, 0)
Fail(T, 1)
})
it('Should validate when enum has one or more string values', () => {
enum Kind {
Foo,
Bar = 'bar',
}
const T = Type.Enum(Kind)
Ok(T, 0)
Ok(T, 'bar')
Fail(T, 'baz')
Fail(T, 'Foo')
Fail(T, 1)
})
})

View File

@@ -0,0 +1,25 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Function', () => {
it('Should validate function 1', () => {
const T = Type.Function([Type.Number()], Type.Number())
Ok(T, function () {})
})
it('Should validate function 2', () => {
const T = Type.Function([Type.Number()], Type.Number())
// note: validation only checks typeof 'function'
Ok(T, function (a: string, b: string, c: string, d: string) {})
})
it('Should validate function 3', () => {
const T = Type.Function([Type.Number()], Type.Number())
// note: validation only checks typeof 'function'
Ok(T, function () {
return 'not-a-number'
})
})
it('Should not validate function', () => {
const T = Type.Function([Type.Number()], Type.Number())
Fail(T, 1)
})
})

View File

@@ -0,0 +1,46 @@
import './__members'
import './any'
import './argument'
import './array'
import './async-iterator'
import './bigint'
import './boolean'
import './composite'
import './const'
import './constructor'
import './date'
import './unicode'
import './enum'
import './function'
import './integer'
import './intersect'
import './iterator'
import './keyof'
import './kind'
import './literal'
import './module'
import './never'
import './not'
import './null'
import './number'
import './object'
import './omit'
import './optional'
import './partial'
import './pick'
import './readonly-optional'
import './readonly'
import './recursive'
import './record'
import './ref'
import './regexp'
import './required'
import './string-pattern'
import './string'
import './symbol'
import './template-literal'
import './tuple'
import './uint8array'
import './union'
import './unknown'
import './void'

View File

@@ -0,0 +1,77 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Integer', () => {
it('Should not validate number', () => {
const T = Type.Integer()
Fail(T, 3.14)
})
it('Should not validate NaN', () => {
const T = Type.Integer()
Fail(T, NaN)
})
it('Should not validate +Infinity', () => {
const T = Type.Integer()
Fail(T, Infinity)
})
it('Should not validate -Infinity', () => {
const T = Type.Integer()
Fail(T, -Infinity)
})
it('Should validate integer', () => {
const T = Type.Integer()
Ok(T, 1)
})
it('Should not validate string', () => {
const T = Type.Integer()
Fail(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.Integer()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.Integer()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.Integer()
Fail(T, { a: 1, b: 2 })
})
it('Should not validate null', () => {
const T = Type.Integer()
Fail(T, null)
})
it('Should not validate undefined', () => {
const T = Type.Integer()
Fail(T, undefined)
})
it('Should not validate bigint', () => {
const T = Type.Integer()
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.Integer()
Fail(T, Symbol(1))
})
it('Should validate minimum', () => {
const T = Type.Integer({ minimum: 10 })
Fail(T, 9)
Ok(T, 10)
})
it('Should validate maximum', () => {
const T = Type.Integer({ maximum: 10 })
Ok(T, 10)
Fail(T, 11)
})
it('Should validate Date exclusiveMinimum', () => {
const T = Type.Integer({ exclusiveMinimum: 10 })
Fail(T, 10)
Ok(T, 11)
})
it('Should validate Date exclusiveMaximum', () => {
const T = Type.Integer({ exclusiveMaximum: 10 })
Ok(T, 9)
Fail(T, 10)
})
})

View File

@@ -0,0 +1,225 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Intersect', () => {
it('Should intersect number and number', () => {
const A = Type.Number()
const B = Type.Number()
const T = Type.Intersect([A, B], {})
Ok(T, 1)
})
it('Should not intersect string and number', () => {
const A = Type.String()
const B = Type.Number()
const T = Type.Intersect([A, B], {})
Fail(T, 1)
Fail(T, '1')
})
it('Should intersect two objects', () => {
const A = Type.Object({ x: Type.Number() })
const B = Type.Object({ y: Type.Number() })
const T = Type.Intersect([A, B], {})
Ok(T, { x: 1, y: 1 })
})
it('Should not intersect two objects with internal additionalProperties false', () => {
const A = Type.Object({ x: Type.Number() }, { additionalProperties: false })
const B = Type.Object({ y: Type.Number() }, { additionalProperties: false })
const T = Type.Intersect([A, B], {})
Fail(T, { x: 1, y: 1 })
})
it('Should intersect two objects and mandate required properties', () => {
const A = Type.Object({ x: Type.Number() })
const B = Type.Object({ y: Type.Number() })
const T = Type.Intersect([A, B], {})
Ok(T, { x: 1, y: 1 })
Fail(T, { x: 1 })
Fail(T, { y: 1 })
})
it('Should intersect two objects with unevaluated properties', () => {
const A = Type.Object({ x: Type.Number() })
const B = Type.Object({ y: Type.Number() })
const T = Type.Intersect([A, B], {})
Ok(T, { x: 1, y: 2, z: 1 })
})
it('Should intersect two objects and restrict unevaluated properties', () => {
const A = Type.Object({ x: Type.Number() })
const B = Type.Object({ y: Type.Number() })
const T = Type.Intersect([A, B], { unevaluatedProperties: false })
Fail(T, { x: 1, y: 2, z: 1 })
})
it('Should intersect two objects and allow unevaluated properties of number', () => {
const A = Type.Object({ x: Type.Number() })
const B = Type.Object({ y: Type.Number() })
const T = Type.Intersect([A, B], { unevaluatedProperties: Type.Number() })
Ok(T, { x: 1, y: 2, z: 3 })
Fail(T, { x: 1, y: 2, z: '1' })
})
it('Should intersect two nested objects and allow unevaluated properties of number', () => {
const A = Type.Object({ x: Type.Number() })
const B = Type.Object({ y: Type.Number() })
const T = Type.Object({ nested: Type.Intersect([A, B], { unevaluatedProperties: Type.Number() }) })
Ok(T, { nested: { x: 1, y: 2, z: 3 } })
Fail(T, { nested: { x: 1, y: 2, z: '1' } })
})
it('Should intersect two union objects with overlapping properties of the same type', () => {
const A = Type.Union([Type.Object({ x: Type.Number() })])
const B = Type.Union([Type.Object({ x: Type.Number() })])
const T = Type.Intersect([A, B])
Ok(T, { x: 1 })
Fail(T, { x: '1' })
})
it('Should not intersect two union objects with overlapping properties of varying types', () => {
const A = Type.Union([Type.Object({ x: Type.Number() })])
const B = Type.Union([Type.Object({ x: Type.String() })])
const T = Type.Intersect([A, B])
Fail(T, { x: 1 })
Fail(T, { x: '1' })
})
it('Should intersect two union objects with non-overlapping properties', () => {
const A = Type.Union([Type.Object({ x: Type.Number() })])
const B = Type.Union([Type.Object({ y: Type.Number() })])
const T = Type.Intersect([A, B])
Ok(T, { x: 1, y: 1 })
})
it('Should not intersect two union objects with non-overlapping properties for additionalProperties false', () => {
const A = Type.Union([Type.Object({ x: Type.Number() }, { additionalProperties: false })])
const B = Type.Union([Type.Object({ y: Type.Number() }, { additionalProperties: false })])
const T = Type.Intersect([A, B])
Fail(T, { x: 1, y: 1 })
})
it('unevaluatedProperties with Record 1', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: false,
},
)
Ok(T, { x: 1, y: 2 })
})
it('unevaluatedProperties with Record 2', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: false,
},
)
Ok(T, { x: 1, y: 2, 0: 'hello' })
})
it('unevaluatedProperties with Record 3', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: false,
},
)
Fail(T, { x: 1, y: 2, 0: 1 })
})
it('unevaluatedProperties with Record 4', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: Type.Boolean(),
},
)
Ok(T, { x: 1, y: 2 })
})
it('unevaluatedProperties with Record 5', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: Type.Boolean(),
},
)
Ok(T, { x: 1, y: 2, z: true })
})
it('unevaluatedProperties with Record 6', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: Type.Boolean(),
},
)
Fail(T, { x: 1, y: 2, z: 1 })
})
it('unevaluatedProperties with Record 7', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: Type.Boolean(),
},
)
Ok(T, { x: 1, y: 2, 0: '' })
})
it('unevaluatedProperties with Record 8', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: Type.Boolean(),
},
)
Ok(T, { x: 1, y: 2, 0: '', z: true })
})
it('unevaluatedProperties with Record 9', () => {
const T = Type.Intersect(
[
Type.Record(Type.Number(), Type.String()),
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
],
{
unevaluatedProperties: Type.Boolean(),
},
)
Fail(T, { x: 1, y: 2, 0: '', z: 1 })
})
})

View File

@@ -0,0 +1,20 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Iterator', () => {
it('Should validate a iterator 1', () => {
function* f() {}
const T = Type.Iterator(Type.Any())
Ok(T, f())
})
it('Should validate a iterator 2', () => {
const T = Type.Iterator(Type.Any())
Ok(T, {
[Symbol.iterator]: () => {},
})
})
it('Should not validate a iterator', () => {
const T = Type.Iterator(Type.Any())
Fail(T, {})
})
})

View File

@@ -0,0 +1,48 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/KeyOf', () => {
it('Should validate with all object keys as a kind of union', () => {
const T = Type.KeyOf(
Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}),
)
Ok(T, 'x')
Ok(T, 'y')
Ok(T, 'z')
Fail(T, 'w')
})
it('Should validate when using pick', () => {
const T = Type.KeyOf(
Type.Pick(
Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}),
['x', 'y'],
),
)
Ok(T, 'x')
Ok(T, 'y')
Fail(T, 'z')
})
it('Should validate when using omit', () => {
const T = Type.KeyOf(
Type.Omit(
Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}),
['x', 'y'],
),
)
Fail(T, 'x')
Fail(T, 'y')
Ok(T, 'z')
})
})

View File

@@ -0,0 +1,123 @@
import { TypeRegistry, Type, Kind, TSchema } from '@sinclair/typebox'
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Ok, Fail } from './validate'
import { Assert } from '../assert'
describe('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')
})
// ------------------------------------------------------------
// Instances Retain
// ------------------------------------------------------------
it('Should retain kind instances on subsequent compile', () => {
let 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 C = { [Kind]: 'Kind', $id: 'C' } as TSchema
const D = { [Kind]: 'Kind', $id: 'D' } as TSchema
const T1 = Type.Object({ a: A, b: B })
const T2 = Type.Object({ a: C, b: D })
// Compile T1 and run check, expect A and B
const C1 = TypeCompiler.Compile(T1)
const R1 = C1.Check({ a: null, b: null })
Assert.IsTrue(R1)
Assert.IsEqual(stack.length, 2)
Assert.IsEqual(stack[0], 'A')
Assert.IsEqual(stack[1], 'B')
stack = []
// compile T2 and force instance.clear()
const C2 = TypeCompiler.Compile(T2)
// run T1 check
const R2 = C1.Check({ a: null, b: null })
Assert.IsTrue(R2)
Assert.IsEqual(stack.length, 2)
Assert.IsEqual(stack[0], 'A')
Assert.IsEqual(stack[1], 'B')
stack = []
// run T2 check
const R3 = C2.Check({ a: null, b: null })
Assert.IsTrue(R3)
Assert.IsEqual(stack.length, 2)
Assert.IsEqual(stack[0], 'C')
Assert.IsEqual(stack[1], 'D')
stack = []
// run T1 check
const R4 = C1.Check({ a: null, b: null })
Assert.IsTrue(R4)
Assert.IsEqual(stack.length, 2)
Assert.IsEqual(stack[0], 'A')
Assert.IsEqual(stack[1], 'B')
stack = []
TypeRegistry.Delete('Kind')
})
})

View File

@@ -0,0 +1,50 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Literal', () => {
it('Should validate literal number', () => {
const T = Type.Literal(42)
Ok(T, 42)
})
it('Should validate literal string', () => {
const T = Type.Literal('hello')
Ok(T, 'hello')
})
it('Should validate literal boolean', () => {
const T = Type.Literal(true)
Ok(T, true)
})
it('Should not validate invalid literal number', () => {
const T = Type.Literal(42)
Fail(T, 43)
})
it('Should not validate invalid literal string', () => {
const T = Type.Literal('hello')
Fail(T, 'world')
})
it('Should not validate invalid literal boolean', () => {
const T = Type.Literal(false)
Fail(T, true)
})
it('Should validate literal union', () => {
const T = Type.Union([Type.Literal(42), Type.Literal('hello')])
Ok(T, 42)
Ok(T, 'hello')
})
it('Should not validate invalid literal union', () => {
const T = Type.Union([Type.Literal(42), Type.Literal('hello')])
Fail(T, 43)
Fail(T, 'world')
})
// reference: https://github.com/sinclairzx81/typebox/issues/539
it('Should escape single quote literals', () => {
const T = Type.Literal("it's")
Ok(T, "it's")
Fail(T, "it''s")
})
it('Should escape multiple single quote literals', () => {
const T = Type.Literal("'''''''''")
Ok(T, "'''''''''")
Fail(T, "''''''''") // minus 1
})
})

View File

@@ -0,0 +1,144 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Module', () => {
it('Should validate string', () => {
const Module = Type.Module({
A: Type.String(),
})
const T = Module.Import('A')
Ok(T, 'hello')
Fail(T, true)
})
it('Should validate referenced string', () => {
const Module = Type.Module({
A: Type.String(),
B: Type.Ref('A'),
})
const T = Module.Import('B')
Ok(T, 'hello')
Fail(T, true)
})
it('Should validate self referential', () => {
const Module = Type.Module({
A: Type.Object({
nodes: Type.Array(Type.Ref('A')),
}),
})
const T = Module.Import('A')
Ok(T, { nodes: [{ nodes: [{ nodes: [] }, { nodes: [] }] }] })
Fail(T, { nodes: [{ nodes: [{ nodes: [] }, { nodes: false }] }] })
Fail(T, true)
})
it('Should validate mutual recursive', () => {
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B'),
}),
B: Type.Object({
a: Type.Union([Type.Ref('A'), Type.Null()]),
}),
})
const T = Module.Import('A')
Ok(T, { b: { a: null } })
Ok(T, { b: { a: { b: { a: null } } } })
Fail(T, { b: { a: 1 } })
Fail(T, { b: { a: { b: { a: 1 } } } })
Fail(T, true)
})
it('Should validate mutual recursive (Array)', () => {
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B'),
}),
B: Type.Object({
a: Type.Array(Type.Ref('A')),
}),
})
const T = Module.Import('A')
Ok(T, { b: { a: [{ b: { a: [] } }] } })
Fail(T, { b: { a: [{ b: { a: [null] } }] } })
Fail(T, true)
})
it('Should validate deep referential', () => {
const Module = Type.Module({
A: Type.Ref('B'),
B: Type.Ref('C'),
C: Type.Ref('D'),
D: Type.Ref('E'),
E: Type.Ref('F'),
F: Type.Ref('G'),
G: Type.Ref('H'),
H: Type.Literal('hello'),
})
const T = Module.Import('A')
Ok(T, 'hello')
Fail(T, 'world')
})
// ----------------------------------------------------------------
// Modifiers
// ----------------------------------------------------------------
it('Should validate objects with property modifiers 1', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Null()),
y: Type.Readonly(Type.Null()),
z: Type.Optional(Type.Null()),
w: Type.Null(),
}),
})
const T = Module.Import('T')
Ok(T, { x: null, y: null, w: null })
Ok(T, { y: null, w: null })
Fail(T, { x: 1, y: null, w: null })
})
it('Should validate objects with property modifiers 2', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Array(Type.Null())),
y: Type.Readonly(Type.Array(Type.Null())),
z: Type.Optional(Type.Array(Type.Null())),
w: Type.Array(Type.Null()),
}),
})
const T = Module.Import('T')
Ok(T, { x: [null], y: [null], w: [null] })
Ok(T, { y: [null], w: [null] })
Fail(T, { x: [1], y: [null], w: [null] })
})
// ----------------------------------------------------------------
// https://github.com/sinclairzx81/typebox/issues/1109
// ----------------------------------------------------------------
it('Should validate deep referential 1', () => {
const Module = Type.Module({
A: Type.Union([Type.Literal('Foo'), Type.Literal('Bar')]),
B: Type.Ref('A'),
C: Type.Object({ ref: Type.Ref('B') }),
D: Type.Union([Type.Ref('B'), Type.Ref('C')]),
})
Ok(Module.Import('A') as never, 'Foo')
Ok(Module.Import('A') as never, 'Bar')
Ok(Module.Import('B') as never, 'Foo')
Ok(Module.Import('B') as never, 'Bar')
Ok(Module.Import('C') as never, { ref: 'Foo' })
Ok(Module.Import('C') as never, { ref: 'Bar' })
Ok(Module.Import('D') as never, 'Foo')
Ok(Module.Import('D') as never, 'Bar')
Ok(Module.Import('D') as never, { ref: 'Foo' })
Ok(Module.Import('D') as never, { ref: 'Bar' })
})
it('Should validate deep referential 2', () => {
const Module = Type.Module({
A: Type.Literal('Foo'),
B: Type.Ref('A'),
C: Type.Ref('B'),
D: Type.Ref('C'),
E: Type.Ref('D'),
})
Ok(Module.Import('A'), 'Foo')
Ok(Module.Import('B'), 'Foo')
Ok(Module.Import('C'), 'Foo')
Ok(Module.Import('D'), 'Foo')
Ok(Module.Import('E'), 'Foo')
})
})

View File

@@ -0,0 +1,41 @@
import { Type } from '@sinclair/typebox'
import { Fail } from './validate'
describe('compiler/Never', () => {
it('Should not validate number', () => {
const T = Type.Never()
Fail(T, 1)
})
it('Should not validate string', () => {
const T = Type.Never()
Fail(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.Never()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.Never()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.Never()
Fail(T, { a: 1, b: 2 })
})
it('Should not validate null', () => {
const T = Type.Never()
Fail(T, null)
})
it('Should not validate undefined', () => {
const T = Type.Never()
Fail(T, undefined)
})
it('Should not validate bigint', () => {
const T = Type.Never()
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.Never()
Fail(T, Symbol(1))
})
})

View File

@@ -0,0 +1,45 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Not', () => {
it('Should validate not number', () => {
const T = Type.Not(Type.Number())
Fail(T, 1)
Ok(T, '1')
})
it('Should validate not not number', () => {
const T = Type.Not(Type.Not(Type.Number()))
Ok(T, 1)
Fail(T, '1')
})
it('Should validate not union', () => {
// prettier-ignore
const T = Type.Not(Type.Union([
Type.Literal('A'),
Type.Literal('B'),
Type.Literal('C')
]))
Fail(T, 'A')
Fail(T, 'B')
Fail(T, 'C')
Ok(T, 'D')
})
it('Should validate not object intersection', () => {
const T = Type.Intersect([
Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}),
Type.Object({
x: Type.Not(Type.Literal(0)),
y: Type.Not(Type.Literal(0)),
z: Type.Not(Type.Literal(0)),
}),
])
Fail(T, { x: 0, y: 0, z: 0 })
Fail(T, { x: 1, y: 0, z: 0 })
Fail(T, { x: 1, y: 1, z: 0 })
Ok(T, { x: 1, y: 1, z: 1 })
})
})

View File

@@ -0,0 +1,41 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Null', () => {
it('Should not validate number', () => {
const T = Type.Null()
Fail(T, 1)
})
it('Should not validate string', () => {
const T = Type.Null()
Fail(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.Null()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.Null()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.Null()
Fail(T, { a: 1, b: 2 })
})
it('Should not validate null', () => {
const T = Type.Null()
Ok(T, null)
})
it('Should not validate undefined', () => {
const T = Type.Null()
Fail(T, undefined)
})
it('Should not validate bigint', () => {
const T = Type.Null()
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.Null()
Fail(T, Symbol(1))
})
})

View File

@@ -0,0 +1,77 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Number', () => {
it('Should validate number', () => {
const T = Type.Number()
Ok(T, 3.14)
})
it('Should not validate NaN', () => {
const T = Type.Number()
Fail(T, NaN)
})
it('Should not validate +Infinity', () => {
const T = Type.Number()
Fail(T, Infinity)
})
it('Should not validate -Infinity', () => {
const T = Type.Number()
Fail(T, -Infinity)
})
it('Should validate integer', () => {
const T = Type.Number()
Ok(T, 1)
})
it('Should not validate string', () => {
const T = Type.Number()
Fail(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.Number()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.Number()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.Number()
Fail(T, { a: 1, b: 2 })
})
it('Should not validate null', () => {
const T = Type.Number()
Fail(T, null)
})
it('Should not validate undefined', () => {
const T = Type.Number()
Fail(T, undefined)
})
it('Should not validate bigint', () => {
const T = Type.Number()
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.Number()
Fail(T, Symbol(1))
})
it('Should validate minimum', () => {
const T = Type.Number({ minimum: 10 })
Fail(T, 9)
Ok(T, 10)
})
it('Should validate maximum', () => {
const T = Type.Number({ maximum: 10 })
Ok(T, 10)
Fail(T, 11)
})
it('Should validate Date exclusiveMinimum', () => {
const T = Type.Number({ exclusiveMinimum: 10 })
Fail(T, 10)
Ok(T, 11)
})
it('Should validate Date exclusiveMaximum', () => {
const T = Type.Number({ exclusiveMaximum: 10 })
Ok(T, 9)
Fail(T, 10)
})
})

View File

@@ -0,0 +1,390 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Object', () => {
// -----------------------------------------------------
// TypeCompiler Only
// -----------------------------------------------------
it('Should handle extends undefined check 1', () => {
const T = Type.Object({
A: Type.Not(Type.Number()),
B: Type.Union([Type.Number(), Type.Undefined()]),
C: Type.Intersect([Type.Undefined(), Type.Undefined()]),
})
Ok(T, {
A: undefined,
B: undefined,
C: undefined,
})
})
// https://github.com/sinclairzx81/typebox/issues/437
it('Should handle extends undefined check 2', () => {
const T = Type.Object({
A: Type.Not(Type.Null()),
})
Ok(T, { A: undefined })
Fail(T, { A: null })
Fail(T, {})
})
// -----------------------------------------------------
// Standard Checks
// -----------------------------------------------------
it('Should not validate a number', () => {
const T = Type.Object({})
Fail(T, 42)
})
it('Should not validate a string', () => {
const T = Type.Object({})
Fail(T, 'hello')
})
it('Should not validate a boolean', () => {
const T = Type.Object({})
Fail(T, true)
})
it('Should not validate a null', () => {
const T = Type.Object({})
Fail(T, null)
})
it('Should not validate undefined', () => {
const T = Type.Object({})
Fail(T, undefined)
})
it('Should not validate bigint', () => {
const T = Type.Object({})
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.Object({})
Fail(T, Symbol(1))
})
it('Should not validate an array', () => {
const T = Type.Object({})
Fail(T, [1, 2])
})
it('Should validate with correct property values', () => {
const T = Type.Object({
a: Type.Number(),
b: Type.String(),
c: Type.Boolean(),
d: Type.Array(Type.Number()),
e: Type.Object({ x: Type.Number(), y: Type.Number() }),
})
Ok(T, {
a: 10,
b: 'hello',
c: true,
d: [1, 2, 3],
e: { x: 10, y: 20 },
})
})
it('Should not validate with incorrect property values', () => {
const T = Type.Object({
a: Type.Number(),
b: Type.String(),
c: Type.Boolean(),
d: Type.Array(Type.Number()),
e: Type.Object({ x: Type.Number(), y: Type.Number() }),
})
Fail(T, {
a: 'not a number', // error
b: 'hello',
c: true,
d: [1, 2, 3],
e: { x: 10, y: 20 },
})
})
it('Should allow additionalProperties by default', () => {
const T = Type.Object({
a: Type.Number(),
b: Type.String(),
})
Ok(T, {
a: 1,
b: 'hello',
c: true,
})
})
it('Should not allow an empty object if minProperties is set to 1', () => {
const T = Type.Object(
{
a: Type.Optional(Type.Number()),
b: Type.Optional(Type.String()),
},
{ additionalProperties: false, minProperties: 1 },
)
Ok(T, { a: 1 })
Ok(T, { b: 'hello' })
Fail(T, {})
})
it('Should not allow 3 properties if maxProperties is set to 2', () => {
const T = Type.Object(
{
a: Type.Optional(Type.Number()),
b: Type.Optional(Type.String()),
c: Type.Optional(Type.Boolean()),
},
{ additionalProperties: false, maxProperties: 2 },
)
Ok(T, { a: 1 })
Ok(T, { a: 1, b: 'hello' })
Fail(T, {
a: 1,
b: 'hello',
c: true,
})
})
it('Should not allow additionalProperties if additionalProperties is false', () => {
const T = Type.Object(
{
a: Type.Number(),
b: Type.String(),
},
{ additionalProperties: false },
)
Fail(T, {
a: 1,
b: 'hello',
c: true,
})
})
it('Should not allow properties for an empty object when additionalProperties is false', () => {
const T = Type.Object({}, { additionalProperties: false })
Ok(T, {})
Fail(T, { a: 10 })
})
it('Should validate with non-syntax property keys', () => {
const T = Type.Object({
'with-hyphen': Type.Literal(1),
'0-leading': Type.Literal(2),
'$-leading': Type.Literal(3),
'!@#$%^&*(': Type.Literal(4),
'node-mirror:release:0': Type.Literal(5), // issue: 353
'node-mirror:release:1': Type.Optional(Type.Literal(6)), // issue: 356
'node-mirror:release:2': Type.Union([Type.Literal(7), Type.Undefined()]), // key known
"a'a": Type.Literal(8),
'@onlyAtSymbol': Type.Literal(9),
})
Ok(T, {
'with-hyphen': 1,
'0-leading': 2,
'$-leading': 3,
'!@#$%^&*(': 4,
'node-mirror:release:0': 5,
'node-mirror:release:1': 6,
'node-mirror:release:2': 7,
"a'a": 8,
'@onlyAtSymbol': 9,
})
})
it('Should validate schema additional properties of string', () => {
const T = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
)
Ok(T, {
x: 1,
y: 2,
z: 'hello',
})
Fail(T, {
x: 1,
y: 2,
z: 3,
})
})
it('Should validate schema additional properties of array', () => {
const T = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.Array(Type.Number()),
},
)
Ok(T, {
x: 1,
y: 2,
z: [0, 1, 2],
})
Fail(T, {
x: 1,
y: 2,
z: 3,
})
})
it('Should validate schema additional properties of object', () => {
const T = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.Object({
z: Type.Number(),
}),
},
)
Ok(T, {
x: 1,
y: 2,
z: { z: 1 },
})
Fail(T, {
x: 1,
y: 2,
z: 3,
})
})
it('Should validate nested schema additional properties of string', () => {
const T = Type.Object({
nested: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
})
Ok(T, {
nested: {
x: 1,
y: 2,
z: 'hello',
},
})
Fail(T, {
nested: {
x: 1,
y: 2,
z: 3,
},
})
})
it('Should validate nested schema additional properties of array', () => {
const T = Type.Object({
nested: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.Array(Type.Number()),
},
),
})
Ok(T, {
nested: {
x: 1,
y: 2,
z: [0, 1, 2],
},
})
Fail(T, {
nested: {
x: 1,
y: 2,
z: 3,
},
})
})
it('Should validate nested schema additional properties of object', () => {
const T = Type.Object({
nested: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.Object({
z: Type.Number(),
}),
},
),
})
Ok(T, {
nested: {
x: 1,
y: 2,
z: { z: 1 },
},
})
Fail(T, {
nested: {
x: 1,
y: 2,
z: 3,
},
})
})
it('Should check for property key if property type is undefined', () => {
const T = Type.Object({ x: Type.Undefined() })
Ok(T, { x: undefined })
Fail(T, {})
})
it('Should check for property key if property type extends undefined', () => {
const T = Type.Object({ x: Type.Union([Type.Number(), Type.Undefined()]) })
Ok(T, { x: 1 })
Ok(T, { x: undefined })
Fail(T, {})
})
it('Should not check for property key if property type is undefined and optional', () => {
const T = Type.Object({ x: Type.Optional(Type.Undefined()) })
Ok(T, { x: undefined })
Ok(T, {})
})
it('Should not check for property key if property type extends undefined and optional', () => {
const T = Type.Object({ x: Type.Optional(Type.Union([Type.Number(), Type.Undefined()])) })
Ok(T, { x: 1 })
Ok(T, { x: undefined })
Ok(T, {})
})
it('Should check undefined for optional property of number', () => {
const T = Type.Object({ x: Type.Optional(Type.Number()) })
Ok(T, { x: 1 })
Ok(T, { x: undefined }) // allowed by default
Ok(T, {})
})
it('Should check undefined for optional property of undefined', () => {
const T = Type.Object({ x: Type.Optional(Type.Undefined()) })
Fail(T, { x: 1 })
Ok(T, { x: undefined })
Ok(T, {})
})
it('Should check for required property of any', () => {
const T = Type.Object({ x: Type.Any() })
Fail(T, {})
Ok(T, { x: undefined })
Ok(T, { x: 1 })
Ok(T, { x: true })
})
it('Should check for required property of unknown', () => {
const T = Type.Object({ x: Type.Unknown() })
Fail(T, {})
Ok(T, { x: undefined })
Ok(T, { x: 1 })
Ok(T, { x: true })
})
it('Should check for required property of any (when optional)', () => {
const T = Type.Object({ x: Type.Optional(Type.Any()) })
Ok(T, {})
Ok(T, { x: undefined })
Ok(T, { x: 1 })
Ok(T, { x: true })
})
it('Should check for required property of unknown (when optional)', () => {
const T = Type.Object({ x: Type.Optional(Type.Unknown()) })
Ok(T, {})
Ok(T, { x: undefined })
Ok(T, { x: 1 })
Ok(T, { x: true })
})
})

View File

@@ -0,0 +1,83 @@
import { Type, Kind } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
import { deepEqual, strictEqual } from 'assert'
describe('compiler/Omit', () => {
it('Should omit properties on the source schema', () => {
const A = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Omit(A, ['z'])
Ok(T, { x: 1, y: 1 })
})
it('Should remove required properties on the target schema', () => {
const A = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Omit(A, ['z'])
strictEqual(T.required!.includes('z'), false)
})
it('Should inherit options from the source object', () => {
const A = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Omit(A, ['z'])
strictEqual(A.additionalProperties, false)
strictEqual(T.additionalProperties, false)
})
it('Should omit with keyof object', () => {
const A = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
})
const B = Type.Object({
x: Type.Number(),
y: Type.Number(),
})
const T = Type.Omit(A, Type.KeyOf(B), { additionalProperties: false })
Ok(T, { z: 0 })
Fail(T, { x: 0, y: 0, z: 0 })
})
it('Should support Omit of Literal', () => {
const A = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
})
const T = Type.Omit(A, Type.Literal('x'), {
additionalProperties: false,
})
Ok(T, { y: 1, z: 1 })
Fail(T, { x: 1, y: 1, z: 1 })
})
it('Should support Omit of Never', () => {
const A = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Omit(A, Type.Never())
Fail(T, { y: 1, z: 1 })
Ok(T, { x: 1, y: 1, z: 1 })
})
})

View File

@@ -0,0 +1,27 @@
import { strictEqual } from 'assert'
import { Type } from '@sinclair/typebox'
import { Ok } from './validate'
describe('compiler/Optional', () => {
it('Should validate object with optional', () => {
const T = Type.Object(
{
a: Type.Optional(Type.String()),
b: Type.String(),
},
{ additionalProperties: false },
)
Ok(T, { a: 'hello', b: 'world' })
Ok(T, { b: 'world' })
})
it('Should remove required value from schema', () => {
const T = Type.Object(
{
a: Type.Optional(Type.String()),
b: Type.String(),
},
{ additionalProperties: false },
)
strictEqual(T.required!.includes('a'), false)
})
})

View File

@@ -0,0 +1,53 @@
import { TypeSystem } from '@sinclair/typebox/system'
import { Type, OptionalKind, ReadonlyKind } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
import { strictEqual } from 'assert'
describe('compiler/Partial', () => {
it('Should convert a required object into a partial', () => {
const A = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Partial(A)
Ok(T, { x: 1, y: 1, z: 1 })
Ok(T, { x: 1, y: 1 })
Ok(T, { x: 1 })
Ok(T, {})
})
it('Should update modifier types correctly when converting to partial', () => {
const A = Type.Object(
{
x: Type.Readonly(Type.Optional(Type.Number())),
y: Type.Readonly(Type.Number()),
z: Type.Optional(Type.Number()),
w: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Partial(A)
strictEqual(T.properties.x[ReadonlyKind], 'Readonly')
strictEqual(T.properties.x[OptionalKind], 'Optional')
strictEqual(T.properties.y[ReadonlyKind], 'Readonly')
strictEqual(T.properties.y[OptionalKind], 'Optional')
strictEqual(T.properties.z[OptionalKind], 'Optional')
strictEqual(T.properties.w[OptionalKind], 'Optional')
})
it('Should inherit options from the source object', () => {
const A = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Partial(A)
strictEqual(A.additionalProperties, false)
strictEqual(T.additionalProperties, false)
})
})

View File

@@ -0,0 +1,82 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
import { strictEqual } from 'assert'
describe('compiler/Pick', () => {
it('Should pick properties from the source schema', () => {
const Vector3 = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Pick(Vector3, ['x', 'y'])
Ok(T, { x: 1, y: 1 })
})
it('Should remove required properties on the target schema', () => {
const A = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Pick(A, ['x', 'y'])
strictEqual(T.required!.includes('z'), false)
})
it('Should inherit options from the source object', () => {
const A = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ additionalProperties: false },
)
const T = Type.Pick(A, ['x', 'y'])
strictEqual(A.additionalProperties, false)
strictEqual(T.additionalProperties, false)
})
it('Should pick with keyof object', () => {
const A = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
})
const B = Type.Object({
x: Type.Number(),
y: Type.Number(),
})
const T = Type.Pick(A, Type.KeyOf(B), { additionalProperties: false })
Ok(T, { x: 0, y: 0 })
Fail(T, { x: 0, y: 0, z: 0 })
})
it('Should support Pick of Literal', () => {
const A = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
})
const T = Type.Pick(A, Type.Literal('x'), {
additionalProperties: false,
})
Ok(T, { x: 1 })
Fail(T, { x: 1, y: 1, z: 1 })
})
it('Should support Pick of Never', () => {
const A = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
})
const T = Type.Pick(A, Type.Never(), {
additionalProperties: false,
})
Fail(T, { x: 1, y: 1, z: 1 })
Ok(T, {})
})
})

View File

@@ -0,0 +1,27 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
import { strictEqual } from 'assert'
describe('compiler/ReadonlyOptional', () => {
it('Should validate object with optional', () => {
const T = Type.Object(
{
a: Type.Readonly(Type.Optional(Type.String())),
b: Type.String(),
},
{ additionalProperties: false },
)
Ok(T, { a: 'hello', b: 'world' })
Ok(T, { b: 'world' })
})
it('Should remove required value from schema', () => {
const T = Type.Object(
{
a: Type.Readonly(Type.Optional(Type.String())),
b: Type.String(),
},
{ additionalProperties: false },
)
strictEqual(T.required!.includes('a'), false)
})
})

View File

@@ -0,0 +1,27 @@
import { deepStrictEqual, strictEqual } from 'assert'
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Readonly', () => {
it('Should validate object with readonly', () => {
const T = Type.Object(
{
a: Type.Readonly(Type.String()),
b: Type.Readonly(Type.String()),
},
{ additionalProperties: false },
)
Ok(T, { a: 'hello', b: 'world' })
})
it('Should retain required array on object', () => {
const T = Type.Object(
{
a: Type.Readonly(Type.String()),
b: Type.Readonly(Type.String()),
},
{ additionalProperties: false },
)
strictEqual(T.required!.includes('a'), true)
strictEqual(T.required!.includes('b'), true)
})
})

View File

@@ -0,0 +1,324 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Record', () => {
// -------------------------------------------------------------
// Issues
// -------------------------------------------------------------
it('Issue: https://github.com/sinclairzx81/typebox/issues/402', () => {
const T = Type.Object({
foo: Type.Object({
bar: Type.Record(Type.String(), Type.Number()),
}),
})
Ok(T, { foo: { bar: { x: 42 } } })
Ok(T, { foo: { bar: {} } })
Fail(T, { foo: { bar: { x: '42' } } })
Fail(T, { foo: { bar: [] } })
Fail(T, { foo: {} })
Fail(T, { foo: [] })
Fail(T, {})
})
// -------------------------------------------------------------
// TypeBox Only: Date and Record
// -------------------------------------------------------------
it('Should fail record with Date', () => {
const T = Type.Record(Type.String(), Type.String())
Fail(T, new Date())
})
it('Should fail record with Uint8Array', () => {
const T = Type.Record(Type.String(), Type.String())
Fail(T, new Uint8Array())
})
// -------------------------------------------------------------
// Standard Assertions
// -------------------------------------------------------------
it('Should validate when all property values are numbers', () => {
const T = Type.Record(Type.String(), Type.Number())
Ok(T, { a: 1, b: 2, c: 3 })
})
it('Should validate when all property keys are strings', () => {
const T = Type.Record(Type.String(), Type.Number())
Ok(T, { a: 1, b: 2, c: 3, '0': 4 })
})
it('Should not validate when below minProperties', () => {
const T = Type.Record(Type.String(), Type.Number(), { minProperties: 4 })
Ok(T, { a: 1, b: 2, c: 3, d: 4 })
Fail(T, { a: 1, b: 2, c: 3 })
})
it('Should not validate when above maxProperties', () => {
const T = Type.Record(Type.String(), Type.Number(), { maxProperties: 4 })
Ok(T, { a: 1, b: 2, c: 3, d: 4 })
Fail(T, { a: 1, b: 2, c: 3, d: 4, e: 5 })
})
it('Should not validate with illogical minProperties | maxProperties', () => {
const T = Type.Record(Type.String(), Type.Number(), { minProperties: 5, maxProperties: 4 })
Fail(T, { a: 1, b: 2, c: 3 })
Fail(T, { a: 1, b: 2, c: 3, d: 4 })
Fail(T, { a: 1, b: 2, c: 3, d: 4, e: 5 })
})
it('Should validate when specifying string union literals when additionalProperties is true', () => {
const K = Type.Union([Type.Literal('a'), Type.Literal('b'), Type.Literal('c')])
const T = Type.Record(K, Type.Number())
Ok(T, { a: 1, b: 2, c: 3, d: 'hello' })
})
it('Should not validate when specifying string union literals when additionalProperties is false', () => {
const K = Type.Union([Type.Literal('a'), Type.Literal('b'), Type.Literal('c')])
const T = Type.Record(K, Type.Number(), { additionalProperties: false })
Fail(T, { a: 1, b: 2, c: 3, d: 'hello' })
})
it('Should validate for keyof records', () => {
const T = Type.Object({
a: Type.String(),
b: Type.Number(),
c: Type.String(),
})
const R = Type.Record(Type.KeyOf(T), Type.Number())
Ok(R, { a: 1, b: 2, c: 3 })
})
it('Should not validate for unknown key via keyof', () => {
const T = Type.Object({
a: Type.String(),
b: Type.Number(),
c: Type.String(),
})
const R = Type.Record(Type.KeyOf(T), Type.Number(), { additionalProperties: false })
Fail(R, { a: 1, b: 2, c: 3, d: 4 })
})
it('Should validate when specifying regular expressions', () => {
const K = Type.RegExp(/^op_.*$/)
const T = Type.Record(K, Type.Number())
Ok(T, {
op_a: 1,
op_b: 2,
op_c: 3,
})
})
it('Should not validate when specifying regular expressions and passing invalid property', () => {
const K = Type.RegExp(/^op_.*$/)
const T = Type.Record(K, Type.Number(), { additionalProperties: false })
Fail(T, {
op_a: 1,
op_b: 2,
aop_c: 3,
})
})
it('Should validate with quoted string pattern', () => {
const K = Type.String({ pattern: "'(a|b|c)" })
const T = Type.Record(K, Type.Number())
Ok(T, {
"'a": 1,
"'b": 2,
"'c": 3,
})
})
it('Should validate with forward-slash pattern', () => {
const K = Type.String({ pattern: '/(a|b|c)' })
const T = Type.Record(K, Type.Number())
Ok(T, {
'/a': 1,
'/b': 2,
'/c': 3,
})
})
// ------------------------------------------------------------
// Integer Keys
// ------------------------------------------------------------
it('Should validate when all property keys are integers', () => {
const T = Type.Record(Type.Integer(), Type.Number())
Ok(T, { '0': 1, '1': 2, '2': 3, '3': 4 })
})
it('Should validate when all property keys are integers, but one property is a string with varying type', () => {
const T = Type.Record(Type.Integer(), Type.Number(), { additionalProperties: false })
Fail(T, { '0': 1, '1': 2, '2': 3, '3': 4, a: 'hello' })
})
it('Should not validate if passing a leading zeros for integers keys', () => {
const T = Type.Record(Type.Integer(), Type.Number(), { additionalProperties: false })
Fail(T, {
'00': 1,
'01': 2,
'02': 3,
'03': 4,
})
})
it('Should not validate if passing a signed integers keys', () => {
const T = Type.Record(Type.Integer(), Type.Number(), { additionalProperties: false })
Fail(T, {
'-0': 1,
'-1': 2,
'-2': 3,
'-3': 4,
})
})
// ------------------------------------------------------------
// Number Keys
// ------------------------------------------------------------
it('Should validate when all property keys are numbers', () => {
const T = Type.Record(Type.Number(), Type.Number())
Ok(T, { '0': 1, '1': 2, '2': 3, '3': 4 })
})
it('Should validate when all property keys are numbers, but one property is a string with varying type', () => {
const T = Type.Record(Type.Number(), Type.Number(), { additionalProperties: false })
Fail(T, { '0': 1, '1': 2, '2': 3, '3': 4, a: 'hello' })
})
it('Should not validate if passing a leading zeros for numeric keys', () => {
const T = Type.Record(Type.Number(), Type.Number(), { additionalProperties: false })
Fail(T, {
'00': 1,
'01': 2,
'02': 3,
'03': 4,
})
})
it('Should not validate if passing a signed numeric keys', () => {
const T = Type.Record(Type.Number(), Type.Number(), { additionalProperties: false })
Fail(T, {
'-0': 1,
'-1': 2,
'-2': 3,
'-3': 4,
})
})
it('Should not validate when all property keys are numbers, but one property is a string with varying type', () => {
const T = Type.Record(Type.Number(), Type.Number(), { additionalProperties: false })
Fail(T, { '0': 1, '1': 2, '2': 3, '3': 4, a: 'hello' })
})
// ------------------------------------------------------------
// AdditionalProperties
// ------------------------------------------------------------
it('AdditionalProperties 1', () => {
const T = Type.Record(Type.Number(), Type.String(), { additionalProperties: true })
Ok(T, { 1: '', 2: '', x: 1, y: 2, z: 3 })
})
it('AdditionalProperties 2', () => {
const T = Type.Record(Type.Number(), Type.String(), { additionalProperties: false })
Ok(T, { 1: '', 2: '', 3: '' })
})
it('AdditionalProperties 3', () => {
const T = Type.Record(Type.Number(), Type.String(), { additionalProperties: false })
Fail(T, { 1: '', 2: '', x: '' })
})
it('AdditionalProperties 4', () => {
const T = Type.Record(Type.Number(), Type.String(), { additionalProperties: Type.Boolean() })
Fail(T, { 1: '', 2: '', x: '' })
})
it('AdditionalProperties 5', () => {
const T = Type.Record(Type.Number(), Type.String(), { additionalProperties: Type.Boolean() })
Ok(T, { 1: '', 2: '', x: true })
})
// ----------------------------------------------------------------
// TemplateLiteral
// ----------------------------------------------------------------
it('TemplateLiteral 1', () => {
const K = Type.TemplateLiteral('key${number}')
const R = Type.Record(K, Type.Number(), { additionalProperties: false })
Ok(R, {
key0: 1,
key1: 1,
key2: 1,
})
})
it('TemplateLiteral 2', () => {
const K = Type.TemplateLiteral('key${number}')
const R = Type.Record(K, Type.Number())
Ok(R, { keyA: 0 })
})
it('TemplateLiteral 3', () => {
const K = Type.TemplateLiteral('key${number}')
const R = Type.Record(K, Type.Number(), { additionalProperties: false })
Fail(R, { keyA: 0 })
})
it('TemplateLiteral 4', () => {
const K = Type.TemplateLiteral('key${number}')
const R = Type.Record(K, Type.Number())
const T = Type.Object({ x: Type.Number(), y: Type.Number() })
const I = Type.Intersect([R, T], { unevaluatedProperties: false })
Ok(I, {
x: 1,
y: 2,
key0: 1,
key1: 1,
key2: 1,
})
})
it('TemplateLiteral 5', () => {
const K = Type.TemplateLiteral('key${number}')
const R = Type.Record(K, Type.Number())
const T = Type.Object({ x: Type.Number(), y: Type.Number() })
const I = Type.Intersect([R, T])
Ok(I, {
x: 1,
y: 2,
z: 3,
key0: 1,
key1: 1,
key2: 1,
})
})
it('TemplateLiteral 6', () => {
const K = Type.TemplateLiteral('key${number}')
const R = Type.Record(K, Type.Number())
const T = Type.Object({ x: Type.Number(), y: Type.Number() })
const I = Type.Intersect([R, T], { unevaluatedProperties: false })
Fail(I, {
x: 1,
y: 2,
z: 3,
key0: 1,
key1: 1,
key2: 1,
})
})
// ----------------------------------------------------------------
// https://github.com/sinclairzx81/typebox/issues/916
// ----------------------------------------------------------------
it('Should validate for string keys', () => {
const T = Type.Record(Type.String(), Type.Null(), {
additionalProperties: false,
})
Ok(T, {
a: null,
b: null,
0: null,
1: null,
})
})
it('Should validate for number keys', () => {
const T = Type.Record(Type.Number(), Type.Null(), {
additionalProperties: false,
})
Fail(T, {
a: null,
b: null,
0: null,
1: null,
})
Ok(T, {
0: null,
1: null,
})
})
it('Should validate for any keys', () => {
const T = Type.Record(Type.Any(), Type.Null(), {
additionalProperties: false,
})
Ok(T, {
a: null,
b: null,
0: null,
1: null,
})
})
it('Should validate for never keys', () => {
const T = Type.Record(Type.Never(), Type.Null(), {
additionalProperties: false,
})
Ok(T, {})
Fail(T, {
a: null,
b: null,
0: null,
1: null,
})
})
})

View File

@@ -0,0 +1,79 @@
import { Type } from '@sinclair/typebox'
import { Assert } from '../assert/index'
import { Ok, Fail } from './validate'
describe('compiler/Recursive', () => {
it('Should generate default ordinal $id if not specified', () => {
const Node = Type.Recursive((Node) =>
Type.Object({
id: Type.String(),
nodes: Type.Array(Node),
}),
)
Assert.IsEqual(Node.$id === undefined, false)
})
it('Should override default ordinal $id if specified', () => {
const Node = Type.Recursive(
(Node) =>
Type.Object({
id: Type.String(),
nodes: Type.Array(Node),
}),
{ $id: 'Node' },
)
Assert.IsEqual(Node.$id === 'Node', true)
})
it('Should validate recursive node type', () => {
const Node = Type.Recursive((This) =>
Type.Object({
id: Type.String(),
nodes: Type.Array(This),
}),
)
Ok(Node, {
id: 'A',
nodes: [
{ id: 'B', nodes: [] },
{ id: 'C', nodes: [] },
],
})
})
it('Should validate wrapped recursive node type', () => {
const Node = Type.Tuple([
Type.Recursive((This) =>
Type.Object({
id: Type.String(),
nodes: Type.Array(This),
}),
),
])
Ok(Node, [
{
id: 'A',
nodes: [
{ id: 'B', nodes: [] },
{ id: 'C', nodes: [] },
],
},
])
})
it('Should not validate wrapped recursive node type with invalid id', () => {
const Node = Type.Tuple([
Type.Recursive((This) =>
Type.Object({
id: Type.String(),
nodes: Type.Array(This),
}),
),
])
Fail(Node, [
{
id: 'A',
nodes: [
{ id: 1, nodes: [] },
{ id: 'C', nodes: [] },
],
},
])
})
})

View File

@@ -0,0 +1,89 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
import { Assert } from '../assert/index'
describe('compiler/Ref', () => {
// ----------------------------------------------------------------
// Deprecated
// ----------------------------------------------------------------
it('Should validate for Ref(Schema)', () => {
const T = Type.Number({ $id: 'T' })
const R = Type.Ref(T)
Ok(R, 1234, [T])
Fail(R, 'hello', [T])
})
// ----------------------------------------------------------------
// Standard
// ----------------------------------------------------------------
it('Should should validate when referencing a type', () => {
const T = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ $id: Assert.NextId() },
)
const R = Type.Ref(T.$id!)
Ok(
R,
{
x: 1,
y: 2,
z: 3,
},
[T],
)
})
it('Should not validate when passing invalid data', () => {
const T = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ $id: Assert.NextId() },
)
const R = Type.Ref(T.$id!)
Fail(
R,
{
x: 1,
y: 2,
},
[T],
)
})
it('Should de-reference object property schema', () => {
const T = Type.Object(
{
name: Type.String(),
},
{ $id: 'R' },
)
const R = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
r: Type.Optional(Type.Ref(T.$id!)),
},
{ $id: 'T' },
)
Ok(R, { x: 1, y: 2, z: 3 }, [T])
Ok(R, { x: 1, y: 2, z: 3, r: { name: 'hello' } }, [T])
Fail(R, { x: 1, y: 2, z: 3, r: { name: 1 } }, [T])
Fail(R, { x: 1, y: 2, z: 3, r: {} }, [T])
})
it('Should reference recursive schema', () => {
const T = Type.Recursive((Node) =>
Type.Object({
id: Type.String(),
nodes: Type.Array(Node),
}),
)
const R = Type.Ref(T.$id!)
Ok(R, { id: '', nodes: [{ id: '', nodes: [] }] }, [T])
Fail(R, { id: '', nodes: [{ id: 1, nodes: [] }] }, [T])
})
})

View File

@@ -0,0 +1,30 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/RegExp', () => {
it('Should validate regular expression 1', () => {
const T = Type.RegExp(/foo/i)
Ok(T, 'foo')
Ok(T, 'Foo')
Ok(T, 'fOO')
Fail(T, 'bar')
})
it('Should validate regular expression 2', () => {
const T = Type.RegExp(/<a?:.+?:\d{18}>|\p{Extended_Pictographic}/gu)
Ok(T, '♥️♦️♠️♣️')
})
it('Should validate with minLength constraint', () => {
const T = Type.RegExp(/(.*)/, {
minLength: 3,
})
Ok(T, 'xxx')
Fail(T, 'xx')
})
it('Should validate with maxLength constraint', () => {
const T = Type.RegExp(/(.*)/, {
maxLength: 3,
})
Ok(T, 'xxx')
Fail(T, 'xxxx')
})
})

View File

@@ -0,0 +1,55 @@
import { Type, ReadonlyKind, OptionalKind } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
import { strictEqual } from 'assert'
describe('compiler/Required', () => {
it('Should convert a partial object into a required object', () => {
const A = Type.Object(
{
x: Type.Optional(Type.Number()),
y: Type.Optional(Type.Number()),
z: Type.Optional(Type.Number()),
},
{ additionalProperties: false },
)
const T = Type.Required(A)
Ok(T, { x: 1, y: 1, z: 1 })
Fail(T, { x: 1, y: 1 })
Fail(T, { x: 1 })
Fail(T, {})
})
it('Should update modifier types correctly when converting to required', () => {
const A = Type.Object({
x: Type.Readonly(Type.Optional(Type.Number())),
y: Type.Readonly(Type.Number()),
z: Type.Optional(Type.Number()),
w: Type.Number(),
})
const T = Type.Required(A)
strictEqual(T.properties.x[ReadonlyKind], 'Readonly')
strictEqual(T.properties.y[ReadonlyKind], 'Readonly')
strictEqual(T.properties.z[OptionalKind], undefined)
strictEqual(T.properties.w[OptionalKind], undefined)
})
it('Should inherit options from the source object', () => {
const A = Type.Object(
{
x: Type.Optional(Type.Number()),
y: Type.Optional(Type.Number()),
z: Type.Optional(Type.Number()),
},
{ additionalPropeties: false },
)
const T = Type.Required(A)
strictEqual(A.additionalPropeties, false)
strictEqual(T.additionalPropeties, false)
})
// it('Should construct new object when targetting reference', () => {
// const T = Type.Object({ a: Type.String(), b: Type.String() }, { $id: 'T' })
// const R = Type.Ref(T)
// const P = Type.Required(R)
// strictEqual(P.properties.a.type, 'string')
// strictEqual(P.properties.b.type, 'string')
// })
})

View File

@@ -0,0 +1,65 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/StringPattern', () => {
//-----------------------------------------------------
// Regular Expression
//-----------------------------------------------------
it('Should validate regular expression 1', () => {
const T = Type.String({ pattern: /[012345]/.source })
Ok(T, '0')
Ok(T, '1')
Ok(T, '2')
Ok(T, '3')
Ok(T, '4')
Ok(T, '5')
})
it('Should validate regular expression 2', () => {
const T = Type.String({ pattern: /true|false/.source })
Ok(T, 'true')
Ok(T, 'true')
Ok(T, 'true')
Ok(T, 'false')
Ok(T, 'false')
Ok(T, 'false')
Fail(T, '6')
})
it('Should validate regular expression 3', () => {
const T = Type.String({ pattern: /true|false/.source })
Fail(T, 'unknown')
})
it('Should validate regular expression 4', () => {
const T = Type.String({ pattern: /[\d]{5}/.source })
Ok(T, '12345')
})
//-----------------------------------------------------
// Regular Pattern
//-----------------------------------------------------
it('Should validate pattern 1', () => {
const T = Type.String({ pattern: '[012345]' })
Ok(T, '0')
Ok(T, '1')
Ok(T, '2')
Ok(T, '3')
Ok(T, '4')
Ok(T, '5')
})
it('Should validate pattern 2', () => {
const T = Type.String({ pattern: 'true|false' })
Ok(T, 'true')
Ok(T, 'true')
Ok(T, 'true')
Ok(T, 'false')
Ok(T, 'false')
Ok(T, 'false')
Fail(T, '6')
})
it('Should validate pattern 3', () => {
const T = Type.String({ pattern: 'true|false' })
Fail(T, 'unknown')
})
it('Should validate pattern 4', () => {
const T = Type.String({ pattern: '[\\d]{5}' })
Ok(T, '12345')
})
})

View File

@@ -0,0 +1,71 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/String', () => {
it('Should not validate number', () => {
const T = Type.String()
Fail(T, 1)
})
it('Should validate string', () => {
const T = Type.String()
Ok(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.String()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.String()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.String()
Fail(T, { a: 1, b: 2 })
})
it('Should not validate null', () => {
const T = Type.String()
Fail(T, null)
})
it('Should not validate undefined', () => {
const T = Type.String()
Fail(T, undefined)
})
it('Should not validate bigint', () => {
const T = Type.String()
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.String()
Fail(T, Symbol(1))
})
it('Should validate string format as email', () => {
const T = Type.String({ format: 'email' })
Ok(T, 'name@domain.com')
})
it('Should validate string format as uuid', () => {
const T = Type.String({ format: 'uuid' })
Ok(T, '4a7a17c9-2492-4a53-8e13-06ea2d3f3bbf')
})
it('Should validate string format as iso8601 date', () => {
const T = Type.String({ format: 'date-time' })
Ok(T, '2021-06-11T20:30:00-04:00')
})
it('Should validate minLength', () => {
const T = Type.String({ minLength: 4 })
Ok(T, '....')
Fail(T, '...')
})
it('Should validate maxLength', () => {
const T = Type.String({ maxLength: 4 })
Ok(T, '....')
Fail(T, '.....')
})
it('Should pass numeric 5 digit test', () => {
const T = Type.String({ pattern: '[\\d]{5}' })
Ok(T, '12345')
})
it('Should should escape characters in the pattern', () => {
const T = Type.String({ pattern: '/a/' })
Ok(T, '/a/')
})
})

View File

@@ -0,0 +1,42 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Symbol', () => {
it('Should not validate a boolean', () => {
const T = Type.Symbol()
Fail(T, true)
Fail(T, false)
})
it('Should not validate a number', () => {
const T = Type.Symbol()
Fail(T, 1)
})
it('Should not validate a string', () => {
const T = Type.Symbol()
Fail(T, 'true')
})
it('Should not validate an array', () => {
const T = Type.Symbol()
Fail(T, [true])
})
it('Should not validate an object', () => {
const T = Type.Symbol()
Fail(T, {})
})
it('Should not validate an null', () => {
const T = Type.Symbol()
Fail(T, null)
})
it('Should not validate an undefined', () => {
const T = Type.Symbol()
Fail(T, undefined)
})
it('Should not validate bigint', () => {
const T = Type.Symbol()
Fail(T, BigInt(1))
})
it('Should not validate symbol', () => {
const T = Type.Symbol()
Ok(T, Symbol(1))
})
})

View File

@@ -0,0 +1,209 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/TemplateLiteral', () => {
// --------------------------------------------------------
// Finite
// --------------------------------------------------------
it('Should validate finite pattern 1', () => {
// prettier-ignore
const T = Type.TemplateLiteral([])
Ok(T, '')
Fail(T, 'X')
})
it('Should validate finite pattern 1', () => {
// prettier-ignore
const T = Type.TemplateLiteral([Type.Boolean()])
Ok(T, 'true')
Ok(T, 'false')
Fail(T, 'X')
})
it('Should validate finite pattern 2', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Literal('A')
])
Ok(T, 'A')
Fail(T, 'X')
})
it('Should validate finite pattern 3', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Literal('A'),
Type.Literal('B')
])
Ok(T, 'AB')
Fail(T, 'X')
})
it('Should validate finite pattern 4', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Literal('A'),
Type.Union([
Type.Literal('B'),
Type.Literal('C')
]),
])
Ok(T, 'AB')
Ok(T, 'AC')
Fail(T, 'X')
})
it('Should validate finite pattern 5', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Literal('A'),
Type.Union([
Type.Literal('B'),
Type.Literal('C')
]),
Type.Literal('D'),
])
Ok(T, 'ABD')
Ok(T, 'ACD')
Fail(T, 'X')
})
it('Should validate finite pattern 6', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Union([
Type.Literal('0'),
Type.Literal('1')
]),
Type.Union([
Type.Literal('0'),
Type.Literal('1')
]),
])
Ok(T, '00')
Ok(T, '01')
Ok(T, '10')
Ok(T, '11')
Fail(T, 'X')
})
// --------------------------------------------------------
// Infinite
// --------------------------------------------------------
it('Should validate infinite pattern 1', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Number()
])
Ok(T, '1')
Ok(T, '22')
Ok(T, '333')
Ok(T, '4444')
Fail(T, 'X')
})
it('Should validate infinite pattern 2', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Integer()
])
Ok(T, '1')
Ok(T, '22')
Ok(T, '333')
Ok(T, '4444')
Fail(T, 'X')
})
it('Should validate infinite pattern 3', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.BigInt()
])
Ok(T, '1')
Ok(T, '22')
Ok(T, '333')
Ok(T, '4444')
Fail(T, 'X')
})
it('Should validate infinite pattern 4', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.String()
])
Ok(T, '1')
Ok(T, '22')
Ok(T, '333')
Ok(T, '4444')
Ok(T, 'a')
Ok(T, 'bb')
Ok(T, 'ccc')
Ok(T, 'dddd')
})
it('Should validate infinite pattern 5', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Literal('A'),
Type.Number()
])
Ok(T, 'A1')
Ok(T, 'A22')
Ok(T, 'A333')
Ok(T, 'A4444')
Fail(T, 'X')
})
it('Should validate infinite pattern 6', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Literal('A'),
Type.Integer()
])
Ok(T, 'A1')
Ok(T, 'A22')
Ok(T, 'A333')
Ok(T, 'A4444')
Fail(T, 'X')
})
it('Should validate infinite pattern 7', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Literal('A'),
Type.BigInt()
])
Ok(T, 'A1')
Ok(T, 'A22')
Ok(T, 'A333')
Ok(T, 'A4444')
Fail(T, 'X')
})
it('Should validate infinite pattern 8', () => {
// prettier-ignore
const T = Type.TemplateLiteral([
Type.Literal('A'),
Type.String()
])
Ok(T, 'A1')
Ok(T, 'A22')
Ok(T, 'A333')
Ok(T, 'A4444')
Ok(T, 'Aa')
Ok(T, 'Abb')
Ok(T, 'Accc')
Ok(T, 'Adddd')
Fail(T, 'X')
})
it('Should validate enum (implicit)', () => {
enum E {
A,
B,
C,
}
const T = Type.TemplateLiteral([Type.Literal('hello'), Type.Enum(E)])
Ok(T, 'hello0')
Ok(T, 'hello1')
Ok(T, 'hello2')
Fail(T, 'hello3')
})
it('Should validate enum (explicit)', () => {
enum E {
A,
B = 'B',
C = 'C',
}
const T = Type.TemplateLiteral([Type.Literal('hello'), Type.Enum(E)])
Ok(T, 'hello0')
Ok(T, 'helloB')
Ok(T, 'helloC')
Fail(T, 'helloD')
})
})

View File

@@ -0,0 +1,53 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Tuple', () => {
it('Should validate tuple of [string, number]', () => {
const A = Type.String()
const B = Type.Number()
const T = Type.Tuple([A, B])
Ok(T, ['hello', 42])
})
it('Should not validate tuple of [string, number] when reversed', () => {
const A = Type.String()
const B = Type.Number()
const T = Type.Tuple([A, B])
Fail(T, [42, 'hello'])
})
it('Should validate with empty tuple', () => {
const T = Type.Tuple([])
Ok(T, [])
})
it('Should not validate with empty tuple with more items', () => {
const T = Type.Tuple([])
Fail(T, [1])
})
it('Should not validate with empty tuple with less items', () => {
const T = Type.Tuple([Type.Number(), Type.Number()])
Fail(T, [1])
})
it('Should validate tuple of objects', () => {
const A = Type.Object({ a: Type.String() })
const B = Type.Object({ b: Type.Number() })
const T = Type.Tuple([A, B])
Ok(T, [{ a: 'hello' }, { b: 42 }])
})
it('Should not validate tuple of objects when reversed', () => {
const A = Type.Object({ a: Type.String() })
const B = Type.Object({ b: Type.Number() })
const T = Type.Tuple([A, B])
Fail(T, [{ b: 42 }, { a: 'hello' }])
})
it('Should not validate tuple when array is less than tuple length', () => {
const A = Type.Object({ a: Type.String() })
const B = Type.Object({ b: Type.Number() })
const T = Type.Tuple([A, B])
Fail(T, [{ a: 'hello' }])
})
it('Should not validate tuple when array is greater than tuple length', () => {
const A = Type.Object({ a: Type.String() })
const B = Type.Object({ b: Type.Number() })
const T = Type.Tuple([A, B])
Fail(T, [{ a: 'hello' }, { b: 42 }, { b: 42 }])
})
})

View File

@@ -0,0 +1,47 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Uint8Array', () => {
it('Should not validate number', () => {
const T = Type.Uint8Array()
Fail(T, 1)
})
it('Should not validate string', () => {
const T = Type.Uint8Array()
Fail(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.Uint8Array()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.Uint8Array()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.Uint8Array()
Fail(T, { a: 1, b: 2 })
})
it('Should not validate null', () => {
const T = Type.Uint8Array()
Fail(T, null)
})
it('Should not validate undefined', () => {
const T = Type.Uint8Array()
Fail(T, undefined)
})
it('Should validate Uint8Array', () => {
const T = Type.Uint8Array()
Ok(T, new Uint8Array(100))
})
it('Should validate minByteLength', () => {
const T = Type.Uint8Array({ minByteLength: 4 })
Ok(T, new Uint8Array(4))
Fail(T, new Uint8Array(3))
})
it('Should validate maxByteLength', () => {
const T = Type.Uint8Array({ maxByteLength: 4 })
Ok(T, new Uint8Array(4))
Fail(T, new Uint8Array(5))
})
})

View File

@@ -0,0 +1,84 @@
import { Type } from '@sinclair/typebox'
import { Ok } from './validate'
describe('compiler/Unicode', () => {
// ---------------------------------------------------------
// Identifiers
// ---------------------------------------------------------
it('Should support unicode identifiers', () => {
const T = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
$id: '식별자',
},
)
Ok(T, {
x: 1,
y: 2,
})
})
it('Should support unicode identifier references', () => {
const R = Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
$id: '식별자',
},
)
const T = Type.Object({
vector: Type.Ref(R.$id!),
})
Ok(
T,
{
vector: {
x: 1,
y: 2,
},
},
[R],
)
})
it('Should support unicode identifier recursion', () => {
const Node = Type.Recursive(
(Node) =>
Type.Object({
id: Type.String(),
nodes: Type.Array(Node),
}),
{
$id: '식별자',
},
)
Ok(Node, {
id: 'A',
nodes: [
{
id: 'B',
nodes: [
{
id: 'C',
nodes: [],
},
],
},
],
})
})
// ---------------------------------------------------------
// Properties
// ---------------------------------------------------------
it('Should support unicode properties', () => {
const T = Type.Object({
이름: Type.String(),
})
Ok(T, {
: 'dave',
})
})
})

View File

@@ -0,0 +1,56 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Union', () => {
it('Should validate union of string, number and boolean', () => {
const A = Type.String()
const B = Type.Number()
const C = Type.Boolean()
const T = Type.Union([A, B, C])
Ok(T, 'hello')
Ok(T, true)
Ok(T, 42)
})
it('Should validate union of objects', () => {
const A = Type.Object({ a: Type.String() }, { additionalProperties: false })
const B = Type.Object({ b: Type.String() }, { additionalProperties: false })
const T = Type.Union([A, B])
Ok(T, { a: 'hello' })
Ok(T, { b: 'world' })
})
it('Should fail to validate for descriminated union types', () => {
const A = Type.Object({ kind: Type.Literal('A'), value: Type.String() })
const B = Type.Object({ kind: Type.Literal('B'), value: Type.Number() })
const T = Type.Union([A, B])
Fail(T, { kind: 'A', value: 42 }) // expect { kind: 'A', value: string }
Fail(T, { kind: 'B', value: 'hello' }) // expect { kind: 'B', value: number }
})
it('Should validate union of objects where properties overlap', () => {
const A = Type.Object({ a: Type.String() }, { additionalProperties: false })
const B = Type.Object({ a: Type.String(), b: Type.String() }, { additionalProperties: false })
const T = Type.Union([A, B])
Ok(T, { a: 'hello' }) // A
Ok(T, { a: 'hello', b: 'world' }) // B
})
it('Should validate union of overlapping property of varying type', () => {
const A = Type.Object({ a: Type.String(), b: Type.Number() }, { additionalProperties: false })
const B = Type.Object({ a: Type.String(), b: Type.String() }, { additionalProperties: false })
const T = Type.Union([A, B])
Ok(T, { a: 'hello', b: 42 }) // A
Ok(T, { a: 'hello', b: 'world' }) // B
})
it('Should validate union of literal strings', () => {
const A = Type.Literal('hello')
const B = Type.Literal('world')
const T = Type.Union([A, B])
Ok(T, 'hello') // A
Ok(T, 'world') // B
})
it('Should not validate union of literal strings for unknown string', () => {
const A = Type.Literal('hello')
const B = Type.Literal('world')
const T = Type.Union([A, B])
Fail(T, 'foo') // A
Fail(T, 'bar') // B
})
})

View File

@@ -0,0 +1,33 @@
import { Type } from '@sinclair/typebox'
import { Ok } from './validate'
describe('compiler/Unknown', () => {
it('Should validate number', () => {
const T = Type.Any()
Ok(T, 1)
})
it('Should validate string', () => {
const T = Type.Any()
Ok(T, 'hello')
})
it('Should validate boolean', () => {
const T = Type.Any()
Ok(T, true)
})
it('Should validate array', () => {
const T = Type.Any()
Ok(T, [1, 2, 3])
})
it('Should validate object', () => {
const T = Type.Any()
Ok(T, { a: 1, b: 2 })
})
it('Should validate null', () => {
const T = Type.Any()
Ok(T, null)
})
it('Should validate undefined', () => {
const T = Type.Any()
Ok(T, undefined)
})
})

View File

@@ -0,0 +1,119 @@
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Value } from '@sinclair/typebox/value'
import { TSchema, FormatRegistry } from '@sinclair/typebox'
// -------------------------------------------------------------------------
// Test Formats: https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts
//
// - date-time
// - email
// - uuid
//
// -------------------------------------------------------------------------
const EMAIL = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i
const UUID = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i
const DATE_TIME_SEPARATOR = /t|\s/i
const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i
const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
function isLeapYear(year: number): boolean {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
}
function isDate(str: string): boolean {
const matches: string[] | null = DATE.exec(str)
if (!matches) return false
const year: number = +matches[1]
const month: number = +matches[2]
const day: number = +matches[3]
return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month])
}
function isTime(str: string, strictTimeZone?: boolean): boolean {
const matches: string[] | null = TIME.exec(str)
if (!matches) return false
const hr: number = +matches[1]
const min: number = +matches[2]
const sec: number = +matches[3]
const tz: string | undefined = matches[4]
const tzSign: number = matches[5] === '-' ? -1 : 1
const tzH: number = +(matches[6] || 0)
const tzM: number = +(matches[7] || 0)
if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false
if (hr <= 23 && min <= 59 && sec < 60) return true
// leap second
const utcMin = min - tzM * tzSign
const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0)
return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61
}
function isDateTime(str: string, strictTimeZone?: boolean): boolean {
const dateTime: string[] = str.split(DATE_TIME_SEPARATOR)
return dateTime.length === 2 && isDate(dateTime[0]) && isTime(dateTime[1], strictTimeZone)
}
// -------------------------------------------------------------------------
// Use Formats
// -------------------------------------------------------------------------
FormatRegistry.Set('email', (value) => EMAIL.test(value))
FormatRegistry.Set('uuid', (value) => UUID.test(value))
FormatRegistry.Set('date-time', (value) => isDateTime(value, true))
export function Ok<T extends TSchema>(schema: T, data: unknown, references: any[] = []) {
const C = TypeCompiler.Compile(schema, references)
const result = C.Check(data)
if (result !== Value.Check(schema, references, data)) {
throw Error('Compiler and Value Check disparity')
}
if (result === false) {
const errors = [...Value.Errors(schema, references, data)]
if (errors.length === 0) throw Error('expected at least 1 error')
}
if (result === true) {
const errors = [...Value.Errors(schema, references, data)]
if (errors.length > 0) throw Error('expected no errors')
}
if (!result) {
console.log('---------------------------')
console.log('type')
console.log('---------------------------')
console.log(JSON.stringify(schema, null, 2))
console.log('---------------------------')
console.log('data')
console.log('---------------------------')
console.log(JSON.stringify(data, null, 2))
console.log('---------------------------')
console.log('errors')
console.log('---------------------------')
console.log(result)
throw Error('expected ok')
}
}
export function Fail<T extends TSchema>(schema: T, data: unknown, references: any[] = []) {
const C = TypeCompiler.Compile(schema, references)
const result = C.Check(data)
if (result !== Value.Check(schema, references, data)) {
throw Error('Compiler and Value Check disparity')
}
if (result === false) {
const errors = [...Value.Errors(schema, references, data)]
if (errors.length === 0) throw Error('expected at least 1 error')
}
if (result === true) {
const errors = [...Value.Errors(schema, references, data)]
if (errors.length > 0) throw Error('expected no errors')
}
if (result) {
console.log('---------------------------')
console.log('type')
console.log('---------------------------')
console.log(JSON.stringify(schema, null, 2))
console.log('---------------------------')
console.log('data')
console.log('---------------------------')
console.log(JSON.stringify(data, null, 2))
console.log('---------------------------')
console.log('errors')
console.log('---------------------------')
console.log('none')
throw Error('expected ok')
}
}

View File

@@ -0,0 +1,37 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Void', () => {
it('Should not validate number', () => {
const T = Type.Void()
Fail(T, 1)
})
it('Should not validate string', () => {
const T = Type.Void()
Fail(T, 'hello')
})
it('Should not validate boolean', () => {
const T = Type.Void()
Fail(T, true)
})
it('Should not validate array', () => {
const T = Type.Void()
Fail(T, [1, 2, 3])
})
it('Should not validate object', () => {
const T = Type.Void()
Fail(T, { a: 1, b: 2 })
})
it('Should validate null', () => {
const T = Type.Void()
Fail(T, null)
})
it('Should validate undefined', () => {
const T = Type.Void()
Ok(T, undefined)
})
it('Should validate void 0', () => {
const T = Type.Void()
Ok(T, void 0)
})
})