diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d64a140..a075975 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,4 +20,4 @@ jobs: - name: Build Library run: npm run build - name: Test Library - run: npm run test + run: npm run test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2c085d1..adfbc99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules target +dist diff --git a/.vscode/settings.json b/.vscode/settings.json index a923211..df31827 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,6 @@ "node_modules": true, "package-lock.json": true }, - "typescript.tsdk": "node_modules\\typescript\\lib", - "editor.suggest.showStatusBar": false + "editor.suggest.showStatusBar": false, + "typescript.tsdk": "node_modules\\typescript\\lib" } \ No newline at end of file diff --git a/benchmark/compression/module/typebox-conditional.ts b/benchmark/compression/module/typebox-conditional.ts deleted file mode 100644 index 3a12edf..0000000 --- a/benchmark/compression/module/typebox-conditional.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Conditional } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' - -const T = Conditional.Extends(Type.String(), Type.String(), Type.String(), Type.String()) diff --git a/benchmark/compression/module/typebox-custom.ts b/benchmark/compression/module/typebox-custom.ts deleted file mode 100644 index 8620c02..0000000 --- a/benchmark/compression/module/typebox-custom.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Custom } from 'src/custom/index' -import { Type } from '@sinclair/typebox' - -Custom.Set('custom', (value) => true) diff --git a/benchmark/compression/module/typebox-errors.ts b/benchmark/compression/module/typebox-errors.ts new file mode 100644 index 0000000..883bf96 --- /dev/null +++ b/benchmark/compression/module/typebox-errors.ts @@ -0,0 +1,3 @@ +import { ValueErrorType } from '@sinclair/typebox/errors' + +console.log(ValueErrorType) diff --git a/benchmark/compression/module/typebox-format.ts b/benchmark/compression/module/typebox-format.ts deleted file mode 100644 index 360a2ce..0000000 --- a/benchmark/compression/module/typebox-format.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Format } from 'src/format' -import { Type } from '@sinclair/typebox' - -Format.Set('custom', (value) => true) diff --git a/benchmark/compression/module/typebox-guard.ts b/benchmark/compression/module/typebox-guard.ts deleted file mode 100644 index 9c453c2..0000000 --- a/benchmark/compression/module/typebox-guard.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { TypeGuard } from '@sinclair/typebox/guard' -import { Type } from '@sinclair/typebox' - -const T = TypeGuard.TSchema(Type.String()) diff --git a/benchmark/compression/module/typebox-hash.ts b/benchmark/compression/module/typebox-hash.ts deleted file mode 100644 index 94c5787..0000000 --- a/benchmark/compression/module/typebox-hash.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ValueHash } from '@sinclair/typebox/hash' - -const H = ValueHash.Create(1) diff --git a/benchmark/measurement/module/cases.ts b/benchmark/measurement/module/cases.ts index fb88ceb..e59f281 100644 --- a/benchmark/measurement/module/cases.ts +++ b/benchmark/measurement/module/cases.ts @@ -1,42 +1,40 @@ import { Type } from '@sinclair/typebox' export namespace Cases { - export const Number = Type.Number() + export const Literal_String = Type.Literal('hello') - export const String = Type.String() + export const Literal_Number = Type.Literal(1) - export const Boolean = Type.Boolean() + export const Literal_Boolean = Type.Literal(true) - export const Null = Type.Null() + export const Primitive_Number = Type.Number() - export const RegEx = Type.RegEx(/foo/, { default: 'foo' }) + export const Primitive_String = Type.String() - export const ObjectA = Type.Object({ - p0: Type.String(), - p1: Type.Number(), - p2: Type.Number(), - p3: Type.Array(Type.Number(), { minItems: 4 }), - p4: Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), - }), - p5: Type.Object({ - a: Type.String(), - b: Type.String(), - c: Type.String(), + export const Primitive_String_Pattern = Type.RegEx(/foo/, { default: 'foo' }) + + export const Primitive_Boolean = Type.Boolean() + + export const Primitive_Null = Type.Null() + + export const Object_Unconstrained = Type.Object({ + number: Type.Number(), + negNumber: Type.Number(), + maxNumber: Type.Number(), + string: Type.String(), + longString: Type.String(), + boolean: Type.Boolean(), + deeplyNested: Type.Object({ + foo: Type.String(), + num: Type.Number(), + bool: Type.Boolean(), }), }) - export const ObjectB = Type.Object(ObjectA.properties, { + export const Object_Constrained = Type.Object(Object_Unconstrained.properties, { additionalProperties: false, }) - - export const Tuple = Type.Tuple([Type.String(), Type.Number(), Type.Boolean()]) - - export const Union = Type.Union([Type.Object({ x: Type.Number(), y: Type.Number() }), Type.Object({ a: Type.String(), b: Type.String() })], { default: { a: 'a', b: 'b' } }) - - export const Recursive = Type.Recursive( + export const Object_Recursive = Type.Recursive( (Recursive) => Type.Object({ id: Type.String(), @@ -75,9 +73,32 @@ export namespace Cases { }, ) - export const Vector4 = Type.Tuple([Type.Number(), Type.Number(), Type.Number(), Type.Number()]) + // prettier-ignore + export const Tuple_Primitive = Type.Tuple([ + Type.String(), + Type.Number(), + Type.Boolean() + ]) + // prettier-ignore + export const Tuple_Object = Type.Tuple([ + Type.Object({ x: Type.Number(), y: Type.Number() }), + Type.Object({ a: Type.String(), b: Type.String() }) + ]) + // prettier-ignore + export const Composite_Intersect = Type.Intersect([ + Type.Object({ x: Type.Number(), y: Type.Number() }), + Type.Object({ a: Type.String(), b: Type.String() }) + ], { default: { x: 1, y: 2, a: 'a', b: 'b' } }) - export const Matrix4 = Type.Array(Type.Array(Type.Number()), { + // prettier-ignore + export const Composite_Union = Type.Union([ + Type.Object({ x: Type.Number(), y: Type.Number() }), + Type.Object({ a: Type.String(), b: Type.String() }) + ], { default: { a: 'a', b: 'b' } }) + + export const Math_Vector4 = Type.Tuple([Type.Number(), Type.Number(), Type.Number(), Type.Number()]) + + export const Math_Matrix4 = Type.Array(Type.Array(Type.Number()), { default: [ [1, 0, 0, 0], [0, 1, 0, 0], @@ -86,29 +107,27 @@ export namespace Cases { ], }) - export const Literal_String = Type.Literal('foo') + export const Array_Primitive_Number = Type.Array(Type.Number(), { minItems: 4 }) - export const Literal_Number = Type.Literal(1) + export const Array_Primitive_String = Type.Array(Type.String(), { minItems: 4 }) - export const Literal_Boolean = Type.Literal(true) + export const Array_Primitive_Boolean = Type.Array(Type.Boolean(), { minItems: 4 }) - export const Array_Number = Type.Array(Type.Number(), { minItems: 16 }) + export const Array_Object_Unconstrained = Type.Array(Object_Unconstrained, { minItems: 4 }) - export const Array_String = Type.Array(Type.String(), { minItems: 16 }) + export const Array_Object_Constrained = Type.Array(Object_Constrained, { minItems: 4 }) - export const Array_Boolean = Type.Array(Type.Boolean(), { minItems: 16 }) + export const Array_Object_Recursive = Type.Array(Object_Recursive, { minItems: 4 }) - export const Array_ObjectA = Type.Array(ObjectA, { minItems: 16 }) + export const Array_Tuple_Primitive = Type.Array(Tuple_Primitive, { minItems: 4 }) - export const Array_ObjectB = Type.Array(ObjectB, { minItems: 16 }) + export const Array_Tuple_Object = Type.Array(Tuple_Object, { minItems: 4 }) - export const Array_Tuple = Type.Array(Tuple, { minItems: 16 }) + export const Array_Composite_Intersect = Type.Array(Composite_Intersect, { minItems: 4 }) - export const Array_Union = Type.Array(Union, { minItems: 16 }) + export const Array_Composite_Union = Type.Array(Composite_Union, { minItems: 4 }) - export const Array_Recursive = Type.Array(Recursive, { minItems: 16 }) + export const Array_Math_Vector4 = Type.Array(Math_Vector4, { minItems: 4 }) - export const Array_Vector4 = Type.Array(Vector4, { minItems: 16 }) - - export const Array_Matrix4 = Type.Array(Matrix4, { minItems: 16 }) + export const Array_Math_Matrix4 = Type.Array(Math_Matrix4, { minItems: 4 }) } diff --git a/benchmark/measurement/module/check.ts b/benchmark/measurement/module/check.ts index 3b8f975..a6e2e43 100644 --- a/benchmark/measurement/module/check.ts +++ b/benchmark/measurement/module/check.ts @@ -1,8 +1,7 @@ import { Cases } from './cases' import { Benchmark } from './benchmark' import { TypeCompiler } from '@sinclair/typebox/compiler' -import { TypeGuard } from '@sinclair/typebox/guard' -import { TSchema } from '@sinclair/typebox' +import { TSchema, TypeGuard } from '@sinclair/typebox' import { Value } from '@sinclair/typebox/value' import Ajv from 'ajv' diff --git a/benchmark/measurement/module/compile.ts b/benchmark/measurement/module/compile.ts index 1dd0396..98b61c0 100644 --- a/benchmark/measurement/module/compile.ts +++ b/benchmark/measurement/module/compile.ts @@ -1,15 +1,14 @@ import { Cases } from './cases' import { Benchmark } from './benchmark' import { TypeCompiler } from '@sinclair/typebox/compiler' -import { TypeGuard } from '@sinclair/typebox/guard' -import { TSchema } from '@sinclair/typebox' +import { TSchema, TypeGuard } from '@sinclair/typebox' import Ajv from 'ajv' const ajv = new Ajv() // ensure single instance export namespace CompileBenchmark { function Measure(type: string, schema: T) { - const iterations = 2000 + const iterations = 1000 console.log('CompileBenchmark.Measure(', type, ')') // ------------------------------------------------------------------------------- // Note: Ajv caches schemas by reference. To ensure we measure actual @@ -28,7 +27,7 @@ export namespace CompileBenchmark { // track duplicate $id (resulting in compile error). It is not possible to ammend // recursive $id's without potentially biasing results, so we omit on this case. // ------------------------------------------------------------------------------- - if (type === 'Recursive' || type === 'Array_Recursive') continue + if (type === 'Object_Recursive' || type === 'Array_Object_Recursive') continue yield Measure(type, schema) } diff --git a/benchmark/measurement/module/index.ts b/benchmark/measurement/module/index.ts index 5b6d434..65b47f7 100644 --- a/benchmark/measurement/module/index.ts +++ b/benchmark/measurement/module/index.ts @@ -9,7 +9,7 @@ export function present(results: Result[]) { if (result.value) { return { ...acc, - [result.type.padStart(16, ' ')]: { + [result.type.padEnd(26, ' ')]: { Iterations: result.compiler.iterations, ValueCheck: `${result.value.completed} ms`.padStart(10), Ajv: `${result.ajv.completed} ms`.padStart(10), @@ -20,7 +20,7 @@ export function present(results: Result[]) { } else { return { ...acc, - [result.type.padStart(16, ' ')]: { + [result.type.padEnd(26, ' ')]: { Iterations: result.compiler.iterations, Ajv: `${result.ajv.completed} ms`.padStart(10), TypeCompiler: `${result.compiler.completed} ms`.padStart(10), diff --git a/changelog.md b/changelog.md deleted file mode 100644 index fa1f19c..0000000 --- a/changelog.md +++ /dev/null @@ -1,410 +0,0 @@ -## [0.25.24](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.23) - -Updates: - -- [331](https://github.com/sinclairzx81/typebox/pull/331) Implements an additional check specific to property types of `required & undefined`. This to ensure the property key exists when the property value extends `undefined`. -- [331](https://github.com/sinclairzx81/typebox/pull/331) Documentation updates for Ajv and TypeCompiler - -Additional: - -- [331](https://github.com/sinclairzx81/typebox/pull/331) Remove unusued recursive code paths for create and cast. - -## [0.25.23](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.23) - -Updates: - -- [324](https://github.com/sinclairzx81/typebox/pull/324) TypeScript Language Service now presents JSDoc comments when inferring static object properties. (IntelliSense) -- [325](https://github.com/sinclairzx81/typebox/pull/325) Additional property inference optimizations. - -Additional: - -- Huge thank you to Github user [stevezhu](https://github.com/stevezhu) for these excellent contributions. - - -## [0.25.22](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.22) - -Updates: - -- [323](https://github.com/sinclairzx81/typebox/pull/323) adds compiler support for UTF-16 (unicode) characters for schema identifiers. - -## [0.25.18](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.18) - -Updates: - -- [307](https://github.com/sinclairzx81/typebox/pull/307) implements date conversion when casting values with `Value.Cast(Type.Date(), ...)`. Castable values include numbers (interpretted as timestamps) and iso8601 string values. Uncastable values will result in dates with values of `1970-01-01T00:00:00.000Z`. This version also includes more robust checks for Dates initialized with invalid values. - -## [0.25.11](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.11) - -Updates: - -- [286](https://github.com/sinclairzx81/typebox/pull/286) implements a FNV1A-64 non cryptographic hashing function in TypeBox. This function should not be used in place of cryptographic hashing functions, rather it's purpose is to provide relatively fast, hashing mechanism to assist with checks for arrays with uniqueItems constraints, specifically for cases where the array may contains reference values (such as objects, arrays, Dates and Uint8Array). This function is provided via `Value.Hash()` for convenience as the hash may be useful to generate a numeric identifier for values (with some considerations to React array rendering in absence of key or identifier) - -## [0.25.10](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.10) - -Updates: - -- [283](https://github.com/sinclairzx81/typebox/pull/283) Updates the custom type validator callback signature to accept a schema instance. The schema instance may include additional constraints (such as options) that may be used during the validation process. `Custom.Set('', (schema, value) => { ... })`. - -## [0.25.9](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.9) - -Updates: - -- [282](https://github.com/sinclairzx81/typebox/pull/282) TypeBox now supports custom types. These types require the user to specify a custom `[Kind]` string on the type. Custom types can be registered via `Custom.Set('', (value) => { ... })` which allow the TypeCompiler and Value API's to make use of user defined validation logic. - -## [0.25.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.0) - -Updates: - -- [271](https://github.com/sinclairzx81/typebox/pull/271) Adds a new non-standard `Type.Date()` type. This type joins the existing `Type.UInt8Array()` as a promoted extended type used to represent core JavaScript primitives. It's inclusion was prompted by end user requirements to validate Date objects prior to writing them to Date supported API's and where serialization of the Date object is handled internally by the API. - -- [271](https://github.com/sinclairzx81/typebox/pull/271) Redesign of Extended Type representations. Extended types been updated to provide external validators (such as Ajv) additional standard proporties to use when defining the custom schema. These properties are `instanceOf` (used for validating a class `object` instances), and `typeOf` (when validating `value` types). Information on configuring Ajv for these properties can be found in the Ajv section of the TypeBox readme. - -## [0.24.49](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.49) - -Updates: - -- [264](https://github.com/sinclairzx81/typebox/pull/264) TypeBox now provides preliminary support for non-boolean `additionalProperties`. This allows existing `TObject` schemas to be augmented with additional properties of a known type. - -Additional: - -- TypeBox provides an additional reference `codegen` module for generating raw JSON Schema from TypeScript types via the TS compiler API. This generator may be used in future tooling. - -## [0.24.44](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.44) - -Updates: -- [189](https://github.com/sinclairzx81/typebox/pull/189) Both `Value.Error(T, value)` and `TypeCheck.Error(value)` now return an iterator for validation errors. -- [191](https://github.com/sinclairzx81/typebox/pull/191) TypeBox now provides a `TypeGuard` API that can be used to check the structural validity of TypeBox type. The TypeGuard can be used in reflection / code generation scenarios to resolve the appropriate inner `TSchema` type while traversing a outer type. -- [197](https://github.com/sinclairzx81/typebox/pull/197) TypeBox now implements conditional runtime type mapping. This functionality is offered as seperate import for the `0.24.0` release but may be provided as standard type in later releases. This API enables `type T = Foo extends Bar ? true : false` conditional checks to be implemented at runtime. This API also provides the `Exclude` and `Extract` utility types which are implemented through conditional types in TypeScript. -- [199](https://github.com/sinclairzx81/typebox/pull/199) TypeBox now provides better support for varidiac function and constructor signatures. Currently Variadics are mapped through `Tuple` types. -- [200](https://github.com/sinclairzx81/typebox/pull/200) The types `TPick` and `TOmit` now support types of `TUnion[]>` to be used to select properties. Additionally, `KeyOf` now returns `TUnion[]>`, allowing `KeyOf` schemas to be passed to `TPick` and `TOmit`. -- [214](https://github.com/sinclairzx81/typebox/pull/214) TypeBox now provides better support for i18n. To achieve this, TypeBox includes fixed mappable error codes on the `ValueError` type. These codes can be used by external implementors to create localized error messages. TypeBox may include localized error codes as an optional import in future releases. -- [288](https://github.com/sinclairzx81/typebox/pull/228) TypeBox now allows users to implement custom string validator formats. These formats are internally shared between the `Value` and `TypeCompiler` API's. TypeBox does not currently provide any built in formats, however the standard expected set (email, uuid, uri, etc) may be provided via optional import (inline with ajv-formats usage) -- [229](https://github.com/sinclairzx81/typebox/pull/229) The `Value.Cast()` function now implements automatic coersion of string, number and boolean types. -- [231](https://github.com/sinclairzx81/typebox/pull/231) TypeBox provides a new `Value.Diff()` and `Value.Patch()` utility API for JavaScript values. This API is intended to provide a basis for the efficient transmission of state updates across a network. This API can diff any JavaScript value (typed or untyped) but is recommended to be used in conjunction with a formal static type. -- [236](https://github.com/sinclairzx81/typebox/pull/236) TypeBox now implements the `TNever` type. This type is analogous to TypeScript's `never` type and is used in instances a composition results in a non-reconcilable type. Currently this type is implemented for empty `TUnion<[]>` types only. Future releases may utilize this type for planned updates to `TIntersect` (for example `string & number` resolves to `never`) -- [241](https://github.com/sinclairzx81/typebox/pull/241) [247](https://github.com/sinclairzx81/typebox/pull/247) TypeBox now exposes a ValuePointer API that can be used to mutate a value via an RFC6901 JSON Pointer. Previously this functionality was internally used by `Value.Diff()` and `Value.Patch()` functions but is now offered as an optional import for implementations that need to update values manually through pointer references. - -Additional: - -- This project now includes two reference code generation utilities that can be used in custom build tooling. The first is `TypeScriptCodeGen` which will remap TypeScript `interface` and `type` definitions to TypeBox types. The second is `TypeBoxCodeGen` which will map existing TypeBox types into TypeScript type definitions. These implementations are not expected to be part of the TypeBox package, but users are free to clone and enhance them in their existing tool chains. Reference implementations can be found https://github.com/sinclairzx81/typebox/tree/master/codegen - - - -## [0.24.15](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.15) - -Added: -- `Conditional.Extends(...)` This enables TypeBox to conditionally map types inline with TypeScripts structural equivelence checks. Tested against TypeScript 4.7.4. -- `Conditional.Extract(...)` Which analogs TypeScripts `Extract<...>` utility type. Additional information [here](https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union) -- `Conditional.Exclude(...)` Which analogs TypeScripts `Exclude<...>` utility type. Additional information [here](https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers) -- `Type.Parameters(...)` Returns the parameters of a `TFunction` as a `TTuple` -- `Type.ReturnType(...)` Returns the return type schema of a `TFunction` -- `Type.ConstructorParameters(...)` Returns the parameters of a `TConstructor` as a `TTuple` -- `Type.InstanceType(...)` Returns the instance type schema of a `TConstructor` - -## [0.24.8](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.8) - -Added: -- `Value.Cast(T, value)` structurally casts a value into another form while retaining information within the original value. -- `Value.Check(T, value)` provides slow dynamic type checking for values. For performance, one should consider the `TypeCompiler` or `Ajv` validator. -- `Value.Errors(T, value)` returns an iterable iterator errors found in a given value. - -## [0.24.6](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.6) - -Added: - -- TypeBox now offers a `TypeGuard` module for structurally checking TypeBox schematics. This module can be used in runtime type reflection scenarios where it's helpful to test a schema is of a particular form. This module can be imported under the `@sinclair/typebox/guard` import path. - -Example: - -```typescript -import { TypeGuard } from '@sinclair/typebox/guard' - -const T: any = {} // T is any - -const { type } = T // unsafe: type is any - -if(TypeGuard.TString(T)) { - - const { type } = T // safe: type is 'string' -} - -``` - -## [0.24.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.0) - -Changes: - -- The `kind` and `modifier` keywords are now expressed as symbol keys. This change allows AJV to leverage TypeBox schemas directly without explicit configuration of `kind` and `modifier` in strict mode. -- `Type.Intersect([...])` now returns a composite `TObject` instead of a `allOf` schema representation. This change allows intersected types to be leveraged in calls to `Omit`, `Pick`, `Partial`, `Required`. -- `Type.Void(...)` now generates a `{ type: null }` schema representation. This is principally used for RPC implementations where a RPC target function needs to respond with a serializable value for `void` return. -- `Type.Rec(...)` renamed to `Type.Recursive(...)` and now supports non-mutual recursive type inference. - -Added: - -- `Type.Unsafe(...)`. This type enables custom schema representations whose static type is informed by generic type T. -- `Type.Uint8Array(...)`. This is a non-standard schema that can be configured on AJV to enable binary buffer range validation. -- Added optional extended `design` property on all schema options. This property can be used to specify design time metadata when rendering forms. - -Compiler: - -- TypeBox now provides an optional experimental type compiler that can be used to validate types without AJV. This compiler is not a standard JSON schema compiler and will only compile TypeBox's known schema representations. For full JSON schema validation, AJV should still be the preference. This compiler is a work in progress. - -Value: - -- TypeBox now provides a value generator that can generate default values from TypeBox types. - -Breaking Changes: - -- `Type.Intersect(...)` is constrained to accept types of `TObject` only. -- `Type.Namespace(...)` has been removed. -- The types `TUnion`, `TEnum`, `KeyOf` and `TLiteral[]` are all now expressed via `allOf`. For Open API users, Please consider `Type.Unsafe()` to express `enum` string union representations. Documentation on using `Type.Unsafe()` can be found [here](https://github.com/sinclairzx81/typebox#Unsafe-Types) - -## [0.23.3](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.3) - -Updates: - -- Fix: Rename BoxKind to NamespaceKind - -## [0.23.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.1) - -Updates: - -- The `Type.KeyOf(...)` type can now accept references of `Type.Ref(TObject)` - -## [0.23.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.0) - -Updates: - -- The types `Type.Namespace(...)` and `Type.Ref(...)` are promoted to `Standard`. -- TypeBox now includes an additional type named `TRef<...>` that is returned on calls to `Type.Ref(...)`. The `TRef<...>` includes a new `RefKind` symbol for introspection of the reference type. -- TypeBox now maintains an internal dictionary of all schemas passed that contain an `$id` property. This dictionary is checked whenever a user attempts to reference a type and will throw if attempting to reference a target schema with no `$id`. -- The types `Type.Partial(...)`, `Type.Required(...)`, `Type.Omit()` and `Type.Pick(...)` now support reference types. Note that when using these functions with references, TypeBox will replicate the source schema and apply the nessasary modifiers to the replication. - -## [0.22.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.22.0) - -Updates: - -- The type `TSchema` is now expressed as an HKT compatible interface. All types now extend the `TSchema` interface and are themselves also expressed as interfaces. This work was undertaken to explore recursive type aliases in future releases. -- The phantom property `_infer` has been renamed to `$static`. Callers should not interact with this property as it will always be `undefined` and used exclusively for optimizing type inference in TypeScript 4.5 and above. -- TypeBox re-adds the feature to deeply introspect schema properties. This feature was temporarily removed on the `0.21.0` update to resolve deep instantiation errors on TypeScript 4.5. -- The `Type.Box(...)` and `Type.Rec(...)` functions internally rename the property `definitions` to `$defs` inline with JSON schema draft 2019-09 conventions. Reference [here](https://opis.io/json-schema/2.x/definitions.html). - -## [0.21.2](https://www.npmjs.com/package/@sinclair/typebox/v/0.21.2) - -Updates: - -- TypeBox now correctly infers for nested union and intersect types. - -Before - -```typescript -const A = Type.Object({ a: Type.String() }) -const B = Type.Object({ b: Type.String() }) -const C = Type.Object({ c: Type.String() }) -const T = Type.Intersect([A, Type.Union([B, C])]) - -// type T = { a: string } & { b: string } & { c: string } -``` -After - -```typescript -const A = Type.Object({ a: Type.String() }) -const B = Type.Object({ b: Type.String() }) -const C = Type.Object({ c: Type.String() }) -const T = Type.Intersect([A, Type.Union([B, C])]) - -// type T = { a: string } & ({ b: string } | { c: string }) -``` - -## [0.21.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.21.0) - -Updates: - -- TypeBox static inference has been updated inline with additional inference constraints added in TypeScript 4.5. All types now include a phantom `_infer` property which contains the inference TS type for a given schema. The type of this property is inferred at the construction of the schema, and referenced directly via `Static`. -- `Type.Box(...)` has been renamed to `Type.Namespace(...)` to draw an analogy with XML's `xmlns` XSD types. - -## [0.20.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.20.1) - -Updates: - -- TypeBox mandates TypeScript compiler version `4.3.5` and above. - -## [0.20.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.20.0) - -Updates: - -- Function `Type.Rec(...)` signature change. -- Minor documentation updates. - -Notes: - -The `Type.Rec(...)` function signature has been changed to allow passing the `$id` as a custom option. This is to align `Type.Rec(...)` with other functions that accept `$id` as an option. `Type.Rec(...)` can work with or without an explicit `$id`, but it is recommend to specify one if the recursive type is nested in an outer schema. - -```typescript -const Node = Type.Rec(Self => Type.Object({ - id: Type.String(), - nodes: Type.Array(Self) -}), { $id: 'Node' }) -``` - -## [0.19.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.19.0) - -Updates: - -- Function `Type.Box(...)` removes `$id` parameter as first argument. -- Function `Type.Ref(...)` is now overloaded to support referencing `Type.Box(...)` and `TSchema`. - -Notes: - -This update changes the signature of `Type.Box(...)` and removes the explicit `$id` passing on the first parameter. The `$id` must be passed as an option if the caller wants to reference that type. - -```typescript -const T = Type.String({ $id: 'T' }) - -const B = Type.Box({ T }, { $id: 'B' }) - -const R1 = Type.Ref(T) // const R1 = { $ref: 'T' } - -const R2 = Type.Ref(B, 'T') // const R2 = { $ref: 'B#/definitions/T' } -``` - -## [0.18.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.18.1) - -- Function `Type.Enum(...)` now expressed with `anyOf`. This to remove the `allowUnionTypes` configuration required to use `enum` with in AJV strict. -- Function `Type.Rec(...)` now takes a required `$id` as the first parameter. -- Function `Type.Strict(...)` no longer includes a `$schema`. Callers can now optionally pass `CustomOptions` on `Type.Strict(...)` - -## [0.18.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.18.0) - -Changes: - -- Function `Type.Intersect(...)` is now implemented with `allOf` and constrained with `unevaluatedProperties` (draft `2019-09`) -- Function `Type.Dict(...)` has been deprecated and replaced with `Type.Record(...)`. -- Function `Type.Strict(...)` now includes the `$schema` property referencing the `2019-09` draft. - -### Type.Intersect(...) - -TypeBox now targets JSON schema draft `2019-09` for expressing `Type.Intersect(...)`. This is now expressed via `allOf` with additionalProperties constrained with `unevaluatedProperties`. Note that `unevaluatedProperties` is a feature of the `2019-09` specification. - -### Type.Record(K, V) - -TypeBox has deprecated `Type.Dict(...)` in favor of the more generic `Type.Record(...)`. Where as `Type.Dict(...)` was previously expressed with `additionalProperties: { ... }`, `Type.Record(...)` is expressed with `patternProperties` and supports both `string` and `number` indexer keys. Additionally, `Type.Record(...)` supports string union arguments. This is analogous to TypeScript's utility record type `Record<'a' | 'b' | 'c', T>`. - -## [0.17.7](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.7) - -Changes: - -- Added optional `$id` argument on `Type.Rec()`. -- Documentation updates. - -## [0.17.6](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.6) - -Changes: - -- Added `Type.Rec(...)` function. - -Notes: - -This update introduces the `Type.Rec()` function for enabling Recursive Types. Please note that due to current inference limitations in TypeScript, TypeBox is unable to infer the type and resolves inner types to `any`. - -This functionality enables for complex self referential schemas to be composed. The following creates a binary expression syntax node with the expression self referential for left and right oprands. - -```typescript -const Operator = Type.Union([ - Type.Literal('+'), - Type.Literal('-'), - Type.Literal('/'), - Type.Literal('*') -]) - -type Expression = Static - -// Defines a self referencing type. -const Expression = Type.Rec(Self => Type.Object({ - left: Type.Union([Self, Type.Number()]), - right: Type.Union([Self, Type.Number()]), - operator: Operator -})) - -function evaluate(expression: Expression): number { - const left = typeof expression.left !== 'number' - ? evaluate(expression.left as Expression) // assert as Expression - : expression.left - const right = typeof expression.right !== 'number' - ? evaluate(expression.right as Expression) // assert as Expression - : expression.right - switch(expression.operator) { - case '+': return left + right - case '-': return left - right - case '*': return left * right - case '/': return left / right - } -} - -const result = evaluate({ - left: { - left: 10, - operator: '*', - right: 4, - }, - operator: '+', - right: 2, -}) // -> 42 -``` - -This functionality is flagged as `EXPERIMENTAL` and awaits community feedback. - -## [0.17.4](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.4) - -Changes: - -- Added `Type.Box()` and `Type.Ref()` functions. - -Notes: - -This update provides the `Type.Box()` function to enable common related schemas to grouped under a common namespace; typically expressed as a `URI`. This functionality is primarily geared towards allowing one to define a common set of domain objects that may be shared across application domains running over a network. The `Type.Box()` is intended to be an analog to `XML` `xmlns` namespacing. - -The `Type.Ref()` function is limited to referencing from boxes only. The following is an example. - -```typescript -// Domain objects for the fruit service. -const Fruit = Type.Box('https://fruit.domain.com', { - Apple: Type.Object({ ... }), - Orange: Type.Object({ ... }), -}) - -// An order referencing types of the fruit service. -const Order = Type.Object({ - id: Type.String(), - quantity: Type.Number(), - item: Type.Union([ - Type.Ref(Fruit, 'Apple'), - Type.Ref(Fruit, 'Orange') - ]) -}) -``` -> Note: As of this release, the `Type.Omit()`, `Type.Pick()`, `Type.Partial()`, `Type.Readonly()` and `Type.Intersect()` functions do not work with Reference Types. This may change in later revisions. - -For validation using `Ajv`, its possible to apply the `Box` directly as a schema. - -```typescript -ajv.addSchema(Fruit) // makes all boxed types known to Ajv -``` - -This functionality is flagged as `EXPERIMENTAL` and awaits community feedback. - -## [0.17.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.1) - -- Remove default `additionalProperties: false` constraint from all object schemas. - -This update removes the `additionalProperties: false` constraint on all object schemas. This constraint was introduced on `0.16.x` but has resulted in significant downstream problems composing schemas whose types `intersect`. This is due to a JSON schema design principle where constraints should only be added (never removed), and that intersection types may require removal of the `additionalProperties` constraint in some cases, this had resulted in some ambiguity with respect to how TypeBox should handle such intersections. - -This update can also be seen as a precursor towards TypeBox potentially leveraging `unevaluatedProperties` for type intersection in future releases. Implementors should take note that in order to constrain the schema to known properties, one should apply the `additionalProperties: false` as the second argument to `Type.Object({...})`. - -```typescript -const T = Type.Object({ - a: Type.String(), - b: Type.Number() -}, { - additionalProperties: false -}) diff --git a/changelog/0.17.1.md b/changelog/0.17.1.md new file mode 100644 index 0000000..b458c0b --- /dev/null +++ b/changelog/0.17.1.md @@ -0,0 +1,15 @@ +## [0.17.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.1) + +- Remove default `additionalProperties: false` constraint from all object schemas. + +This update removes the `additionalProperties: false` constraint on all object schemas. This constraint was introduced on `0.16.x` but has resulted in significant downstream problems composing schemas whose types `intersect`. This is due to a JSON schema design principle where constraints should only be added (never removed), and that intersection types may require removal of the `additionalProperties` constraint in some cases, this had resulted in some ambiguity with respect to how TypeBox should handle such intersections. + +This update can also be seen as a precursor towards TypeBox potentially leveraging `unevaluatedProperties` for type intersection in future releases. Implementers should take note that in order to constrain the schema to known properties, one should apply the `additionalProperties: false` as the second argument to `Type.Object({...})`. + +```typescript +const T = Type.Object({ + a: Type.String(), + b: Type.Number() +}, { + additionalProperties: false +}) \ No newline at end of file diff --git a/changelog/0.17.4.md b/changelog/0.17.4.md new file mode 100644 index 0000000..07a8cf5 --- /dev/null +++ b/changelog/0.17.4.md @@ -0,0 +1,38 @@ +## [0.17.4](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.4) + +Changes: + +- Added `Type.Box()` and `Type.Ref()` functions. + +Notes: + +This update provides the `Type.Box()` function to enable common related schemas to grouped under a common namespace; typically expressed as a `URI`. This functionality is primarily geared towards allowing one to define a common set of domain objects that may be shared across application domains running over a network. The `Type.Box()` is intended to be an analog to `XML` `xmlns` namespaces. + +The `Type.Ref()` function is limited to referencing from boxes only. The following is an example. + +```typescript +// Domain objects for the fruit service. +const Fruit = Type.Box('https://fruit.domain.com', { + Apple: Type.Object({ ... }), + Orange: Type.Object({ ... }), +}) + +// An order referencing types of the fruit service. +const Order = Type.Object({ + id: Type.String(), + quantity: Type.Number(), + item: Type.Union([ + Type.Ref(Fruit, 'Apple'), + Type.Ref(Fruit, 'Orange') + ]) +}) +``` +> Note: As of this release, the `Type.Omit()`, `Type.Pick()`, `Type.Partial()`, `Type.Readonly()` and `Type.Intersect()` functions do not work with Reference Types. This may change in later revisions. + +For validation using `Ajv`, its possible to apply the `Box` directly as a schema. + +```typescript +ajv.addSchema(Fruit) // makes all boxed types known to Ajv +``` + +This functionality is flagged as `EXPERIMENTAL` and awaits community feedback. \ No newline at end of file diff --git a/changelog/0.17.6.md b/changelog/0.17.6.md new file mode 100644 index 0000000..291ea5f --- /dev/null +++ b/changelog/0.17.6.md @@ -0,0 +1,56 @@ +## [0.17.6](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.6) + +Changes: + +- Added `Type.Rec(...)` function. + +Notes: + +This update introduces the `Type.Rec()` function for enabling Recursive Types. Please note that due to current inference limitations in TypeScript, TypeBox is unable to infer the type and resolves inner types to `any`. + +This functionality enables for complex self referential schemas to be composed. The following creates a binary expression syntax node with the expression self referential for left and right operands. + +```typescript +const Operator = Type.Union([ + Type.Literal('+'), + Type.Literal('-'), + Type.Literal('/'), + Type.Literal('*') +]) + +type Expression = Static + +// Defines a self referencing type. +const Expression = Type.Rec(Self => Type.Object({ + left: Type.Union([Self, Type.Number()]), + right: Type.Union([Self, Type.Number()]), + operator: Operator +})) + +function evaluate(expression: Expression): number { + const left = typeof expression.left !== 'number' + ? evaluate(expression.left as Expression) // assert as Expression + : expression.left + const right = typeof expression.right !== 'number' + ? evaluate(expression.right as Expression) // assert as Expression + : expression.right + switch(expression.operator) { + case '+': return left + right + case '-': return left - right + case '*': return left * right + case '/': return left / right + } +} + +const result = evaluate({ + left: { + left: 10, + operator: '*', + right: 4, + }, + operator: '+', + right: 2, +}) // -> 42 +``` + +This functionality is flagged as `EXPERIMENTAL` and awaits community feedback. \ No newline at end of file diff --git a/changelog/0.18.0.md b/changelog/0.18.0.md new file mode 100644 index 0000000..88a7d4f --- /dev/null +++ b/changelog/0.18.0.md @@ -0,0 +1,22 @@ +## [0.18.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.18.0) + +Changes: + +- Function `Type.Intersect(...)` is now implemented with `allOf` and constrained with `unevaluatedProperties` (draft `2019-09`) +- Function `Type.Dict(...)` has been deprecated and replaced with `Type.Record(...)`. +- Function `Type.Strict(...)` now includes the `$schema` property referencing the `2019-09` draft. + +### Type.Intersect(...) + +TypeBox now targets JSON schema draft `2019-09` for expressing `Type.Intersect(...)`. This is now expressed via `allOf` with additionalProperties constrained with `unevaluatedProperties`. Note that `unevaluatedProperties` is a feature of the `2019-09` specification. + +### Type.Record(K, V) + +TypeBox has deprecated `Type.Dict(...)` in favor of the more generic `Type.Record(...)`. Where as `Type.Dict(...)` was previously expressed with `additionalProperties: { ... }`, `Type.Record(...)` is expressed with `patternProperties` and supports both `string` and `number` indexer keys. Additionally, `Type.Record(...)` supports string union arguments. This is analogous to TypeScript's utility record type `Record<'a' | 'b' | 'c', T>`. + +## [0.17.7](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.7) + +Changes: + +- Added optional `$id` argument on `Type.Rec()`. +- Documentation updates. \ No newline at end of file diff --git a/changelog/0.18.1.md b/changelog/0.18.1.md new file mode 100644 index 0000000..1a0bbe7 --- /dev/null +++ b/changelog/0.18.1.md @@ -0,0 +1,5 @@ +## [0.18.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.18.1) + +- Function `Type.Enum(...)` now expressed with `anyOf`. This to remove the `allowUnionTypes` configuration required to use `enum` with in AJV strict. +- Function `Type.Rec(...)` now takes a required `$id` as the first parameter. +- Function `Type.Strict(...)` no longer includes a `$schema`. Callers can now optionally pass `CustomOptions` on `Type.Strict(...)` \ No newline at end of file diff --git a/changelog/0.19.0.md b/changelog/0.19.0.md new file mode 100644 index 0000000..377ccdb --- /dev/null +++ b/changelog/0.19.0.md @@ -0,0 +1,20 @@ +## [0.19.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.19.0) + +Updates: + +- Function `Type.Box(...)` removes `$id` parameter as first argument. +- Function `Type.Ref(...)` is now overloaded to support referencing `Type.Box(...)` and `TSchema`. + +Notes: + +This update changes the signature of `Type.Box(...)` and removes the explicit `$id` passing on the first parameter. The `$id` must be passed as an option if the caller wants to reference that type. + +```typescript +const T = Type.String({ $id: 'T' }) + +const B = Type.Box({ T }, { $id: 'B' }) + +const R1 = Type.Ref(T) // const R1 = { $ref: 'T' } + +const R2 = Type.Ref(B, 'T') // const R2 = { $ref: 'B#/definitions/T' } +``` \ No newline at end of file diff --git a/changelog/0.20.0.md b/changelog/0.20.0.md new file mode 100644 index 0000000..80a01da --- /dev/null +++ b/changelog/0.20.0.md @@ -0,0 +1,17 @@ +## [0.20.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.20.0) + +Updates: + +- Function `Type.Rec(...)` signature change. +- Minor documentation updates. + +Notes: + +The `Type.Rec(...)` function signature has been changed to allow passing the `$id` as a custom option. This is to align `Type.Rec(...)` with other functions that accept `$id` as an option. `Type.Rec(...)` can work with or without an explicit `$id`, but it is recommend to specify one if the recursive type is nested in an outer schema. + +```typescript +const Node = Type.Rec(Self => Type.Object({ + id: Type.String(), + nodes: Type.Array(Self) +}), { $id: 'Node' }) +``` \ No newline at end of file diff --git a/changelog/0.20.1.md b/changelog/0.20.1.md new file mode 100644 index 0000000..2dfec12 --- /dev/null +++ b/changelog/0.20.1.md @@ -0,0 +1,5 @@ +## [0.20.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.20.1) + +Updates: + +- TypeBox mandates TypeScript compiler version `4.3.5` and above. \ No newline at end of file diff --git a/changelog/0.21.0.md b/changelog/0.21.0.md new file mode 100644 index 0000000..31df5f7 --- /dev/null +++ b/changelog/0.21.0.md @@ -0,0 +1,7 @@ +## [0.21.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.21.0) + +Updates: + +- TypeBox static inference has been updated inline with additional inference constraints added in TypeScript 4.5. All types now include a phantom `_infer` property which contains the inference TS type for a given schema. The type of this property is inferred at the construction of the schema, and referenced directly via `Static`. +- `Type.Box(...)` has been renamed to `Type.Namespace(...)` to draw an analogy with XML's `xmlns` XSD types. + diff --git a/changelog/0.21.2.md b/changelog/0.21.2.md new file mode 100644 index 0000000..12d31df --- /dev/null +++ b/changelog/0.21.2.md @@ -0,0 +1,26 @@ +## [0.21.2](https://www.npmjs.com/package/@sinclair/typebox/v/0.21.2) + +Updates: + +- TypeBox now correctly infers for nested union and intersect types. + +Before + +```typescript +const A = Type.Object({ a: Type.String() }) +const B = Type.Object({ b: Type.String() }) +const C = Type.Object({ c: Type.String() }) +const T = Type.Intersect([A, Type.Union([B, C])]) + +// type T = { a: string } & { b: string } & { c: string } +``` +After + +```typescript +const A = Type.Object({ a: Type.String() }) +const B = Type.Object({ b: Type.String() }) +const C = Type.Object({ c: Type.String() }) +const T = Type.Intersect([A, Type.Union([B, C])]) + +// type T = { a: string } & ({ b: string } | { c: string }) +``` \ No newline at end of file diff --git a/changelog/0.22.0.md b/changelog/0.22.0.md new file mode 100644 index 0000000..f65eeeb --- /dev/null +++ b/changelog/0.22.0.md @@ -0,0 +1,8 @@ +## [0.22.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.22.0) + +Updates: + +- The type `TSchema` is now expressed as an HKT compatible interface. All types now extend the `TSchema` interface and are themselves also expressed as interfaces. This work was undertaken to explore recursive type aliases in future releases. +- The phantom property `_infer` has been renamed to `$static`. Callers should not interact with this property as it will always be `undefined` and used exclusively for optimizing type inference in TypeScript 4.5 and above. +- TypeBox re-adds the feature to deeply introspect schema properties. This feature was temporarily removed on the `0.21.0` update to resolve deep instantiation errors on TypeScript 4.5. +- The `Type.Box(...)` and `Type.Rec(...)` functions internally rename the property `definitions` to `$defs` inline with JSON schema draft 2019-09 conventions. Reference [here](https://opis.io/json-schema/2.x/definitions.html). \ No newline at end of file diff --git a/changelog/0.23.0.md b/changelog/0.23.0.md new file mode 100644 index 0000000..5dce7d9 --- /dev/null +++ b/changelog/0.23.0.md @@ -0,0 +1,8 @@ +## [0.23.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.0) + +Updates: + +- The types `Type.Namespace(...)` and `Type.Ref(...)` are promoted to `Standard`. +- TypeBox now includes an additional type named `TRef<...>` that is returned on calls to `Type.Ref(...)`. The `TRef<...>` includes a new `RefKind` symbol for introspection of the reference type. +- TypeBox now maintains an internal dictionary of all schemas passed that contain an `$id` property. This dictionary is checked whenever a user attempts to reference a type and will throw if attempting to reference a target schema with no `$id`. +- The types `Type.Partial(...)`, `Type.Required(...)`, `Type.Omit()` and `Type.Pick(...)` now support reference types. Note that when using these functions with references, TypeBox will replicate the source schema and apply the necessary modifiers to the replication. \ No newline at end of file diff --git a/changelog/0.23.1.md b/changelog/0.23.1.md new file mode 100644 index 0000000..565f779 --- /dev/null +++ b/changelog/0.23.1.md @@ -0,0 +1,5 @@ +## [0.23.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.1) + +Updates: + +- The `Type.KeyOf(...)` type can now accept references of `Type.Ref(TObject)` \ No newline at end of file diff --git a/changelog/0.23.3.md b/changelog/0.23.3.md new file mode 100644 index 0000000..bb89450 --- /dev/null +++ b/changelog/0.23.3.md @@ -0,0 +1,5 @@ +## [0.23.3](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.3) + +Updates: + +- Fix: Rename BoxKind to NamespaceKind \ No newline at end of file diff --git a/changelog/0.24.0.md b/changelog/0.24.0.md new file mode 100644 index 0000000..b433b58 --- /dev/null +++ b/changelog/0.24.0.md @@ -0,0 +1,28 @@ +## [0.24.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.0) + +Changes: + +- The `kind` and `modifier` keywords are now expressed as symbol keys. This change allows AJV to leverage TypeBox schemas directly without explicit configuration of `kind` and `modifier` in strict mode. +- `Type.Intersect([...])` now returns a composite `TObject` instead of a `allOf` schema representation. This change allows intersected types to be leveraged in calls to `Omit`, `Pick`, `Partial`, `Required`. +- `Type.Void(...)` now generates a `{ type: null }` schema representation. This is principally used for RPC implementations where a RPC target function needs to respond with a serializable value for `void` return. +- `Type.Rec(...)` renamed to `Type.Recursive(...)` and now supports non-mutual recursive type inference. + +Added: + +- `Type.Unsafe(...)`. This type enables custom schema representations whose static type is informed by generic type T. +- `Type.Uint8Array(...)`. This is a non-standard schema that can be configured on AJV to enable binary buffer range validation. +- Added optional extended `design` property on all schema options. This property can be used to specify design time metadata when rendering forms. + +Compiler: + +- TypeBox now provides an optional experimental type compiler that can be used to validate types without AJV. This compiler is not a standard JSON schema compiler and will only compile TypeBox's known schema representations. For full JSON schema validation, AJV should still be the preference. This compiler is a work in progress. + +Value: + +- TypeBox now provides a value generator that can generate default values from TypeBox types. + +Breaking Changes: + +- `Type.Intersect(...)` is constrained to accept types of `TObject` only. +- `Type.Namespace(...)` has been removed. +- The types `TUnion`, `TEnum`, `KeyOf` and `TLiteral[]` are all now expressed via `allOf`. For Open API users, Please consider `Type.Unsafe()` to express `enum` string union representations. Documentation on using `Type.Unsafe()` can be found [here](https://github.com/sinclairzx81/typebox#Unsafe-Types) \ No newline at end of file diff --git a/changelog/0.24.15.md b/changelog/0.24.15.md new file mode 100644 index 0000000..69745b8 --- /dev/null +++ b/changelog/0.24.15.md @@ -0,0 +1,10 @@ +## [0.24.15](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.15) + +Added: +- `Conditional.Extends(...)` This enables TypeBox to conditionally map types inline with TypeScripts structural equivalence checks. Tested against TypeScript 4.7.4. +- `Conditional.Extract(...)` Which analogs TypeScripts `Extract<...>` utility type. Additional information [here](https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union) +- `Conditional.Exclude(...)` Which analogs TypeScripts `Exclude<...>` utility type. Additional information [here](https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers) +- `Type.Parameters(...)` Returns the parameters of a `TFunction` as a `TTuple` +- `Type.ReturnType(...)` Returns the return type schema of a `TFunction` +- `Type.ConstructorParameters(...)` Returns the parameters of a `TConstructor` as a `TTuple` +- `Type.InstanceType(...)` Returns the instance type schema of a `TConstructor` \ No newline at end of file diff --git a/changelog/0.24.44.md b/changelog/0.24.44.md new file mode 100644 index 0000000..e5aac8b --- /dev/null +++ b/changelog/0.24.44.md @@ -0,0 +1,18 @@ +## [0.24.44](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.44) + +Updates: +- [189](https://github.com/sinclairzx81/typebox/pull/189) Both `Value.Error(T, value)` and `TypeCheck.Error(value)` now return an iterator for validation errors. +- [191](https://github.com/sinclairzx81/typebox/pull/191) TypeBox now provides a `TypeGuard` API that can be used to check the structural validity of TypeBox type. The TypeGuard can be used in reflection / code generation scenarios to resolve the appropriate inner `TSchema` type while traversing a outer type. +- [197](https://github.com/sinclairzx81/typebox/pull/197) TypeBox now implements conditional runtime type mapping. This functionality is offered as separate import for the `0.24.0` release but may be provided as standard type in later releases. This API enables `type T = Foo extends Bar ? true : false` conditional checks to be implemented at runtime. This API also provides the `Exclude` and `Extract` utility types which are implemented through conditional types in TypeScript. +- [199](https://github.com/sinclairzx81/typebox/pull/199) TypeBox now provides better support for variadic function and constructor signatures. Currently variadic types are mapped as `Tuple` types. +- [200](https://github.com/sinclairzx81/typebox/pull/200) The types `TPick` and `TOmit` now support types of `TUnion[]>` to be used to select properties. Additionally, `KeyOf` now returns `TUnion[]>`, allowing `KeyOf` schemas to be passed to `TPick` and `TOmit`. +- [214](https://github.com/sinclairzx81/typebox/pull/214) TypeBox now provides better support for i18n. To achieve this, TypeBox includes fixed mappable error codes on the `ValueError` type. These codes can be used by external implementers to create localized error messages. TypeBox may include localized error codes as an optional import in future releases. +- [288](https://github.com/sinclairzx81/typebox/pull/228) TypeBox now allows users to implement custom string validator formats. These formats are internally shared between the `Value` and `TypeCompiler` API's. TypeBox does not currently provide any built in formats, however the standard expected set (email, uuid, uri, etc) may be provided via optional import (inline with ajv-formats usage) +- [229](https://github.com/sinclairzx81/typebox/pull/229) The `Value.Cast()` function now implements automatic coercion of string, number and Boolean types. +- [231](https://github.com/sinclairzx81/typebox/pull/231) TypeBox provides a new `Value.Diff()` and `Value.Patch()` utility API for JavaScript values. This API is intended to provide a basis for the efficient transmission of state updates across a network. This API can diff any JavaScript value (typed or untyped) but is recommended to be used in conjunction with a formal static type. +- [236](https://github.com/sinclairzx81/typebox/pull/236) TypeBox now implements the `TNever` type. This type is analogous to TypeScript's `never` type and is used in instances a composition results in a non-reconcilable type. Currently this type is implemented for empty `TUnion<[]>` types only. Future releases may utilize this type for planned updates to `TIntersect` (for example `string & number` resolves to `never`) +- [241](https://github.com/sinclairzx81/typebox/pull/241) [247](https://github.com/sinclairzx81/typebox/pull/247) TypeBox now exposes a ValuePointer API that can be used to mutate a value via an RFC6901 JSON Pointer. Previously this functionality was internally used by `Value.Diff()` and `Value.Patch()` functions but is now offered as an optional import for implementations that need to update values manually through pointer references. + +Additional: + +- This project now includes two reference code generation utilities that can be used in custom build tooling. The first is `TypeScriptCodeGen` which will remap TypeScript `interface` and `type` definitions to TypeBox types. The second is `TypeBoxCodeGen` which will map existing TypeBox types into TypeScript type definitions. These implementations are not expected to be part of the TypeBox package, but users are free to clone and enhance them in their existing tool chains. Reference implementations can be found https://github.com/sinclairzx81/typebox/tree/master/codegen \ No newline at end of file diff --git a/changelog/0.24.49.md b/changelog/0.24.49.md new file mode 100644 index 0000000..e0fd0a6 --- /dev/null +++ b/changelog/0.24.49.md @@ -0,0 +1,9 @@ +## [0.24.49](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.49) + +Updates: + +- [264](https://github.com/sinclairzx81/typebox/pull/264) TypeBox now provides preliminary support for non Boolean `additionalProperties`. This allows existing `TObject` schemas to be augmented with additional properties of a known type. + +Additional: + +- TypeBox provides an additional reference `codegen` module for generating raw JSON Schema from TypeScript types via the TS compiler API. This generator may be used in future tooling. \ No newline at end of file diff --git a/changelog/0.24.6.md b/changelog/0.24.6.md new file mode 100644 index 0000000..68477e0 --- /dev/null +++ b/changelog/0.24.6.md @@ -0,0 +1,20 @@ +## [0.24.6](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.6) + +Added: + +- TypeBox now offers a `TypeGuard` module for structurally checking TypeBox schematics. This module can be used in runtime type reflection scenarios where it's helpful to test a schema is of a particular form. This module can be imported under the `@sinclair/typebox/guard` import path. + +Example: + +```typescript +import { TypeGuard } from '@sinclair/typebox/guard' + +const T: any = {} // T is any + +const { type } = T // unsafe: type is any + +if(TypeGuard.TString(T)) { + + const { type } = T // safe: type is 'string' +} +``` \ No newline at end of file diff --git a/changelog/0.24.8.md b/changelog/0.24.8.md new file mode 100644 index 0000000..05f1576 --- /dev/null +++ b/changelog/0.24.8.md @@ -0,0 +1,6 @@ +## [0.24.8](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.8) + +Added: +- `Value.Cast(T, value)` structurally casts a value into another form while retaining information within the original value. +- `Value.Check(T, value)` provides slow dynamic type checking for values. For performance, one should consider the `TypeCompiler` or `Ajv` validator. +- `Value.Errors(T, value)` returns an iterator of errors found in a given value. \ No newline at end of file diff --git a/changelog/0.25.0.md b/changelog/0.25.0.md new file mode 100644 index 0000000..5085679 --- /dev/null +++ b/changelog/0.25.0.md @@ -0,0 +1,7 @@ +## [0.25.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.0) + +Updates: + +- [271](https://github.com/sinclairzx81/typebox/pull/271) Adds a new non-standard `Type.Date()` type. This type joins the existing `Type.UInt8Array()` as a promoted extended type used to represent core JavaScript primitives. It's inclusion was prompted by end user requirements to validate Date objects prior to writing them to Date supported API's and where serialization of the Date object is handled internally by the API. + +- [271](https://github.com/sinclairzx81/typebox/pull/271) Redesign of Extended Type representations. Extended types been updated to provide external validators (such as Ajv) additional standard proporties to use when defining the custom schema. These properties are `instanceOf` (used for validating a class `object` instances), and `typeOf` (when validating `value` types). Information on configuring AJV for these properties can be found in the AJV section of the TypeBox readme. \ No newline at end of file diff --git a/changelog/0.25.10.md b/changelog/0.25.10.md new file mode 100644 index 0000000..fc1d3d5 --- /dev/null +++ b/changelog/0.25.10.md @@ -0,0 +1,5 @@ +## [0.25.10](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.10) + +Updates: + +- [283](https://github.com/sinclairzx81/typebox/pull/283) Updates the custom type validator callback signature to accept a schema instance. The schema instance may include additional constraints (such as options) that may be used during the validation process. `Custom.Set('', (schema, value) => { ... })`. \ No newline at end of file diff --git a/changelog/0.25.11.md b/changelog/0.25.11.md new file mode 100644 index 0000000..ad5144b --- /dev/null +++ b/changelog/0.25.11.md @@ -0,0 +1,5 @@ +## [0.25.11](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.11) + +Updates: + +- [286](https://github.com/sinclairzx81/typebox/pull/286) implements a FNV1A-64 non cryptographic hashing function in TypeBox. This function should not be used in place of cryptographic hashing functions, rather it's purpose is to provide relatively fast, hashing mechanism to assist with checks for arrays with uniqueItems constraints, specifically for cases where the array may contains reference values (such as objects, arrays, Dates and Uint8Array). This function is provided via `Value.Hash()` for convenience as the hash may be useful to generate a numeric identifier for values (with some considerations to React array rendering in absence of key or identifier) \ No newline at end of file diff --git a/changelog/0.25.18.md b/changelog/0.25.18.md new file mode 100644 index 0000000..d762e17 --- /dev/null +++ b/changelog/0.25.18.md @@ -0,0 +1,5 @@ +## [0.25.18](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.18) + +Updates: + +- [307](https://github.com/sinclairzx81/typebox/pull/307) implements date conversion when casting values with `Value.Cast(Type.Date(), ...)`. Castable values include numbers (interpretted as timestamps) and iso8601 string values. UNCASTABLE values will result in dates with values of `1970-01-01T00:00:00.000Z`. This version also includes more robust checks for Dates initialized with invalid values. \ No newline at end of file diff --git a/changelog/0.25.22.md b/changelog/0.25.22.md new file mode 100644 index 0000000..3e92403 --- /dev/null +++ b/changelog/0.25.22.md @@ -0,0 +1,5 @@ +## [0.25.22](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.22) + +Updates: + +- [323](https://github.com/sinclairzx81/typebox/pull/323) adds compiler support for UTF-16 (unicode) characters for schema identifiers. \ No newline at end of file diff --git a/changelog/0.25.23.md b/changelog/0.25.23.md new file mode 100644 index 0000000..76e9304 --- /dev/null +++ b/changelog/0.25.23.md @@ -0,0 +1,10 @@ +## [0.25.23](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.23) + +Updates: + +- [324](https://github.com/sinclairzx81/typebox/pull/324) TypeScript Language Service now presents JSDoc comments when inferring static object properties. (IntelliSense) +- [325](https://github.com/sinclairzx81/typebox/pull/325) Additional property inference optimizations. + +Additional: + +- Huge thank you to GITHUB user [stevezhu](https://github.com/stevezhu) for these excellent contributions. \ No newline at end of file diff --git a/changelog/0.25.24.md b/changelog/0.25.24.md new file mode 100644 index 0000000..f45dc03 --- /dev/null +++ b/changelog/0.25.24.md @@ -0,0 +1,10 @@ +## [0.25.24](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.23) + +Updates: + +- [331](https://github.com/sinclairzx81/typebox/pull/331) Implements an additional check specific to property types of `required & undefined`. This to ensure the property key exists when the property value extends `undefined`. +- [331](https://github.com/sinclairzx81/typebox/pull/331) Documentation updates for AJV and TypeCompiler + +Additional: + +- [331](https://github.com/sinclairzx81/typebox/pull/331) Remove unused recursive code paths for create and cast. \ No newline at end of file diff --git a/changelog/0.25.9.md b/changelog/0.25.9.md new file mode 100644 index 0000000..c0155a8 --- /dev/null +++ b/changelog/0.25.9.md @@ -0,0 +1,5 @@ +## [0.25.9](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.9) + +Updates: + +- [282](https://github.com/sinclairzx81/typebox/pull/282) TypeBox now supports custom types. These types require the user to specify a custom `[Kind]` string on the type. Custom types can be registered via `Custom.Set('', (value) => { ... })` which allow the TypeCompiler and Value API's to make use of user defined validation logic. \ No newline at end of file diff --git a/changelog/0.26.0.md b/changelog/0.26.0.md new file mode 100644 index 0000000..34b69e8 --- /dev/null +++ b/changelog/0.26.0.md @@ -0,0 +1,394 @@ +## [0.26.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.26.0) + +## Overview + +TypeBox now provides "runtime conditional types" (formally the `Conditional` module) as standard on `Type.*`. Additional updates in this revision include automatic union and intersection unwrap, universal support for utility types, several ergonomic enhancements and additional options for framework integrators. This revision also carries out a number an internal refactorings to reduce the amount of submodule imports. + +Revision 0.26.0 is a milestone release for the TypeBox project and requires a minor semver update. + +## Contents + +- Enhancements + - [Standard Type Builder](#Standard-Type-Builder) + - [Automatic Unwrap for Union and Intersect](#Automatic-Unwrap-for-Union-and-Intersect) + - [Intersect and Union now Compose](#Intersect-and-Union-now-Compose) + - [Runtime Conditional Types](#Runtime-Conditional-Types) + - [Value Convert](#Value-Convert) + - [Error Iterator](#Error-Iterator) + - [Codegen without JIT](#Codegen-without-JIT) + - [Standard Type (Composite)](#Standard-Type-Composite) + - [Standard Type (Not)](#Standard-Type-Not) + - [Extended Type (BigInt)](#Extended-Type-BigInt) + - [Extended Type (Symbol)](#Extended-Type-Symbol) +- Breaking + - [Minimum TypeScript Version](#Minimum-TypeScript-Version) + - [Intersect Schema Representation](#Intersect-Schema-Representation) + - [Never Schema Representation](#Never-Schema-Representation) + - [Value Cast and Convert](#Value-Cast-and-Convert) + - [Moved TypeGuard Module](#Moved-TypeGuard-Module) + - [Format Renamed to FormatRegistry](#Format-Renamed-to-FormatRegistry) + - [Custom Renamed to TypeRegistry](#Custom-Renamed-to-TypeRegistry) + + + +## Standard Type Builder + +Revision 0.26.0 exports a new type builder called `StandardType`. This builder only allows for the construction JSON Schema compliant types by omitting all Extended types. + +```typescript +import { StandardType as Type, Static } from '@sinclair/typebox' + +const T = Type.Date() // error: no such function +``` + + + +## Automatic Unwrap for Union and Intersect + +Revision 0.26.0 will automatically unwrap unions and intersections for the following cases. + +```typescript +const T1 = Type.Union([Type.String(), Type.Number()]) // TUnion<[TString, TNumber]> + +const T2 = Type.Union([Type.String()]) // TString + +const T3 = Type.Union([]) // TNever +``` + + + +## Intersect and Union now Compose + +Revision 0.26.0 re-enables support for union and intersection type composition. These types are also made compatible with `Pick`, `Omit`, `Partial`, `Required` and `KeyOf` utility types. + +```typescript +const A = Type.Object({ type: Type.Literal('A') }) +const B = Type.Object({ type: Type.Literal('B') }) +const C = Type.Object({ type: Type.Literal('C') }) + +const Union = Type.Union([A, B, C]) + +const Extended = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number() +}) + +const T = Type.Intersect([Union, Extended]) // type T = ({ + // type: "A"; + // } | { + // type: "B"; + // } | { + // type: "C"; + // }) & { + // x: number; + // y: number; + // z: number; + // } + +const K = Type.KeyOf(T) // type K = "type" | "x" | "y" | "z" + +const P = Type.Pick(T, ['type', 'x']) // type P = ({ + // type: "A"; + // } | { + // type: "B"; + // } | { + // type: "C"; + // }) & { + // x: number; + // } + +const O = Type.Partial(P) // type O = ({ + // type?: "A" | undefined; + // } | { + // type?: "B" | undefined; + // } | { + // type?: "C" | undefined; + // }) & { + // x?: number | undefined; + // } +``` + + + +## Runtime Conditional Types + +Revision 0.26.0 adds the runtime conditional types `Extends`, `Extract` and `Exclude` as standard. + +#### TypeScript + +```typescript +type T0 = string extends number ? true : false +// ^ false +type T1 = Extract +// ^ number +type T2 = Exclude +// ^ string +``` + +#### TypeBox +```typescript +const T0 = Type.Extends(Type.String(), Type.Number(), Type.Literal(true), Type.Literal(false)) +// ^ TLiteral +const T1 = Type.Extract(Type.Union([Type.String(), Type.Number()]), Type.Number()) +// ^ TNumber +const T2 = Type.Exclude(Type.Union([Type.String(), Type.Number()]), Type.Number()) +// ^ TString +``` + + + +## Value Convert + +Revision 0.26.0 adds a new `Convert` function to the `Value.*` module. This function will perform a type coercion for any value mismatched to its type if a reasonable conversion is possible. + +```typescript +const T = Type.Number() + +const A = Value.Convert(T, '42') // const A: unknown = 42 - ... Convert(...) will return `unknown` + +const B = Value.Check(T, A) // const B = true - ... so should be checked +``` + + + +## Error Iterator + +Revision 0.26.0 now returns a `ValueErrorIterator` for `.Errors(...)`. This iterator provides a utility function to obtain the first error only. To obtain all errors, continue to use `for-of` enumeration or array spread syntax. + +```typescript +const T = Type.Number() + +const First = Value.Errors(T, 'foo').First() // const First = { path: '', message: 'Expected number', ... } + +const All = [...Value.Errors(T, 'foo')] // const All = [{ path: '', message: 'Expected number', ... }] +``` + + + + +## Codegen without JIT + +Revision 0.26.0 adds a `.Code()` function to the `TypeCompiler` to enable code generation without JIT evaluation. + +```typescript +import { TypeCompiler } from '@sinclair/typebox/compiler' + +const T = Type.Object({ + x: Type.Number(), + y: Type.Number() +}) + +const C = TypeCompiler.Code(T) // return function check(value) { + // return ( + // (typeof value === 'object' && value !== null) && + // !Array.isArray(value) && + // typeof value.x === 'number' && + // Number.isFinite(value.x) && + // typeof value.y === 'number' && + // Number.isFinite(value.y) + // ) + // } +``` + + + +## Standard Type (Not) + +Revision 0.26.0 introduces the `Not` standard type. This type allows for the inversion of assertion logic which can be useful to narrow for broader types. + +#### Example 1 + +```typescript +const T = Type.Not(Type.String({ pattern: 'A|B|C' }), Type.String()) + +Value.Check(T, 'A') // false +Value.Check(T, 'B') // false +Value.Check(T, 'C') // false +Value.Check(T, 'D') // true +``` + +#### Example 2 + +```typescript +const Even = Type.Number({ multipleOf: 2 }) +const Odd = Type.Not(Even, Type.Number()) + +Value.Check(Even, 0) // true +Value.Check(Even, 1) // false +Value.Check(Even, 2) // true + +Value.Check(Odd, 0) // false +Value.Check(Odd, 1) // true +Value.Check(Odd, 2) // false +``` + + + +## Standard Type (Composite) + +Revision 0.26.0 includes a new `Composite` standard type. This type will combine an array of `TObject[]` into a `TObject` by taking a union of any overlapping properties. + +```typescript +const A = Type.Object({ type: Type.Literal('A') }) + +const B = Type.Object({ type: Type.Literal('B') }) + +const C = Type.Object({ type: Type.Literal('C'), value: Type.Number() }) + +const T = Type.Composite([A, B, C]) // type T = { + // type: 'A' | 'B' | 'C' + // value: number + // } +``` + + + +## Extended Type (Symbol) + +Revision 0.26.0 provides provisional support for `Symbol` type validation. + +```typescript +const T = Type.Symbol() + +Value.Check(A, Symbol('Foo')) // true +``` + + + +## Extended Type (BigInt) + +Revision 0.26.0 provides provisional support for `BigInt` type validation. + +```typescript +const T = Type.BigInt({ minimum: 10n }) + +Value.Check(B, 1_000_000n) // true +``` + + + +## Breaking Changes + +The following are breaking changed in Revision 0.26.0 + + + +## Minimum TypeScript Version + +Revision 0.26.0 requires a minimum recommended TypeScript version of `4.2.3`. Version `4.1.5` is no longer supported. + + + +## Intersect Schema Representation + +Revision 0.26.0 changes the schema representation for `Intersect`. Revision 0.25.0 would construct a composite `object` type, in 0.26.0, `Intersect` is expressed as `anyOf`. If upgrading, consider using `Type.Composite(...)` to return backwards compatible representations. + +#### Intersect 0.25.0 + +```typescript +const T = Type.Intersect([ // const U = { + Type.Object({ // type: 'object', + x: Type.Number(), // required: ['x', 'y'], + }), // properties: { + Type.Object({ // x: { + y: Type.Number(), // type: 'number' + }) // }, +]) // y: { + // type: 'number' + // } + // } + // } +``` +#### Intersect 0.26.0 + +```typescript +const T = Type.Intersect([ // const U = { + Type.Object({ // type: 'object', + x: Type.Number(), // allOf: [{ + }), // type: 'object', + Type.Object({ // required: [ 'x' ], + y: Type.Number(), // properties: { + }) // x: { type: 'number' } +]) // } + // }, { + // type: 'object', + // required: ['y'], + // properties: { + // y: { type: 'number' } + // } + // }] + // } +``` + + + +## Never Schema Representation + +Revision 0.26.0 simplifies the representation for `TNever`. Previous versions of TypeBox used an illogical intersection of Boolean constants via `allOf`. In 0.26.0, `never` is expressed as a `not` schema of type `any`. + +#### Intersect 0.25.0 + +```typescript +const T = Type.Never() // const T = { + // allOf: [ + // { type: 'boolean', const: true } + // { type: 'boolean', const: false } + // ] + // } +``` +#### Intersect 0.26.0 + +```typescript +const T = Type.Never() // const T = { not: {} } +``` + + + +## Value Cast and Convert + +Revision 0.26.0 removes the `Cast` functions ability to coerce values. Use the new `Convert` function prior to `Cast`. + +```typescript +const T = Type.Number() + +const V = Value.Cast(T, '42') // const V = 42 - 0.25.0 coerces to 42 + +const V = Value.Cast(T, Value.Convert(T, '42')) // const V = 42 - 0.26.0 convert then cast +``` + + + +## Moved TypeGuard Module + +The `TypeGuard` is now imported via the `@sinclair/typebox` module. This move is due to the TypeBox compositor internally using the guard when constructing types. + +```typescript +import { TypeGuard } from '@sinclair/typebox/guard' // 0.25.0 + +import { TypeGuard } from '@sinclair/typebox' // 0.26.0 +``` + + + +## Format Renamed to FormatRegistry + +The `Format` module has been renamed to `FormatRegistry` and moved to the `typebox.ts` module. + +```typescript +import { Format } from '@sinclair/typebox/format' // 0.25.0 + +import { FormatRegistry } from '@sinclair/typebox' // 0.26.0 +``` + + + +## Custom Renamed to TypeRegistry + +The `Format` module has been renamed to `FormatRegistry` and moved to the `typebox.ts` module. + +```typescript +import { Custom } from '@sinclair/typebox/format' // 0.25.0 + +import { TypeRegistry } from '@sinclair/typebox' // 0.26.0 +``` \ No newline at end of file diff --git a/codegen/codegen.ts b/codegen/codegen.ts deleted file mode 100644 index 81d3797..0000000 --- a/codegen/codegen.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/codegen - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import { TSchema } from '@sinclair/typebox' -import { TypeBoxCodegen } from './typebox' -import { TypeScriptCodegen } from './typescript' -import { JsonSchemaCodegen } from './jsonschema' -import { ZodCodegen, ZodCodegenOptions } from './zod' - -export namespace Codegen { - /** Generates TypeScript type definitions from TypeBox types */ - export function TypeScript(schema: TSchema, references: TSchema[] = []): string { - return TypeScriptCodegen.Generate(schema, references) - } - /** Generates Zod type definitions from TypeBox types */ - export function Zod(schema: TSchema, references: TSchema[] = [], options: ZodCodegenOptions = { imports: true, exports: false }): string { - return ZodCodegen.Generate(schema, references, options) - } - /** Generates TypeBox type definitions from TypeScript code */ - export function TypeBox(code: string): string { - return TypeBoxCodegen.Generate(code) - } - - /** Generates JsonSchema definitions from TypeScript code */ - export function JsonSchema(code: string): string { - return JsonSchemaCodegen.Generate(code) - } -} diff --git a/codegen/index.ts b/codegen/index.ts index 01fc5f5..0513ff9 100644 --- a/codegen/index.ts +++ b/codegen/index.ts @@ -26,4 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -export * from './codegen' +export { TypeBoxToTypeScript } from './typebox-to-typescript' +export { TypeBoxToZod } from './typebox-to-zod' +export { TypeScriptToJsonSchema } from './typescript-to-json-schema' +export { TypeScriptToTypeBox } from './typescript-to-typebox' diff --git a/codegen/typescript.ts b/codegen/typebox-to-typescript.ts similarity index 76% rename from codegen/typescript.ts rename to codegen/typebox-to-typescript.ts index bb1d7a6..6629c60 100644 --- a/codegen/typescript.ts +++ b/codegen/typebox-to-typescript.ts @@ -27,39 +27,35 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import { Formatter } from './formatter' -import { TypeGuard } from '@sinclair/typebox/guard' import * as Types from '@sinclair/typebox' -export namespace TypeScriptCodegen { +export namespace TypeBoxToTypeScript { function Any(schema: Types.TAny) { return 'any' } - function Array(schema: Types.TArray) { const items = Visit(schema.items) return `Array<${items}>` } - function Boolean(schema: Types.TBoolean) { return 'boolean' } - function Constructor(schema: Types.TConstructor) { const params = schema.parameters.map((param) => Visit(param)).join(', ') const returns = Visit(schema.returns) return `new (${params}) => ${returns}` } - function Function(schema: Types.TFunction) { const params = schema.parameters.map((param) => Visit(param)).join(', ') const returns = Visit(schema.returns) return `(${params}) => ${returns}` } - function Integer(schema: Types.TInteger) { return 'number' } - + function Intersect(schema: Types.TIntersect) { + return `(${schema.allOf.map((schema) => Visit(schema)).join(' & ')})` + } function Literal(schema: Types.TLiteral) { if (typeof schema.const === 'string') { return `'${schema.const}'` @@ -67,23 +63,18 @@ export namespace TypeScriptCodegen { return `${schema.const}` } } - function Never(schema: Types.TNever) { return 'never' } - function Null(schema: Types.TNull) { return 'null' } - function String(schema: Types.TString) { return 'string' } - function Number(schema: Types.TNumber) { return 'number' } - function Object(schema: Types.TObject) { const properties: string = globalThis.Object.entries(schema.properties) .map(([key, value]) => { @@ -92,12 +83,10 @@ export namespace TypeScriptCodegen { .join(',\n') return `{\n${properties}\n}` } - function Promise(schema: Types.TPromise) { const item = Visit(schema.item) return `Promise<${item}>` } - function Record(schema: Types.TRecord) { for (const [key, value] of globalThis.Object.entries(schema.patternProperties)) { const type = Visit(value) @@ -107,93 +96,85 @@ export namespace TypeScriptCodegen { return `Record` } } - throw Error('TypeScriptCodeGen: Unreachable') + throw Error('TypeBoxToTypeScript: Unreachable') } - function Ref(schema: Types.TRef) { return schema.$ref } - function Self(schema: Types.TSelf) { return schema.$ref } - function Tuple(schema: Types.TTuple) { if (schema.items === undefined) return `[]` const items = schema.items.map((schema) => Visit(schema)).join(', ') return `[${items}]` } - function UInt8Array(schema: Types.TUint8Array) { return `Uint8Array` } - function Undefined(schema: Types.TUndefined) { return `undefined` } - function Union(schema: Types.TUnion) { - return schema.anyOf.map((schema) => Visit(schema)).join(' | ') + return `${schema.anyOf.map((schema) => Visit(schema)).join(' | ')}` } - function Unknown(schema: Types.TUnknown) { return `unknown` } - function Void(schema: Types.TVoid) { return `void` } - function Visit(schema: Types.TSchema): string { - if (TypeGuard.TAny(schema)) { + if (Types.TypeGuard.TAny(schema)) { return Any(schema) - } else if (TypeGuard.TArray(schema)) { + } else if (Types.TypeGuard.TArray(schema)) { return Array(schema) - } else if (TypeGuard.TBoolean(schema)) { + } else if (Types.TypeGuard.TBoolean(schema)) { return Boolean(schema) - } else if (TypeGuard.TConstructor(schema)) { + } else if (Types.TypeGuard.TConstructor(schema)) { return Constructor(schema) - } else if (TypeGuard.TFunction(schema)) { + } else if (Types.TypeGuard.TFunction(schema)) { return Function(schema) - } else if (TypeGuard.TInteger(schema)) { + } else if (Types.TypeGuard.TInteger(schema)) { return Integer(schema) - } else if (TypeGuard.TLiteral(schema)) { + } else if (Types.TypeGuard.TIntersect(schema)) { + return Intersect(schema) + } else if (Types.TypeGuard.TLiteral(schema)) { return Literal(schema) - } else if (TypeGuard.TNever(schema)) { + } else if (Types.TypeGuard.TNever(schema)) { return Never(schema) - } else if (TypeGuard.TNull(schema)) { + } else if (Types.TypeGuard.TNull(schema)) { return Null(schema) - } else if (TypeGuard.TNumber(schema)) { + } else if (Types.TypeGuard.TNumber(schema)) { return Number(schema) - } else if (TypeGuard.TObject(schema)) { + } else if (Types.TypeGuard.TObject(schema)) { return Object(schema) - } else if (TypeGuard.TPromise(schema)) { + } else if (Types.TypeGuard.TPromise(schema)) { return Promise(schema) - } else if (TypeGuard.TRecord(schema)) { + } else if (Types.TypeGuard.TRecord(schema)) { return Record(schema) - } else if (TypeGuard.TRef(schema)) { + } else if (Types.TypeGuard.TRef(schema)) { return Ref(schema) - } else if (TypeGuard.TSelf(schema)) { + } else if (Types.TypeGuard.TSelf(schema)) { return Self(schema) - } else if (TypeGuard.TString(schema)) { + } else if (Types.TypeGuard.TString(schema)) { return String(schema) - } else if (TypeGuard.TTuple(schema)) { + } else if (Types.TypeGuard.TTuple(schema)) { return Tuple(schema) - } else if (TypeGuard.TUint8Array(schema)) { + } else if (Types.TypeGuard.TUint8Array(schema)) { return UInt8Array(schema) - } else if (TypeGuard.TUndefined(schema)) { + } else if (Types.TypeGuard.TUndefined(schema)) { return Undefined(schema) - } else if (TypeGuard.TUnion(schema)) { + } else if (Types.TypeGuard.TUnion(schema)) { return Union(schema) - } else if (TypeGuard.TUnknown(schema)) { + } else if (Types.TypeGuard.TUnknown(schema)) { return Unknown(schema) - } else if (TypeGuard.TVoid(schema)) { + } else if (Types.TypeGuard.TVoid(schema)) { return Void(schema) } else { - throw Error('TypeScriptCodeGen: Unknown type') + throw Error('TypeBoxToTypeScript: Unknown type') } } - /** Generates TypeScript code from TypeBox types */ export function Generate(schema: Types.TSchema, references: Types.TSchema[] = []) { const result: string[] = [] diff --git a/codegen/zod.ts b/codegen/typebox-to-zod.ts similarity index 74% rename from codegen/zod.ts rename to codegen/typebox-to-zod.ts index bfa9eae..84d8470 100644 --- a/codegen/zod.ts +++ b/codegen/typebox-to-zod.ts @@ -27,24 +27,29 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import { Formatter } from './formatter' -import { TypeGuard } from '@sinclair/typebox/guard' import * as Types from '@sinclair/typebox' -export class ZodNonReferentialType extends Error { +// -------------------------------------------------------------------------- +// Errors +// -------------------------------------------------------------------------- +export class TypeBoxToZodNonReferentialType extends Error { constructor(message: string) { - super(`ZodNonReferentialType: ${message}`) + super(`TypeBoxToZod: ${message}`) } } -export class ZodUnsupportedType extends Error { +export class TypeBoxToZodUnsupportedType extends Error { constructor(message: string) { - super(`ZodUnsupportedType: ${message}`) + super(`TypeBoxToZod: ${message}`) } } +// -------------------------------------------------------------------------- +// Transform +// -------------------------------------------------------------------------- export interface ZodCodegenOptions { imports: boolean exports: boolean } -export namespace ZodCodegen { +export namespace TypeBoxToZod { function Any(schema: Types.TAny) { return `z.any()` } @@ -56,7 +61,7 @@ export namespace ZodCodegen { return `z.boolean()` } function Constructor(schema: Types.TConstructor): string { - throw new ZodUnsupportedType(`TConstructor`) + throw new TypeBoxToZodUnsupportedType(`TConstructor`) } function Function(schema: Types.TFunction) { const params = schema.parameters.map((param) => Visit(param)).join(`, `) @@ -73,6 +78,17 @@ export namespace ZodCodegen { if (schema.multipleOf !== undefined) buffer.push(`.multipleOf(${schema.multipleOf})`) return buffer.join(``) } + function Intersect(schema: Types.TIntersect) { + // note: Zod only supports binary intersection. While correct, this is partially at odds with TypeScript's + // ability to distribute across (A & B & C). This code reduces intersection to binary ops. + function reduce(rest: Types.TSchema[]): string { + if (rest.length === 0) throw Error('Expected at least one intersect type') + if (rest.length === 1) return Visit(rest[0]) + const [left, right] = [rest[0], rest.slice(1)] + return `z.intersection(${Visit(left)}, ${reduce(right)})` + } + return reduce(schema.allOf) + } function Literal(schema: Types.TLiteral) { if (typeof schema.const === `string`) { return `z.literal('${schema.const}')` @@ -124,19 +140,19 @@ export namespace ZodCodegen { for (const [key, value] of globalThis.Object.entries(schema.patternProperties)) { const type = Visit(value) if (key === `^(0|[1-9][0-9]*)$`) { - throw new ZodUnsupportedType(`TRecord`) + throw new TypeBoxToZodUnsupportedType(`TRecord`) } else { return `z.record(${type})` } } - throw Error(`TypeScriptCodeGen: Unreachable`) + throw Error(`TypeBoxToZod: Unreachable`) } function Ref(schema: Types.TRef) { - if (!reference_map.has(schema.$ref!)) throw new ZodNonReferentialType(schema.$ref!) + if (!reference_map.has(schema.$ref!)) throw new TypeBoxToZodNonReferentialType(schema.$ref!) return schema.$ref } function Self(schema: Types.TSelf) { - if (!reference_map.has(schema.$ref!)) throw new ZodNonReferentialType(schema.$ref!) + if (!reference_map.has(schema.$ref!)) throw new TypeBoxToZodNonReferentialType(schema.$ref!) recursive_set.add(schema.$ref) return schema.$ref } @@ -146,7 +162,7 @@ export namespace ZodCodegen { return `z.tuple([${items}])` } function UInt8Array(schema: Types.TUint8Array): string { - throw new ZodUnsupportedType(`TUint8Array`) + throw new TypeBoxToZodUnsupportedType(`TUint8Array`) } function Undefined(schema: Types.TUndefined) { return `z.undefined()` @@ -162,52 +178,54 @@ export namespace ZodCodegen { } function Visit(schema: Types.TSchema): string { if (schema.$id !== undefined) reference_map.set(schema.$id, schema) - if (TypeGuard.TAny(schema)) { + if (Types.TypeGuard.TAny(schema)) { return Any(schema) - } else if (TypeGuard.TArray(schema)) { + } else if (Types.TypeGuard.TArray(schema)) { return Array(schema) - } else if (TypeGuard.TBoolean(schema)) { + } else if (Types.TypeGuard.TBoolean(schema)) { return Boolean(schema) - } else if (TypeGuard.TConstructor(schema)) { + } else if (Types.TypeGuard.TConstructor(schema)) { return Constructor(schema) - } else if (TypeGuard.TFunction(schema)) { + } else if (Types.TypeGuard.TFunction(schema)) { return Function(schema) - } else if (TypeGuard.TInteger(schema)) { + } else if (Types.TypeGuard.TInteger(schema)) { return Integer(schema) - } else if (TypeGuard.TLiteral(schema)) { + } else if (Types.TypeGuard.TIntersect(schema)) { + return Intersect(schema) + } else if (Types.TypeGuard.TLiteral(schema)) { return Literal(schema) - } else if (TypeGuard.TNever(schema)) { + } else if (Types.TypeGuard.TNever(schema)) { return Never(schema) - } else if (TypeGuard.TNull(schema)) { + } else if (Types.TypeGuard.TNull(schema)) { return Null(schema) - } else if (TypeGuard.TNumber(schema)) { + } else if (Types.TypeGuard.TNumber(schema)) { return Number(schema) - } else if (TypeGuard.TObject(schema)) { + } else if (Types.TypeGuard.TObject(schema)) { return Object(schema) - } else if (TypeGuard.TPromise(schema)) { + } else if (Types.TypeGuard.TPromise(schema)) { return Promise(schema) - } else if (TypeGuard.TRecord(schema)) { + } else if (Types.TypeGuard.TRecord(schema)) { return Record(schema) - } else if (TypeGuard.TRef(schema)) { + } else if (Types.TypeGuard.TRef(schema)) { return Ref(schema) - } else if (TypeGuard.TSelf(schema)) { + } else if (Types.TypeGuard.TSelf(schema)) { return Self(schema) - } else if (TypeGuard.TString(schema)) { + } else if (Types.TypeGuard.TString(schema)) { return String(schema) - } else if (TypeGuard.TTuple(schema)) { + } else if (Types.TypeGuard.TTuple(schema)) { return Tuple(schema) - } else if (TypeGuard.TUint8Array(schema)) { + } else if (Types.TypeGuard.TUint8Array(schema)) { return UInt8Array(schema) - } else if (TypeGuard.TUndefined(schema)) { + } else if (Types.TypeGuard.TUndefined(schema)) { return Undefined(schema) - } else if (TypeGuard.TUnion(schema)) { + } else if (Types.TypeGuard.TUnion(schema)) { return Union(schema) - } else if (TypeGuard.TUnknown(schema)) { + } else if (Types.TypeGuard.TUnknown(schema)) { return Unknown(schema) - } else if (TypeGuard.TVoid(schema)) { + } else if (Types.TypeGuard.TVoid(schema)) { return Void(schema) } else { - throw Error(`TypeScriptCodeGen: Unknown type`) + throw Error(`TypeBoxToZod: Unknown type`) } } const reference_map = new Map() @@ -222,7 +240,7 @@ export namespace ZodCodegen { // initialize root schematic and reference map if (schema.$id === undefined) schema.$id = `T_Generated` for (const reference of references) { - if (reference.$id === undefined) throw new ZodNonReferentialType(JSON.stringify(reference)) + if (reference.$id === undefined) throw new TypeBoxToZodNonReferentialType(JSON.stringify(reference)) reference_map.set(reference.$id, reference) } // render-code: Imports required for the generated code diff --git a/codegen/jsonschema.ts b/codegen/typescript-to-json-schema.ts similarity index 85% rename from codegen/jsonschema.ts rename to codegen/typescript-to-json-schema.ts index c429a11..2f198db 100644 --- a/codegen/jsonschema.ts +++ b/codegen/typescript-to-json-schema.ts @@ -29,32 +29,32 @@ THE SOFTWARE. import { Formatter } from './formatter' import * as ts from 'typescript' -export class NonExpressable extends Error { +// -------------------------------------------------------------------------- +// Errors +// -------------------------------------------------------------------------- +export class TypeScriptToJsonSchemaNonExpressable extends Error { constructor(type: string) { - super(`JsonSchemaCodeGen: Cannot express syntax type '${type}'`) + super(`TypeScriptToJsonSchema: Cannot express syntax type '${type}'`) } } - -/** Generates JsonSchema from TypeScript interface and type definitions */ -export namespace JsonSchemaCodegen { +// -------------------------------------------------------------------------- +// Transform +// -------------------------------------------------------------------------- +export namespace TypeScriptToJsonSchema { function isReadonlyProperty(node: ts.PropertySignature): boolean { return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'readonly') !== undefined } - function isOptionalProperty(node: ts.PropertySignature) { return node.questionToken !== undefined } - function isExport(node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration): boolean { return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'export') !== undefined } - function* SourceFile(node: ts.SourceFile): IterableIterator { for (const next of node.getChildren()) { yield* Visit(next) } } - function* PropertySignature(node: ts.PropertySignature): IterableIterator { const [readonly, optional] = [isReadonlyProperty(node), isOptionalProperty(node)] const type = Collect(node.type) @@ -68,7 +68,6 @@ export namespace JsonSchemaCodegen { return yield `${node.name.getText()}: ${type}` } } - function* ArrayTypeNode(node: ts.ArrayTypeNode): IterableIterator { const type = Collect(node.elementType) yield `{ @@ -76,7 +75,6 @@ export namespace JsonSchemaCodegen { items: ${type} }` } - function* TupleTypeNode(node: ts.TupleTypeNode): IterableIterator { const types = node.elements.map((type) => Collect(type)).join(',\n') yield `{ @@ -86,7 +84,6 @@ export namespace JsonSchemaCodegen { maxItems: ${node.elements.length} }` } - function* UnionTypeNode(node: ts.UnionTypeNode): IterableIterator { const types = node.types.map((type) => Collect(type)).join(',\n') yield `{ @@ -95,7 +92,6 @@ export namespace JsonSchemaCodegen { ] }` } - function* IntersectionTypeNode(node: ts.IntersectionTypeNode): IterableIterator { const types = node.types.map((type) => Collect(type)).join(',\n') yield `{ @@ -104,23 +100,18 @@ export namespace JsonSchemaCodegen { ] }` } - function* TypeOperatorNode(node: ts.TypeOperatorNode): IterableIterator { - throw new NonExpressable('TypeOperatorNode') + throw new TypeScriptToJsonSchemaNonExpressable('TypeOperatorNode') } - function* Parameter(node: ts.ParameterDeclaration): IterableIterator { yield Collect(node.type) } - function* FunctionTypeNode(node: ts.FunctionTypeNode): IterableIterator { - throw new NonExpressable('FunctionTypeNode') + throw new TypeScriptToJsonSchemaNonExpressable('FunctionTypeNode') } - function* ConstructorTypeNode(node: ts.ConstructorTypeNode): IterableIterator { - throw new NonExpressable('ConstructorTypeNode') + throw new TypeScriptToJsonSchemaNonExpressable('ConstructorTypeNode') } - function* EnumMember(node: ts.EnumMember): IterableIterator { if (node.initializer) { return yield `{ @@ -131,7 +122,6 @@ export namespace JsonSchemaCodegen { const: '${node.name.getText()}' }` } - function* EnumDeclaration(node: ts.EnumDeclaration): IterableIterator { const exports = isExport(node) ? 'export ' : '' const name = node.name.getText() @@ -142,7 +132,6 @@ export namespace JsonSchemaCodegen { ] }` } - function* InterfaceDeclaration(node: ts.InterfaceDeclaration): IterableIterator { if (node.typeParameters) { const exports = isExport(node) ? 'export ' : '' @@ -206,11 +195,11 @@ export namespace JsonSchemaCodegen { } function* RestTypeNode(node: ts.RestTypeNode): IterableIterator { - throw new NonExpressable('RestTypeNode') + throw new TypeScriptToJsonSchemaNonExpressable('RestTypeNode') } function* ConditionalTypeNode(node: ts.ConditionalTypeNode): IterableIterator { - throw new NonExpressable('ConditionalTypeNode') + throw new TypeScriptToJsonSchemaNonExpressable('ConditionalTypeNode') } function* TypeReferenceNode(node: ts.TypeReferenceNode): IterableIterator { @@ -222,31 +211,31 @@ export namespace JsonSchemaCodegen { items: ${args} }` } else if (name === 'Record') { - throw new NonExpressable('TypeReferenceNode:Record') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Record') } else if (name === 'Partial') { - throw new NonExpressable('TypeReferenceNode:Partial') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Partial') } else if (name === 'Uint8Array') { - throw new NonExpressable('TypeReferenceNode:Uint8Array') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Uint8Array') } else if (name === 'Required') { - throw new NonExpressable('TypeReferenceNode:Required') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Required') } else if (name === 'Omit') { - throw new NonExpressable('TypeReferenceNode:Omit') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Omit') } else if (name === 'Pick') { - throw new NonExpressable('TypeReferenceNode:Pick') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Pick') } else if (name === 'Promise') { - throw new NonExpressable('TypeReferenceNode:Promise') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Promise') } else if (name === 'ReturnType') { - throw new NonExpressable('TypeReferenceNode:ReturnType') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:ReturnType') } else if (name === 'InstanceType') { - throw new NonExpressable('TypeReferenceNode:InstanceType') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:InstanceType') } else if (name === 'Parameters') { - throw new NonExpressable('TypeReferenceNode:Parameters') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Parameters') } else if (name === 'ConstructorParameters') { - throw new NonExpressable('TypeReferenceNode:ConstructorParameters') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:ConstructorParameters') } else if (name === 'Exclude') { - throw new NonExpressable('TypeReferenceNode:Exclude') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Exclude') } else if (name === 'Extract') { - throw new NonExpressable('TypeReferenceNode:Extract') + throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Extract') } else { return yield `${name}${args}` } @@ -345,7 +334,7 @@ export namespace JsonSchemaCodegen { } else if (ts.isConditionalTypeNode(node)) { return yield* ConditionalTypeNode(node) } else if (node.kind === ts.SyntaxKind.KeyOfKeyword) { - throw new NonExpressable('KeyOfKeyword') + throw new TypeScriptToJsonSchemaNonExpressable('KeyOfKeyword') } else if (node.kind === ts.SyntaxKind.NumberKeyword) { return yield `{ type: 'number' @@ -359,7 +348,7 @@ export namespace JsonSchemaCodegen { type: 'boolean' }` } else if (node.kind === ts.SyntaxKind.UndefinedKeyword) { - throw new NonExpressable('UndefinedKeyword') + throw new TypeScriptToJsonSchemaNonExpressable('UndefinedKeyword') } else if (node.kind === ts.SyntaxKind.UnknownKeyword) { return yield `{ @@ -386,7 +375,7 @@ export namespace JsonSchemaCodegen { type: 'null' }` } else if (node.kind === ts.SyntaxKind.VoidKeyword) { - throw new NonExpressable('KeyOfKeyword') + throw new TypeScriptToJsonSchemaNonExpressable('KeyOfKeyword') } else if (node.kind === ts.SyntaxKind.EndOfFileToken) { return } else if (node.kind === ts.SyntaxKind.SyntaxList) { diff --git a/codegen/typebox.ts b/codegen/typescript-to-typebox.ts similarity index 91% rename from codegen/typebox.ts rename to codegen/typescript-to-typebox.ts index 598921b..a42f117 100644 --- a/codegen/typebox.ts +++ b/codegen/typescript-to-typebox.ts @@ -29,26 +29,29 @@ THE SOFTWARE. import { Formatter } from './formatter' import * as ts from 'typescript' -/** Generates TypeBox types from TypeScript interface and type definitions */ -export namespace TypeBoxCodegen { +// -------------------------------------------------------------------------- +// Transform +// -------------------------------------------------------------------------- + +/** Generates TypeBox types from TypeScript code */ +export namespace TypeScriptToTypeBox { function isReadonlyProperty(node: ts.PropertySignature): boolean { return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'readonly') !== undefined } - function isOptionalProperty(node: ts.PropertySignature) { return node.questionToken !== undefined } - - function isExport(node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration): boolean { + function isExport(node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration | ts.ModuleDeclaration): boolean { return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'export') !== undefined } - + function isNamespace(node: ts.ModuleDeclaration) { + return node.flags === ts.NodeFlags.Namespace + } function* SourceFile(node: ts.SourceFile): IterableIterator { for (const next of node.getChildren()) { yield* Visit(next) } } - function* PropertySignature(node: ts.PropertySignature): IterableIterator { const [readonly, optional] = [isReadonlyProperty(node), isOptionalProperty(node)] const type = Collect(node.type) @@ -62,50 +65,41 @@ export namespace TypeBoxCodegen { return yield `${node.name.getText()}: ${type}` } } - function* ArrayTypeNode(node: ts.ArrayTypeNode): IterableIterator { const type = Collect(node.elementType) yield `Type.Array(${type})` } - function* TupleTypeNode(node: ts.TupleTypeNode): IterableIterator { const types = node.elements.map((type) => Collect(type)).join(',\n') yield `Type.Tuple([\n${types}\n])` } - function* UnionTypeNode(node: ts.UnionTypeNode): IterableIterator { const types = node.types.map((type) => Collect(type)).join(',\n') yield `Type.Union([\n${types}\n])` } - function* IntersectionTypeNode(node: ts.IntersectionTypeNode): IterableIterator { const types = node.types.map((type) => Collect(type)).join(',\n') yield `Type.Intersect([\n${types}\n])` } - function* TypeOperatorNode(node: ts.TypeOperatorNode): IterableIterator { if (node.operator === ts.SyntaxKind.KeyOfKeyword) { const type = Collect(node.type) yield `Type.KeyOf(${type})` } } - function* Parameter(node: ts.ParameterDeclaration): IterableIterator { yield Collect(node.type) } - function* FunctionTypeNode(node: ts.FunctionTypeNode): IterableIterator { const parameters = node.parameters.map((param) => Collect(param)).join(', ') const returns = Collect(node.type) yield `Type.Function([${parameters}], ${returns})` } - function* ConstructorTypeNode(node: ts.ConstructorTypeNode): IterableIterator { const parameters = node.parameters.map((param) => Collect(param)).join(', ') const returns = Collect(node.type) yield `Type.Constructor([${parameters}], ${returns})` } - function* EnumDeclaration(node: ts.EnumDeclaration): IterableIterator { const exports = isExport(node) ? 'export ' : '' const name = node.name.getText() @@ -114,7 +108,6 @@ export namespace TypeBoxCodegen { const type = `${exports}const ${name} = Type.Enum(${name}Enum)` yield [enumType, '', type].join('\n') } - function* InterfaceDeclaration(node: ts.InterfaceDeclaration): IterableIterator { useImports = true if (node.typeParameters) { @@ -135,7 +128,6 @@ export namespace TypeBoxCodegen { yield `${staticDeclaration}\n${typeDeclaration}` } } - function* TypeAliasDeclaration(node: ts.TypeAliasDeclaration): IterableIterator { useImports = true if (node.typeParameters) { @@ -156,28 +148,22 @@ export namespace TypeBoxCodegen { yield `${staticDeclaration}\n${typeDeclaration}` } } - function* TypeParameterDeclaration(node: ts.TypeParameterDeclaration): IterableIterator { yield node.name.getText() } - function* ParenthesizedTypeNode(node: ts.ParenthesizedTypeNode): IterableIterator { yield Collect(node.type) } - function* RestTypeNode(node: ts.RestTypeNode): IterableIterator { yield `Type.Rest()` } - function* ConditionalTypeNode(node: ts.ConditionalTypeNode): IterableIterator { - useConditional = true const checkType = Collect(node.checkType) const extendsType = Collect(node.extendsType) const trueType = Collect(node.trueType) const falseType = Collect(node.falseType) - yield `Conditional.Extends(${checkType}, ${extendsType}, ${trueType}, ${falseType})` + yield `Type.Extends(${checkType}, ${extendsType}, ${trueType}, ${falseType})` } - function* TypeReferenceNode(node: ts.TypeReferenceNode): IterableIterator { const name = node.typeName.getText() const args = node.typeArguments ? `(${node.typeArguments.map((type) => Collect(type)).join(', ')})` : '' @@ -206,41 +192,46 @@ export namespace TypeBoxCodegen { } else if (name === 'ConstructorParameters') { return yield `Type.ConstructorParameters${args}` } else if (name === 'Exclude') { - return yield `Conditional.Exclude${args}` + return yield `Type.Exclude${args}` } else if (name === 'Extract') { - return yield `Conditional.Extract${args}` + return yield `Type.Extract${args}` } else { return yield `${name}${args}` } } - function* TypeLiteralNode(node: ts.TypeLiteralNode): IterableIterator { const members = node.members.map((member) => Collect(member)).join(',\n') yield `Type.Object({\n${members}\n})` } - function* LiteralTypeNode(node: ts.LiteralTypeNode): IterableIterator { const text = node.getText() if (text === 'null') return yield `Type.Null()` yield `Type.Literal(${node.getText()})` } - + function* ModuleDeclaration(node: ts.ModuleDeclaration): IterableIterator { + const export_specifier = isExport(node) ? 'export ' : '' + const module_specifier = isNamespace(node) ? 'namespace' : 'module' + yield `${export_specifier}${module_specifier} ${node.name.getText()} {` + yield* Visit(node.body) + yield `}` + } + function* ModuleBlock(node: ts.ModuleBlock): IterableIterator { + for (const statement of node.statements) { + yield* Visit(statement) + } + } function* FunctionDeclaration(node: ts.FunctionDeclaration): IterableIterator { yield node.getText() } - function* ClassDeclaration(node: ts.ClassDeclaration): IterableIterator { yield node.getText() } - function Collect(node: ts.Node | undefined): string { return `${[...Visit(node)].join('')}` } - function CollectNewLine(node: ts.Node | undefined): string { return [...Visit(node)].join('\n\n') } - function* Visit(node: ts.Node | undefined): IterableIterator { if (node === undefined) return if (ts.isSourceFile(node)) { @@ -265,6 +256,10 @@ export namespace TypeBoxCodegen { return yield* TypeLiteralNode(node) } else if (ts.isLiteralTypeNode(node)) { return yield* LiteralTypeNode(node) + } else if (ts.isModuleDeclaration(node)) { + return yield* ModuleDeclaration(node) + } else if (ts.isModuleBlock(node)) { + return yield* ModuleBlock(node) } else if (ts.isArrayTypeNode(node)) { return yield* ArrayTypeNode(node) } else if (ts.isTupleTypeNode(node)) { @@ -287,6 +282,8 @@ export namespace TypeBoxCodegen { return yield* ClassDeclaration(node) } else if (ts.isConditionalTypeNode(node)) { return yield* ConditionalTypeNode(node) + } else if (node.kind === ts.SyntaxKind.ExportKeyword) { + return yield `export` } else if (node.kind === ts.SyntaxKind.KeyOfKeyword) { return yield `Type.KeyOf()` } else if (node.kind === ts.SyntaxKind.NumberKeyword) { @@ -319,22 +316,16 @@ export namespace TypeBoxCodegen { return yield node.getText() } } - let useImports = false - let useConditional = false let useGenerics = false - /** Generates TypeBox types from TypeScript interface and type definitions */ export function Generate(typescriptCode: string) { useImports = false - useConditional = false useGenerics = false - const source = ts.createSourceFile('code.ts', typescriptCode, ts.ScriptTarget.ESNext, true) const typeDeclarations = CollectNewLine(source) const importStatments: string[] = [] if (useImports) { - if (useConditional) importStatments.push(`import { Conditional } from '@sinclair/typebox/conditional'`) if (useGenerics) importStatments.push(`import { Type, Static, TSchema } from '@sinclair/typebox'`) if (!useGenerics) importStatments.push(`import { Type, Static } from '@sinclair/typebox'`) } diff --git a/example/experimental/experimental.ts b/example/experimental/experimental.ts new file mode 100644 index 0000000..facf224 --- /dev/null +++ b/example/experimental/experimental.ts @@ -0,0 +1,109 @@ + +/*-------------------------------------------------------------------------- + +@sinclair/typebox/experimental + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import * as Types from '@sinclair/typebox' +import { Value } from '@sinclair/typebox/value' + +// ------------------------------------------------------------------------------------- +// TReadonlyObject +// ------------------------------------------------------------------------------------- +export type TReadonlyArray = Types.Assert<{ [K in keyof T]: TReadonlyObject> }, Types.TSchema[]> +// prettier-ignore +export type TReadonlyProperties = Types.Evaluate ? Types.TReadonlyOptional : + T[K] extends Types.TReadonly ? Types.TReadonly : + T[K] extends Types.TOptional ? Types.TReadonlyOptional : + Types.TReadonly +}, Types.TProperties>> +// prettier-ignore +export type TReadonlyObject = + T extends Types.TIntersect ? Types.TIntersect> : + T extends Types.TUnion ? Types.TUnion> : + T extends Types.TObject ? Types.TObject> : + Types.TReadonly + +// ------------------------------------------------------------------------------------- +// TUnionEnum +// ------------------------------------------------------------------------------------- +export interface TUnionEnum extends Types.TSchema { + [Types.Kind]: 'UnionEnum' + static: T[number] + enum: T +} +// ------------------------------------------------------------------------------------- +// UnionOneOf +// ------------------------------------------------------------------------------------- +export interface UnionOneOf extends Types.TSchema { + [Types.Kind]: 'UnionOneOf' + static: { [K in keyof T]: Types.Static }[number] + oneOf: T +} +// ------------------------------------------------------------------------------------- +// ExperimentalTypeBuilder +// ------------------------------------------------------------------------------------- +export class ExperimentalTypeBuilder extends Types.ExtendedTypeBuilder { + /** `[Experimental]` Remaps a Intersect, Union or Object as readonly */ + public ReadonlyObject(schema: T): TReadonlyObject { + function Apply(property: Types.TSchema): any { + // prettier-ignore + switch (property[Types.Modifier]) { + case 'ReadonlyOptional': property[Types.Modifier] = 'ReadonlyOptional'; break + case 'Readonly': property[Types.Modifier] = 'Readonly'; break + case 'Optional': property[Types.Modifier] = 'ReadonlyOptional'; break + default: property[Types.Modifier] = 'Readonly'; break + } + return property + } + // prettier-ignore + return (Types.TypeGuard.TIntersect(schema) || Types.TypeGuard.TUnion(schema) || Types.TypeGuard.TObject(schema)) + ? Types.ObjectMap.Map>(schema, (schema) => { + globalThis.Object.keys(schema.properties).forEach(key => Apply(schema.properties[key])) + return schema + }, {}) : Apply(schema) + } + /** `[Experimental]` Creates a Union type with a `enum` schema representation */ + public UnionEnum(values: [...T], options: Types.SchemaOptions = {}) { + function UnionEnumCheck(schema: TUnionEnum<(string | number)[]>, value: unknown) { + return (typeof value === 'string' || typeof value === 'number') && schema.enum.includes(value) + } + if (!Types.TypeRegistry.Has('UnionEnum')) Types.TypeRegistry.Set('UnionEnum', UnionEnumCheck) + return { ...options, [Types.Kind]: 'UnionEnum', enum: values } as TUnionEnum + } + /** `[Experimental]` Creates a Union type with a `oneOf` schema representation */ + public UnionOneOf(oneOf: [...T], options: Types.SchemaOptions = {}) { + function UnionOneOfCheck(schema: UnionOneOf, value: unknown) { + return 1 === schema.oneOf.reduce((acc: number, schema: any) => (Value.Check(schema, value) ? acc + 1 : acc), 0) + } + if (!Types.TypeRegistry.Has('UnionOneOf')) Types.TypeRegistry.Set('UnionOneOf', UnionOneOfCheck) + return { ...options, [Types.Kind]: 'UnionOneOf', oneOf } as UnionOneOf + } +} + +export const Type = new ExperimentalTypeBuilder() \ No newline at end of file diff --git a/src/hash/index.ts b/example/experimental/index.ts similarity index 95% rename from src/hash/index.ts rename to example/experimental/index.ts index c1f89ba..9dcc036 100644 --- a/src/hash/index.ts +++ b/example/experimental/index.ts @@ -1,6 +1,6 @@ /*-------------------------------------------------------------------------- -@sinclair/typebox/hash +@sinclair/typebox/extensions The MIT License (MIT) @@ -26,4 +26,4 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -export * from './hash' +export * from './experimental' diff --git a/example/experimental/readme.md b/example/experimental/readme.md new file mode 100644 index 0000000..16f8b38 --- /dev/null +++ b/example/experimental/readme.md @@ -0,0 +1,30 @@ +# ExperimentalTypeBuilder + +An experimental TypeBox type builder with additional custom types. + +## Overview + +The TypeBox TypeBuilder classes are designed to be extended with user defined types. Instances where you may wish to do this are if your application is dependent on custom schematics and/or non-JSON serializable values (an example of which might be a Mongo's `ObjectId` or other such non-serializable value) + +## Application Type Builder + +The following shows creating a simple `ApplicationTypeBuilder` with additional types `Nullable` and `StringEnum`. These types are fairly common in OpenAPI implementations. + +```typescript +import { StandardTypeBuilder, Static, TSchema } from '@sinclair/typebox' + +export class ApplicationTypeBuilder extends StandardTypeBuilder { // only JSON Schema types + public Nullable(schema: T) { + return this.Unsafe | null>({ ...schema, nullable: true }) + } + public StringEnum(values: [...T]) { + return this.Unsafe({ type: 'string', enum: values }) + } +} + +export const Type = new ApplicationTypeBuilder() // re-export! +``` + +## Experimental Type Builder + +The `experimental.ts` file provided with this example shows advanced usage by creating complex types for potential inclusion in the TypeBox library in later revisions. It is offered for reference, experimentation and is open to contributor submission. \ No newline at end of file diff --git a/example/extensions/index.ts b/example/extensions/index.ts deleted file mode 100644 index c8ec283..0000000 --- a/example/extensions/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/extensions - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -export * from './intersect-allof' -export * from './readonly-object' -export * from './union-oneof' -export * from './union-enum' diff --git a/example/extensions/intersect-allof.ts b/example/extensions/intersect-allof.ts deleted file mode 100644 index 4a3a601..0000000 --- a/example/extensions/intersect-allof.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/extensions - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import { Kind, TSchema, SchemaOptions, IntersectReduce, IntersectEvaluate } from '@sinclair/typebox' -import { TypeGuard } from '@sinclair/typebox/guard' -import { Custom } from '@sinclair/typebox/custom' -import { Value } from '@sinclair/typebox/value' - -function IntersectAllOfCheck(schema: IntersectAllOf, value: unknown) { - if (!schema.allOf.every((schema) => Value.Check(schema, value))) return false - if (schema.unevaluatedProperties === undefined || schema.unevaluatedProperties === true) return true - const valueKeys = typeof value === 'object' && value !== null ? globalThis.Object.keys(value) : [] - const knownKeys = schema.allOf.reduce((set, schema) => { - if (!TypeGuard.TObject(schema)) return set - Object.keys(schema.properties).forEach((key) => set.add(key)) - return set - }, new Set()) - return valueKeys.every((key) => knownKeys.has(key)) -} - -export interface IntersectAllOfOptions extends SchemaOptions { - unevaluatedProperties?: boolean -} - -export interface IntersectAllOf extends TSchema, IntersectAllOfOptions { - [Kind]: 'IntersectAllOf' - static: IntersectReduce> - allOf: T -} - -/** Creates a Intersect type with a `allOf` schema representation */ -export function IntersectAllOf(allOf: [...T], options: IntersectAllOfOptions = {}) { - if (!Custom.Has('IntersectAllOf')) Custom.Set('IntersectAllOf', IntersectAllOfCheck) - return { ...options, [Kind]: 'IntersectAllOf', allOf } as IntersectAllOf -} diff --git a/example/extensions/readonly-object.ts b/example/extensions/readonly-object.ts deleted file mode 100644 index cf92fd2..0000000 --- a/example/extensions/readonly-object.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/extensions - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import { Type, TSchema, TObject, TProperties, Modifier, TReadonly, TReadonlyOptional, TOptional } from '@sinclair/typebox' - -// prettier-ignore -export type TReadonlyObject = TObject<{ - [K in keyof T['properties']]: - T['properties'][K] extends TReadonlyOptional ? TReadonlyOptional : - T['properties'][K] extends TReadonly ? TReadonly : - T['properties'][K] extends TOptional ? TReadonlyOptional : - TReadonly -}> - -/** Remaps all properties of an object to be readonly */ -export function ReadonlyObject(schema: T): TReadonlyObject { - return Type.Object( - Object.keys(schema.properties).reduce((acc, key) => { - const property = schema.properties[key] as TSchema - const modifier = property[Modifier] - // prettier-ignore - const mapped = ( - (modifier === 'ReadonlyOptional') ? { ...property, [Modifier]: 'ReadonlyOptional' } : - (modifier === 'Readonly') ? { ...property, [Modifier]: 'Readonly' } : - (modifier === 'Optional') ? { ...property, [Modifier]: 'ReadonlyOptional' } : - ({...property, [Modifier]: 'Readonly' }) - ) - return { ...acc, [key]: mapped } - }, {} as TProperties), - ) as TReadonlyObject -} diff --git a/example/extensions/union-enum.ts b/example/extensions/union-enum.ts deleted file mode 100644 index 6f61163..0000000 --- a/example/extensions/union-enum.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/extensions - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import { Kind, TSchema, SchemaOptions } from '@sinclair/typebox' -import { Custom } from '@sinclair/typebox/custom' - -function UnionEnumCheck(schema: TUnionEnum<(string | number)[]>, value: unknown) { - return (typeof value === 'string' || typeof value === 'number') && schema.enum.includes(value) -} - -export interface TUnionEnum extends TSchema { - [Kind]: 'UnionEnum' - static: T[number] - enum: T -} - -/** Creates a Union type with a `enum` schema representation */ -export function UnionEnum(values: [...T], options: SchemaOptions = {}) { - if (!Custom.Has('UnionEnum')) Custom.Set('UnionEnum', UnionEnumCheck) - return { ...options, [Kind]: 'UnionEnum', enum: values } as TUnionEnum -} diff --git a/example/extensions/union-oneof.ts b/example/extensions/union-oneof.ts deleted file mode 100644 index ef749b7..0000000 --- a/example/extensions/union-oneof.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/extensions - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import { Kind, TSchema, SchemaOptions, Static } from '@sinclair/typebox' -import { Custom } from '@sinclair/typebox/custom' -import { Value } from '@sinclair/typebox/value' - -function UnionOneOfCheck(schema: UnionOneOf, value: unknown) { - return 1 === schema.oneOf.reduce((acc: number, schema: any) => (Value.Check(schema, value) ? acc + 1 : acc), 0) -} - -export interface UnionOneOf extends TSchema { - [Kind]: 'UnionOneOf' - static: { [K in keyof T]: Static }[number] - oneOf: T -} - -/** Creates a Union type with a `oneOf` schema representation */ -export function UnionOneOf(oneOf: [...T], options: SchemaOptions = {}) { - if (!Custom.Has('UnionOneOf')) Custom.Set('UnionOneOf', UnionOneOfCheck) - return { ...options, [Kind]: 'UnionOneOf', oneOf } as UnionOneOf -} diff --git a/example/formats/additional.ts b/example/formats/additional.ts deleted file mode 100644 index 60396d3..0000000 --- a/example/formats/additional.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Format } from '@sinclair/typebox/format' - -/** Configures TypeBox for additional string formats */ -export namespace AdditionalFormats { - // ------------------------------------------------------------------------------------------- - // These formats are ported from the ajv-formats library. All credit goes to the original - // authors with the original code located at the following location. - // - // https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts - // ------------------------------------------------------------------------------------------- - - 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] - const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/ - const IPV6 = - /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i - const URL = - /^(?:https?|wss?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu - 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 - - 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(value: string, strictTimeZone?: boolean): boolean { - const dateTime: string[] = value.split(DATE_TIME_SEPARATOR) - return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1], strictTimeZone) - } - - function IsEmail(value: string) { - return EMAIL.test(value) - } - - function IsUuid(value: string) { - return UUID.test(value) - } - - function IsUrl(value: string) { - return URL.test(value) - } - - function IsIPv6(value: string) { - return IPV6.test(value) - } - - function IsIPv4(value: string) { - return IPV4.test(value) - } - - /** Configures additional string formats */ - export function Configure(): void { - Format.Set('date-time', (value) => IsDateTime(value, true)) - Format.Set('date', (value) => IsDate(value)) - Format.Set('time', (value) => IsTime(value)) - Format.Set('email', (value) => IsEmail(value)) - Format.Set('uuid', (value) => IsUuid(value)) - Format.Set('url', (value) => IsUrl(value)) - Format.Set('ipv6', (value) => IsIPv6(value)) - Format.Set('ipv4', (value) => IsIPv4(value)) - // - // optional: add additional formats - // - } -} diff --git a/example/formats/index.ts b/example/formats/index.ts index 2923362..4251323 100644 --- a/example/formats/index.ts +++ b/example/formats/index.ts @@ -1 +1 @@ -export * from './additional' +export * from './standard' diff --git a/example/formats/readme.md b/example/formats/readme.md new file mode 100644 index 0000000..03eb80d --- /dev/null +++ b/example/formats/readme.md @@ -0,0 +1,43 @@ +# String Formats + +TypeBox does not implement any string formats by default. However it is possible to register user defined formats using the `FormatRegistry`. Once registered, the format becomes available to both `Value` and `TypeCompiler` modules. + +## FormatRegistry + +The following shows basic usage of the format registry + +```typescript +import { Type, FormatRegistry } from '@sinclair/typebox' +import { Value } from '@sinclair/typebox/value' + +// Register the 'foo-only' format. The format checks for 'foo' only. +FormatRegistry.Set('foo-only', value => value === 'foo') + +const T = Type.String({ format: 'foo-only' }) + +// Validate +Value.Check(T, 'foo') // true +Value.Check(T, 'bar') // false +``` + +## Standard Formats + +The `standard.ts` file provided with this example implements several standard string formats. + +```typescript +import './standard' +``` + +The following formats are implemented by `standard.ts` + +| Format | Description | +| --- | --- | +| `email` | Internet email address, see [RFC 5321, section 4.1.2.](http://tools.ietf.org/html/rfc5321#section-4.1.2) | +| `date-time` | ISO8601 DateTime. example, `2018-11-13T20:20:39+00:00` | +| `date` | ISO8601 Date component, for example, `2018-11-13` | +| `time` | ISO8601 Time component, for example, `20:20:39+00:00` | +| `uuid` | A Universally Unique Identifier as defined by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122). | +| `url` | A uniform resource locator as defined in [RFC 1738](https://www.rfc-editor.org/rfc/rfc1738) +| `ipv4` | IPv4 address, according to dotted-quad ABNF syntax as defined in [RFC 2673, section 3.2](http://tools.ietf.org/html/rfc2673#section-3.2). | +| `ipv6` | IPv6 address, as defined in [RFC 2373, section 2.2](http://tools.ietf.org/html/rfc2373#section-2.2). | + diff --git a/example/formats/standard.ts b/example/formats/standard.ts new file mode 100644 index 0000000..7a0d9b0 --- /dev/null +++ b/example/formats/standard.ts @@ -0,0 +1,74 @@ +import { FormatRegistry } from '@sinclair/typebox' + +// ------------------------------------------------------------------------------------------- +// Format Registration +// ------------------------------------------------------------------------------------------- +FormatRegistry.Set('date-time', (value) => IsDateTime(value, true)) +FormatRegistry.Set('date', (value) => IsDate(value)) +FormatRegistry.Set('time', (value) => IsTime(value)) +FormatRegistry.Set('email', (value) => IsEmail(value)) +FormatRegistry.Set('uuid', (value) => IsUuid(value)) +FormatRegistry.Set('url', (value) => IsUrl(value)) +FormatRegistry.Set('ipv6', (value) => IsIPv6(value)) +FormatRegistry.Set('ipv4', (value) => IsIPv4(value)) + +// ------------------------------------------------------------------------------------------- +// https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts +// ------------------------------------------------------------------------------------------- + +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] +const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/ +const IPV6 = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i +const URL = /^(?:https?|wss?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu +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 +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 + 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(value: string, strictTimeZone?: boolean): boolean { + const dateTime: string[] = value.split(DATE_TIME_SEPARATOR) + return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1], strictTimeZone) +} +function IsEmail(value: string) { + return EMAIL.test(value) +} +function IsUuid(value: string) { + return UUID.test(value) +} +function IsUrl(value: string) { + return URL.test(value) +} +function IsIPv6(value: string) { + return IPV6.test(value) +} +function IsIPv4(value: string) { + return IPV4.test(value) +} + diff --git a/example/index.ts b/example/index.ts index ed8d1cd..ed52156 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,12 +1,7 @@ -import { Codegen } from '@sinclair/typebox/codegen' import { TypeSystem } from '@sinclair/typebox/system' import { TypeCompiler } from '@sinclair/typebox/compiler' -import { TypeGuard } from '@sinclair/typebox/guard' -import { Conditional } from '@sinclair/typebox/conditional' -import { Format } from '@sinclair/typebox/format' -import { Custom } from '@sinclair/typebox/custom' import { Value, ValuePointer } from '@sinclair/typebox/value' -import { Type, Kind, Static, TSchema } from '@sinclair/typebox' +import { Type, TypeGuard, Kind, Static, TSchema } from '@sinclair/typebox' // ----------------------------------------------------------- // Create: Type diff --git a/example/trpc/readme.md b/example/trpc/readme.md index f3b26cc..e321ae2 100644 --- a/example/trpc/readme.md +++ b/example/trpc/readme.md @@ -1,6 +1,6 @@ # Using TypeBox with TRPC -To use TypeBox with TRPC, you will need to wrap types passed to procedures within a TRPC compatible assertion function. Once wrapped, TypeBox can provide TRPC auto type inference for procedures, enhanced runtime checking performance and well as providing options for enabling publishable procedure schematics based on the JSON Schema specification. +To use TypeBox with TRPC, you will need to wrap types passed to procedures within a TRPC compatible assertion function. Once wrapped, TypeBox can provide TRPC auto type inference for procedures, enhanced runtime checking performance as well as provide options to enabling publishable procedure schematics based on the JSON Schema specification. ## Contents @@ -57,11 +57,11 @@ import { TSchema } from '@sinclair/typebox' import { TypeCompiler } from '@sinclair/typebox/compiler' import { TRPCError } from '@trpc/server' -export function RpcType(schema: T, references: TSchema[] = []) { - const check = TypeCompiler.Compile(schema, references) +export function RpcType(schema: T) { + const check = TypeCompiler.Compile(schema) return (value: unknown) => { if (check.Check(value)) return value - const { path, message } = [...check.Errors(value)][0] + const { path, message } = check.Errors(value).First()! throw new TRPCError({ message: `${message} for ${path}`, code: 'BAD_REQUEST' }) } } @@ -78,10 +78,10 @@ import { Value } from '@sinclair/typebox/value' import { TSchema } from '@sinclair/typebox' import { TRPCError } from '@trpc/server' -export function RpcType(schema: T, references: TSchema[] = []) { +export function RpcType(schema: T) { return (value: unknown) => { if (Value.Check(schema, value)) return value - const { path, message } = [...Value.Errors(schema, references, value)][0] + const { path, message } = check.Errors(value).First()! throw new TRPCError({ message: `${message} for ${path}`, code: 'BAD_REQUEST' }) } } diff --git a/example/typedef/index.ts b/example/typedef/index.ts index 7b932b2..b5db4f7 100644 --- a/example/typedef/index.ts +++ b/example/typedef/index.ts @@ -1,6 +1,6 @@ /*-------------------------------------------------------------------------- -@sinclair/typebox/extensions +@sinclair/typebox/typedef The MIT License (MIT) diff --git a/example/typedef/readme.md b/example/typedef/readme.md index 0f0106a..07837ad 100644 --- a/example/typedef/readme.md +++ b/example/typedef/readme.md @@ -1,100 +1,81 @@ -# JSON Type Definition +# TypeDef -JSON Type Definition (JTD) specification is an alternative schema specification to JSON Schema that provides better support for nominal type systems. TypeBox doesn't provide JTD support by default, but can be expressed through unsafe types and validated with Ajv. +TypeBox may offer support for [RPC8927](https://www.rfc-editor.org/rfc/rfc8927) JSON Type Definition in future revisions of the library. This specification is much simpler than JSON Schema but can be useful when describing schematics that need to be shared with nominal type languages. -This example provides a reference implementation for JSON Type Definition. +## Usage -## TypeDef - -Refer to the `typedef.ts` file in this directory for a reference implementation of the JSON Type Definition type builder. +The file `typedef.ts` provided with this example contains the provisional implementation for RPC8927. ```typescript -import { Static } from '@sinclair/typebox' -import { TypeDef } from './typedef' +import { Value } from '@sinclair/typebox/value' +import { Type, Static } from './typedef' + +const T = Type.Struct('T', { // const T = { + x: Type.Float32(), // properties: { + y: Type.Float32(), // x: { type: "float32" }, + z: Type.Float32() // y: { type: 'float32' }, +}) // z: { type: 'float32' } + // } + // } + +type T = Static // type T = { + // x: number, + // z: number, + // y: number + // } + +const R = Value.Check(T, { x: 1, y: 2, z: 3 }) // const R = true ``` -## Properties +### Unions + +The JSON Type Definition has a different representation for unions and is primarily orientated towards discriminated unions. To use unions, you will need to name each struct on the first argument. TypeBox will take care of producing the union representation and static type. ```typescript -// ------------------------------------------------------------------------ -// PropertiesType -// -// https://jsontypedef.com/docs/jtd-in-5-minutes/#properties-schemas -// -// ------------------------------------------------------------------------ +import { Type, Static } from './typedef' -export type PropertiesType = Static -export const PropertiesType = TypeDef.Properties({ - x: TypeDef.Float32(), - y: TypeDef.Float32(), - z: TypeDef.Float32(), -}) -``` - -## Values - -```typescript -// ------------------------------------------------------------------------ -// ValuesType -// -// https://jsontypedef.com/docs/jtd-in-5-minutes/#values-schemas -// -// ------------------------------------------------------------------------ - -export type ValuesType = Static -export const ValuesType = TypeDef.Values(TypeDef.Float64()) -``` - -## Enum - -```typescript -// ------------------------------------------------------------------------ -// EnumType -// -// https://jsontypedef.com/docs/jtd-in-5-minutes/#enum-schemas -// -// ------------------------------------------------------------------------ - -export type EnumType = Static -export const EnumType = TypeDef.Enum(['FOO', 'BAR', 'BAZ']) -``` - -## Elements - -```typescript -// ------------------------------------------------------------------------ -// ElementsType -// -// https://jsontypedef.com/docs/jtd-in-5-minutes/#elements-schemas -// -// ------------------------------------------------------------------------ - -export type ElementsType = Static -export const ElementsType = TypeDef.Elements(PropertiesType) -``` - -## Union - -```typescript -// ------------------------------------------------------------------------ -// UnionType -// -// https://jsontypedef.com/docs/jtd-in-5-minutes/#discriminator-schemas -// -// ------------------------------------------------------------------------ - -export type UnionType = Static -export const UnionType = TypeDef.Union('eventType', { - USER_CREATED: TypeDef.Properties({ - id: TypeDef.String(), - }), - USER_PAYMENT_PLAN_CHANGED: TypeDef.Properties({ - id: TypeDef.String(), - plan: TypeDef.Enum(['FREE', 'PAID']), - }), - USER_DELETED: TypeDef.Properties({ - id: TypeDef.String(), - softDelete: TypeDef.Boolean(), - }), -}) +const Vector2 = Type.Struct('Vector2', { // const Vector2 = { + x: Type.Float32(), // properties: { + y: Type.Float32(), // x: { type: 'float32' }, +}) // y: { type: 'float32' } + // } + // } + +const Vector3 = Type.Struct('Vector3', { // const Vector3 = { + x: Type.Float32(), // properties: { + y: Type.Float32(), // x: { type: 'float32' }, + z: Type.Float32() // y: { type: 'float32' }, +}) // z: { type: 'float32' } + // } + // } + +const Union = Type.Union('type', [ // const Union = { + Vector2, // discriminator: 'type', + Vector3 // mapping: { +]) // Vector2: { + // properties: { + // x: { type: 'float32' }, + // y: { type: 'float32' }, + // } + // }, + // Vector3: { + // properties: { + // x: { type: 'float32' }, + // y: { type: 'float32' }, + // z: { type: 'float32' } + // } + // } + // } + // } + +type Union = Static // type Union = { + // type: 'Vector2' + // x: number + // y: number + // } | { + // type: 'Vector3' + // x: number + // y: number + // z: number + // } ``` diff --git a/example/typedef/typedef.ts b/example/typedef/typedef.ts index 06a60c1..544d1b8 100644 --- a/example/typedef/typedef.ts +++ b/example/typedef/typedef.ts @@ -26,63 +26,378 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { Type, Static, TUnsafe } from '@sinclair/typebox' +export { type Static, TSchema, PropertiesReduce, TReadonly, TReadonlyOptional, TOptional } from '@sinclair/typebox' +import * as Types from '@sinclair/typebox' -// ------------------------------------------------------------------------ -// TypeDef Namespace -// -// https://jsontypedef.com/docs/jtd-in-5-minutes/ -// ------------------------------------------------------------------------ - -export type StaticTypeDefUnion>> = { [K in keyof M]: { [P in D]: K } & Static }[keyof M] - -export namespace TypeDef { - export function Boolean() { - return Type.Unsafe({ type: 'boolean' }) - } - export function String() { - return Type.Unsafe({ type: 'string' }) - } - export function TimeStamp() { - return Type.Unsafe({ type: 'timestamp' }) - } - export function Float32() { - return Type.Unsafe({ type: 'float32' }) - } - export function Float64() { - return Type.Unsafe({ type: 'float64' }) - } - export function Int8() { - return Type.Unsafe({ type: 'int8' }) - } - export function Uint8() { - return Type.Unsafe({ type: 'uint8' }) - } - export function Int16() { - return Type.Unsafe({ type: 'int16' }) - } - export function Uint16() { - return Type.Unsafe({ type: 'uint16' }) - } - export function Int32() { - return Type.Unsafe({ type: 'int32' }) - } - export function Uint32() { - return Type.Unsafe({ type: 'uint32' }) - } - export function Enum(values: [...T]) { - return Type.Unsafe({ enum: values }) - } - export function Elements>(element: T) { - return Type.Unsafe>>({ elements: element }) - } - export function Properties>>(properties: T) { - return Type.Unsafe<{ [K in keyof T]: Static }>({ properties }) - } - export function Values>(values: V) { - return Type.Unsafe>>({ values }) - } - export function Union>>(discriminator: D, mapping: M) { - return Type.Unsafe>({ discriminator, mapping }) +// -------------------------------------------------------------------------- +// Symbols +// -------------------------------------------------------------------------- +export const Name = Symbol.for('TypeBox:Name') +// -------------------------------------------------------------------------- +// TArray +// -------------------------------------------------------------------------- +export interface TArray extends Types.TSchema { + [Types.Kind]: 'TypeDef:Array' + static: Types.Static[] + elements: T +} +// -------------------------------------------------------------------------- +// TBoolean +// -------------------------------------------------------------------------- +export interface TBoolean extends Types.TSchema { + [Types.Kind]: 'TypeDef:Boolean' + static: 'boolean' + type: 'boolean' +} +// -------------------------------------------------------------------------- +// TEnum +// -------------------------------------------------------------------------- +export interface TEnum extends Types.TSchema { + [Types.Kind]: 'TypeDef:Enum' + static: T[number] + enum: [...T] +} +// -------------------------------------------------------------------------- +// TFloat32 +// -------------------------------------------------------------------------- +export interface TFloat32 extends Types.TSchema { + [Types.Kind]: 'TypeDef:Float32' + type: 'float32' + static: number +} +// -------------------------------------------------------------------------- +// TFloat64 +// -------------------------------------------------------------------------- +export interface TFloat64 extends Types.TSchema { + [Types.Kind]: 'TypeDef:Float64' + type: 'float64' + static: number +} +// -------------------------------------------------------------------------- +// TInt8 +// -------------------------------------------------------------------------- +export interface TInt8 extends Types.TSchema { + [Types.Kind]: 'TypeDef:Int8' + type: 'int8' + static: number +} +// -------------------------------------------------------------------------- +// TInt16 +// -------------------------------------------------------------------------- +export interface TInt16 extends Types.TSchema { + [Types.Kind]: 'TypeDef:Int16' + type: 'int16' + static: number +} +// -------------------------------------------------------------------------- +// TInt32 +// -------------------------------------------------------------------------- +export interface TInt32 extends Types.TSchema { + [Types.Kind]: 'TypeDef:Int32' + type: 'int32' + static: number +} +// -------------------------------------------------------------------------- +// TUint8 +// -------------------------------------------------------------------------- +export interface TUint8 extends Types.TSchema { + [Types.Kind]: 'TypeDef:Uint8' + type: 'uint8' + static: number +} +// -------------------------------------------------------------------------- +// TUint16 +// -------------------------------------------------------------------------- +export interface TUint16 extends Types.TSchema { + [Types.Kind]: 'TypeDef:Uint16' + type: 'uint16' + static: number +} +// -------------------------------------------------------------------------- +// TUint32 +// -------------------------------------------------------------------------- +export interface TUint32 extends Types.TSchema { + [Types.Kind]: 'TypeDef:Uint32' + type: 'uint32' + static: number +} +// -------------------------------------------------------------------------- +// TProperties +// -------------------------------------------------------------------------- +export type TFields = Record +// -------------------------------------------------------------------------- +// TRecord +// -------------------------------------------------------------------------- +export interface TRecord extends Types.TSchema { + [Types.Kind]: 'TypeDef:Record' + static: Record> + values: T +} +// -------------------------------------------------------------------------- +// TString +// -------------------------------------------------------------------------- +export interface TString extends Types.TSchema { + [Types.Kind]: 'TypeDef:String' + static: string +} +// -------------------------------------------------------------------------- +// TStruct +// -------------------------------------------------------------------------- +type OptionalKeys = { [K in keyof T]: T[K] extends (Types.TReadonlyOptional | Types.TOptional) ? T[K] : never } +type RequiredKeys = { [K in keyof T]: T[K] extends (Types.TReadonlyOptional | Types.TOptional) ? never : T[K] } +export interface StructOptions { + additionalProperties?: boolean +} +export interface TStruct extends Types.TSchema, StructOptions { + [Name]: D + [Types.Kind]: 'TypeDef:Struct' + static: Types.PropertiesReduce + optionalProperties: {[K in Types.Assert, keyof T>]: T[K] } + properties: {[K in Types.Assert, keyof T>]: T[K] } +} +// -------------------------------------------------------------------------- +// TTimestamp +// -------------------------------------------------------------------------- +export interface TTimestamp extends Types.TSchema { + [Types.Kind]: 'TypeDef:Timestamp' + static: number +} +// -------------------------------------------------------------------------- +// TUnion +// -------------------------------------------------------------------------- +export interface TUnion extends Types.TSchema { + [Types.Kind]: 'TypeDef:Union' + static: Types.Evaluate<{ [K in keyof T]: { [key in D]: T[K][typeof Name] } & Types.Static }[number]> + discriminator: D, + mapping: T +} +// -------------------------------------------------------------------------- +// TypeRegistry +// -------------------------------------------------------------------------- +Types.TypeRegistry.Set('TypeDef:Array', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Boolean', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Int8', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Int16', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Int32', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Uint8', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Uint16', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Uint32', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Record', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:String', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Struct', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Timestamp', (schema, value) => TypeDefCheck.Check(schema, value)) +Types.TypeRegistry.Set('TypeDef:Union', (schema, value) => TypeDefCheck.Check(schema, value)) +// -------------------------------------------------------------------------- +// TypeDefCheck +// -------------------------------------------------------------------------- +export class TypeDefCheckUnionTypeError extends Error { + constructor(public readonly schema: Types.TSchema) { + super('TypeDefCheck: Unknown type') } } +export namespace TypeDefCheck { + // ------------------------------------------------------------------------ + // Guards + // ------------------------------------------------------------------------ + function IsObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !globalThis.Array.isArray(value) + } + function IsArray(value: unknown): value is unknown[] { + return globalThis.Array.isArray(value) + } + function IsString(value: unknown): value is string { + return typeof value === 'string' + } + function IsInt(value: unknown, min: number, max: number): value is number { + return typeof value === 'number' && globalThis.Number.isInteger(value) && value >= min && value < max + } + // ------------------------------------------------------------------------ + // Types + // ------------------------------------------------------------------------ + function Array(schema: TArray, value: unknown): boolean { + return IsArray(value) && value.every(value => Visit(schema.elements, value)) + } + function Boolean(schema: TBoolean, value: unknown): boolean { + return typeof value === 'boolean' + } + function Enum(schema: TEnum, value: unknown): boolean { + return typeof value === 'string' && schema.enum.includes(value) + } + function Float32(schema: TFloat32, value: unknown): boolean { + return typeof value === 'number' + } + function Float64(schema: TFloat64, value: unknown): boolean { + return typeof value === 'number' + } + function Int8(schema: TInt8, value: unknown): boolean { + return IsInt(value, -128, 127) + } + function Int16(schema: TInt16, value: unknown): boolean { + return IsInt(value, -32_768, 32_767) + } + function Int32(schema: TInt32, value: unknown): boolean { + return IsInt(value, -2_147_483_648, 2_147_483_647) + } + function Uint8(schema: TUint8, value: unknown): boolean { + return IsInt(value, 0, 255) + } + function Uint16(schema: TUint16, value: unknown): boolean { + return IsInt(value, 0, 65535) + } + function Uint32(schema: TUint32, value: unknown): boolean { + return IsInt(value, 0, 4_294_967_295) + } + function Record(schema: TRecord, value: unknown): boolean { + return IsObject(value) && globalThis.Object.getOwnPropertyNames(value).every(key => Visit(schema.values, value[key])) + } + function String(schema: TString, value: unknown): boolean { + return typeof value === 'string' + } + function Struct(schema: TStruct, value: unknown): boolean { + const optionalKeys = schema.optionalProperties === undefined ? [] : globalThis.Object.getOwnPropertyNames(schema.optionalProperties) + const requiredKeys = schema.properties === undefined ? [] : globalThis.Object.getOwnPropertyNames(schema.properties) + if(!(IsObject(value) && + optionalKeys.every(key => key in value ? Visit((schema.optionalProperties as any)[key], value[key]) : true) && + requiredKeys.every(key => key in value && Visit(((schema as any).properties[key] as any), value[key])))) return false + if(schema.additionalProperties === true) return true + const unknownKeys = globalThis.Object.getOwnPropertyNames(value) + return unknownKeys.every(key => optionalKeys.includes(key) || requiredKeys.includes(key)) + } + function Timestamp(schema: TString, value: unknown): boolean { + return IsInt(value, 0, Number.MAX_SAFE_INTEGER) + } + function Union(schema: TUnion, value: unknown): boolean { + return IsObject(value) && + schema.discriminator in value && + IsString(value[schema.discriminator]) && + value[schema.discriminator] as any in schema.mapping && + Visit(schema.mapping[value[schema.discriminator] as any], value) + } + function Visit(schema: Types.TSchema, value: unknown): boolean { + const anySchema = schema as any + switch(anySchema[Types.Kind]) { + case 'TypeDef:Array': return Array(anySchema, value) + case 'TypeDef:Boolean': return Boolean(anySchema, value) + case 'TypeDef:Enum': return Enum(anySchema, value) + case 'TypeDef:Float32': return Float32(anySchema, value) + case 'TypeDef:Float64': return Float64(anySchema, value) + case 'TypeDef:Int8': return Int8(anySchema, value) + case 'TypeDef:Int16': return Int16(anySchema, value) + case 'TypeDef:Int32': return Int32(anySchema, value) + case 'TypeDef:Uint8': return Uint8(anySchema, value) + case 'TypeDef:Uint16': return Uint16(anySchema, value) + case 'TypeDef:Uint32': return Uint32(anySchema, value) + case 'TypeDef:Record': return Record(anySchema, value) + case 'TypeDef:String': return String(anySchema, value) + case 'TypeDef:Struct': return Struct(anySchema, value) + case 'TypeDef:Timestamp': return Timestamp(anySchema, value) + case 'TypeDef:Union': return Union(anySchema, value) + default: throw new TypeDefCheckUnionTypeError(anySchema) + } + } + export function Check(schema: Types.TSchema, value: unknown): boolean { + return Visit(schema, value) + } +} +// -------------------------------------------------------------------------- +// TypeDefTypeBuilder +// -------------------------------------------------------------------------- +export class TypeDefTypeBuilder extends Types.TypeBuilder { + // ------------------------------------------------------------------------ + // Modifiers + // ------------------------------------------------------------------------ + /** `[Modifier]` Creates a Optional property */ + public Optional(schema: T): Types.TOptional { + return { [Types.Modifier]: 'Optional', ...Types.TypeClone.Clone(schema, {}) } + } + /** `[Modifier]` Creates a ReadonlyOptional property */ + public ReadonlyOptional(schema: T): Types.TReadonlyOptional { + return { [Types.Modifier]: 'ReadonlyOptional', ...Types.TypeClone.Clone(schema, {}) } + } + /** `[Modifier]` Creates a Readonly object or property */ + public Readonly(schema: T): Types.TReadonly { + return { [Types.Modifier]: 'Readonly', ...schema } + } + // ------------------------------------------------------------------------ + // Modifiers + // ------------------------------------------------------------------------ + /** `[Standard]` Creates a TypeDef Array type */ + public Array(elements: T): TArray { + return this.Create({ [Types.Kind]: 'TypeDef:Array', elements }) + } + /** `[Standard]` Creates a TypeDef Boolean type */ + public Boolean(): TBoolean { + return this.Create({ [Types.Kind]: 'TypeDef:Boolean', type: 'boolean' }) + } + /** `[Standard]` Creates a TypeDef Enum type */ + public Enum(values: [...T]): TEnum { + return this.Create({ [Types.Kind]: 'TypeDef:Enum', enum: values }) + } + /** `[Standard]` Creates a TypeDef Float32 type */ + public Float32(): TFloat32 { + return this.Create({ [Types.Kind]: 'TypeDef:Float32', type: 'float32' }) + } + /** `[Standard]` Creates a TypeDef Float64 type */ + public Float64(): TFloat64 { + return this.Create({ [Types.Kind]: 'TypeDef:Float64', type: 'float64' }) + } + /** `[Standard]` Creates a TypeDef Int8 type */ + public Int8(): TInt8 { + return this.Create({ [Types.Kind]: 'TypeDef:Int8', type: 'int8' }) + } + /** `[Standard]` Creates a TypeDef Int16 type */ + public Int16(): TInt16 { + return this.Create({ [Types.Kind]: 'TypeDef:Int16', type: 'int16' }) + } + /** `[Standard]` Creates a TypeDef Int32 type */ + public Int32(): TInt32 { + return this.Create({ [Types.Kind]: 'TypeDef:Int32', type: 'int32' }) + } + /** `[Standard]` Creates a TypeDef Uint8 type */ + public Uint8(): TUint8 { + return this.Create({ [Types.Kind]: 'TypeDef:Uint8', type: 'uint8' }) + } + /** `[Standard]` Creates a TypeDef Uint16 type */ + public Uint16(): TUint16 { + return this.Create({ [Types.Kind]: 'TypeDef:Uint16', type: 'uint16' }) + } + /** `[Standard]` Creates a TypeDef Uint32 type */ + public Uint32(): TUint32 { + return this.Create({ [Types.Kind]: 'TypeDef:Uint32', type: 'uint32' }) + } + /** `[Standard]` Creates a TypeDef Record type */ + public Record(values: T): TRecord { + return this.Create({ [Types.Kind]: 'TypeDef:Record',values }) + } + /** `[Standard]` Creates a TypeDef String type */ + public String(): TString { + return this.Create({ [Types.Kind]: 'TypeDef:String',type: 'string' }) + } + /** `[Standard]` Creates a TypeDef Struct type */ + public Struct(name: N, fields: T, options?: StructOptions): TStruct { + const optionalProperties = globalThis.Object.getOwnPropertyNames(fields).reduce((acc, key) => (Types.TypeGuard.TOptional(fields[key]) || Types.TypeGuard.TReadonlyOptional(fields[key]) ? { ...acc, [key]: fields[key] } : { ...acc }), {} as TFields) + const properties = globalThis.Object.getOwnPropertyNames(fields).reduce((acc, key) => (Types.TypeGuard.TOptional(fields[key]) || Types.TypeGuard.TReadonlyOptional(fields[key]) ? {... acc } : { ...acc, [key]: fields[key] }), {} as TFields) + const optionalPropertiesObject = globalThis.Object.getOwnPropertyNames(optionalProperties).length > 0 ? { optionalProperties: optionalProperties } : {} + const propertiesObject = globalThis.Object.getOwnPropertyNames(properties).length === 0 ? {} : { properties: properties } + return this.Create({ ...options, [Types.Kind]: 'TypeDef:Struct', [Name]: name, ...propertiesObject, ...optionalPropertiesObject }) + } + /** `[Standard]` Creates a TypeDef Timestamp type */ + public Timestamp(): TTimestamp { + return this.Create({ [Types.Kind]: 'TypeDef:Timestamp', type: 'timestamp' }) + } + + /** `[Standard]` Creates a TypeDef Discriminated Union type */ + public Union[]>(discriminator: D, objects: [...T]): TUnion { + if(objects.length === 0) throw new Error('TypeDefTypeBuilder: Union types must have at least one object') + const exists = objects.every(object => typeof object[Name] === 'string') + if(!exists) throw new Error('TypeDefTypeBuilder: All union objects MUST have a descriminator') + const unique = objects.reduce((set, current) => set.add(current[Name]), new Set()) + if(unique.size !== objects.length) throw new Error('TypeDefTypeBuilder: All union objects MUST unique descriminator strings') + const mapping = objects.reduce((acc, current) => ({ ...acc, [current[Name]]: current }), {}) + return this.Create({ [Types.Kind]: 'TypeDef:Union', discriminator, mapping }) + } +} + +/** JSON Type Definition Type Builder */ +export const Type = new TypeDefTypeBuilder() + diff --git a/hammer.mjs b/hammer.mjs index 19e88ce..0957385 100644 --- a/hammer.mjs +++ b/hammer.mjs @@ -1,11 +1,9 @@ import { compression, measurement } from './benchmark' import { readFileSync } from 'fs' - // ------------------------------------------------------------------------------- // Clean // ------------------------------------------------------------------------------- - export async function clean() { await folder('target').delete() } @@ -13,15 +11,13 @@ export async function clean() { // ------------------------------------------------------------------------------- // Format // ------------------------------------------------------------------------------- - export async function format() { - await shell('prettier --no-semi --single-quote --print-width 240 --trailing-comma all --write src test example benchmark codegen') + await shell('prettier --no-semi --single-quote --print-width 240 --trailing-comma all --write src test example/index.ts benchmark codegen') } // ------------------------------------------------------------------------------- // Start // ------------------------------------------------------------------------------- - export async function start(example = 'index') { await shell(`hammer run example/${example}.ts --dist target/example/${example}`) } @@ -29,34 +25,27 @@ export async function start(example = 'index') { // ------------------------------------------------------------------------------- // Benchmark // ------------------------------------------------------------------------------- - export async function benchmark() { await compression() await measurement() } - // ------------------------------------------------------------------------------- // Test // ------------------------------------------------------------------------------- - export async function test_static() { await shell(`tsc -p test/static/tsconfig.json --noEmit --strict`) } - export async function test_runtime(filter) { await shell(`hammer build ./test/runtime/index.ts --dist target/test/runtime --platform node`) await shell(`mocha target/test/runtime/index.js -g "${filter}"`) } - export async function test(filter = '') { await test_static() await test_runtime(filter) } - // ------------------------------------------------------------------------------- // Build // ------------------------------------------------------------------------------- - export async function build(target = 'target/build') { await test() await folder(target).delete() @@ -66,12 +55,9 @@ export async function build(target = 'target/build') { await folder(target).add('license') await shell(`cd ${target} && npm pack`) } - - // ------------------------------------------------------------- // Publish // ------------------------------------------------------------- - export async function publish(otp, target = 'target/build') { const { version } = JSON.parse(readFileSync('package.json', 'utf8')) await shell(`cd ${target} && npm publish sinclair-typebox-${version}.tgz --access=public --otp ${otp}`) diff --git a/package-lock.json b/package-lock.json index 63868b5..e013c68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.25.21", + "version": "0.26.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.25.21", + "version": "0.26.0", "license": "MIT", "devDependencies": { "@sinclair/hammer": "^0.17.1", @@ -18,7 +18,7 @@ "chai": "^4.3.6", "mocha": "^9.2.2", "prettier": "^2.7.1", - "typescript": "^4.9.3" + "typescript": "^5.0.2" } }, "node_modules/@esbuild/linux-loong64": { @@ -1410,16 +1410,16 @@ } }, "node_modules/typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=12.20" } }, "node_modules/uri-js": { @@ -2442,9 +2442,9 @@ "dev": true }, "typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", "dev": true }, "uri-js": { diff --git a/package.json b/package.json index 50ce002..8043b31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.25.24", + "version": "0.26.0", "description": "JSONSchema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", @@ -14,12 +14,7 @@ "types": "./typebox.d.ts", "exports": { "./compiler": "./compiler/index.js", - "./conditional": "./conditional/index.js", - "./custom": "./custom/index.js", "./errors": "./errors/index.js", - "./format": "./format/index.js", - "./guard": "./guard/index.js", - "./hash": "./hash/index.js", "./system": "./system/index.js", "./value": "./value/index.js", ".": "./typebox.js" @@ -47,6 +42,6 @@ "chai": "^4.3.6", "mocha": "^9.2.2", "prettier": "^2.7.1", - "typescript": "^4.9.3" + "typescript": "^5.0.2" } } diff --git a/readme.md b/readme.md index 34acd8b..2d199c1 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ import { Static, Type } from 'npm:@sinclair/typebox' import { Static, Type } from 'https://esm.sh/@sinclair/typebox' ``` -## Usage +## Example ```typescript import { Static, Type } from '@sinclair/typebox' @@ -62,32 +62,33 @@ type T = Static // type T = { ## Overview -TypeBox is a type builder library that creates in-memory JSON Schema objects that can be statically inferred as TypeScript types. The schemas produced by this library are designed to match the static type checking rules of the TypeScript compiler. TypeBox enables one to create a unified type that can be statically checked by TypeScript and runtime asserted using standard JSON Schema validation. +TypeBox is a runtime type builder that creates in-memory JSON Schema objects that can be statically inferred as TypeScript types. The schemas produced by this library are designed to match the static type assertion rules of the TypeScript compiler. TypeBox enables one to create a unified type that can be statically checked by TypeScript and runtime asserted using standard JSON Schema validation. -This library is designed to enable JSON schema to compose with the same flexibility as TypeScript's type system. It can be used either as a simple tool to build up complex schemas or integrated into REST and RPC services to help validate data received over the wire. +This library is designed to enable JSON schema to compose with the same flexibility as TypeScript's type system. It can be used as a simple tool to build up complex schemas or integrated into REST or RPC services to help validate data received over the wire. License MIT ## Contents - [Install](#install) - [Overview](#overview) -- [Example](#Example) +- [Usage](#usage) - [Types](#types) - [Standard](#types-standard) - [Extended](#types-extended) - [Modifiers](#types-modifiers) - [Options](#types-options) - - [Reference](#types-reference) + - [Generics](#types-generics) + - [References](#types-references) - [Recursive](#types-recursive) - - [Generic](#types-generic) - [Conditional](#types-conditional) - - [Unsafe](#types-unsafe) - [Guards](#types-guards) + - [Unsafe](#types-unsafe) - [Strict](#types-strict) - [Values](#values) - [Create](#values-create) - [Clone](#values-clone) - [Check](#values-check) + - [Convert](#values-convert) - [Cast](#values-cast) - [Equal](#values-equal) - [Hash](#values-hash) @@ -101,20 +102,20 @@ License MIT - [TypeSystem](#typecheck) - [Types](#typesystem-types) - [Formats](#typesystem-formats) + - [Policies](#typesystem-policies) - [Benchmark](#benchmark) - [Compile](#benchmark-compile) - [Validate](#benchmark-validate) - [Compression](#benchmark-compression) - [Contribute](#contribute) - + -## Example +## Usage -The following demonstrates TypeBox's general usage. +The following shows general usage. ```typescript - import { Static, Type } from '@sinclair/typebox' //-------------------------------------------------------------------------------------------- @@ -173,9 +174,11 @@ type T = Static // type T = { // //-------------------------------------------------------------------------------------------- -function receive(value: T) { // ... as a Type +import { Value } from '@sinclair/typebox/value' - if(JSON.validate(T, value)) { // ... as a Schema +function receive(value: T) { // ... as a Static Type + + if(Value.Check(T, value)) { // ... as a JSON Schema // ok... } @@ -186,13 +189,13 @@ function receive(value: T) { // ... as a Type ## Types -TypeBox provides a set of functions that allow you to compose JSON Schema similar to how you would compose static types with TypeScript. Each function creates a JSON schema fragment which can compose into more complex types. The schemas produced by TypeBox can be passed directly to any JSON Schema compliant validator, or used to reflect runtime metadata for a type. +TypeBox types are JSON schema fragments that can be composed into more complex types. Each fragment is structured such that a JSON schema compliant validator can runtime assert a value the same way TypeScript will statically assert a type. TypeBox provides a set of Standard types which are used create JSON schema compliant schematics as well as an Extended type set used to create schematics for constructs native to JavaScript. -### Standard +### Standard Types -The following table lists the Standard TypeBox types. +The following table lists the Standard TypeBox types. These types are fully compatible with the JSON Schema Draft 6 specification. ```typescript ┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐ @@ -230,12 +233,6 @@ The following table lists the Standard TypeBox types. │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ -│ const T = Type.RegEx(/foo/) │ type T = string │ const T = { │ -│ │ │ type: 'string', │ -│ │ │ pattern: 'foo' │ -│ │ │ } │ -│ │ │ │ -├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Literal(42) │ type T = 42 │ const T = { │ │ │ │ const: 42, │ │ │ │ type: 'number' │ @@ -252,15 +249,14 @@ The following table lists the Standard TypeBox types. ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Object({ │ type T = { │ const T = { │ │ x: Type.Number(), │ x: number, │ type: 'object', │ -│ y: Type.Number() │ y: number │ properties: { │ -│ }) │ } │ x: { │ -│ │ │ type: 'number' │ -│ │ │ }, │ -│ │ │ y: { │ -│ │ │ type: 'number' │ -│ │ │ } │ -│ │ │ }, │ -│ │ │ required: ['x', 'y'] │ +│ y: Type.Number() │ y: number │ required: ['x', 'y'], │ +│ }) │ } │ properties: { │ +│ │ │ x: { │ +│ │ │ type: 'number' │ +│ │ │ }, { │ +│ │ │ type: 'number' │ +│ │ │ } │ +│ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ @@ -310,13 +306,36 @@ The following table lists the Standard TypeBox types. │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Intersect([ │ type T = { │ const T = { │ -│ Type.Object({ │ x: number │ type: 'object', │ -│ x: Type.Number() │ } & { │ properties: { │ -│ }), │ y: number │ x: { │ -│ Type.Object({ │ } │ type: 'number' │ -│ y: Type.Number() │ │ }, │ -│ }) │ │ y: { │ -│ ]) │ │ type: 'number' │ +│ Type.Object({ │ x: number │ allOf: [{ │ +│ x: Type.Number() │ } & { │ type: 'object', │ +│ }), │ y: number │ required: ['x'], │ +│ Type.Object({ │ } │ properties: { │ +│ y: Type.Number() │ │ x: { │ +│ ]) │ │ type: 'number' │ +│ ]) │ │ } │ +│ │ │ } │ +│ │ │ }, { │ +│ │ │ type: 'object', | +│ │ │ required: ['y'], │ +│ │ │ properties: { │ +│ │ │ y: { │ +│ │ │ type: 'number' │ +│ │ │ } │ +│ │ │ }] │ +│ │ │ } │ +│ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const T = Type.Composite([ │ type T = { │ const T = { │ +│ Type.Object({ │ x: number | string │ type: 'object', │ +│ x: Type.Number() │ y: number │ properties: { │ +│ }), │ } │ x: { │ +│ Type.Object({ │ │ anyOf: [ │ +│ x: Type.String() │ │ { type: 'number' }, │ +│ y: Type.Number() │ │ { type: 'string' } │ +│ }) │ │ ] │ +│ ]) │ │ }, │ +│ │ │ y: { │ +│ │ │ type: 'number' │ │ │ │ } │ │ │ │ }, │ │ │ │ required: ['x', 'y'] │ @@ -324,16 +343,51 @@ The following table lists the Standard TypeBox types. │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Never() │ type T = never │ const T = { │ -│ │ │ allOf: [{ │ -│ │ │ type: 'boolean', │ -│ │ │ const: false │ +│ │ │ not: {} │ +│ │ │ } │ +│ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const T = Type.Not( | type T = string │ const T = { │ +| Type.Union([ │ │ allOf: [{ │ +│ Type.Literal('x'), │ │ not: { │ +│ Type.Literal('y'), │ │ anyOf: [ │ +│ Type.Literal('z') │ │ { const: 'x' }, │ +│ ]), │ │ { const: 'y' }, │ +│ Type.String() │ │ { const: 'z' } │ +│ ) │ │ ] │ +│ │ │ } │ │ │ │ }, { │ -│ │ │ type: 'boolean', │ -│ │ │ const: true │ +│ │ │ type: 'string' │ │ │ │ }] │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const T = Type.Extends( │ type T = │ const T = { │ +│ Type.String(), │ string extends number │ const: false, │ +│ Type.Number(), │ true : false │ type: 'boolean' │ +│ Type.Literal(true), │ │ } │ +│ Type.Literal(false) │ │ │ +│ ) │ │ │ +│ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const T = Type.Extract( │ type T = Extract< │ const T = { │ +│ Type.Union([ │ string | number, │ type: 'string' │ +│ Type.String(), │ string │ } │ +│ Type.Number(), │ > │ │ +│ ]), │ │ │ +│ Type.String() │ │ │ +│ ) │ │ │ +│ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const T = Type.Exclude( │ type T = Exclude< │ const T = { │ +│ Type.Union([ │ string | number, │ type: 'number' │ +│ Type.String(), │ string │ } │ +│ Type.Number(), │ > │ │ +│ ]), │ │ │ +│ Type.String() │ │ │ +│ ) │ │ │ +│ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Record( │ type T = Record< │ const T = { │ │ Type.String(), │ string, │ type: 'object', │ │ Type.Number() │ number, │ patternProperties: { │ @@ -359,15 +413,15 @@ The following table lists the Standard TypeBox types. ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Required( │ type T = Required<{ │ const T = { │ │ Type.Object({ │ x?: number, │ type: 'object', │ -│ x: Type.Optional( │ y?: number │ properties: { │ -│ Type.Number() | }> │ x: { │ -│ ), │ │ type: 'number' │ -│ y: Type.Optional( │ │ }, │ -│ Type.Number() │ │ y: { │ -│ ) │ │ type: 'number' │ -│ }) │ │ } │ -│ ) │ │ }, │ -│ │ │ required: ['x', 'y'] │ +│ x: Type.Optional( │ y?: number │ required: ['x', 'y'], │ +│ Type.Number() | }> │ properties: { │ +│ ), │ │ x: { │ +│ y: Type.Optional( │ │ type: 'number' │ +│ Type.Number() │ │ }, │ +│ ) │ │ y: { │ +│ }) │ │ type: 'number' │ +│ ) │ │ } │ +│ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ @@ -392,14 +446,25 @@ The following table lists the Standard TypeBox types. │ │ │ required: ['y'] │ │ │ │ } │ │ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const A = Type.Object({ │ type A = { │ const T = { │ +│ x: Type.Number(), │ x: number, │ $ref: 'A' │ +│ y: Type.Number() │ y: number │ } │ +│ }, { $id: 'T' }) | } │ │ +│ │ │ │ +│ const T = Type.Ref(A) │ type T = A │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ └────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘ ``` -### Extended +### Extended Types -TypeBox provides a set of extended types that can be used to express schematics for core JavaScript constructs and primitives. Extended types are not valid JSON Schema and will not validate using typical validation. These types however can be used to frame JSON schema and describe callable RPC interfaces that may receive JSON validated data. +TypeBox provides several extended types that can be used to produce schematics for common JavaScript constructs. These types can not be used with standard JSON schema validators; but are useful to help frame schematics for RPC interfaces that may receive JSON validated data. Extended types are prefixed with the `[Extended]` doc comment for convenience. The following table lists the supported types. ```typescript ┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐ @@ -462,6 +527,24 @@ TypeBox provides a set of extended types that can be used to express schematics │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const T = Type.RegEx(/foo/) │ type T = string │ const T = { │ +│ │ │ type: 'string', │ +│ │ │ pattern: 'foo' │ +│ │ │ } │ +│ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const T = Type.Symbol() │ type T = symbol │ const T = { │ +│ │ │ type: 'null', │ +│ │ │ typeOf: 'Symbol' │ +│ │ │ } │ +│ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ +│ const T = Type.BigInt() │ type T = bigint │ const T = { │ +│ │ │ type: 'null', │ +│ │ │ typeOf: 'BigInt' │ +│ │ │ } │ +│ │ │ │ +├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Void() │ type T = void │ const T = { │ │ │ │ type: 'null' │ │ │ │ typeOf: 'Void' │ @@ -474,7 +557,7 @@ TypeBox provides a set of extended types that can be used to express schematics ### Modifiers -TypeBox provides modifiers that can be applied to an objects properties. This allows for `optional` and `readonly` to be applied to that property. The following table illustates how they map between TypeScript and JSON Schema. +TypeBox provides modifiers that allow schema properties to be statically inferred as `readonly` or `optional`. The following table shows the supported modifiers and how they map between TypeScript and JSON Schema. ```typescript ┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐ @@ -484,9 +567,9 @@ TypeBox provides modifiers that can be applied to an objects properties. This al │ const T = Type.Object({ │ type T = { │ const T = { │ │ name: Type.Optional( │ name?: string │ type: 'object', │ │ Type.String() │ } │ properties: { │ -│ ) │ │ name: { │ -│ }) │ │ type: 'string' │ -│ │ │ } │ +│ ) │ │ name: { │ +│ }) │ │ type: 'string' │ +│ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ @@ -518,24 +601,96 @@ TypeBox provides modifiers that can be applied to an objects properties. This al ### Options -You can pass additional JSON schema options on the last argument of any given type. The following are some examples. +You can pass JSON Schema options on the last argument of any type. Option hints specific to each type are provided for convenience. ```typescript -// string must be an email -const T = Type.String({ format: 'email' }) +// String must be an email +const T = Type.String({ // const T = { + format: 'email' // type: 'string', +}) // format: 'email' + // } -// number must be a multiple of 2 -const T = Type.Number({ multipleOf: 2 }) +// Mumber must be a multiple of 2 +const T = Type.Number({ // const T = { + multipleOf: 2 // type: 'number', +}) // multipleOf: 2 + // } + +// Array must have at least 5 integer values +const T = Type.Array(Type.Integer(), { // const T = { + minItems: 5 // type: 'array', +}) // minItems: 5, + // items: { + // type: 'integer' + // } + // } -// array must have at least 5 integer values -const T = Type.Array(Type.Integer(), { minItems: 5 }) ``` - + -### Reference +### Generic Types -Use `Type.Ref(...)` to create referenced types. The target type must specify an `$id`. +Generic types can be created with generic functions constrained to type `TSchema`. The following creates a generic `Vector` type. + +```typescript +import { Type, Static, TSchema } from '@sinclair/typebox' + +const Vector = (t: T) => Type.Object({ x: t, y: t, z: t }) + +const NumberVector = Vector(Type.Number()) // const NumberVector = { + // type: 'object', + // required: ['x', 'y', 'z'], + // properties: { + // x: { type: 'number' }, + // y: { type: 'number' }, + // z: { type: 'number' } + // } + // } + +type NumberVector = Static // type NumberVector = { + // x: number, + // y: number, + // z: number + // } + +const BooleanVector = Vector(Type.Boolean()) // const BooleanVector = { + // type: 'object', + // required: ['x', 'y', 'z'], + // properties: { + // x: { type: 'boolean' }, + // y: { type: 'boolean' }, + // z: { type: 'boolean' } + // } + // } + +type BooleanVector = Static // type BooleanVector = { + // x: boolean, + // y: boolean, + // z: boolean + // } +``` + +The following creates a generic `Nullable` type. + +```typescript +const Nullable = (schema: T) => Type.Union([schema, Type.Null()]) + +const T = Nullable(Type.String()) // const T = { + // anyOf: [ + // { type: 'string' }, + // { type: 'null' } + // ] + // } + +type T = Static // type T = string | null +``` + + + +### Reference Types + +Reference types are supported with `Type.Ref(...)`. The target type must specify a valid `$id`. ```typescript const T = Type.String({ $id: 'T' }) // const T = { @@ -550,9 +705,9 @@ const R = Type.Ref(T) // const R = { -### Recursive +### Recursive Types -Use `Type.Recursive(...)` to create recursive types. +Recursive types are supported with `Type.Recursive(...)`. ```typescript const Node = Type.Recursive(Node => Type.Object({ // const Node = { @@ -581,102 +736,41 @@ type Node = Static // type Node = { // } function test(node: Node) { - const id = node.nodes[0].nodes[0] // id is string - .nodes[0].nodes[0] - .id + const id = node.nodes[0].nodes[0].id // id is string } ``` - - -### Generic - -Use functions to create generic types. The following creates a generic `Nullable` type. - -```typescript -import { Type, Static, TSchema } from '@sinclair/typebox' - -const Nullable = (type: T) => Type.Union([type, Type.Null()]) - -const T = Nullable(Type.String()) // const T = { - // anyOf: [{ - // type: 'string' - // }, { - // type: 'null' - // }] - // } - -type T = Static // type T = string | null - -const U = Nullable(Type.Number()) // const U = { - // anyOf: [{ - // type: 'number' - // }, { - // type: 'null' - // }] - // } - -type U = Static // type U = number | null -``` - -### Conditional +### Conditional Types -Use the conditional module to create [Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html). This module implements TypeScript's structural equivalence checks to enable TypeBox types to be conditionally inferred at runtime. This module also provides the [Extract](https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union) and [Exclude](https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers) utility types which are expressed as conditional types in TypeScript. +Conditional types are supported with `Extends`, `Exclude` and `Extract`. -The conditional module is provided as an optional import. +**TypeScript** ```typescript -import { Conditional } from '@sinclair/typebox/conditional' +type T0 = string extends number ? true : false +// ^ false +type T1 = Extract +// ^ number +type T2 = Exclude +// ^ string ``` -The following table shows the TypeBox mappings between TypeScript and JSON schema. - +**TypeBox** ```typescript -┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐ -│ TypeBox │ TypeScript │ JSON Schema │ -│ │ │ │ -├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ -│ const T = Conditional.Extends( │ type T = │ const T = { │ -│ Type.String(), │ string extends number │ const: false, │ -│ Type.Number(), │ true : false │ type: 'boolean' │ -│ Type.Literal(true), │ │ } │ -│ Type.Literal(false) │ │ │ -│ ) │ │ │ -│ │ │ │ -├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ -│ const T = Conditional.Extract( │ type T = Extract< │ const T = { │ -│ Type.Union([ │ 'a' | 'b' | 'c', │ anyOf: [{ │ -│ Type.Literal('a'), │ 'a' | 'f' │ const: 'a' │ -│ Type.Literal('b'), │ > │ type: 'string' │ -│ Type.Literal('c') │ │ }] │ -│ ]), │ │ } │ -│ Type.Union([ │ │ │ -│ Type.Literal('a'), │ │ │ -│ Type.Literal('f') │ │ │ -│ ]) │ │ │ -│ ) │ │ │ -│ │ │ │ -├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ -│ const T = Conditional.Exclude( │ type T = Exclude< │ const T = { │ -│ Type.Union([ │ 'a' | 'b' | 'c', │ anyOf: [{ │ -│ Type.Literal('a'), │ 'a' │ const: 'b', │ -│ Type.Literal('b'), │ > │ type: 'string' │ -│ Type.Literal('c') │ │ }, { │ -│ ]), │ │ const: 'c', │ -│ Type.Union([ │ │ type: 'string' │ -│ Type.Literal('a') │ │ }] │ -│ ]) │ │ } │ -│ ) │ │ │ -│ │ │ │ -└────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘ +const T0 = Type.Extends(Type.String(), Type.Number(), Type.Literal(true), Type.Literal(false)) +// ^ TLiteral +const T1 = Type.Extract(Type.Union([Type.String(), Type.Number()]), Type.Number()) +// ^ TNumber +const T2 = Type.Exclude(Type.Union([Type.String(), Type.Number()]), Type.Number()) +// ^ TString ``` ### Unsafe -Use `Type.Unsafe(...)` to create custom schemas with user defined inference rules. +Use `Type.Unsafe(...)` to create custom schematics with user defined inference rules. ```typescript const T = Type.Unsafe({ type: 'number' }) // const T = { @@ -686,16 +780,12 @@ const T = Type.Unsafe({ type: 'number' }) // const T = { type T = Static // type T = string ``` -This function can be used to create custom schemas for validators that require specific schema representations. An example of this might be OpenAPI's `nullable` and `enum` schemas which are not provided by TypeBox. The following demonstrates using `Type.Unsafe(...)` to create these types. +The `Type.Unsafe(...)` type can be useful to express specific OpenAPI schema representations. ```typescript import { Type, Static, TSchema } from '@sinclair/typebox' -//-------------------------------------------------------------------------------------------- -// // Nullable -// -//-------------------------------------------------------------------------------------------- function Nullable(schema: T) { return Type.Unsafe | null>({ ...schema, nullable: true }) @@ -708,12 +798,7 @@ const T = Nullable(Type.String()) // const T = { type T = Static // type T = string | null - -//-------------------------------------------------------------------------------------------- -// // StringEnum -// -//-------------------------------------------------------------------------------------------- function StringEnum(values: [...T]) { return Type.Unsafe({ type: 'string', enum: values }) @@ -730,10 +815,10 @@ type T = Static // type T = 'A' | 'B' | 'C' ### Guards -Use the guard module to test if values are TypeBox types. +TypeBox provides a `TypeGuard` module that can be used for reflection and asserting values as types. ```typescript -import { TypeGuard } from '@sinclair/typebox/guard' +import { Type, TypeGuard } from '@sinclair/typebox' const T = Type.String() @@ -747,7 +832,7 @@ if(TypeGuard.TString(T)) { ### Strict -TypeBox schemas contain the `Kind` and `Modifier` symbol properties. These properties are provided to enable runtime type reflection on schemas, as well as helping TypeBox internally compose types. These properties are not strictly valid JSON schema; so in some cases it may be desirable to omit them. TypeBox provides a `Type.Strict()` function that will omit these properties if necessary. +TypeBox schemas contain the `Kind` and `Modifier` symbol properties. These properties are used for type composition and reflection. These properties are not strictly valid JSON schema; so in some cases it may be desirable to omit them. TypeBox provides a `Type.Strict()` function that will omit these properties if necessary. ```typescript const T = Type.Object({ // const T = { @@ -776,7 +861,7 @@ const U = Type.Strict(T) // const U = { ## Values -TypeBox includes an optional values module that can be used to perform common operations on JavaScript values. This module enables one to create, check and cast values from types. It also provides functionality to check equality, clone and diff and patch JavaScript values. The value module is provided as an optional import. +TypeBox provides an optional utility module that can be used to perform common operations on JavaScript values. This module includes functionality to create, check and cast values from types as well as check equality, clone, diff and patch JavaScript values. This module is provided via optional import. ```typescript import { Value } from '@sinclair/typebox/value' @@ -786,7 +871,7 @@ import { Value } from '@sinclair/typebox/value' ### Create -Use the Create function to create a value from a TypeBox type. TypeBox will use default values if specified. +Use the Create function to create a value from a type. TypeBox will use default values if specified. ```typescript const T = Type.Object({ x: Type.Number(), y: Type.Number({ default: 42 }) }) @@ -816,6 +901,20 @@ const T = Type.Object({ x: Type.Number() }) const R = Value.Check(T, { x: 1 }) // const R = true ``` + + +### Convert + +Use the Convert function to convert a value into its target type if a reasonable conversion is possible. + +```typescript +const T = Type.Object({ x: Type.Number(), y: Type.Number() }) + +const R1 = Value.Convert(T, { x: '3.14' }) // const R1 = { x: 3.14 } + +const R2 = Value.Convert(T, { x: 'not a number' }) // const R2 = { x: 'not a number' } +``` + ### Cast @@ -918,7 +1017,7 @@ const R = [...Value.Errors(T, { x: '42' })] // const R = [{ ### Pointer -Use ValuePointer to perform mutable updates on existing values using [RFC6901](https://www.rfc-editor.org/rfc/rfc6901) Json Pointers. +Use ValuePointer to perform mutable updates on existing values using [RFC6901](https://www.rfc-editor.org/rfc/rfc6901) JSON Pointers. ```typescript import { ValuePointer } from '@sinclair/typebox/value' @@ -933,15 +1032,15 @@ ValuePointer.Set(A, '/z', 1) // const A = { x: 1, y: 1, ## TypeCheck -TypeBox constructs JSON Schema draft 6 compliant schematics and can be used with any validator that supports this specification. In JavaScript, an ideal validator to use is Ajv which supports draft 6 as well as more recent revisions to the specification. In addition to Ajv, TypeBox provides an optional built in type compiler which can offer faster runtime type compilation, as well as providing high performance data validation for TypeBox types only. +TypeBox types target JSON Schema draft 6 so are compatible with any validator that supports this specification. TypeBox also provides a built in type checking compiler designed specifically for high performance compilation and value assertion. -The following sections detail using these validators. +The following sections detail using Ajv and TypeBox's compiler infrastructure. ## Ajv -The following shows the recommended setup for Ajv. +The following shows the recommended setup for Ajv. ```bash $ npm install ajv ajv-formats --save @@ -982,7 +1081,7 @@ const R = C({ x: 1, y: 2, z: 3 }) // const R = true ### TypeCompiler -The TypeCompiler is a Just-In-Time (JIT) runtime compiler that can be used to convert TypeBox types into fast validation routines. This compiler is specifically tuned for fast compilation and validation for TypeBox types only. +The TypeBox TypeCompiler is a high performance JIT compiler that transforms TypeBox types into optimized JavaScript validation routines. The compiler is tuned for fast compilation as well as fast value assertion. It is designed to serve as a validation backend that can be integrated into larger applications; but can also be used as a general purpose validator. The TypeCompiler is provided as an optional import. @@ -1002,7 +1101,7 @@ const C = TypeCompiler.Compile(Type.Object({ // const C: TypeCheck Type -const BigNumber = TypeSystem.CreateType( - 'BigNumber', - (options, value) => { - if (typeof value !== 'bigint') return false - if (options.maximum !== undefined && value > options.maximum) return false - if (options.minimum !== undefined && value < options.minimum) return false - return true - } -) +const Point = TypeSystem.Type('Point', (options, value) => { + return ( + typeof value === 'object' && value !== null && + typeof value.x === 'number' && + typeof value.y === 'number' + ) +}) -//-------------------------------------------------------------------------------------------- -// -// Use the custom type like any other type -// -//-------------------------------------------------------------------------------------------- +const T = Point() -const T = BigNumber({ minimum: 10n, maximum: 20n }) // const T = { - // minimum: 10n, - // maximum: 20n, - // [Symbol(TypeBox.Kind)]: 'BigNumber' - // } +type T = Static // type T = { x: number, y: number } -const C = TypeCompiler.Compile(T) -const X = C.Check(15n) // const X = true -const Y = C.Check(5n) // const Y = false -const Z = C.Check(25n) // const Z = false +const R = Value.Check(T, { x: 1, y: 2 }) // const R = true ``` ### Formats -Use the `CreateFormat(...)` function to specify user defined string formats. The following creates a custom string format that checks for lowercase. +Use the `Format(...)` function to create a custom string format. The following creates a custom string format that checks for lowercase strings. ```typescript -//-------------------------------------------------------------------------------------------- -// -// Use TypeSystem.CreateFormat(...) to define a custom string format -// -//-------------------------------------------------------------------------------------------- +TypeSystem.Format('lowercase', value => value === value.toLowerCase()) // format should be lowercase -TypeSystem.CreateFormat('lowercase', value => value === value.toLowerCase()) - -//-------------------------------------------------------------------------------------------- -// -// Use the format by creating string types with the 'format' option -// -//-------------------------------------------------------------------------------------------- - -const T = Type.String({ format: 'lowercase' }) +const T = Type.String({ format: 'lowercase' }) const A = Value.Check(T, 'action') // const A = true const B = Value.Check(T, 'ACTION') // const B = false ``` + + +### Policies + +TypeBox validates using JSON Schema assertion policies by default. It is possible to override these policies and have TypeBox assert using TypeScript policies. The following overrides are available. + +```typescript +// Allow arrays to validate as object types (default is false) +// +// const A: {} = [] - allowed in TS + +TypeSystem.AllowArrayObjects = true + +// Allow numeric values to be NaN or + or - Infinity (default is false) +// +// const A: number = NaN - allowed in TS + +TypeSystem.AllowNaN = true +``` + ## Benchmark @@ -1141,33 +1231,37 @@ For additional comparative benchmarks, please refer to [typescript-runtime-type- This benchmark measures compilation performance for varying types. You can review this benchmark [here](https://github.com/sinclairzx81/typebox/blob/master/benchmark/measurement/module/compile.ts). ```typescript -┌──────────────────┬────────────┬──────────────┬──────────────┬──────────────┐ -│ (index) │ Iterations │ Ajv │ TypeCompiler │ Performance │ -├──────────────────┼────────────┼──────────────┼──────────────┼──────────────┤ -│ Number │ 2000 │ ' 451 ms' │ ' 16 ms' │ ' 28.19 x' │ -│ String │ 2000 │ ' 338 ms' │ ' 14 ms' │ ' 24.14 x' │ -│ Boolean │ 2000 │ ' 297 ms' │ ' 13 ms' │ ' 22.85 x' │ -│ Null │ 2000 │ ' 265 ms' │ ' 8 ms' │ ' 33.13 x' │ -│ RegEx │ 2000 │ ' 492 ms' │ ' 18 ms' │ ' 27.33 x' │ -│ ObjectA │ 2000 │ ' 2744 ms' │ ' 55 ms' │ ' 49.89 x' │ -│ ObjectB │ 2000 │ ' 3005 ms' │ ' 44 ms' │ ' 68.30 x' │ -│ Tuple │ 2000 │ ' 1283 ms' │ ' 26 ms' │ ' 49.35 x' │ -│ Union │ 2000 │ ' 1263 ms' │ ' 27 ms' │ ' 46.78 x' │ -│ Vector4 │ 2000 │ ' 1622 ms' │ ' 23 ms' │ ' 70.52 x' │ -│ Matrix4 │ 2000 │ ' 888 ms' │ ' 12 ms' │ ' 74.00 x' │ -│ Literal_String │ 2000 │ ' 344 ms' │ ' 14 ms' │ ' 24.57 x' │ -│ Literal_Number │ 2000 │ ' 389 ms' │ ' 8 ms' │ ' 48.63 x' │ -│ Literal_Boolean │ 2000 │ ' 374 ms' │ ' 9 ms' │ ' 41.56 x' │ -│ Array_Number │ 2000 │ ' 710 ms' │ ' 12 ms' │ ' 59.17 x' │ -│ Array_String │ 2000 │ ' 739 ms' │ ' 9 ms' │ ' 82.11 x' │ -│ Array_Boolean │ 2000 │ ' 732 ms' │ ' 7 ms' │ ' 104.57 x' │ -│ Array_ObjectA │ 2000 │ ' 3733 ms' │ ' 42 ms' │ ' 88.88 x' │ -│ Array_ObjectB │ 2000 │ ' 3602 ms' │ ' 42 ms' │ ' 85.76 x' │ -│ Array_Tuple │ 2000 │ ' 2204 ms' │ ' 20 ms' │ ' 110.20 x' │ -│ Array_Union │ 2000 │ ' 1533 ms' │ ' 24 ms' │ ' 63.88 x' │ -│ Array_Vector4 │ 2000 │ ' 2263 ms' │ ' 21 ms' │ ' 107.76 x' │ -│ Array_Matrix4 │ 2000 │ ' 1576 ms' │ ' 14 ms' │ ' 112.57 x' │ -└──────────────────┴────────────┴──────────────┴──────────────┴──────────────┘ +┌────────────────────────────┬────────────┬──────────────┬──────────────┬──────────────┐ +│ (index) │ Iterations │ Ajv │ TypeCompiler │ Performance │ +├────────────────────────────┼────────────┼──────────────┼──────────────┼──────────────┤ +│ Literal_String │ 1000 │ ' 260 ms' │ ' 8 ms' │ ' 32.50 x' │ +│ Literal_Number │ 1000 │ ' 198 ms' │ ' 4 ms' │ ' 49.50 x' │ +│ Literal_Boolean │ 1000 │ ' 185 ms' │ ' 5 ms' │ ' 37.00 x' │ +│ Primitive_Number │ 1000 │ ' 176 ms' │ ' 9 ms' │ ' 19.56 x' │ +│ Primitive_String │ 1000 │ ' 161 ms' │ ' 9 ms' │ ' 17.89 x' │ +│ Primitive_String_Pattern │ 1000 │ ' 215 ms' │ ' 12 ms' │ ' 17.92 x' │ +│ Primitive_Boolean │ 1000 │ ' 133 ms' │ ' 5 ms' │ ' 26.60 x' │ +│ Primitive_Null │ 1000 │ ' 143 ms' │ ' 8 ms' │ ' 17.88 x' │ +│ Object_Unconstrained │ 1000 │ ' 1181 ms' │ ' 38 ms' │ ' 31.08 x' │ +│ Object_Constrained │ 1000 │ ' 1168 ms' │ ' 32 ms' │ ' 36.50 x' │ +│ Tuple_Primitive │ 1000 │ ' 557 ms' │ ' 16 ms' │ ' 34.81 x' │ +│ Tuple_Object │ 1000 │ ' 1119 ms' │ ' 17 ms' │ ' 65.82 x' │ +│ Composite_Intersect │ 1000 │ ' 569 ms' │ ' 22 ms' │ ' 25.86 x' │ +│ Composite_Union │ 1000 │ ' 513 ms' │ ' 23 ms' │ ' 22.30 x' │ +│ Math_Vector4 │ 1000 │ ' 802 ms' │ ' 10 ms' │ ' 80.20 x' │ +│ Math_Matrix4 │ 1000 │ ' 395 ms' │ ' 12 ms' │ ' 32.92 x' │ +│ Array_Primitive_Number │ 1000 │ ' 282 ms' │ ' 8 ms' │ ' 35.25 x' │ +│ Array_Primitive_String │ 1000 │ ' 321 ms' │ ' 5 ms' │ ' 64.20 x' │ +│ Array_Primitive_Boolean │ 1000 │ ' 364 ms' │ ' 5 ms' │ ' 72.80 x' │ +│ Array_Object_Unconstrained │ 1000 │ ' 1573 ms' │ ' 18 ms' │ ' 87.39 x' │ +│ Array_Object_Constrained │ 1000 │ ' 1270 ms' │ ' 20 ms' │ ' 63.50 x' │ +│ Array_Tuple_Primitive │ 1000 │ ' 973 ms' │ ' 18 ms' │ ' 54.06 x' │ +│ Array_Tuple_Object │ 1000 │ ' 1253 ms' │ ' 16 ms' │ ' 78.31 x' │ +│ Array_Composite_Intersect │ 1000 │ ' 927 ms' │ ' 20 ms' │ ' 46.35 x' │ +│ Array_Composite_Union │ 1000 │ ' 1123 ms' │ ' 16 ms' │ ' 70.19 x' │ +│ Array_Math_Vector4 │ 1000 │ ' 1068 ms' │ ' 10 ms' │ ' 106.80 x' │ +│ Array_Math_Matrix4 │ 1000 │ ' 488 ms' │ ' 7 ms' │ ' 69.71 x' │ +└────────────────────────────┴────────────┴──────────────┴──────────────┴──────────────┘ ``` @@ -1177,35 +1271,39 @@ This benchmark measures compilation performance for varying types. You can revie This benchmark measures validation performance for varying types. You can review this benchmark [here](https://github.com/sinclairzx81/typebox/blob/master/benchmark/measurement/module/check.ts). ```typescript -┌──────────────────┬────────────┬──────────────┬──────────────┬──────────────┬──────────────┐ -│ (index) │ Iterations │ ValueCheck │ Ajv │ TypeCompiler │ Performance │ -├──────────────────┼────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ -│ Number │ 1000000 │ ' 30 ms' │ ' 7 ms' │ ' 6 ms' │ ' 1.17 x' │ -│ String │ 1000000 │ ' 23 ms' │ ' 21 ms' │ ' 11 ms' │ ' 1.91 x' │ -│ Boolean │ 1000000 │ ' 22 ms' │ ' 21 ms' │ ' 10 ms' │ ' 2.10 x' │ -│ Null │ 1000000 │ ' 27 ms' │ ' 20 ms' │ ' 10 ms' │ ' 2.00 x' │ -│ RegEx │ 1000000 │ ' 163 ms' │ ' 47 ms' │ ' 38 ms' │ ' 1.24 x' │ -│ ObjectA │ 1000000 │ ' 654 ms' │ ' 41 ms' │ ' 24 ms' │ ' 1.71 x' │ -│ ObjectB │ 1000000 │ ' 1173 ms' │ ' 59 ms' │ ' 41 ms' │ ' 1.44 x' │ -│ Tuple │ 1000000 │ ' 124 ms' │ ' 24 ms' │ ' 17 ms' │ ' 1.41 x' │ -│ Union │ 1000000 │ ' 332 ms' │ ' 26 ms' │ ' 16 ms' │ ' 1.63 x' │ -│ Recursive │ 1000000 │ ' 3129 ms' │ ' 412 ms' │ ' 102 ms' │ ' 4.04 x' │ -│ Vector4 │ 1000000 │ ' 147 ms' │ ' 26 ms' │ ' 13 ms' │ ' 2.00 x' │ -│ Matrix4 │ 1000000 │ ' 576 ms' │ ' 41 ms' │ ' 28 ms' │ ' 1.46 x' │ -│ Literal_String │ 1000000 │ ' 51 ms' │ ' 21 ms' │ ' 10 ms' │ ' 2.10 x' │ -│ Literal_Number │ 1000000 │ ' 47 ms' │ ' 21 ms' │ ' 11 ms' │ ' 1.91 x' │ -│ Literal_Boolean │ 1000000 │ ' 47 ms' │ ' 21 ms' │ ' 10 ms' │ ' 2.10 x' │ -│ Array_Number │ 1000000 │ ' 490 ms' │ ' 33 ms' │ ' 18 ms' │ ' 1.83 x' │ -│ Array_String │ 1000000 │ ' 502 ms' │ ' 31 ms' │ ' 25 ms' │ ' 1.24 x' │ -│ Array_Boolean │ 1000000 │ ' 465 ms' │ ' 33 ms' │ ' 27 ms' │ ' 1.22 x' │ -│ Array_ObjectA │ 1000000 │ ' 15463 ms' │ ' 2470 ms' │ ' 2052 ms' │ ' 1.20 x' │ -│ Array_ObjectB │ 1000000 │ ' 18047 ms' │ ' 2497 ms' │ ' 2348 ms' │ ' 1.06 x' │ -│ Array_Tuple │ 1000000 │ ' 1958 ms' │ ' 99 ms' │ ' 77 ms' │ ' 1.29 x' │ -│ Array_Union │ 1000000 │ ' 5348 ms' │ ' 254 ms' │ ' 89 ms' │ ' 2.85 x' │ -│ Array_Recursive │ 1000000 │ ' 54643 ms' │ ' 8870 ms' │ ' 1158 ms' │ ' 7.66 x' │ -│ Array_Vector4 │ 1000000 │ ' 2724 ms' │ ' 105 ms' │ ' 48 ms' │ ' 2.19 x' │ -│ Array_Matrix4 │ 1000000 │ ' 13821 ms' │ ' 437 ms' │ ' 266 ms' │ ' 1.64 x' │ -└──────────────────┴────────────┴──────────────┴──────────────┴──────────────┴──────────────┘ +┌────────────────────────────┬────────────┬──────────────┬──────────────┬──────────────┬──────────────┐ +│ (index) │ Iterations │ ValueCheck │ Ajv │ TypeCompiler │ Performance │ +├────────────────────────────┼────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ +│ Literal_String │ 1000000 │ ' 26 ms' │ ' 6 ms' │ ' 6 ms' │ ' 1.00 x' │ +│ Literal_Number │ 1000000 │ ' 21 ms' │ ' 19 ms' │ ' 11 ms' │ ' 1.73 x' │ +│ Literal_Boolean │ 1000000 │ ' 19 ms' │ ' 18 ms' │ ' 10 ms' │ ' 1.80 x' │ +│ Primitive_Number │ 1000000 │ ' 24 ms' │ ' 19 ms' │ ' 11 ms' │ ' 1.73 x' │ +│ Primitive_String │ 1000000 │ ' 26 ms' │ ' 17 ms' │ ' 10 ms' │ ' 1.70 x' │ +│ Primitive_String_Pattern │ 1000000 │ ' 159 ms' │ ' 45 ms' │ ' 37 ms' │ ' 1.22 x' │ +│ Primitive_Boolean │ 1000000 │ ' 23 ms' │ ' 17 ms' │ ' 10 ms' │ ' 1.70 x' │ +│ Primitive_Null │ 1000000 │ ' 23 ms' │ ' 18 ms' │ ' 10 ms' │ ' 1.80 x' │ +│ Object_Unconstrained │ 1000000 │ ' 809 ms' │ ' 35 ms' │ ' 30 ms' │ ' 1.17 x' │ +│ Object_Constrained │ 1000000 │ ' 1060 ms' │ ' 56 ms' │ ' 45 ms' │ ' 1.24 x' │ +│ Object_Recursive │ 1000000 │ ' 4965 ms' │ ' 397 ms' │ ' 100 ms' │ ' 3.97 x' │ +│ Tuple_Primitive │ 1000000 │ ' 159 ms' │ ' 22 ms' │ ' 16 ms' │ ' 1.38 x' │ +│ Tuple_Object │ 1000000 │ ' 658 ms' │ ' 31 ms' │ ' 27 ms' │ ' 1.15 x' │ +│ Composite_Intersect │ 1000000 │ ' 695 ms' │ ' 26 ms' │ ' 22 ms' │ ' 1.18 x' │ +│ Composite_Union │ 1000000 │ ' 503 ms' │ ' 24 ms' │ ' 19 ms' │ ' 1.26 x' │ +│ Math_Vector4 │ 1000000 │ ' 259 ms' │ ' 22 ms' │ ' 14 ms' │ ' 1.57 x' │ +│ Math_Matrix4 │ 1000000 │ ' 1007 ms' │ ' 40 ms' │ ' 29 ms' │ ' 1.38 x' │ +│ Array_Primitive_Number │ 1000000 │ ' 262 ms' │ ' 23 ms' │ ' 17 ms' │ ' 1.35 x' │ +│ Array_Primitive_String │ 1000000 │ ' 241 ms' │ ' 27 ms' │ ' 24 ms' │ ' 1.13 x' │ +│ Array_Primitive_Boolean │ 1000000 │ ' 141 ms' │ ' 23 ms' │ ' 20 ms' │ ' 1.15 x' │ +│ Array_Object_Unconstrained │ 1000000 │ ' 4976 ms' │ ' 70 ms' │ ' 67 ms' │ ' 1.04 x' │ +│ Array_Object_Constrained │ 1000000 │ ' 5234 ms' │ ' 143 ms' │ ' 120 ms' │ ' 1.19 x' │ +│ Array_Object_Recursive │ 1000000 │ ' 19605 ms' │ ' 1909 ms' │ ' 350 ms' │ ' 5.45 x' │ +│ Array_Tuple_Primitive │ 1000000 │ ' 706 ms' │ ' 39 ms' │ ' 32 ms' │ ' 1.22 x' │ +│ Array_Tuple_Object │ 1000000 │ ' 2951 ms' │ ' 67 ms' │ ' 63 ms' │ ' 1.06 x' │ +│ Array_Composite_Intersect │ 1000000 │ ' 2969 ms' │ ' 49 ms' │ ' 44 ms' │ ' 1.11 x' │ +│ Array_Composite_Union │ 1000000 │ ' 2191 ms' │ ' 77 ms' │ ' 41 ms' │ ' 1.88 x' │ +│ Array_Math_Vector4 │ 1000000 │ ' 1164 ms' │ ' 41 ms' │ ' 25 ms' │ ' 1.64 x' │ +│ Array_Math_Matrix4 │ 1000000 │ ' 4903 ms' │ ' 115 ms' │ ' 99 ms' │ ' 1.16 x' │ +└────────────────────────────┴────────────┴──────────────┴──────────────┴──────────────┴──────────────┘ ``` @@ -1218,15 +1316,11 @@ The following table lists esbuild compiled and minified sizes for each TypeBox m ┌──────────────────────┬────────────┬────────────┬─────────────┐ │ (index) │ Compiled │ Minified │ Compression │ ├──────────────────────┼────────────┼────────────┼─────────────┤ -│ typebox/compiler │ ' 65.4 kb' │ ' 32.2 kb' │ '2.03 x' │ -│ typebox/conditional │ ' 45.5 kb' │ ' 18.6 kb' │ '2.45 x' │ -│ typebox/custom │ ' 0.6 kb' │ ' 0.2 kb' │ '2.61 x' │ -│ typebox/format │ ' 0.6 kb' │ ' 0.2 kb' │ '2.66 x' │ -│ typebox/guard │ ' 23.8 kb' │ ' 11.4 kb' │ '2.08 x' │ -│ typebox/hash │ ' 4.2 kb' │ ' 1.8 kb' │ '2.30 x' │ -│ typebox/system │ ' 14.0 kb' │ ' 7.1 kb' │ '1.96 x' │ -│ typebox/value │ ' 90.0 kb' │ ' 41.8 kb' │ '2.15 x' │ -│ typebox │ ' 12.0 kb' │ ' 6.4 kb' │ '1.89 x' │ +│ typebox/compiler │ '108.8 kb' │ ' 48.9 kb' │ '2.23 x' │ +│ typebox/errors │ ' 93.2 kb' │ ' 41.5 kb' │ '2.24 x' │ +│ typebox/system │ ' 60.0 kb' │ ' 24.6 kb' │ '2.43 x' │ +│ typebox/value │ '153.5 kb' │ ' 66.7 kb' │ '2.30 x' │ +│ typebox │ ' 58.7 kb' │ ' 24.1 kb' │ '2.43 x' │ └──────────────────────┴────────────┴────────────┴─────────────┘ ``` diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index de1b766..f773b01 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -26,37 +26,28 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { ValueErrors, ValueError } from '../errors/index' -import { TypeSystem } from '../system/index' -import { TypeExtends, TypeGuard } from '../guard/index' -import { Format } from '../format/index' -import { Custom } from '../custom/index' -import { ValueHash } from '../hash/index' import * as Types from '../typebox' +import { ValueErrors, ValueErrorIterator } from '../errors/index' +import { TypeSystem } from '../system/index' +import { ValueHash } from '../value/hash' // ------------------------------------------------------------------- // CheckFunction // ------------------------------------------------------------------- - export type CheckFunction = (value: unknown) => boolean - // ------------------------------------------------------------------- // TypeCheck // ------------------------------------------------------------------- - export class TypeCheck { constructor(private readonly schema: T, private readonly references: Types.TSchema[], private readonly checkFunc: CheckFunction, private readonly code: string) {} - - /** Returns the generated validation code used to validate this type. */ + /** Returns the generated assertion code used to validate this type. */ public Code(): string { return this.code } - /** Returns an iterator for each error in this value. */ - public Errors(value: unknown): IterableIterator { + public Errors(value: unknown): ValueErrorIterator { return ValueErrors.Errors(this.schema, this.references, value) } - /** Returns true if the value matches the compiled type. */ public Check(value: unknown): value is Types.Static { return this.checkFunc(value) @@ -79,7 +70,6 @@ namespace Character { return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) } } - // ------------------------------------------------------------------- // Identifier // ------------------------------------------------------------------- @@ -97,7 +87,6 @@ namespace Identifier { return buffer.join('').replace(/__/g, '_') } } - // ------------------------------------------------------------------- // MemberExpression // ------------------------------------------------------------------- @@ -122,369 +111,386 @@ export namespace MemberExpression { return !Check(key) ? `${object}['${key}']` : `${object}.${key}` } } - // ------------------------------------------------------------------- // TypeCompiler // ------------------------------------------------------------------- - export class TypeCompilerUnknownTypeError extends Error { constructor(public readonly schema: Types.TSchema) { super('TypeCompiler: Unknown type') } } - +export class TypeCompilerDereferenceError extends Error { + constructor(public readonly schema: Types.TRef) { + super(`TypeCompiler: Unable to dereference schema with $id '${schema.$ref}'`) + } +} +export class TypeCompilerTypeGuardError extends Error { + constructor(public readonly schema: Types.TSchema) { + super('TypeCompiler: Preflight validation check failed to guard for the given schema') + } +} /** Compiles Types for Runtime Type Checking */ export namespace TypeCompiler { // ------------------------------------------------------------------- // Guards // ------------------------------------------------------------------- - function IsNumber(value: unknown): value is number { - return typeof value === 'number' && !globalThis.isNaN(value) + function IsBigInt(value: unknown): value is bigint { + return typeof value === 'bigint' + } + function IsNumber(value: unknown): value is number { + return typeof value === 'number' && globalThis.Number.isFinite(value) + } + function IsString(value: unknown): value is string { + return typeof value === 'string' + } + // ------------------------------------------------------------------- + // Overrides + // ------------------------------------------------------------------- + function IsNumberCheck(value: string): string { + return !TypeSystem.AllowNaN ? `(typeof ${value} === 'number' && Number.isFinite(${value}))` : `typeof ${value} === 'number'` + } + function IsObjectCheck(value: string): string { + return !TypeSystem.AllowArrayObjects ? `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}))` : `(typeof ${value} === 'object' && ${value} !== null)` + } + function IsRecordCheck(value: string): string { + return !TypeSystem.AllowArrayObjects + ? `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}) && !(${value} instanceof Date) && !(${value} instanceof Uint8Array))` + : `(typeof ${value} === 'object' && ${value} !== null && !(${value} instanceof Date) && !(${value} instanceof Uint8Array))` + } + function IsVoidCheck(value: string): string { + return TypeSystem.AllowVoidNull ? `(${value} === undefined || ${value} === null)` : `${value} === undefined` } - // ------------------------------------------------------------------- // Types // ------------------------------------------------------------------- - function* Any(schema: Types.TAny, value: string): IterableIterator { - yield '(true)' + function* Any(schema: Types.TAny, references: Types.TSchema[], value: string): IterableIterator { + yield 'true' } - - function* Array(schema: Types.TArray, value: string): IterableIterator { - const expression = CreateExpression(schema.items, 'value') - yield `(Array.isArray(${value}) && ${value}.every(value => ${expression}))` - if (IsNumber(schema.minItems)) yield `(${value}.length >= ${schema.minItems})` - if (IsNumber(schema.maxItems)) yield `(${value}.length <= ${schema.maxItems})` + function* Array(schema: Types.TArray, references: Types.TSchema[], value: string): IterableIterator { + const expression = CreateExpression(schema.items, references, 'value') + yield `Array.isArray(${value}) && ${value}.every(value => ${expression})` + if (IsNumber(schema.minItems)) yield `${value}.length >= ${schema.minItems}` + if (IsNumber(schema.maxItems)) yield `${value}.length <= ${schema.maxItems}` if (schema.uniqueItems === true) yield `((function() { const set = new Set(); for(const element of ${value}) { const hashed = hash(element); if(set.has(hashed)) { return false } else { set.add(hashed) } } return true })())` } - - function* Boolean(schema: Types.TBoolean, value: string): IterableIterator { - yield `(typeof ${value} === 'boolean')` + function* BigInt(schema: Types.TBigInt, references: Types.TSchema[], value: string): IterableIterator { + yield `(typeof ${value} === 'bigint')` + if (IsBigInt(schema.multipleOf)) yield `(${value} % BigInt(${schema.multipleOf})) === 0` + if (IsBigInt(schema.exclusiveMinimum)) yield `${value} > BigInt(${schema.exclusiveMinimum})` + if (IsBigInt(schema.exclusiveMaximum)) yield `${value} < BigInt(${schema.exclusiveMaximum})` + if (IsBigInt(schema.minimum)) yield `${value} >= BigInt(${schema.minimum})` + if (IsBigInt(schema.maximum)) yield `${value} <= BigInt(${schema.maximum})` } - - function* Constructor(schema: Types.TConstructor, value: string): IterableIterator { - yield* Visit(schema.returns, `${value}.prototype`) + function* Boolean(schema: Types.TBoolean, references: Types.TSchema[], value: string): IterableIterator { + yield `typeof ${value} === 'boolean'` } - - function* Date(schema: Types.TDate, value: string): IterableIterator { - yield `(${value} instanceof Date) && !isNaN(${value}.getTime())` - if (IsNumber(schema.exclusiveMinimumTimestamp)) yield `(${value}.getTime() > ${schema.exclusiveMinimumTimestamp})` - if (IsNumber(schema.exclusiveMaximumTimestamp)) yield `(${value}.getTime() < ${schema.exclusiveMaximumTimestamp})` - if (IsNumber(schema.minimumTimestamp)) yield `(${value}.getTime() >= ${schema.minimumTimestamp})` - if (IsNumber(schema.maximumTimestamp)) yield `(${value}.getTime() <= ${schema.maximumTimestamp})` + function* Constructor(schema: Types.TConstructor, references: Types.TSchema[], value: string): IterableIterator { + yield* Visit(schema.returns, references, `${value}.prototype`) } - - function* Function(schema: Types.TFunction, value: string): IterableIterator { - yield `(typeof ${value} === 'function')` + function* Date(schema: Types.TDate, references: Types.TSchema[], value: string): IterableIterator { + yield `(${value} instanceof Date) && Number.isFinite(${value}.getTime())` + if (IsNumber(schema.exclusiveMinimumTimestamp)) yield `${value}.getTime() > ${schema.exclusiveMinimumTimestamp}` + if (IsNumber(schema.exclusiveMaximumTimestamp)) yield `${value}.getTime() < ${schema.exclusiveMaximumTimestamp}` + if (IsNumber(schema.minimumTimestamp)) yield `${value}.getTime() >= ${schema.minimumTimestamp}` + if (IsNumber(schema.maximumTimestamp)) yield `${value}.getTime() <= ${schema.maximumTimestamp}` } - - function* Integer(schema: Types.TInteger, value: string): IterableIterator { + function* Function(schema: Types.TFunction, references: Types.TSchema[], value: string): IterableIterator { + yield `typeof ${value} === 'function'` + } + function* Integer(schema: Types.TInteger, references: Types.TSchema[], value: string): IterableIterator { yield `(typeof ${value} === 'number' && Number.isInteger(${value}))` - if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf} === 0)` - if (IsNumber(schema.exclusiveMinimum)) yield `(${value} > ${schema.exclusiveMinimum})` - if (IsNumber(schema.exclusiveMaximum)) yield `(${value} < ${schema.exclusiveMaximum})` - if (IsNumber(schema.minimum)) yield `(${value} >= ${schema.minimum})` - if (IsNumber(schema.maximum)) yield `(${value} <= ${schema.maximum})` + if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0` + if (IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}` + if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}` + if (IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}` + if (IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}` } - - function* Literal(schema: Types.TLiteral, value: string): IterableIterator { + function* Intersect(schema: Types.TIntersect, references: Types.TSchema[], value: string): IterableIterator { + if (schema.unevaluatedProperties === undefined) { + const expressions = schema.allOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value)) + yield `${expressions.join(' && ')}` + } else if (schema.unevaluatedProperties === false) { + // prettier-ignore + const schemaKeys = Types.KeyResolver.Resolve(schema).map((key) => `'${key}'`).join(', ') + const expressions = schema.allOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value)) + const expression1 = `Object.getOwnPropertyNames(${value}).every(key => [${schemaKeys}].includes(key))` + yield `${expressions.join(' && ')} && ${expression1}` + } else if (typeof schema.unevaluatedProperties === 'object') { + // prettier-ignore + const schemaKeys = Types.KeyResolver.Resolve(schema).map((key) => `'${key}'`).join(', ') + const expressions = schema.allOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value)) + const expression1 = CreateExpression(schema.unevaluatedProperties, references, 'value[key]') + const expression2 = `Object.getOwnPropertyNames(${value}).every(key => [${schemaKeys}].includes(key) || ${expression1})` + yield `${expressions.join(' && ')} && ${expression2}` + } + } + function* Literal(schema: Types.TLiteral, references: Types.TSchema[], value: string): IterableIterator { if (typeof schema.const === 'number' || typeof schema.const === 'boolean') { - yield `(${value} === ${schema.const})` + yield `${value} === ${schema.const}` } else { - yield `(${value} === '${schema.const}')` + yield `${value} === '${schema.const}'` } } - - function* Never(schema: Types.TNever, value: string): IterableIterator { - yield `(false)` + function* Never(schema: Types.TNever, references: Types.TSchema[], value: string): IterableIterator { + yield `false` + } + function* Not(schema: Types.TNot, references: Types.TSchema[], value: string): IterableIterator { + const left = CreateExpression(schema.allOf[0].not, references, value) + const right = CreateExpression(schema.allOf[1], references, value) + yield `!${left} && ${right}` + } + function* Null(schema: Types.TNull, references: Types.TSchema[], value: string): IterableIterator { + yield `${value} === null` + } + function* Number(schema: Types.TNumber, references: Types.TSchema[], value: string): IterableIterator { + yield IsNumberCheck(value) + if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0` + if (IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}` + if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}` + if (IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}` + if (IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}` } - function* Null(schema: Types.TNull, value: string): IterableIterator { - yield `(${value} === null)` - } - - function* Number(schema: Types.TNumber, value: string): IterableIterator { - if (TypeSystem.AllowNaN) { - yield `(typeof ${value} === 'number')` - } else { - yield `(typeof ${value} === 'number' && !isNaN(${value}))` - } - if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf} === 0)` - if (IsNumber(schema.exclusiveMinimum)) yield `(${value} > ${schema.exclusiveMinimum})` - if (IsNumber(schema.exclusiveMaximum)) yield `(${value} < ${schema.exclusiveMaximum})` - if (IsNumber(schema.minimum)) yield `(${value} >= ${schema.minimum})` - if (IsNumber(schema.maximum)) yield `(${value} <= ${schema.maximum})` - } - - function* Object(schema: Types.TObject, value: string): IterableIterator { - if (TypeSystem.AllowArrayObjects) { - yield `(typeof ${value} === 'object' && ${value} !== null)` - } else { - yield `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}))` - } - if (IsNumber(schema.minProperties)) yield `(Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties})` - if (IsNumber(schema.maxProperties)) yield `(Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties})` - const propertyKeys = globalThis.Object.getOwnPropertyNames(schema.properties) - if (schema.additionalProperties === false) { - // Optimization: If the property key length matches the required keys length - // then we only need check that the values property key length matches that - // of the property key length. This is because exhaustive testing for values - // will occur in subsequent property tests. - if (schema.required && schema.required.length === propertyKeys.length) { - yield `(Object.getOwnPropertyNames(${value}).length === ${propertyKeys.length})` + function* Object(schema: Types.TObject, references: Types.TSchema[], value: string): IterableIterator { + yield IsObjectCheck(value) + if (!TypeSystem.AllowArrayObjects) yield `!Array.isArray(${value})` + if (IsNumber(schema.minProperties)) yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}` + if (IsNumber(schema.maxProperties)) yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}` + const schemaKeys = globalThis.Object.getOwnPropertyNames(schema.properties) + for (const schemaKey of schemaKeys) { + const memberExpression = MemberExpression.Encode(value, schemaKey) + const property = schema.properties[schemaKey] + if (schema.required && schema.required.includes(schemaKey)) { + yield* Visit(property, references, memberExpression) + if (Types.ExtendsUndefined.Check(property)) yield `('${schemaKey}' in ${value})` } else { - const keys = `[${propertyKeys.map((key) => `'${key}'`).join(', ')}]` - yield `(Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key)))` + const expression = CreateExpression(property, references, memberExpression) + yield `('${schemaKey}' in ${value} ? ${expression} : true)` } } - if (TypeGuard.TSchema(schema.additionalProperties)) { - const expression = CreateExpression(schema.additionalProperties, 'value[key]') - const keys = `[${propertyKeys.map((key) => `'${key}'`).join(', ')}]` + if (schema.additionalProperties === false) { + if (schema.required && schema.required.length === schemaKeys.length) { + yield `Object.getOwnPropertyNames(${value}).length === ${schemaKeys.length}` + } else { + const keys = `[${schemaKeys.map((key) => `'${key}'`).join(', ')}]` + yield `Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key))` + } + } + if (typeof schema.additionalProperties === 'object') { + const expression = CreateExpression(schema.additionalProperties, references, 'value[key]') + const keys = `[${schemaKeys.map((key) => `'${key}'`).join(', ')}]` yield `(Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key) || ${expression}))` } - for (const propertyKey of propertyKeys) { - const memberExpression = MemberExpression.Encode(value, propertyKey) - const propertySchema = schema.properties[propertyKey] - if (schema.required && schema.required.includes(propertyKey)) { - yield* Visit(propertySchema, memberExpression) - if (TypeExtends.Undefined(propertySchema)) { - yield `('${propertyKey}' in ${value})` - } - } else { - const expression = CreateExpression(propertySchema, memberExpression) - yield `(${memberExpression} === undefined ? true : (${expression}))` - } - } } - - function* Promise(schema: Types.TPromise, value: string): IterableIterator { + function* Promise(schema: Types.TPromise, references: Types.TSchema[], value: string): IterableIterator { yield `(typeof value === 'object' && typeof ${value}.then === 'function')` } - function* Record(schema: Types.TRecord, value: string): IterableIterator { - if (TypeSystem.AllowArrayObjects) { - yield `(typeof ${value} === 'object' && ${value} !== null && !(${value} instanceof Date))` - } else { - yield `(typeof ${value} === 'object' && ${value} !== null && !(${value} instanceof Date) && !Array.isArray(${value}))` - } + function* Record(schema: Types.TRecord, references: Types.TSchema[], value: string): IterableIterator { + yield IsRecordCheck(value) const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0] const local = PushLocal(`new RegExp(/${keyPattern}/)`) yield `(Object.getOwnPropertyNames(${value}).every(key => ${local}.test(key)))` - const expression = CreateExpression(valueSchema, 'value') - yield `(Object.values(${value}).every(value => ${expression}))` + const expression = CreateExpression(valueSchema, references, 'value') + yield `Object.values(${value}).every(value => ${expression})` } - - function* Ref(schema: Types.TRef, value: string): IterableIterator { + function* Ref(schema: Types.TRef, references: Types.TSchema[], value: string): IterableIterator { + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new TypeCompilerDereferenceError(schema) + const target = references[index] // Reference: If we have seen this reference before we can just yield and return // the function call. If this isn't the case we defer to visit to generate and // set the function for subsequent passes. Consider for refactor. - if (state_local_function_names.has(schema.$ref)) return yield `(${CreateFunctionName(schema.$ref)}(${value}))` - if (!state_reference_map.has(schema.$ref)) throw Error(`TypeCompiler.Ref: Cannot de-reference schema with $id '${schema.$ref}'`) - const reference = state_reference_map.get(schema.$ref)! - yield* Visit(reference, value) + if (state_local_function_names.has(schema.$ref)) return yield `${CreateFunctionName(schema.$ref)}(${value})` + yield* Visit(target, references, value) } - - function* Self(schema: Types.TSelf, value: string): IterableIterator { + function* Self(schema: Types.TSelf, references: Types.TSchema[], value: string): IterableIterator { const func = CreateFunctionName(schema.$ref) - yield `(${func}(${value}))` + yield `${func}(${value})` } - - function* String(schema: Types.TString, value: string): IterableIterator { + function* String(schema: Types.TString, references: Types.TSchema[], value: string): IterableIterator { yield `(typeof ${value} === 'string')` - if (IsNumber(schema.minLength)) yield `(${value}.length >= ${schema.minLength})` - if (IsNumber(schema.maxLength)) yield `(${value}.length <= ${schema.maxLength})` + if (IsNumber(schema.minLength)) yield `${value}.length >= ${schema.minLength}` + if (IsNumber(schema.maxLength)) yield `${value}.length <= ${schema.maxLength}` if (schema.pattern !== undefined) { const local = PushLocal(`${new RegExp(schema.pattern)};`) - yield `(${local}.test(${value}))` + yield `${local}.test(${value})` } if (schema.format !== undefined) { - yield `(format('${schema.format}', ${value}))` + yield `format('${schema.format}', ${value})` } } - - function* Tuple(schema: Types.TTuple, value: string): IterableIterator { + function* Symbol(schema: Types.TSymbol, references: Types.TSchema[], value: string): IterableIterator { + yield `(typeof ${value} === 'symbol')` + } + function* Tuple(schema: Types.TTuple, references: Types.TSchema[], value: string): IterableIterator { yield `(Array.isArray(${value}))` - if (schema.items === undefined) return yield `(${value}.length === 0)` + if (schema.items === undefined) return yield `${value}.length === 0` yield `(${value}.length === ${schema.maxItems})` for (let i = 0; i < schema.items.length; i++) { - const expression = CreateExpression(schema.items[i], `${value}[${i}]`) - yield `(${expression})` + const expression = CreateExpression(schema.items[i], references, `${value}[${i}]`) + yield `${expression}` } } - - function* Undefined(schema: Types.TUndefined, value: string): IterableIterator { - yield `(${value} === undefined)` + function* Undefined(schema: Types.TUndefined, references: Types.TSchema[], value: string): IterableIterator { + yield `${value} === undefined` } - - function* Union(schema: Types.TUnion, value: string): IterableIterator { - const expressions = schema.anyOf.map((schema: Types.TSchema) => CreateExpression(schema, value)) + function* Union(schema: Types.TUnion, references: Types.TSchema[], value: string): IterableIterator { + const expressions = schema.anyOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value)) yield `(${expressions.join(' || ')})` } - - function* Uint8Array(schema: Types.TUint8Array, value: string): IterableIterator { - yield `(${value} instanceof Uint8Array)` + function* Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: string): IterableIterator { + yield `${value} instanceof Uint8Array` if (IsNumber(schema.maxByteLength)) yield `(${value}.length <= ${schema.maxByteLength})` if (IsNumber(schema.minByteLength)) yield `(${value}.length >= ${schema.minByteLength})` } - - function* Unknown(schema: Types.TUnknown, value: string): IterableIterator { - yield '(true)' + function* Unknown(schema: Types.TUnknown, references: Types.TSchema[], value: string): IterableIterator { + yield 'true' } - - function* Void(schema: Types.TVoid, value: string): IterableIterator { - yield `(${value} === null)` + function* Void(schema: Types.TVoid, references: Types.TSchema[], value: string): IterableIterator { + yield IsVoidCheck(value) } - - function* UserDefined(schema: Types.TSchema, value: string): IterableIterator { + function* UserDefined(schema: Types.TSchema, references: Types.TSchema[], value: string): IterableIterator { const schema_key = `schema_key_${state_remote_custom_types.size}` state_remote_custom_types.set(schema_key, schema) - yield `(custom('${schema[Types.Kind]}', '${schema_key}', ${value}))` + yield `custom('${schema[Types.Kind]}', '${schema_key}', ${value})` } - - function* Visit(schema: T, value: string): IterableIterator { + function* Visit(schema: T, references: Types.TSchema[], value: string): IterableIterator { + const references_ = IsString(schema.$id) ? [...references, schema] : references + const schema_ = schema as any // Reference: Referenced schemas can originate from either additional schemas // or inline in the schema itself. Ideally the recursive path should align to // reference path. Consider for refactor. - if (schema.$id && !state_local_function_names.has(schema.$id)) { + if (IsString(schema.$id) && !state_local_function_names.has(schema.$id)) { state_local_function_names.add(schema.$id) const name = CreateFunctionName(schema.$id) - const body = CreateFunction(name, schema, 'value') + const body = CreateFunction(name, schema, references, 'value') PushFunction(body) - yield `(${name}(${value}))` + yield `${name}(${value})` return } - const anySchema = schema as any - switch (anySchema[Types.Kind]) { + switch (schema_[Types.Kind]) { case 'Any': - return yield* Any(anySchema, value) + return yield* Any(schema_, references_, value) case 'Array': - return yield* Array(anySchema, value) + return yield* Array(schema_, references_, value) + case 'BigInt': + return yield* BigInt(schema_, references_, value) case 'Boolean': - return yield* Boolean(anySchema, value) + return yield* Boolean(schema_, references_, value) case 'Constructor': - return yield* Constructor(anySchema, value) + return yield* Constructor(schema_, references_, value) case 'Date': - return yield* Date(anySchema, value) + return yield* Date(schema_, references_, value) case 'Function': - return yield* Function(anySchema, value) + return yield* Function(schema_, references_, value) case 'Integer': - return yield* Integer(anySchema, value) + return yield* Integer(schema_, references_, value) + case 'Intersect': + return yield* Intersect(schema_, references_, value) case 'Literal': - return yield* Literal(anySchema, value) + return yield* Literal(schema_, references_, value) case 'Never': - return yield* Never(anySchema, value) + return yield* Never(schema_, references_, value) + case 'Not': + return yield* Not(schema_, references_, value) case 'Null': - return yield* Null(anySchema, value) + return yield* Null(schema_, references_, value) case 'Number': - return yield* Number(anySchema, value) + return yield* Number(schema_, references_, value) case 'Object': - return yield* Object(anySchema, value) + return yield* Object(schema_, references_, value) case 'Promise': - return yield* Promise(anySchema, value) + return yield* Promise(schema_, references_, value) case 'Record': - return yield* Record(anySchema, value) + return yield* Record(schema_, references_, value) case 'Ref': - return yield* Ref(anySchema, value) + return yield* Ref(schema_, references_, value) case 'Self': - return yield* Self(anySchema, value) + return yield* Self(schema_, references_, value) case 'String': - return yield* String(anySchema, value) + return yield* String(schema_, references_, value) + case 'Symbol': + return yield* Symbol(schema_, references_, value) case 'Tuple': - return yield* Tuple(anySchema, value) + return yield* Tuple(schema_, references_, value) case 'Undefined': - return yield* Undefined(anySchema, value) + return yield* Undefined(schema_, references_, value) case 'Union': - return yield* Union(anySchema, value) + return yield* Union(schema_, references_, value) case 'Uint8Array': - return yield* Uint8Array(anySchema, value) + return yield* Uint8Array(schema_, references_, value) case 'Unknown': - return yield* Unknown(anySchema, value) + return yield* Unknown(schema_, references_, value) case 'Void': - return yield* Void(anySchema, value) + return yield* Void(schema_, references_, value) default: - if (!Custom.Has(anySchema[Types.Kind])) throw new TypeCompilerUnknownTypeError(schema) - return yield* UserDefined(anySchema, value) + if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new TypeCompilerUnknownTypeError(schema) + return yield* UserDefined(schema_, references_, value) } } - // ------------------------------------------------------------------- // Compiler State // ------------------------------------------------------------------- - - const state_reference_map = new Map() // tracks schemas with identifiers const state_local_variables = new Set() // local variables and functions const state_local_function_names = new Set() // local function names used call ref validators const state_remote_custom_types = new Map() // remote custom types used during compilation - function ResetCompiler() { - state_reference_map.clear() state_local_variables.clear() state_local_function_names.clear() state_remote_custom_types.clear() } - - function AddReferences(schemas: Types.TSchema[] = []) { - for (const schema of schemas) { - if (!schema.$id) throw new Error(`TypeCompiler: Referenced schemas must specify an $id.`) - if (state_reference_map.has(schema.$id)) throw new Error(`TypeCompiler: Duplicate schema $id found for '${schema.$id}'`) - state_reference_map.set(schema.$id, schema) - } + function CreateExpression(schema: Types.TSchema, references: Types.TSchema[], value: string): string { + return `(${[...Visit(schema, references, value)].join(' && ')})` } - - function CreateExpression(schema: Types.TSchema, value: string): string { - return `(${[...Visit(schema, value)].join(' && ')})` - } - function CreateFunctionName($id: string) { return `check_${Identifier.Encode($id)}` } - - function CreateFunction(name: string, schema: Types.TSchema, value: string): string { - const expression = [...Visit(schema, value)].map((condition) => ` ${condition}`).join(' &&\n') + function CreateFunction(name: string, schema: Types.TSchema, references: Types.TSchema[], value: string): string { + const expression = [...Visit(schema, references, value)].map((condition) => ` ${condition}`).join(' &&\n') return `function ${name}(value) {\n return (\n${expression}\n )\n}` } - function PushFunction(functionBody: string) { state_local_variables.add(functionBody) } - function PushLocal(expression: string) { const local = `local_${state_local_variables.size}` state_local_variables.add(`const ${local} = ${expression}`) return local } - function GetLocals() { return [...state_local_variables.values()] } - // ------------------------------------------------------------------- // Compile // ------------------------------------------------------------------- - - function Build(schema: T, references: Types.TSchema[] = []): string { + function Build(schema: T, references: Types.TSchema[]): string { ResetCompiler() - AddReferences(references) - const check = CreateFunction('check', schema, 'value') + const check = CreateFunction('check', schema, references, 'value') const locals = GetLocals() return `${locals.join('\n')}\nreturn ${check}` } - + /** Returns the generated assertion code used to validate this type. */ + export function Code(schema: T, references: Types.TSchema[] = []) { + if (!Types.TypeGuard.TSchema(schema)) throw new TypeCompilerTypeGuardError(schema) + for (const schema of references) if (!Types.TypeGuard.TSchema(schema)) throw new TypeCompilerTypeGuardError(schema) + return Build(schema, references) + } /** Compiles the given type for runtime type checking. This compiler only accepts known TypeBox types non-inclusive of unsafe types. */ export function Compile(schema: T, references: Types.TSchema[] = []): TypeCheck { - TypeGuard.Assert(schema, references) - const code = Build(schema, references) + const code = Code(schema, references) const custom_schemas = new Map(state_remote_custom_types) const compiledFunction = globalThis.Function('custom', 'format', 'hash', code) const checkFunction = compiledFunction( (kind: string, schema_key: string, value: unknown) => { - if (!Custom.Has(kind) || !custom_schemas.has(schema_key)) return false + if (!Types.TypeRegistry.Has(kind) || !custom_schemas.has(schema_key)) return false const schema = custom_schemas.get(schema_key)! - const func = Custom.Get(kind)! + const func = Types.TypeRegistry.Get(kind)! return func(schema, value) }, (format: string, value: string) => { - if (!Format.Has(format)) return false - const func = Format.Get(format)! + if (!Types.FormatRegistry.Has(format)) return false + const func = Types.FormatRegistry.Get(format)! return func(value) }, (value: unknown) => { diff --git a/src/conditional/conditional.ts b/src/conditional/conditional.ts deleted file mode 100644 index a71a8d4..0000000 --- a/src/conditional/conditional.ts +++ /dev/null @@ -1,114 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/conditional - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import * as Types from '../typebox' -import { Structural, StructuralResult } from './structural' -import { TypeGuard } from '../guard/index' - -// ------------------------------------------------------------------------ -// Extends -// ------------------------------------------------------------------------ - -export type TExtends = Types.Static extends Types.Static ? T : U - -// ------------------------------------------------------------------------ -// Exclude -// ------------------------------------------------------------------------ - -export interface TExclude extends Types.TUnion { - static: Exclude, Types.Static> -} - -// ------------------------------------------------------------------------ -// Extract -// ------------------------------------------------------------------------ - -export interface TExtract extends Types.TUnion { - static: Extract, Types.Static> -} - -/** Conditional type mapping for TypeBox types */ -export namespace Conditional { - /** (Experimental) Creates a conditional expression type */ - export function Extends(left: L, right: R, ok: T, fail: U): TExtends { - switch (Structural.Check(left, right)) { - case StructuralResult.Union: - return Types.Type.Union([Clone(ok), Clone(fail)]) as any as TExtends - case StructuralResult.True: - return Clone(ok) - case StructuralResult.False: - return Clone(fail) - } - } - - /** (Experimental) Constructs a type by excluding from UnionType all union members that are assignable to ExcludedMembers. */ - export function Exclude(unionType: T, excludedMembers: U, options: Types.SchemaOptions = {}): TExclude { - const anyOf = unionType.anyOf - .filter((schema) => { - const check = Structural.Check(schema, excludedMembers) - return !(check === StructuralResult.True || check === StructuralResult.Union) - }) - .map((schema) => Clone(schema)) - return { ...options, [Types.Kind]: 'Union', anyOf } as any - } - - /** (Experimental) Constructs a type by extracting from Type all union members that are assignable to Union. */ - export function Extract(type: T, union: U, options: Types.SchemaOptions = {}): TExtract { - if (TypeGuard.TUnion(type)) { - const anyOf = type.anyOf.filter((schema: Types.TSchema) => Structural.Check(schema, union) === StructuralResult.True).map((schema: Types.TSchema) => Clone(schema)) - return { ...options, [Types.Kind]: 'Union', anyOf } as any - } else { - const anyOf = union.anyOf.filter((schema) => Structural.Check(type, schema) === StructuralResult.True).map((schema) => Clone(schema)) - return { ...options, [Types.Kind]: 'Union', anyOf } as any - } - } - - function Clone(value: any): any { - const isObject = (object: any): object is Record => typeof object === 'object' && object !== null && !Array.isArray(object) - const isArray = (object: any): object is any[] => typeof object === 'object' && object !== null && Array.isArray(object) - if (isObject(value)) { - return Object.keys(value).reduce( - (acc, key) => ({ - ...acc, - [key]: Clone(value[key]), - }), - Object.getOwnPropertySymbols(value).reduce( - (acc, key) => ({ - ...acc, - [key]: Clone(value[key]), - }), - {}, - ), - ) - } else if (isArray(value)) { - return value.map((item: any) => Clone(item)) - } else { - return value - } - } -} diff --git a/src/conditional/index.ts b/src/conditional/index.ts deleted file mode 100644 index c6f9714..0000000 --- a/src/conditional/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/conditional - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -export * from './conditional' -export * from './structural' diff --git a/src/conditional/structural.ts b/src/conditional/structural.ts deleted file mode 100644 index ec44495..0000000 --- a/src/conditional/structural.ts +++ /dev/null @@ -1,579 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/conditional - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import * as Types from '../typebox' -import { TypeGuard } from '../guard' - -// -------------------------------------------------------------------------- -// StructuralResult -// -------------------------------------------------------------------------- - -export enum StructuralResult { - Union, - True, - False, -} - -// -------------------------------------------------------------------------- -// Structural -// -------------------------------------------------------------------------- - -/** Performs structural equivalence checks against TypeBox types. */ -export namespace Structural { - const referenceMap = new Map() - - // ------------------------------------------------------------------------ - // Rules - // ------------------------------------------------------------------------ - - function AnyUnknownOrCustomRule(right: Types.TSchema) { - // https://github.com/microsoft/TypeScript/issues/40049 - if (TypeGuard.TUnion(right) && right.anyOf.some((schema: Types.TSchema) => schema[Types.Kind] === 'Any' || schema[Types.Kind] === 'Unknown')) return true - if (TypeGuard.TUnknown(right)) return true - if (TypeGuard.TAny(right)) return true - if (TypeGuard.TUserDefined(right)) throw Error(`Structural: Cannot structurally compare custom type '${right[Types.Kind]}'`) - return false - } - - function ObjectRightRule(left: Types.TAnySchema, right: Types.TObject) { - // type A = boolean extends {} ? 1 : 2 // additionalProperties: false - // type B = boolean extends object ? 1 : 2 // additionalProperties: true - const additionalProperties = right.additionalProperties - const propertyLength = globalThis.Object.keys(right.properties).length - return additionalProperties === false && propertyLength === 0 - } - - function UnionRightRule(left: Types.TAnySchema, right: Types.TUnion): StructuralResult { - const result = right.anyOf.some((right: Types.TSchema) => Visit(left, right) !== StructuralResult.False) - return result ? StructuralResult.True : StructuralResult.False - } - - // ------------------------------------------------------------------------ - // Records - // ------------------------------------------------------------------------ - - function RecordPattern(schema: Types.TRecord) { - return globalThis.Object.keys(schema.patternProperties)[0] as string - } - - function RecordNumberOrStringKey(schema: Types.TRecord) { - const pattern = RecordPattern(schema) - return pattern === '^.*$' || pattern === '^(0|[1-9][0-9]*)$' - } - - function RecordValue(schema: Types.TRecord) { - const pattern = RecordPattern(schema) - return schema.patternProperties[pattern] - } - - function RecordKey(schema: Types.TRecord) { - const pattern = RecordPattern(schema) - if (pattern === '^.*$') { - return Types.Type.String() - } else if (pattern === '^(0|[1-9][0-9]*)$') { - return Types.Type.Number() - } else { - const keys = pattern.slice(1, pattern.length - 1).split('|') - const schemas = keys.map((key) => (isNaN(+key) ? Types.Type.Literal(key) : Types.Type.Literal(parseFloat(key)))) - return Types.Type.Union(schemas) - } - } - - function PropertyMap(schema: Types.TObject | Types.TRecord) { - const comparable = new Map() - if (TypeGuard.TRecord(schema)) { - const propertyPattern = RecordPattern(schema as Types.TRecord) - if (propertyPattern === '^.*$' || propertyPattern === '^(0|[1-9][0-9]*)$') throw Error('Cannot extract record properties without property constraints') - const propertySchema = schema.patternProperties[propertyPattern] as Types.TSchema - const propertyKeys = propertyPattern.slice(1, propertyPattern.length - 1).split('|') - propertyKeys.forEach((propertyKey) => { - comparable.set(propertyKey, propertySchema) - }) - } else { - globalThis.Object.entries(schema.properties).forEach(([propertyKey, propertySchema]) => { - comparable.set(propertyKey, propertySchema as Types.TSchema) - }) - } - return comparable - } - - // ------------------------------------------------------------------------ - // Indexable - // ------------------------------------------------------------------------ - - function Indexable(left: Left, right: Types.TSchema): StructuralResult { - if (TypeGuard.TUnion(right)) { - return StructuralResult.False - } else { - return Visit(left, right) - } - } - - // ------------------------------------------------------------------------ - // Checks - // ------------------------------------------------------------------------ - - function Any(left: Types.TAny, right: Types.TSchema): StructuralResult { - return AnyUnknownOrCustomRule(right) ? StructuralResult.True : StructuralResult.Union - } - - function Array(left: Types.TArray, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right)) { - if (right.properties['length'] !== undefined && right.properties['length'][Types.Kind] === 'Number') return StructuralResult.True - if (globalThis.Object.keys(right.properties).length === 0) return StructuralResult.True - return StructuralResult.False - } else if (!TypeGuard.TArray(right)) { - return StructuralResult.False - } else if (left.items === undefined && right.items !== undefined) { - return StructuralResult.False - } else if (left.items !== undefined && right.items === undefined) { - return StructuralResult.False - } else if (left.items === undefined && right.items === undefined) { - return StructuralResult.False - } else { - const result = Visit(left.items, right.items) !== StructuralResult.False - return result ? StructuralResult.True : StructuralResult.False - } - } - - function Boolean(left: Types.TBoolean, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) { - return StructuralResult.True - } else if (TypeGuard.TBoolean(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Constructor(left: Types.TConstructor, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right) && globalThis.Object.keys(right.properties).length === 0) { - return StructuralResult.True - } else if (!TypeGuard.TConstructor(right)) { - return StructuralResult.False - } else if (right.parameters.length < left.parameters.length) { - return StructuralResult.False - } else { - if (Visit(left.returns, right.returns) === StructuralResult.False) { - return StructuralResult.False - } - for (let i = 0; i < left.parameters.length; i++) { - const result = Visit(right.parameters[i], left.parameters[i]) - if (result === StructuralResult.False) return StructuralResult.False - } - return StructuralResult.True - } - } - - function Date(left: Types.TDate, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) { - return StructuralResult.True - } else if (TypeGuard.TRecord(right)) { - return StructuralResult.False - } else if (TypeGuard.TDate(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Function(left: Types.TFunction, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right)) { - if (right.properties['length'] !== undefined && right.properties['length'][Types.Kind] === 'Number') return StructuralResult.True - if (globalThis.Object.keys(right.properties).length === 0) return StructuralResult.True - return StructuralResult.False - } else if (!TypeGuard.TFunction(right)) { - return StructuralResult.False - } else if (right.parameters.length < left.parameters.length) { - return StructuralResult.False - } else if (Visit(left.returns, right.returns) === StructuralResult.False) { - return StructuralResult.False - } else { - for (let i = 0; i < left.parameters.length; i++) { - const result = Visit(right.parameters[i], left.parameters[i]) - if (result === StructuralResult.False) return StructuralResult.False - } - return StructuralResult.True - } - } - - function Integer(left: Types.TInteger, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) { - return StructuralResult.True - } else if (TypeGuard.TInteger(right) || TypeGuard.TNumber(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Literal(left: Types.TLiteral, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) { - return StructuralResult.True - } else if (TypeGuard.TRecord(right)) { - if (typeof left.const === 'string') { - return Indexable(left, RecordValue(right as Types.TRecord)) - } else { - return StructuralResult.False - } - } else if (TypeGuard.TLiteral(right) && left.const === right.const) { - return StructuralResult.True - } else if (TypeGuard.TString(right) && typeof left.const === 'string') { - return StructuralResult.True - } else if (TypeGuard.TNumber(right) && typeof left.const === 'number') { - return StructuralResult.True - } else if (TypeGuard.TInteger(right) && typeof left.const === 'number') { - return StructuralResult.True - } else if (TypeGuard.TBoolean(right) && typeof left.const === 'boolean') { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Number(left: Types.TNumber, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) { - return StructuralResult.True - } else if (TypeGuard.TNumber(right)) { - return StructuralResult.True - } else if (TypeGuard.TInteger(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Null(left: Types.TNull, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TNull(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Properties(left: Map, right: Map) { - if (right.size > left.size) return StructuralResult.False - if (![...right.keys()].every((rightKey) => left.has(rightKey))) return StructuralResult.False - for (const rightKey of right.keys()) { - const leftProp = left.get(rightKey)! - const rightProp = right.get(rightKey)! - if (Visit(leftProp, rightProp) === StructuralResult.False) { - return StructuralResult.False - } - } - return StructuralResult.True - } - - function Object(left: Types.TObject, right: Types.TAnySchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right)) { - return Properties(PropertyMap(left), PropertyMap(right)) - } else if (TypeGuard.TRecord(right)) { - if (!RecordNumberOrStringKey(right as Types.TRecord)) { - return Properties(PropertyMap(left), PropertyMap(right)) - } else { - return StructuralResult.True - } - } else { - return StructuralResult.False - } - } - - function Promise(left: Types.TPromise, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right)) { - if (ObjectRightRule(left, right) || globalThis.Object.keys(right.properties).length === 0) { - return StructuralResult.True - } else { - return StructuralResult.False - } - } else if (!TypeGuard.TPromise(right)) { - return StructuralResult.False - } else { - const result = Visit(left.item, right.item) !== StructuralResult.False - return result ? StructuralResult.True : StructuralResult.False - } - } - - function Record(left: Types.TRecord, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right)) { - if (RecordPattern(left) === '^.*$' && right[Types.Hint] === 'Record') { - return StructuralResult.True - } else if (RecordPattern(left) === '^.*$') { - return StructuralResult.False - } else { - return globalThis.Object.keys(right.properties).length === 0 ? StructuralResult.True : StructuralResult.False - } - } else if (TypeGuard.TRecord(right)) { - if (!RecordNumberOrStringKey(left as Types.TRecord) && !RecordNumberOrStringKey(right as Types.TRecord)) { - return Properties(PropertyMap(left), PropertyMap(right)) - } else if (RecordNumberOrStringKey(left as Types.TRecord) && !RecordNumberOrStringKey(right as Types.TRecord)) { - const leftKey = RecordKey(left as Types.TRecord) - const rightKey = RecordKey(right as Types.TRecord) - if (Visit(rightKey, leftKey) === StructuralResult.False) { - return StructuralResult.False - } else { - return StructuralResult.True - } - } else { - return StructuralResult.True - } - } else { - return StructuralResult.False - } - } - - function Ref(left: Types.TRef, right: Types.TSchema): StructuralResult { - if (!referenceMap.has(left.$ref)) throw Error(`Cannot locate referenced $id '${left.$ref}'`) - const resolved = referenceMap.get(left.$ref)! - return Visit(resolved, right) - } - - function Self(left: Types.TSelf, right: Types.TSchema): StructuralResult { - if (!referenceMap.has(left.$ref)) throw Error(`Cannot locate referenced self $id '${left.$ref}'`) - const resolved = referenceMap.get(left.$ref)! - return Visit(resolved, right) - } - - function String(left: Types.TString, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) { - return StructuralResult.True - } else if (TypeGuard.TRecord(right)) { - return Indexable(left, RecordValue(right)) - } else if (TypeGuard.TString(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Tuple(left: Types.TTuple, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right)) { - const result = ObjectRightRule(left, right) || globalThis.Object.keys(right.properties).length === 0 - return result ? StructuralResult.True : StructuralResult.False - } else if (TypeGuard.TRecord(right)) { - return Indexable(left, RecordValue(right)) - } else if (TypeGuard.TArray(right)) { - if (right.items === undefined) { - return StructuralResult.False - } else if (TypeGuard.TUnion(right.items) && left.items) { - const result = left.items.every((left: Types.TSchema) => UnionRightRule(left, right.items as Types.TUnion) !== StructuralResult.False) - return result ? StructuralResult.True : StructuralResult.False - } else if (TypeGuard.TAny(right.items)) { - return StructuralResult.True - } else { - return StructuralResult.False - } - } - if (!TypeGuard.TTuple(right)) return StructuralResult.False - if (left.items === undefined && right.items === undefined) return StructuralResult.True - if (left.items === undefined && right.items !== undefined) return StructuralResult.False - if (left.items !== undefined && right.items === undefined) return StructuralResult.False - if (left.items === undefined && right.items === undefined) return StructuralResult.True - if (left.minItems !== right.minItems || left.maxItems !== right.maxItems) return StructuralResult.False - for (let i = 0; i < left.items!.length; i++) { - if (Visit(left.items![i], right.items![i]) === StructuralResult.False) return StructuralResult.False - } - return StructuralResult.True - } - - function Uint8Array(left: Types.TUint8Array, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) { - return StructuralResult.True - } else if (TypeGuard.TRecord(right)) { - return Indexable(left, RecordValue(right as Types.TRecord)) - } else if (TypeGuard.TUint8Array(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Undefined(left: Types.TUndefined, right: Types.TSchema): StructuralResult { - if (AnyUnknownOrCustomRule(right)) { - return StructuralResult.True - } else if (TypeGuard.TUndefined(right)) { - return StructuralResult.True - } else if (TypeGuard.TVoid(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnion(right)) { - return UnionRightRule(left, right) - } else { - return StructuralResult.False - } - } - - function Union(left: Types.TUnion, right: Types.TSchema): StructuralResult { - if (left.anyOf.some((left: Types.TSchema) => TypeGuard.TAny(left))) { - return StructuralResult.Union - } else if (TypeGuard.TUnion(right)) { - const result = left.anyOf.every((left: Types.TSchema) => right.anyOf.some((right: Types.TSchema) => Visit(left, right) !== StructuralResult.False)) - return result ? StructuralResult.True : StructuralResult.False - } else { - const result = left.anyOf.every((left: Types.TSchema) => Visit(left, right) !== StructuralResult.False) - return result ? StructuralResult.True : StructuralResult.False - } - } - - function Unknown(left: Types.TUnknown, right: Types.TSchema): StructuralResult { - if (TypeGuard.TUnion(right)) { - const result = right.anyOf.some((right: Types.TSchema) => TypeGuard.TAny(right) || TypeGuard.TUnknown(right)) - return result ? StructuralResult.True : StructuralResult.False - } else if (TypeGuard.TAny(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnknown(right)) { - return StructuralResult.True - } else { - return StructuralResult.False - } - } - - function Void(left: Types.TVoid, right: Types.TSchema): StructuralResult { - if (TypeGuard.TUnion(right)) { - const result = right.anyOf.some((right: Types.TSchema) => TypeGuard.TAny(right) || TypeGuard.TUnknown(right)) - return result ? StructuralResult.True : StructuralResult.False - } else if (TypeGuard.TAny(right)) { - return StructuralResult.True - } else if (TypeGuard.TUnknown(right)) { - return StructuralResult.True - } else if (TypeGuard.TVoid(right)) { - return StructuralResult.True - } else { - return StructuralResult.False - } - } - - let recursionDepth = 0 - function Visit(left: Left, right: Types.TSchema): StructuralResult { - recursionDepth += 1 - if (recursionDepth >= 1000) return StructuralResult.True - if (left.$id !== undefined) referenceMap.set(left.$id!, left) - if (right.$id !== undefined) referenceMap.set(right.$id!, right) - const resolvedRight = right[Types.Kind] === 'Self' ? referenceMap.get(right.$ref)! : right - if (TypeGuard.TAny(left)) { - return Any(left, resolvedRight) - } else if (TypeGuard.TArray(left)) { - return Array(left, resolvedRight) - } else if (TypeGuard.TBoolean(left)) { - return Boolean(left, resolvedRight) - } else if (TypeGuard.TConstructor(left)) { - return Constructor(left, resolvedRight) - } else if (TypeGuard.TDate(left)) { - return Date(left, resolvedRight) - } else if (TypeGuard.TFunction(left)) { - return Function(left, resolvedRight) - } else if (TypeGuard.TInteger(left)) { - return Integer(left, resolvedRight) - } else if (TypeGuard.TLiteral(left)) { - return Literal(left, resolvedRight) - } else if (TypeGuard.TNull(left)) { - return Null(left, resolvedRight) - } else if (TypeGuard.TNumber(left)) { - return Number(left, resolvedRight) - } else if (TypeGuard.TObject(left)) { - return Object(left, resolvedRight) - } else if (TypeGuard.TPromise(left)) { - return Promise(left, resolvedRight) - } else if (TypeGuard.TRecord(left)) { - return Record(left, resolvedRight) - } else if (TypeGuard.TRef(left)) { - return Ref(left, resolvedRight) - } else if (TypeGuard.TSelf(left)) { - return Self(left, resolvedRight) - } else if (TypeGuard.TString(left)) { - return String(left, resolvedRight) - } else if (TypeGuard.TTuple(left)) { - return Tuple(left, resolvedRight) - } else if (TypeGuard.TUndefined(left)) { - return Undefined(left, resolvedRight) - } else if (TypeGuard.TUint8Array(left)) { - return Uint8Array(left, resolvedRight) - } else if (TypeGuard.TUnion(left)) { - return Union(left, resolvedRight) - } else if (TypeGuard.TUnknown(left)) { - return Unknown(left, resolvedRight) - } else if (TypeGuard.TVoid(left)) { - return Void(left, resolvedRight) - } else if (TypeGuard.TUserDefined(left)) { - throw Error(`Structural: Cannot structurally compare custom type '${left[Types.Kind]}'`) - } else { - throw Error(`Structural: Unknown left operand '${left[Types.Kind]}'`) - } - } - - /** Structurally tests if the left schema extends the right. */ - export function Check(left: Types.TSchema, right: Types.TSchema): StructuralResult { - referenceMap.clear() - recursionDepth = 0 - return Visit(left, right) - } -} diff --git a/src/custom/custom.ts b/src/custom/custom.ts deleted file mode 100644 index f1d2cfc..0000000 --- a/src/custom/custom.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/custom - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -export type CustomValidationFunction = (schema: TSchema, value: unknown) => boolean - -/** Provides functions to create user defined types */ -export namespace Custom { - const customs = new Map>() - - /** Clears all user defined types */ - export function Clear() { - return customs.clear() - } - - /** Returns true if this user defined type exists */ - export function Has(kind: string) { - return customs.has(kind) - } - - /** Sets a validation function for a user defined type */ - export function Set(kind: string, func: CustomValidationFunction) { - customs.set(kind, func) - } - - /** Gets a custom validation function for a user defined type */ - export function Get(kind: string) { - return customs.get(kind) - } -} diff --git a/src/custom/index.ts b/src/custom/index.ts deleted file mode 100644 index b962088..0000000 --- a/src/custom/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/custom - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -export * from './custom' diff --git a/src/errors/errors.ts b/src/errors/errors.ts index 0cbf894..780b6d7 100644 --- a/src/errors/errors.ts +++ b/src/errors/errors.ts @@ -25,23 +25,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------------------------*/ - import * as Types from '../typebox' import { TypeSystem } from '../system/index' -import { TypeExtends } from '../guard/extends' -import { Format } from '../format/index' -import { Custom } from '../custom/index' -import { ValueHash } from '../hash/index' +import { ValueHash } from '../value/hash' // ------------------------------------------------------------------- // ValueErrorType // ------------------------------------------------------------------- - export enum ValueErrorType { Array, ArrayMinItems, ArrayMaxItems, ArrayUniqueItems, + BigInt, + BigIntMultipleOf, + BigIntExclusiveMinimum, + BigIntExclusiveMaximum, + BigIntMinimum, + BigIntMaximum, Boolean, Date, DateExclusiveMinimumTimestamp, @@ -55,8 +56,11 @@ export enum ValueErrorType { IntegerExclusiveMaximum, IntegerMinimum, IntegerMaximum, + Intersect, + IntersectUnevaluatedProperties, Literal, Never, + Not, Null, Number, NumberMultipleOf, @@ -78,6 +82,7 @@ export enum ValueErrorType { StringPattern, StringFormatUnknown, StringFormat, + Symbol, TupleZeroLength, TupleLength, Undefined, @@ -88,11 +93,9 @@ export enum ValueErrorType { Void, Custom, } - // ------------------------------------------------------------------- // ValueError // ------------------------------------------------------------------- - export interface ValueError { type: ValueErrorType schema: Types.TSchema @@ -100,33 +103,77 @@ export interface ValueError { value: unknown message: string } - +// ------------------------------------------------------------------- +// ValueErrorIterator +// ------------------------------------------------------------------- +export class ValueErrorIterator { + constructor(private readonly iterator: IterableIterator) {} + public [Symbol.iterator]() { + return this.iterator + } + /** Returns the first value error or undefined if no errors */ + public First(): ValueError | undefined { + const next = this.iterator.next() + return next.done ? undefined : next.value + } +} // ------------------------------------------------------------------- // ValueErrors // ------------------------------------------------------------------- - export class ValueErrorsUnknownTypeError extends Error { constructor(public readonly schema: Types.TSchema) { super('ValueErrors: Unknown type') } } - +export class ValueErrorsDereferenceError extends Error { + constructor(public readonly schema: Types.TRef | Types.TSelf) { + super(`ValueErrors: Unable to dereference schema with $id '${schema.$ref}'`) + } +} /** Provides functionality to generate a sequence of errors against a TypeBox type. */ export namespace ValueErrors { - function IsNumber(value: unknown): value is number { - return typeof value === 'number' && !isNaN(value) + // ---------------------------------------------------------------------- + // Guards + // ---------------------------------------------------------------------- + function IsObject(value: unknown): value is Record { + const result = typeof value === 'object' && value !== null + return TypeSystem.AllowArrayObjects ? result : result && !globalThis.Array.isArray(value) } - + function IsRecordObject(value: unknown): value is Record { + return IsObject(value) && !(value instanceof globalThis.Date) && !(value instanceof globalThis.Uint8Array) + } + function IsBigInt(value: unknown): value is bigint { + return typeof value === 'bigint' + } + function IsNumber(value: unknown): value is number { + const result = typeof value === 'number' + return TypeSystem.AllowNaN ? result : result && globalThis.Number.isFinite(value) + } + function IsInteger(value: unknown): value is number { + return globalThis.Number.isInteger(value) + } + function IsString(value: unknown): value is string { + return typeof value === 'string' + } + function IsVoid(value: unknown): value is void { + const result = value === undefined + return TypeSystem.AllowVoidNull ? result || value === null : result + } + function IsDefined(value: unknown): value is T { + return value !== undefined + } + // ---------------------------------------------------------------------- + // Types + // ---------------------------------------------------------------------- function* Any(schema: Types.TAny, references: Types.TSchema[], path: string, value: any): IterableIterator {} - function* Array(schema: Types.TArray, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!globalThis.Array.isArray(value)) { return yield { type: ValueErrorType.Array, schema, path, value, message: `Expected array` } } - if (IsNumber(schema.minItems) && !(value.length >= schema.minItems)) { + if (IsDefined(schema.minItems) && !(value.length >= schema.minItems)) { yield { type: ValueErrorType.ArrayMinItems, schema, path, value, message: `Expected array length to be greater or equal to ${schema.minItems}` } } - if (IsNumber(schema.maxItems) && !(value.length <= schema.maxItems)) { + if (IsDefined(schema.maxItems) && !(value.length <= schema.maxItems)) { yield { type: ValueErrorType.ArrayMinItems, schema, path, value, message: `Expected array length to be less or equal to ${schema.maxItems}` } } // prettier-ignore @@ -137,176 +184,204 @@ export namespace ValueErrors { yield* Visit(schema.items, references, `${path}/${i}`, value[i]) } } - + function* BigInt(schema: Types.TBigInt, references: Types.TSchema[], path: string, value: any): IterableIterator { + if (!IsBigInt(value)) { + return yield { type: ValueErrorType.BigInt, schema, path, value, message: `Expected bigint` } + } + if (IsDefined(schema.multipleOf) && !(value % schema.multipleOf === globalThis.BigInt(0))) { + yield { type: ValueErrorType.BigIntMultipleOf, schema, path, value, message: `Expected bigint to be a multiple of ${schema.multipleOf}` } + } + if (IsDefined(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { + yield { type: ValueErrorType.BigIntExclusiveMinimum, schema, path, value, message: `Expected bigint to be greater than ${schema.exclusiveMinimum}` } + } + if (IsDefined(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { + yield { type: ValueErrorType.BigIntExclusiveMaximum, schema, path, value, message: `Expected bigint to be less than ${schema.exclusiveMaximum}` } + } + if (IsDefined(schema.minimum) && !(value >= schema.minimum)) { + yield { type: ValueErrorType.BigIntMinimum, schema, path, value, message: `Expected bigint to be greater or equal to ${schema.minimum}` } + } + if (IsDefined(schema.maximum) && !(value <= schema.maximum)) { + yield { type: ValueErrorType.BigIntMaximum, schema, path, value, message: `Expected bigint to be less or equal to ${schema.maximum}` } + } + } function* Boolean(schema: Types.TBoolean, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!(typeof value === 'boolean')) { return yield { type: ValueErrorType.Boolean, schema, path, value, message: `Expected boolean` } } } - function* Constructor(schema: Types.TConstructor, references: Types.TSchema[], path: string, value: any): IterableIterator { yield* Visit(schema.returns, references, path, value.prototype) } - function* Date(schema: Types.TNumeric, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!(value instanceof globalThis.Date)) { return yield { type: ValueErrorType.Date, schema, path, value, message: `Expected Date object` } } - if (isNaN(value.getTime())) { + if (!globalThis.isFinite(value.getTime())) { return yield { type: ValueErrorType.Date, schema, path, value, message: `Invalid Date` } } - if (IsNumber(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) { + if (IsDefined(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) { yield { type: ValueErrorType.DateExclusiveMinimumTimestamp, schema, path, value, message: `Expected Date timestamp to be greater than ${schema.exclusiveMinimum}` } } - if (IsNumber(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) { + if (IsDefined(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) { yield { type: ValueErrorType.DateExclusiveMaximumTimestamp, schema, path, value, message: `Expected Date timestamp to be less than ${schema.exclusiveMaximum}` } } - if (IsNumber(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) { + if (IsDefined(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) { yield { type: ValueErrorType.DateMinimumTimestamp, schema, path, value, message: `Expected Date timestamp to be greater or equal to ${schema.minimum}` } } - if (IsNumber(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) { + if (IsDefined(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) { yield { type: ValueErrorType.DateMaximumTimestamp, schema, path, value, message: `Expected Date timestamp to be less or equal to ${schema.maximum}` } } } - function* Function(schema: Types.TFunction, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!(typeof value === 'function')) { return yield { type: ValueErrorType.Function, schema, path, value, message: `Expected function` } } } - function* Integer(schema: Types.TNumeric, references: Types.TSchema[], path: string, value: any): IterableIterator { - if (!(typeof value === 'number' && globalThis.Number.isInteger(value))) { + if (!IsInteger(value)) { return yield { type: ValueErrorType.Integer, schema, path, value, message: `Expected integer` } } - if (IsNumber(schema.multipleOf) && !(value % schema.multipleOf === 0)) { + if (IsDefined(schema.multipleOf) && !(value % schema.multipleOf === 0)) { yield { type: ValueErrorType.IntegerMultipleOf, schema, path, value, message: `Expected integer to be a multiple of ${schema.multipleOf}` } } - if (IsNumber(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { + if (IsDefined(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { yield { type: ValueErrorType.IntegerExclusiveMinimum, schema, path, value, message: `Expected integer to be greater than ${schema.exclusiveMinimum}` } } - if (IsNumber(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { + if (IsDefined(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { yield { type: ValueErrorType.IntegerExclusiveMaximum, schema, path, value, message: `Expected integer to be less than ${schema.exclusiveMaximum}` } } - if (IsNumber(schema.minimum) && !(value >= schema.minimum)) { + if (IsDefined(schema.minimum) && !(value >= schema.minimum)) { yield { type: ValueErrorType.IntegerMinimum, schema, path, value, message: `Expected integer to be greater or equal to ${schema.minimum}` } } - if (IsNumber(schema.maximum) && !(value <= schema.maximum)) { + if (IsDefined(schema.maximum) && !(value <= schema.maximum)) { yield { type: ValueErrorType.IntegerMaximum, schema, path, value, message: `Expected integer to be less or equal to ${schema.maximum}` } } } - + function* Intersect(schema: Types.TIntersect, references: Types.TSchema[], path: string, value: any): IterableIterator { + for (const subschema of schema.allOf) { + const next = Visit(subschema, references, path, value).next() + if (!next.done) { + yield next.value + yield { type: ValueErrorType.Intersect, schema, path, value, message: `Expected all sub schemas to be valid` } + return + } + } + if (schema.unevaluatedProperties === false) { + const schemaKeys = Types.KeyResolver.Resolve(schema) + const valueKeys = globalThis.Object.getOwnPropertyNames(value) + for (const valueKey of valueKeys) { + if (!schemaKeys.includes(valueKey)) { + yield { type: ValueErrorType.IntersectUnevaluatedProperties, schema, path: `${path}/${valueKey}`, value, message: `Unexpected property` } + } + } + } + if (typeof schema.unevaluatedProperties === 'object') { + const schemaKeys = Types.KeyResolver.Resolve(schema) + const valueKeys = globalThis.Object.getOwnPropertyNames(value) + for (const valueKey of valueKeys) { + if (!schemaKeys.includes(valueKey)) { + const next = Visit(schema.unevaluatedProperties, references, `${path}/${valueKey}`, value[valueKey]).next() + if (!next.done) { + yield next.value + yield { type: ValueErrorType.IntersectUnevaluatedProperties, schema, path: `${path}/${valueKey}`, value, message: `Invalid additional property` } + return + } + } + } + } + } function* Literal(schema: Types.TLiteral, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!(value === schema.const)) { const error = typeof schema.const === 'string' ? `'${schema.const}'` : schema.const return yield { type: ValueErrorType.Literal, schema, path, value, message: `Expected ${error}` } } } - function* Never(schema: Types.TNever, references: Types.TSchema[], path: string, value: any): IterableIterator { yield { type: ValueErrorType.Never, schema, path, value, message: `Value cannot be validated` } } - + function* Not(schema: Types.TNot, references: Types.TSchema[], path: string, value: any): IterableIterator { + if (Visit(schema.allOf[0].not, references, path, value).next().done === true) { + yield { type: ValueErrorType.Not, schema, path, value, message: `Value should not validate` } + } + yield* Visit(schema.allOf[1], references, path, value) + } function* Null(schema: Types.TNull, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!(value === null)) { return yield { type: ValueErrorType.Null, schema, path, value, message: `Expected null` } } } - function* Number(schema: Types.TNumeric, references: Types.TSchema[], path: string, value: any): IterableIterator { - if (TypeSystem.AllowNaN) { - if (!(typeof value === 'number')) { - return yield { type: ValueErrorType.Number, schema, path, value, message: `Expected number` } - } - } else { - if (!(typeof value === 'number' && !isNaN(value))) { - return yield { type: ValueErrorType.Number, schema, path, value, message: `Expected number` } - } + if (!IsNumber(value)) { + return yield { type: ValueErrorType.Number, schema, path, value, message: `Expected number` } } - if (IsNumber(schema.multipleOf) && !(value % schema.multipleOf === 0)) { + if (IsDefined(schema.multipleOf) && !(value % schema.multipleOf === 0)) { yield { type: ValueErrorType.NumberMultipleOf, schema, path, value, message: `Expected number to be a multiple of ${schema.multipleOf}` } } - if (IsNumber(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { + if (IsDefined(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { yield { type: ValueErrorType.NumberExclusiveMinimum, schema, path, value, message: `Expected number to be greater than ${schema.exclusiveMinimum}` } } - if (IsNumber(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { + if (IsDefined(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { yield { type: ValueErrorType.NumberExclusiveMaximum, schema, path, value, message: `Expected number to be less than ${schema.exclusiveMaximum}` } } - if (IsNumber(schema.minimum) && !(value >= schema.minimum)) { + if (IsDefined(schema.minimum) && !(value >= schema.minimum)) { yield { type: ValueErrorType.NumberMaximum, schema, path, value, message: `Expected number to be greater or equal to ${schema.minimum}` } } - if (IsNumber(schema.maximum) && !(value <= schema.maximum)) { + if (IsDefined(schema.maximum) && !(value <= schema.maximum)) { yield { type: ValueErrorType.NumberMinumum, schema, path, value, message: `Expected number to be less or equal to ${schema.maximum}` } } } - function* Object(schema: Types.TObject, references: Types.TSchema[], path: string, value: any): IterableIterator { - if (TypeSystem.AllowArrayObjects) { - if (!(typeof value === 'object' && value !== null)) { - return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` } - } - } else { - if (!(typeof value === 'object' && value !== null && !globalThis.Array.isArray(value))) { - return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` } - } + if (!IsObject(value)) { + return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` } } - if (IsNumber(schema.minProperties) && !(globalThis.Object.getOwnPropertyNames(value).length >= schema.minProperties)) { + if (IsDefined(schema.minProperties) && !(globalThis.Object.getOwnPropertyNames(value).length >= schema.minProperties)) { yield { type: ValueErrorType.ObjectMinProperties, schema, path, value, message: `Expected object to have at least ${schema.minProperties} properties` } } - if (IsNumber(schema.maxProperties) && !(globalThis.Object.getOwnPropertyNames(value).length <= schema.maxProperties)) { + if (IsDefined(schema.maxProperties) && !(globalThis.Object.getOwnPropertyNames(value).length <= schema.maxProperties)) { yield { type: ValueErrorType.ObjectMaxProperties, schema, path, value, message: `Expected object to have less than ${schema.minProperties} properties` } } - const propertyNames = globalThis.Object.getOwnPropertyNames(schema.properties) - if (schema.additionalProperties === false) { - for (const propertyName of globalThis.Object.getOwnPropertyNames(value)) { - if (!propertyNames.includes(propertyName)) { - yield { type: ValueErrorType.ObjectAdditionalProperties, schema, path: `${path}/${propertyName}`, value: value[propertyName], message: `Unexpected property` } + const requiredKeys = globalThis.Array.isArray(schema.required) ? schema.required : ([] as string[]) + const knownKeys = globalThis.Object.getOwnPropertyNames(schema.properties) + const unknownKeys = globalThis.Object.getOwnPropertyNames(value) + for (const knownKey of knownKeys) { + const property = schema.properties[knownKey] + if (schema.required && schema.required.includes(knownKey)) { + yield* Visit(property, references, `${path}/${knownKey}`, value[knownKey]) + if (Types.ExtendsUndefined.Check(schema) && !(knownKey in value)) { + yield { type: ValueErrorType.ObjectRequiredProperties, schema: property, path: `${path}/${knownKey}`, value: undefined, message: `Expected required property` } + } + } else { + if (knownKey in value) { + yield* Visit(property, references, `${path}/${knownKey}`, value[knownKey]) } } } - if (schema.required && schema.required.length > 0) { - const propertyNames = globalThis.Object.getOwnPropertyNames(value) - for (const requiredKey of schema.required) { - if (propertyNames.includes(requiredKey)) continue - yield { type: ValueErrorType.ObjectRequiredProperties, schema: schema.properties[requiredKey], path: `${path}/${requiredKey}`, value: undefined, message: `Expected required property` } + for (const requiredKey of requiredKeys) { + if (unknownKeys.includes(requiredKey)) continue + yield { type: ValueErrorType.ObjectRequiredProperties, schema: schema.properties[requiredKey], path: `${path}/${requiredKey}`, value: undefined, message: `Expected required property` } + } + if (schema.additionalProperties === false) { + for (const valueKey of unknownKeys) { + if (!knownKeys.includes(valueKey)) { + yield { type: ValueErrorType.ObjectAdditionalProperties, schema, path: `${path}/${valueKey}`, value: value[valueKey], message: `Unexpected property` } + } } } if (typeof schema.additionalProperties === 'object') { - for (const propertyName of globalThis.Object.getOwnPropertyNames(value)) { - if (propertyNames.includes(propertyName)) continue - yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${propertyName}`, value[propertyName]) - } - } - for (const propertyKey of propertyNames) { - const propertySchema = schema.properties[propertyKey] - if (schema.required && schema.required.includes(propertyKey)) { - yield* Visit(propertySchema, references, `${path}/${propertyKey}`, value[propertyKey]) - if (TypeExtends.Undefined(schema) && !(propertyKey in value)) { - yield { type: ValueErrorType.ObjectRequiredProperties, schema: propertySchema, path: `${path}/${propertyKey}`, value: undefined, message: `Expected required property` } - } - } else { - if (value[propertyKey] !== undefined) { - yield* Visit(propertySchema, references, `${path}/${propertyKey}`, value[propertyKey]) - } + for (const valueKey of unknownKeys) { + if (knownKeys.includes(valueKey)) continue + yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${valueKey}`, value[valueKey]) } } } - function* Promise(schema: Types.TPromise, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!(typeof value === 'object' && typeof value.then === 'function')) { yield { type: ValueErrorType.Promise, schema, path, value, message: `Expected Promise` } } } - function* Record(schema: Types.TRecord, references: Types.TSchema[], path: string, value: any): IterableIterator { - if (TypeSystem.AllowArrayObjects) { - if (!(typeof value === 'object' && value !== null && !(value instanceof globalThis.Date))) { - return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` } - } - } else { - if (!(typeof value === 'object' && value !== null && !(value instanceof globalThis.Date) && !globalThis.Array.isArray(value))) { - return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` } - } + if (!IsRecordObject(value)) { + return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected record object` } } const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0] const regex = new RegExp(keyPattern) @@ -320,27 +395,26 @@ export namespace ValueErrors { yield* Visit(valueSchema, references, `${path}/${propKey}`, propValue) } } - function* Ref(schema: Types.TRef, references: Types.TSchema[], path: string, value: any): IterableIterator { - const reference = references.find((reference) => reference.$id === schema.$ref) - if (reference === undefined) throw new Error(`ValueErrors.Ref: Cannot find schema with $id '${schema.$ref}'.`) - yield* Visit(reference, references, path, value) + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new ValueErrorsDereferenceError(schema) + const target = references[index] + yield* Visit(target, references, path, value) } - function* Self(schema: Types.TSelf, references: Types.TSchema[], path: string, value: any): IterableIterator { - const reference = references.find((reference) => reference.$id === schema.$ref) - if (reference === undefined) throw new Error(`ValueErrors.Self: Cannot find schema with $id '${schema.$ref}'.`) - yield* Visit(reference, references, path, value) + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new ValueErrorsDereferenceError(schema) + const target = references[index] + yield* Visit(target, references, path, value) } - function* String(schema: Types.TString, references: Types.TSchema[], path: string, value: any): IterableIterator { - if (!(typeof value === 'string')) { + if (!IsString(value)) { return yield { type: ValueErrorType.String, schema, path, value, message: 'Expected string' } } - if (IsNumber(schema.minLength) && !(value.length >= schema.minLength)) { + if (IsDefined(schema.minLength) && !(value.length >= schema.minLength)) { yield { type: ValueErrorType.StringMinLength, schema, path, value, message: `Expected string length greater or equal to ${schema.minLength}` } } - if (IsNumber(schema.maxLength) && !(value.length <= schema.maxLength)) { + if (IsDefined(schema.maxLength) && !(value.length <= schema.maxLength)) { yield { type: ValueErrorType.StringMaxLength, schema, path, value, message: `Expected string length less or equal to ${schema.maxLength}` } } if (schema.pattern !== undefined) { @@ -350,17 +424,21 @@ export namespace ValueErrors { } } if (schema.format !== undefined) { - if (!Format.Has(schema.format)) { + if (!Types.FormatRegistry.Has(schema.format)) { yield { type: ValueErrorType.StringFormatUnknown, schema, path, value, message: `Unknown string format '${schema.format}'` } } else { - const format = Format.Get(schema.format)! + const format = Types.FormatRegistry.Get(schema.format)! if (!format(value)) { yield { type: ValueErrorType.StringFormat, schema, path, value, message: `Expected string to match format '${schema.format}'` } } } } } - + function* Symbol(schema: Types.TSymbol, references: Types.TSchema[], path: string, value: any): IterableIterator { + if (!(typeof value === 'symbol')) { + return yield { type: ValueErrorType.Symbol, schema, path, value, message: 'Expected symbol' } + } + } function* Tuple(schema: Types.TTuple, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!globalThis.Array.isArray(value)) { return yield { type: ValueErrorType.Array, schema, path, value, message: 'Expected Array' } @@ -378,13 +456,11 @@ export namespace ValueErrors { yield* Visit(schema.items[i], references, `${path}/${i}`, value[i]) } } - function* Undefined(schema: Types.TUndefined, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!(value === undefined)) { yield { type: ValueErrorType.Undefined, schema, path, value, message: `Expected undefined` } } } - function* Union(schema: Types.TUnion, references: Types.TSchema[], path: string, value: any): IterableIterator { const errors: ValueError[] = [] for (const inner of schema.anyOf) { @@ -399,91 +475,94 @@ export namespace ValueErrors { yield { type: ValueErrorType.Union, schema, path, value, message: 'Expected value of union' } } } - function* Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[], path: string, value: any): IterableIterator { if (!(value instanceof globalThis.Uint8Array)) { return yield { type: ValueErrorType.Uint8Array, schema, path, value, message: `Expected Uint8Array` } } - if (IsNumber(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) { + if (IsDefined(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) { yield { type: ValueErrorType.Uint8ArrayMaxByteLength, schema, path, value, message: `Expected Uint8Array to have a byte length less or equal to ${schema.maxByteLength}` } } - if (IsNumber(schema.minByteLength) && !(value.length >= schema.minByteLength)) { + if (IsDefined(schema.minByteLength) && !(value.length >= schema.minByteLength)) { yield { type: ValueErrorType.Uint8ArrayMinByteLength, schema, path, value, message: `Expected Uint8Array to have a byte length greater or equal to ${schema.maxByteLength}` } } } - function* Unknown(schema: Types.TUnknown, references: Types.TSchema[], path: string, value: any): IterableIterator {} - function* Void(schema: Types.TVoid, references: Types.TSchema[], path: string, value: any): IterableIterator { - if (!(value === null)) { - return yield { type: ValueErrorType.Void, schema, path, value, message: `Expected null` } + if (!IsVoid(value)) { + return yield { type: ValueErrorType.Void, schema, path, value, message: `Expected void` } } } - function* UserDefined(schema: Types.TSchema, references: Types.TSchema[], path: string, value: any): IterableIterator { - const func = Custom.Get(schema[Types.Kind])! - if (!func(schema, value)) { + const check = Types.TypeRegistry.Get(schema[Types.Kind])! + if (!check(schema, value)) { return yield { type: ValueErrorType.Custom, schema, path, value, message: `Expected kind ${schema[Types.Kind]}` } } } - function* Visit(schema: T, references: Types.TSchema[], path: string, value: any): IterableIterator { - const anyReferences = schema.$id === undefined ? references : [schema, ...references] - const anySchema = schema as any - switch (anySchema[Types.Kind]) { + const references_ = IsDefined(schema.$id) ? [...references, schema] : references + const schema_ = schema as any + switch (schema_[Types.Kind]) { case 'Any': - return yield* Any(anySchema, anyReferences, path, value) + return yield* Any(schema_, references_, path, value) case 'Array': - return yield* Array(anySchema, anyReferences, path, value) + return yield* Array(schema_, references_, path, value) + case 'BigInt': + return yield* BigInt(schema_, references_, path, value) case 'Boolean': - return yield* Boolean(anySchema, anyReferences, path, value) + return yield* Boolean(schema_, references_, path, value) case 'Constructor': - return yield* Constructor(anySchema, anyReferences, path, value) + return yield* Constructor(schema_, references_, path, value) case 'Date': - return yield* Date(anySchema, anyReferences, path, value) + return yield* Date(schema_, references_, path, value) case 'Function': - return yield* Function(anySchema, anyReferences, path, value) + return yield* Function(schema_, references_, path, value) case 'Integer': - return yield* Integer(anySchema, anyReferences, path, value) + return yield* Integer(schema_, references_, path, value) + case 'Intersect': + return yield* Intersect(schema_, references_, path, value) case 'Literal': - return yield* Literal(anySchema, anyReferences, path, value) + return yield* Literal(schema_, references_, path, value) case 'Never': - return yield* Never(anySchema, anyReferences, path, value) + return yield* Never(schema_, references_, path, value) + case 'Not': + return yield* Not(schema_, references_, path, value) case 'Null': - return yield* Null(anySchema, anyReferences, path, value) + return yield* Null(schema_, references_, path, value) case 'Number': - return yield* Number(anySchema, anyReferences, path, value) + return yield* Number(schema_, references_, path, value) case 'Object': - return yield* Object(anySchema, anyReferences, path, value) + return yield* Object(schema_, references_, path, value) case 'Promise': - return yield* Promise(anySchema, anyReferences, path, value) + return yield* Promise(schema_, references_, path, value) case 'Record': - return yield* Record(anySchema, anyReferences, path, value) + return yield* Record(schema_, references_, path, value) case 'Ref': - return yield* Ref(anySchema, anyReferences, path, value) + return yield* Ref(schema_, references_, path, value) case 'Self': - return yield* Self(anySchema, anyReferences, path, value) + return yield* Self(schema_, references_, path, value) case 'String': - return yield* String(anySchema, anyReferences, path, value) + return yield* String(schema_, references_, path, value) + case 'Symbol': + return yield* Symbol(schema_, references_, path, value) case 'Tuple': - return yield* Tuple(anySchema, anyReferences, path, value) + return yield* Tuple(schema_, references_, path, value) case 'Undefined': - return yield* Undefined(anySchema, anyReferences, path, value) + return yield* Undefined(schema_, references_, path, value) case 'Union': - return yield* Union(anySchema, anyReferences, path, value) + return yield* Union(schema_, references_, path, value) case 'Uint8Array': - return yield* Uint8Array(anySchema, anyReferences, path, value) + return yield* Uint8Array(schema_, references_, path, value) case 'Unknown': - return yield* Unknown(anySchema, anyReferences, path, value) + return yield* Unknown(schema_, references_, path, value) case 'Void': - return yield* Void(anySchema, anyReferences, path, value) + return yield* Void(schema_, references_, path, value) default: - if (!Custom.Has(anySchema[Types.Kind])) throw new ValueErrorsUnknownTypeError(schema) - return yield* UserDefined(anySchema, anyReferences, path, value) + if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueErrorsUnknownTypeError(schema) + return yield* UserDefined(schema_, references_, path, value) } } - - export function* Errors(schema: T, references: Types.TSchema[], value: any): IterableIterator { - yield* Visit(schema, references, '', value) + export function Errors(schema: T, references: Types.TSchema[], value: any): ValueErrorIterator { + const iterator = Visit(schema, references, '', value) + return new ValueErrorIterator(iterator) } } diff --git a/src/format/format.ts b/src/format/format.ts deleted file mode 100644 index 9bf3846..0000000 --- a/src/format/format.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/format - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -export type FormatValidationFunction = (value: string) => boolean - -/** Provides functions to create user defined string formats */ -export namespace Format { - const formats = new Map() - - /** Clears all user defined string formats */ - export function Clear() { - return formats.clear() - } - - /** Returns true if the user defined string format exists */ - export function Has(format: string) { - return formats.has(format) - } - - /** Sets a validation function for a user defined string format */ - export function Set(format: string, func: FormatValidationFunction) { - formats.set(format, func) - } - - /** Gets a validation function for a user defined string format */ - export function Get(format: string) { - return formats.get(format) - } -} diff --git a/src/format/index.ts b/src/format/index.ts deleted file mode 100644 index 58e298c..0000000 --- a/src/format/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/format - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -export * from './format' diff --git a/src/guard/extends.ts b/src/guard/extends.ts deleted file mode 100644 index 441a87d..0000000 --- a/src/guard/extends.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/guard - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, dTribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import * as Types from '../typebox' - -export namespace TypeExtends { - /** - * This function returns true if the given schema is undefined, either directly or - * through union composition. This check is required on object property types of - * undefined, where an additional `'x' in value` check is required to determine - * the keys existence. - */ - export function Undefined(schema: Types.TSchema): boolean { - if (schema[Types.Kind] === 'Undefined') return true - if (schema[Types.Kind] === 'Union') { - const union = schema as Types.TUnion - return union.anyOf.some((schema) => Undefined(schema)) - } - return false - } -} diff --git a/src/guard/guard.ts b/src/guard/guard.ts deleted file mode 100644 index 87d4bc1..0000000 --- a/src/guard/guard.ts +++ /dev/null @@ -1,487 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/guard - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, dTribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -import { Custom } from '../custom/index' -import * as Types from '../typebox' - -export class TypeGuardUnknownTypeError extends Error { - constructor(public readonly schema: unknown) { - super('TypeGuard: Unknown type') - } -} - -/** Provides functionality to test if values are TypeBox types */ -export namespace TypeGuard { - function IsObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) - } - - function IsArray(value: unknown): value is any[] { - return typeof value === 'object' && value !== null && Array.isArray(value) - } - - function IsPattern(value: unknown): value is string { - try { - new RegExp(value as string) - return true - } catch { - return false - } - } - - function IsControlCharacterFree(value: unknown): value is string { - if (typeof value !== 'string') return false - for (let i = 0; i < value.length; i++) { - const code = value.charCodeAt(i) - if ((code >= 7 && code <= 13) || code === 27 || code === 127) { - return false - } - } - return true - } - - function IsString(value: unknown): value is string { - return typeof value === 'string' - } - - function IsNumber(value: unknown): value is number { - return typeof value === 'number' && !isNaN(value) - } - - function IsBoolean(value: unknown): value is boolean { - return typeof value === 'boolean' - } - - function IsOptionalNumber(value: unknown): value is number | undefined { - return value === undefined || (value !== undefined && IsNumber(value)) - } - - function IsOptionalBoolean(value: unknown): value is boolean | undefined { - return value === undefined || (value !== undefined && IsBoolean(value)) - } - - function IsOptionalString(value: unknown): value is string | undefined { - return value === undefined || (value !== undefined && IsString(value)) - } - - function IsOptionalPattern(value: unknown): value is string | undefined { - return value === undefined || (value !== undefined && IsString(value) && IsControlCharacterFree(value) && IsPattern(value)) - } - - function IsOptionalFormat(value: unknown): value is string | undefined { - return value === undefined || (value !== undefined && IsString(value) && IsControlCharacterFree(value)) - } - - function IsOptionalSchema(value: unknown): value is boolean | undefined { - return value === undefined || TSchema(value) - } - - /** Returns true if the given schema is TAny */ - export function TAny(schema: unknown): schema is Types.TAny { - return IsObject(schema) && schema[Types.Kind] === 'Any' && IsOptionalString(schema.$id) - } - - /** Returns true if the given schema is TArray */ - export function TArray(schema: unknown): schema is Types.TArray { - return ( - IsObject(schema) && - schema[Types.Kind] === 'Array' && - schema.type === 'array' && - IsOptionalString(schema.$id) && - TSchema(schema.items) && - IsOptionalNumber(schema.minItems) && - IsOptionalNumber(schema.maxItems) && - IsOptionalBoolean(schema.uniqueItems) - ) - } - - /** Returns true if the given schema is TBoolean */ - export function TBoolean(schema: unknown): schema is Types.TBoolean { - // prettier-ignore - return ( - IsObject(schema) && - schema[Types.Kind] === 'Boolean' && - schema.type === 'boolean' && - IsOptionalString(schema.$id) - ) - } - - /** Returns true if the given schema is TConstructor */ - export function TConstructor(schema: unknown): schema is Types.TConstructor { - // prettier-ignore - if (!( - IsObject(schema) && - schema[Types.Kind] === 'Constructor' && - schema.type === 'object' && - schema.instanceOf === 'Constructor' && - IsOptionalString(schema.$id) && - IsArray(schema.parameters) && - TSchema(schema.returns)) - ) { - return false - } - for (const parameter of schema.parameters) { - if (!TSchema(parameter)) return false - } - return true - } - - /** Returns true if the given schema is TDate */ - export function TDate(schema: unknown): schema is Types.TDate { - return ( - IsObject(schema) && - schema[Types.Kind] === 'Date' && - schema.type === 'object' && - schema.instanceOf === 'Date' && - IsOptionalString(schema.$id) && - IsOptionalNumber(schema.minimumTimestamp) && - IsOptionalNumber(schema.maximumTimestamp) && - IsOptionalNumber(schema.exclusiveMinimumTimestamp) && - IsOptionalNumber(schema.exclusiveMaximumTimestamp) - ) - } - - /** Returns true if the given schema is TFunction */ - export function TFunction(schema: unknown): schema is Types.TFunction { - // prettier-ignore - if (!( - IsObject(schema) && - schema[Types.Kind] === 'Function' && - schema.type === 'object' && - schema.instanceOf === 'Function' && - IsOptionalString(schema.$id) && - IsArray(schema.parameters) && - TSchema(schema.returns)) - ) { - return false - } - for (const parameter of schema.parameters) { - if (!TSchema(parameter)) return false - } - return true - } - - /** Returns true if the given schema is TInteger */ - export function TInteger(schema: unknown): schema is Types.TInteger { - return ( - IsObject(schema) && - schema[Types.Kind] === 'Integer' && - schema.type === 'integer' && - IsOptionalString(schema.$id) && - IsOptionalNumber(schema.multipleOf) && - IsOptionalNumber(schema.minimum) && - IsOptionalNumber(schema.maximum) && - IsOptionalNumber(schema.exclusiveMinimum) && - IsOptionalNumber(schema.exclusiveMaximum) - ) - } - - /** Returns true if the given schema is TLiteral */ - export function TLiteral(schema: unknown): schema is Types.TLiteral { - // prettier-ignore - return ( - IsObject(schema) && - schema[Types.Kind] === 'Literal' && - IsOptionalString(schema.$id) && - ( - IsString(schema.const) || - IsNumber(schema.const) || - IsBoolean(schema.const) - ) - ) - } - - /** Returns true if the given schema is TNever */ - export function TNever(schema: unknown): schema is Types.TNever { - return ( - IsObject(schema) && - schema[Types.Kind] === 'Never' && - IsArray(schema.allOf) && - schema.allOf.length === 2 && - IsObject(schema.allOf[0]) && - IsString(schema.allOf[0].type) && - schema.allOf[0].type === 'boolean' && - schema.allOf[0].const === false && - IsObject(schema.allOf[1]) && - IsString(schema.allOf[1].type) && - schema.allOf[1].type === 'boolean' && - schema.allOf[1].const === true - ) - } - - /** Returns true if the given schema is TNull */ - export function TNull(schema: unknown): schema is Types.TNull { - return IsObject(schema) && schema[Types.Kind] === 'Null' && schema.type === 'null' && IsOptionalString(schema.$id) - } - - /** Returns true if the given schema is TNumber */ - export function TNumber(schema: unknown): schema is Types.TNumber { - return ( - IsObject(schema) && - schema[Types.Kind] === 'Number' && - schema.type === 'number' && - IsOptionalString(schema.$id) && - IsOptionalNumber(schema.multipleOf) && - IsOptionalNumber(schema.minimum) && - IsOptionalNumber(schema.maximum) && - IsOptionalNumber(schema.exclusiveMinimum) && - IsOptionalNumber(schema.exclusiveMaximum) - ) - } - - /** Returns true if the given schema is TObject */ - export function TObject(schema: unknown): schema is Types.TObject { - if ( - !( - IsObject(schema) && - schema[Types.Kind] === 'Object' && - schema.type === 'object' && - IsOptionalString(schema.$id) && - IsObject(schema.properties) && - (IsOptionalBoolean(schema.additionalProperties) || IsOptionalSchema(schema.additionalProperties)) && - IsOptionalNumber(schema.minProperties) && - IsOptionalNumber(schema.maxProperties) - ) - ) { - return false - } - for (const [key, value] of Object.entries(schema.properties)) { - if (!IsControlCharacterFree(key)) return false - if (!TSchema(value)) return false - } - return true - } - - /** Returns true if the given schema is TPromise */ - export function TPromise(schema: unknown): schema is Types.TPromise { - // prettier-ignore - return ( - IsObject(schema) && - schema[Types.Kind] === 'Promise' && - schema.type === 'object' && - schema.instanceOf === 'Promise' && - IsOptionalString(schema.$id) && - TSchema(schema.item) - ) - } - - /** Returns true if the given schema is TRecord */ - export function TRecord(schema: unknown): schema is Types.TRecord { - // prettier-ignore - if (!( - IsObject(schema) && - schema[Types.Kind] === 'Record' && - schema.type === 'object' && - IsOptionalString(schema.$id) && - schema.additionalProperties === false && - IsObject(schema.patternProperties)) - ) { - return false - } - const keys = Object.keys(schema.patternProperties) - if (keys.length !== 1) { - return false - } - if (!IsPattern(keys[0])) { - return false - } - if (!TSchema(schema.patternProperties[keys[0]])) { - return false - } - return true - } - - /** Returns true if the given schema is TSelf */ - export function TSelf(schema: unknown): schema is Types.TSelf { - // prettier-ignore - return ( - IsObject(schema) && - schema[Types.Kind] === 'Self' && - IsOptionalString(schema.$id) && - IsString(schema.$ref) - ) - } - - /** Returns true if the given schema is TRef */ - export function TRef(schema: unknown): schema is Types.TRef { - // prettier-ignore - return ( - IsObject(schema) && - schema[Types.Kind] === 'Ref' && - IsOptionalString(schema.$id) && - IsString(schema.$ref) - ) - } - - /** Returns true if the given schema is TString */ - export function TString(schema: unknown): schema is Types.TString { - return ( - IsObject(schema) && - schema[Types.Kind] === 'String' && - schema.type === 'string' && - IsOptionalString(schema.$id) && - IsOptionalNumber(schema.minLength) && - IsOptionalNumber(schema.maxLength) && - IsOptionalPattern(schema.pattern) && - IsOptionalFormat(schema.format) - ) - } - - /** Returns true if the given schema is TTuple */ - export function TTuple(schema: unknown): schema is Types.TTuple { - // prettier-ignore - if (!( - IsObject(schema) && - schema[Types.Kind] === 'Tuple' && - schema.type === 'array' && - IsOptionalString(schema.$id) && - IsNumber(schema.minItems) && - IsNumber(schema.maxItems) && - schema.minItems === schema.maxItems) - ) { - return false - } - if (schema.items === undefined && schema.additionalItems === undefined && schema.minItems === 0) { - return true - } - if (!IsArray(schema.items)) { - return false - } - for (const inner of schema.items) { - if (!TSchema(inner)) return false - } - return true - } - - /** Returns true if the given schema is TUndefined */ - export function TUndefined(schema: unknown): schema is Types.TUndefined { - // prettier-ignore - return ( - IsObject(schema) && - schema[Types.Kind] === 'Undefined' && - schema.type === 'null' && - schema.typeOf === 'Undefined' && - IsOptionalString(schema.$id) - ) - } - - /** Returns true if the given schema is TUnion */ - export function TUnion(schema: unknown): schema is Types.TUnion { - // prettier-ignore - if (!( - IsObject(schema) && - schema[Types.Kind] === 'Union' && - IsArray(schema.anyOf) && - IsOptionalString(schema.$id)) - ) { - return false - } - for (const inner of schema.anyOf) { - if (!TSchema(inner)) return false - } - return true - } - - /** Returns true if the given schema is TUint8Array */ - export function TUint8Array(schema: unknown): schema is Types.TUint8Array { - return ( - IsObject(schema) && - schema[Types.Kind] === 'Uint8Array' && - schema.type === 'object' && - IsOptionalString(schema.$id) && - schema.instanceOf === 'Uint8Array' && - IsOptionalNumber(schema.minByteLength) && - IsOptionalNumber(schema.maxByteLength) - ) - } - - /** Returns true if the given schema is TUnknown */ - export function TUnknown(schema: unknown): schema is Types.TUnknown { - // prettier-ignore - return ( - IsObject(schema) && - schema[Types.Kind] === 'Unknown' && - IsOptionalString(schema.$id) - ) - } - - /** Returns true if the given schema is TVoid */ - export function TVoid(schema: unknown): schema is Types.TVoid { - // prettier-ignore - return ( - IsObject(schema) && - schema[Types.Kind] === 'Void' && - schema.type === 'null' && - schema.typeOf === 'Void' && - IsOptionalString(schema.$id) - ) - } - - /** Returns true if the given schema is a registered user defined type */ - export function TUserDefined(schema: unknown): schema is Types.TSchema { - return IsObject(schema) && IsString(schema[Types.Kind]) && Custom.Has(schema[Types.Kind]) - } - - /** Returns true if the given schema is TSchema */ - export function TSchema(schema: unknown): schema is Types.TSchema { - return ( - TAny(schema) || - TArray(schema) || - TBoolean(schema) || - TConstructor(schema) || - TDate(schema) || - TFunction(schema) || - TInteger(schema) || - TLiteral(schema) || - TNever(schema) || - TNull(schema) || - TNumber(schema) || - TObject(schema) || - TPromise(schema) || - TRecord(schema) || - TSelf(schema) || - TRef(schema) || - TString(schema) || - TTuple(schema) || - TUndefined(schema) || - TUnion(schema) || - TUint8Array(schema) || - TUnknown(schema) || - TVoid(schema) || - TUserDefined(schema) - ) - } - - /** Asserts if this schema and associated references are valid. */ - export function Assert(schema: T, references: Types.TSchema[] = []) { - if (!TSchema(schema)) throw new TypeGuardUnknownTypeError(schema) - for (const schema of references) { - if (!TSchema(schema)) throw new TypeGuardUnknownTypeError(schema) - } - } -} diff --git a/src/guard/index.ts b/src/guard/index.ts deleted file mode 100644 index 0364195..0000000 --- a/src/guard/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/guards - -The MIT License (MIT) - -Copyright (c) 2017-2023 Haydn Paterson (sinclair) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----------------------------------------------------------------------------*/ - -export * from './guard' -export * from './extends' diff --git a/src/system/system.ts b/src/system/system.ts index 1a637f8..598f6cc 100644 --- a/src/system/system.ts +++ b/src/system/system.ts @@ -26,40 +26,47 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { Kind, Type } from '../typebox' -import { Custom } from '../custom/index' -import { Format } from '../format/index' +import * as Types from '../typebox' export class TypeSystemDuplicateTypeKind extends Error { constructor(kind: string) { - super(`Duplicate kind '${kind}' detected`) + super(`Duplicate type kind '${kind}' detected`) } } export class TypeSystemDuplicateFormat extends Error { constructor(kind: string) { - super(`Duplicate format '${kind}' detected`) + super(`Duplicate string format '${kind}' detected`) } } - /** Creates user defined types and formats and provides overrides for value checking behaviours */ export namespace TypeSystem { - /** Sets whether arrays should be treated as kinds of objects. The default is `false` */ + /** Sets whether arrays should be treated as a kind of objects. The default is `false` */ export let AllowArrayObjects: boolean = false - - /** Sets whether numeric checks should consider NaN a valid number type. The default is `false` */ + /** Sets whether `NaN` or `Infinity` should be treated as valid numeric values. The default is `false` */ export let AllowNaN: boolean = false - - /** Creates a custom type */ - export function CreateType(kind: string, callback: (options: Options, value: unknown) => boolean) { - if (Custom.Has(kind)) throw new TypeSystemDuplicateTypeKind(kind) - Custom.Set(kind, callback) - return (options: Partial = {}) => Type.Unsafe({ ...options, [Kind]: kind }) + /** Sets whether `null` should validate for void types. The default is `false` */ + export let AllowVoidNull: boolean = false + /** Creates a new type */ + export function Type(kind: string, check: (options: Options, value: unknown) => boolean) { + if (Types.TypeRegistry.Has(kind)) throw new TypeSystemDuplicateTypeKind(kind) + Types.TypeRegistry.Set(kind, check) + return (options: Partial = {}) => Types.Type.Unsafe({ ...options, [Types.Kind]: kind }) } - - /** Creates a custom string format */ - export function CreateFormat(format: string, callback: (value: string) => boolean) { - if (Format.Has(format)) throw new TypeSystemDuplicateFormat(format) - Format.Set(format, callback) - return callback + /** Creates a new string format */ + export function Format(format: F, check: (value: string) => boolean): F { + if (Types.FormatRegistry.Has(format)) throw new TypeSystemDuplicateFormat(format) + Types.FormatRegistry.Set(format, check) + return format + } + // ------------------------------------------------------------------------ + // Deprecated + // ------------------------------------------------------------------------ + /** @deprecated Use `TypeSystem.Type()` instead. */ + export function CreateType(kind: string, check: (options: Options, value: unknown) => boolean) { + return Type(kind, check) + } + /** @deprecated Use `TypeSystem.Format()` instead. */ + export function CreateFormat(format: F, check: (value: string) => boolean): F { + return Format(format, check) } } diff --git a/src/tsconfig.json b/src/tsconfig.json index 52cb522..c78307c 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../tsconfig.json", - "files": ["compiler/index.ts", "conditional/index.ts", "custom/index.ts", "format/index.ts", "guard/index.ts", "hash/index.ts", "value/index.ts", "typebox.ts"] + "files": ["compiler/index.ts", "errors/index.ts", "system/index.ts", "value/index.ts", "typebox.ts"] } diff --git a/src/typebox.ts b/src/typebox.ts index e5fb7ff..6c87969 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -27,26 +27,32 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ // -------------------------------------------------------------------------- -// Symbols +// Compositing Symbols // -------------------------------------------------------------------------- - -export const Kind = Symbol.for('TypeBox.Kind') -export const Hint = Symbol.for('TypeBox.Hint') export const Modifier = Symbol.for('TypeBox.Modifier') - +export const Hint = Symbol.for('TypeBox.Hint') +export const Kind = Symbol.for('TypeBox.Kind') +// -------------------------------------------------------------------------- +// Helpers +// -------------------------------------------------------------------------- +export type TupleToIntersect = T extends [infer I] ? I : T extends [infer I, ...infer R] ? I & TupleToIntersect : never +export type TupleToUnion = { [K in keyof T]: T[K] }[number] +export type UnionToIntersect = (U extends unknown ? (arg: U) => 0 : never) extends (arg: infer I) => 0 ? I : never +export type UnionLast = UnionToIntersect 0 : never> extends (x: infer L) => 0 ? L : never +export type UnionToTuple> = [U] extends [never] ? [] : [...UnionToTuple>, L] +export type Assert = T extends E ? T : never +export type Evaluate = T extends infer O ? { [K in keyof O]: O[K] } : never +export type Ensure = T extends infer U ? U : never // -------------------------------------------------------------------------- // Modifiers // -------------------------------------------------------------------------- - export type TModifier = TReadonlyOptional | TOptional | TReadonly export type TReadonly = T & { [Modifier]: 'Readonly' } export type TOptional = T & { [Modifier]: 'Optional' } export type TReadonlyOptional = T & { [Modifier]: 'ReadonlyOptional' } - // -------------------------------------------------------------------------- -// Schema +// TSchema // -------------------------------------------------------------------------- - export interface SchemaOptions { $schema?: string /** Id for this schema */ @@ -57,294 +63,309 @@ export interface SchemaOptions { description?: string /** Default value for this schema */ default?: any - /** Example values matching this schema. */ + /** Example values matching this schema */ examples?: any [prop: string]: any } - -export interface TSchema extends SchemaOptions { +export interface TKind { [Kind]: string - [Hint]?: string +} +export interface TSchema extends SchemaOptions, TKind { [Modifier]?: string + [Hint]?: string params: unknown[] static: unknown } - // -------------------------------------------------------------------------- // TAnySchema // -------------------------------------------------------------------------- - export type TAnySchema = | TSchema | TAny | TArray + | TBigInt | TBoolean | TConstructor | TDate | TEnum | TFunction | TInteger + | TIntersect | TLiteral + | TNot | TNull | TNumber | TObject | TPromise | TRecord - | TSelf | TRef + | TSelf | TString + | TSymbol | TTuple | TUndefined | TUnion | TUint8Array | TUnknown | TVoid - // -------------------------------------------------------------------------- // TNumeric // -------------------------------------------------------------------------- - -export interface NumericOptions extends SchemaOptions { - exclusiveMaximum?: number - exclusiveMinimum?: number - maximum?: number - minimum?: number - multipleOf?: number -} - export type TNumeric = TInteger | TNumber - +export interface NumericOptions extends SchemaOptions { + exclusiveMaximum?: N + exclusiveMinimum?: N + maximum?: N + minimum?: N + multipleOf?: N +} // -------------------------------------------------------------------------- -// Any +// TAny // -------------------------------------------------------------------------- - export interface TAny extends TSchema { [Kind]: 'Any' static: any } - // -------------------------------------------------------------------------- -// Array +// TArray // -------------------------------------------------------------------------- - export interface ArrayOptions extends SchemaOptions { uniqueItems?: boolean minItems?: number maxItems?: number } - export interface TArray extends TSchema, ArrayOptions { [Kind]: 'Array' - static: Array> + static: Static[] type: 'array' items: T } - // -------------------------------------------------------------------------- -// Boolean +// TBigInt +// -------------------------------------------------------------------------- +export interface TBigInt extends TSchema, NumericOptions { + [Kind]: 'BigInt' + static: bigint + type: 'null' + typeOf: 'BigInt' +} +// -------------------------------------------------------------------------- +// TBoolean // -------------------------------------------------------------------------- - export interface TBoolean extends TSchema { [Kind]: 'Boolean' static: boolean type: 'boolean' } - // -------------------------------------------------------------------------- -// Constructor +// TConstructorParameters // -------------------------------------------------------------------------- - export type TConstructorParameters> = TTuple - +// -------------------------------------------------------------------------- +// TInstanceType +// -------------------------------------------------------------------------- export type TInstanceType> = T['returns'] - -export type StaticContructorParameters = [...{ [K in keyof T]: T[K] extends TSchema ? Static : never }] - +// -------------------------------------------------------------------------- +// TComposite +// -------------------------------------------------------------------------- +export type TCompositeUnion = Ensure> +// note: we need to take the left and right as the accumulator is assigned for multiple composite property sets with missing properties. +export type TCompositeUnionLeft = { + [K in keyof T['properties']]: K extends keyof Acc ? TCompositeUnion : T['properties'][K] +} +export type TCompositeUnionRight = { + [K in keyof Acc]: K extends keyof T['properties'] ? TCompositeUnion : Acc[K] +} +export type TCompositeUnionObject = Evaluate & TCompositeUnionRight> +// prettier-ignore +export type TCompositeProperties = + T extends [...infer L, infer R] ? TCompositeProperties, TCompositeUnionObject, Acc>> : + T extends [] ? Acc : + never +export type TComposite = Ensure>> +// -------------------------------------------------------------------------- +// TConstructor +// -------------------------------------------------------------------------- +export type TConstructorParameterArray = [...{ [K in keyof T]: Static, P> }] export interface TConstructor extends TSchema { [Kind]: 'Constructor' - static: new (...param: StaticContructorParameters) => Static + static: new (...param: TConstructorParameterArray) => Static type: 'object' instanceOf: 'Constructor' parameters: T returns: U } - // -------------------------------------------------------------------------- -// Date +// TDate // -------------------------------------------------------------------------- - export interface DateOptions extends SchemaOptions { exclusiveMaximumTimestamp?: number exclusiveMinimumTimestamp?: number maximumTimestamp?: number minimumTimestamp?: number } - export interface TDate extends TSchema, DateOptions { [Kind]: 'Date' static: Date type: 'object' instanceOf: 'Date' } - // -------------------------------------------------------------------------- -// Enum +// TEnum // -------------------------------------------------------------------------- - export interface TEnumOption { type: 'number' | 'string' const: T } - +export type TEnumStatic> = T[keyof T] export interface TEnum = Record> extends TSchema { [Kind]: 'Union' - static: T[keyof T] + static: TEnumStatic anyOf: TLiteral[] } - // -------------------------------------------------------------------------- -// Function +// TExtends // -------------------------------------------------------------------------- - -export type TParameters = TTuple - -export type TReturnType = T['returns'] - -export type StaticFunctionParameters = [...{ [K in keyof T]: T[K] extends TSchema ? Static : never }] - +// prettier-ignore +export type TExtends = + (Static extends Static ? T : U) extends infer O ? + UnionToTuple extends [infer X, infer Y] ? TUnion<[Assert, Assert]> : Assert + : never +// -------------------------------------------------------------------------- +// TExclude +// -------------------------------------------------------------------------- +// prettier-ignore +export type TExcludeArray = Assert> extends Static ? never : T[K] +}[number]>, TSchema[]> extends infer R ? TUnionResult> : never +export type TExclude = T extends TUnion ? TExcludeArray : T extends U ? TNever : T +// -------------------------------------------------------------------------- +// TExtract +// -------------------------------------------------------------------------- +// prettier-ignore +export type TExtractArray = Assert> extends Static ? T[K] : never +}[number]>, TSchema[]> extends infer R ? TUnionResult> : never +export type TExtract = T extends TUnion ? TExtractArray : T extends U ? T : TNever +// -------------------------------------------------------------------------- +// TFunction +// -------------------------------------------------------------------------- +export type TFunctionParameters = [...{ [K in keyof T]: Static, P> }] export interface TFunction extends TSchema { [Kind]: 'Function' - static: (...param: StaticFunctionParameters) => Static + static: (...param: TFunctionParameters) => Static type: 'object' instanceOf: 'Function' parameters: T returns: U } - // -------------------------------------------------------------------------- -// Integer +// TInteger // -------------------------------------------------------------------------- - -export interface TInteger extends TSchema, NumericOptions { +export interface TInteger extends TSchema, NumericOptions { [Kind]: 'Integer' static: number type: 'integer' } - // -------------------------------------------------------------------------- -// Intersect +// TIntersect // -------------------------------------------------------------------------- - -export type IntersectReduce = T extends [infer A, ...infer B] ? IntersectReduce : I extends object ? I : {} - -// note: rename to IntersectStatic in next minor release -export type IntersectEvaluate = { [K in keyof T]: T[K] extends TSchema ? Static : never } - -export type IntersectProperties = { - [K in keyof T]: T[K] extends TObject ? P : {} +export type TUnevaluatedProperties = undefined | TSchema | boolean +export interface IntersectOptions extends SchemaOptions { + unevaluatedProperties?: TUnevaluatedProperties } - -export interface TIntersect extends TObject { - static: IntersectReduce> - properties: IntersectReduce> +export type TIntersectStatic = TupleToIntersect<{ [K in keyof T]: Static, P> }> +export interface TIntersect extends TSchema, IntersectOptions { + [Kind]: 'Intersect' + type?: 'object' + static: TIntersectStatic + allOf: [...T] } - // -------------------------------------------------------------------------- -// KeyOf +// TKeyOf // -------------------------------------------------------------------------- - -export type UnionToIntersect = (U extends unknown ? (arg: U) => 0 : never) extends (arg: infer I) => 0 ? I : never -export type UnionLast = UnionToIntersect 0 : never> extends (x: infer L) => 0 ? L : never -export type UnionToTuple> = [U] extends [never] ? [] : [...UnionToTuple>, L] -export type UnionStringLiteralToTuple = T extends TUnion ? { [I in keyof L]: L[I] extends TLiteral ? C : never } : never -export type UnionLiteralsFromObject = { [K in ObjectPropertyKeys]: TLiteral } extends infer R ? UnionToTuple : never - -// export type TKeyOf = { [K in ObjectPropertyKeys]: TLiteral } extends infer R ? UnionToTuple : never - -export interface TKeyOf extends TUnion> {} - +// prettier-ignore +export type TKeyOfTuple = { + [K in keyof Static]: TLiteral> +} extends infer U + ? UnionToTuple> // optional yields undefined keys + : never +// prettier-ignore +export type TKeyOf = ( + T extends TIntersect ? TKeyOfTuple : + T extends TUnion ? TKeyOfTuple : + T extends TObject ? TKeyOfTuple : + [] +) extends infer R ? TUnionResult> : never // -------------------------------------------------------------------------- -// Literal +// TLiteral // -------------------------------------------------------------------------- - -export type TLiteralValue = string | number | boolean - +export type TLiteralValue = string | number | boolean // | bigint - supported but variant disable due to potential numeric type conflicts export interface TLiteral extends TSchema { [Kind]: 'Literal' static: T const: T } - // -------------------------------------------------------------------------- -// Never +// TNever // -------------------------------------------------------------------------- - export interface TNever extends TSchema { [Kind]: 'Never' static: never allOf: [{ type: 'boolean'; const: false }, { type: 'boolean'; const: true }] } - // -------------------------------------------------------------------------- -// Null +// TNot +// -------------------------------------------------------------------------- +export type TNotStatic<_ extends TSchema = TSchema, T extends TSchema = TSchema> = Static +export interface TNot extends TSchema { + [Kind]: 'Not' + static: TNotStatic + allOf: [{ not: Not }, T] +} +// -------------------------------------------------------------------------- +// TNull // -------------------------------------------------------------------------- - export interface TNull extends TSchema { [Kind]: 'Null' static: null type: 'null' } - // -------------------------------------------------------------------------- -// Number +// TNumber // -------------------------------------------------------------------------- - -export interface TNumber extends TSchema, NumericOptions { +export interface TNumber extends TSchema, NumericOptions { [Kind]: 'Number' static: number type: 'number' } - // -------------------------------------------------------------------------- -// Object +// TObject // -------------------------------------------------------------------------- - export type ReadonlyOptionalPropertyKeys = { [K in keyof T]: T[K] extends TReadonlyOptional ? K : never }[keyof T] export type ReadonlyPropertyKeys = { [K in keyof T]: T[K] extends TReadonly ? K : never }[keyof T] export type OptionalPropertyKeys = { [K in keyof T]: T[K] extends TOptional ? K : never }[keyof T] export type RequiredPropertyKeys = keyof Omit | ReadonlyPropertyKeys | OptionalPropertyKeys> - // prettier-ignore -export type PropertiesReducer> = ( +export type PropertiesReducer> = Evaluate<( Readonly>>> & Readonly>> & Partial>> & Required>> -) extends infer O ? { [K in keyof O]: O[K] } : never - +)> // prettier-ignore -export type PropertiesReduce = PropertiesReducer +export type PropertiesReduce = PropertiesReducer }> - -export type TRecordProperties, T extends TSchema> = Static extends string ? { [X in Static]: T } : never - -export interface TProperties { - [key: string]: TSchema -} - +export type TProperties = Record export type ObjectProperties = T extends TObject ? U : never - export type ObjectPropertyKeys = T extends TObject ? keyof U : never - export type TAdditionalProperties = undefined | TSchema | boolean - export interface ObjectOptions extends SchemaOptions { additionalProperties?: TAdditionalProperties minProperties?: number maxProperties?: number } - export interface TObject extends TSchema, ObjectOptions { [Kind]: 'Object' static: PropertiesReduce @@ -353,117 +374,128 @@ export interface TObject extends TSchema, O properties: T required?: string[] } +// -------------------------------------------------------------------------- +// TOmit +// -------------------------------------------------------------------------- +export type TOmitArray = Assert<{ [K2 in keyof T]: TOmit, K> }, TSchema[]> +export type TOmitProperties = Evaluate, TProperties>> +// prettier-ignore +export type TOmit = + T extends TIntersect ? TIntersect> : + T extends TUnion ? TUnion> : + T extends TObject ? TObject> : + T +// -------------------------------------------------------------------------- +// TParameters +// -------------------------------------------------------------------------- +export type TParameters = TTuple +// -------------------------------------------------------------------------- +// TPartial +// -------------------------------------------------------------------------- +export type TPartialArray = Assert<{ [K in keyof T]: TPartial> }, TSchema[]> +// prettier-ignore +export type TPartialProperties = Evaluate ? TReadonlyOptional : + T[K] extends TReadonly ? TReadonlyOptional : + T[K] extends TOptional ? TOptional : + TOptional +}, TProperties>> +// prettier-ignore +export type TPartial = + T extends TIntersect ? TIntersect> : + T extends TUnion ? TUnion> : + T extends TObject ? TObject> : + T +// -------------------------------------------------------------------------- +// TPick +// -------------------------------------------------------------------------- +export type TPickArray = { [K2 in keyof T]: TPick, K> } +// Note the key K will overlap for varying TProperties gathered via recursive union and intersect traversal. Because of this, +// we need to extract only keys assignable to T on K2. This behavior is only required for Pick only. +// prettier-ignore +export type TPickProperties = + Pick, keyof T>> extends infer R ? ({ + [K in keyof R]: Assert extends TSchema ? R[K] : never + }): never +// prettier-ignore +export type TPick = + T extends TIntersect ? TIntersect> : + T extends TUnion ? TUnion> : + T extends TObject ? TObject> : + T // -------------------------------------------------------------------------- -// Omit +// TPromise // -------------------------------------------------------------------------- - -export interface TOmit[]> extends TObject, ObjectOptions { - static: Omit, Properties[number]> - properties: T extends TObject ? Omit : never -} - -// -------------------------------------------------------------------------- -// Partial -// -------------------------------------------------------------------------- - -export interface TPartial extends TObject { - static: Partial> - properties: { - // prettier-ignore - [K in keyof T['properties']]: - T['properties'][K] extends TReadonlyOptional ? TReadonlyOptional : - T['properties'][K] extends TReadonly ? TReadonlyOptional : - T['properties'][K] extends TOptional ? TOptional : - TOptional - } -} - -// -------------------------------------------------------------------------- -// Pick -// -------------------------------------------------------------------------- - -// export interface TPick[]> extends TObject, ObjectOptions { -// static: Pick, Properties[number]> -// properties: ObjectProperties -// } - -export type TPick[]> = TObject<{ - [K in Properties[number]]: T['properties'][K] -}> - -// -------------------------------------------------------------------------- -// Promise -// -------------------------------------------------------------------------- - +export type TPromiseStatic = Promise> export interface TPromise extends TSchema { [Kind]: 'Promise' - static: Promise> + static: TPromiseStatic type: 'object' instanceOf: 'Promise' item: TSchema } - // -------------------------------------------------------------------------- -// Record +// TRecord // -------------------------------------------------------------------------- - export type TRecordKey = TString | TNumeric | TUnion[]> - +export type TRecordPropertiesFromUnionLiteral[]>, T extends TSchema> = Static extends string ? { [X in Static]: T } : never +export type TRecordPropertiesFromLiteral, T extends TSchema> = Evaluate<{ [K2 in K['const']]: T }> +export type TRecordStatic = Record, Static> export interface TRecord extends TSchema { [Kind]: 'Record' - static: Record, Static> + static: TRecordStatic type: 'object' patternProperties: { [pattern: string]: T } additionalProperties: false } - // -------------------------------------------------------------------------- -// Recursive +// TRecursive // -------------------------------------------------------------------------- - export interface TSelf extends TSchema { [Kind]: 'Self' static: this['params'][0] $ref: string } - export type TRecursiveReduce = Static]> - export interface TRecursive extends TSchema { static: TRecursiveReduce } - // -------------------------------------------------------------------------- -// Ref +// TRef // -------------------------------------------------------------------------- - +export type TRefStatic = Static export interface TRef extends TSchema { [Kind]: 'Ref' - static: Static + static: TRefStatic $ref: string } - // -------------------------------------------------------------------------- -// Required +// TReturnType // -------------------------------------------------------------------------- - -export interface TRequired> extends TObject { - static: Required> - properties: { - // prettier-ignore - [K in keyof T['properties']]: - T['properties'][K] extends TReadonlyOptional ? TReadonly : - T['properties'][K] extends TReadonly ? TReadonly : - T['properties'][K] extends TOptional ? U : - T['properties'][K] - } -} - +export type TReturnType = T['returns'] // -------------------------------------------------------------------------- -// String +// TRequired +// -------------------------------------------------------------------------- +export type TRequiredArray = Assert<{ [K in keyof T]: TRequired> }, TSchema[]> +// prettier-ignore +export type TRequiredProperties = Evaluate ? TReadonly : + T[K] extends TReadonly ? TReadonly : + T[K] extends TOptional ? U : + T[K] +}, TProperties>> +// prettier-ignore +export type TRequired = + T extends TIntersect ? TIntersect> : + T extends TUnion ? TUnion> : + T extends TObject ? TObject> : + T +// -------------------------------------------------------------------------- +// TString // -------------------------------------------------------------------------- - export type StringFormatOption = | 'date-time' | 'time' @@ -483,7 +515,6 @@ export type StringFormatOption = | 'json-pointer' | 'relative-json-pointer' | 'regex' - export interface StringOptions extends SchemaOptions { minLength?: number maxLength?: number @@ -492,529 +523,1689 @@ export interface StringOptions extends SchemaOptions { contentEncoding?: '7bit' | '8bit' | 'binary' | 'quoted-printable' | 'base64' contentMediaType?: string } - export interface TString extends TSchema, StringOptions { [Kind]: 'String' static: string type: 'string' } - // -------------------------------------------------------------------------- -// Tuple +// TSymbol // -------------------------------------------------------------------------- - -export type TupleToArray> = T extends TTuple ? R : never - +export type SymbolValue = string | number | undefined +export interface TSymbol extends TSchema, SchemaOptions { + [Kind]: 'Symbol' + static: symbol + type: 'null' + typeOf: 'Symbol' +} +// -------------------------------------------------------------------------- +// TTuple +// -------------------------------------------------------------------------- +export type TTupleIntoArray> = T extends TTuple ? Assert : never +export type TTupleStatic = { + [K in keyof T]: T[K] extends TSchema ? Static : T[K] +} export interface TTuple extends TSchema { [Kind]: 'Tuple' - static: { [K in keyof T]: T[K] extends TSchema ? Static : T[K] } + static: TTupleStatic type: 'array' items?: T additionalItems?: false minItems: number maxItems: number } - // -------------------------------------------------------------------------- -// Undefined +// TUndefined // -------------------------------------------------------------------------- - export interface TUndefined extends TSchema { [Kind]: 'Undefined' static: undefined type: 'null' typeOf: 'Undefined' } - // -------------------------------------------------------------------------- -// Union +// TUnionOfLiteral +// -------------------------------------------------------------------------- +export type TUnionOfLiteralArray[]> = { [K in keyof T]: Assert['const'] }[number] +export type TUnionOfLiteral[]>> = TUnionOfLiteralArray +// -------------------------------------------------------------------------- +// TUnionResult - Used by Extract, Exclude and KeyOf for normalized union unwrap +// -------------------------------------------------------------------------- +export type TUnionResult = T extends [] ? TNever : T extends [infer S] ? S : TUnion +// -------------------------------------------------------------------------- +// TUnion // -------------------------------------------------------------------------- - export interface TUnion extends TSchema { [Kind]: 'Union' static: { [K in keyof T]: T[K] extends TSchema ? Static : never }[number] anyOf: T } - -// ------------------------------------------------------------------------- -// Uint8Array -// ------------------------------------------------------------------------- - +// -------------------------------------------------------------------------- +// TUint8Array +// -------------------------------------------------------------------------- export interface Uint8ArrayOptions extends SchemaOptions { maxByteLength?: number minByteLength?: number } - export interface TUint8Array extends TSchema, Uint8ArrayOptions { [Kind]: 'Uint8Array' static: Uint8Array instanceOf: 'Uint8Array' type: 'object' } - // -------------------------------------------------------------------------- -// Unknown +// TUnknown // -------------------------------------------------------------------------- - export interface TUnknown extends TSchema { [Kind]: 'Unknown' static: unknown } - // -------------------------------------------------------------------------- -// Unsafe +// TUnsafe // -------------------------------------------------------------------------- - export interface UnsafeOptions extends SchemaOptions { [Kind]?: string } - export interface TUnsafe extends TSchema { [Kind]: string static: T } - // -------------------------------------------------------------------------- -// Void +// TVoid // -------------------------------------------------------------------------- - export interface TVoid extends TSchema { [Kind]: 'Void' static: void type: 'null' typeOf: 'Void' } - // -------------------------------------------------------------------------- // Static // -------------------------------------------------------------------------- - -/** Creates a static type from a TypeBox type */ +/** Creates a TypeScript static type from a TypeBox type */ export type Static = (T & { params: P })['static'] +// -------------------------------------------------------------------------- +// TypeRegistry +// -------------------------------------------------------------------------- +export type TypeRegistryValidationFunction = (schema: TSchema, value: unknown) => boolean +/** A registry for user defined types */ +export namespace TypeRegistry { + const map = new Map>() + /** Returns the entries in this registry */ + export function Entries() { + return new Map(map) + } + /** Clears all user defined types */ + export function Clear() { + return map.clear() + } + /** Returns true if this registry contains this kind */ + export function Has(kind: string) { + return map.has(kind) + } + /** Sets a validation function for a user defined type */ + export function Set(kind: string, func: TypeRegistryValidationFunction) { + map.set(kind, func) + } + /** Gets a custom validation function for a user defined type */ + export function Get(kind: string) { + return map.get(kind) + } +} +// -------------------------------------------------------------------------- +// TypeRegistry +// -------------------------------------------------------------------------- +export type FormatRegistryValidationFunction = (value: string) => boolean +/** A registry for user defined string formats */ +export namespace FormatRegistry { + const map = new Map() + /** Returns the entries in this registry */ + export function Entries() { + return new Map(map) + } + /** Clears all user defined string formats */ + export function Clear() { + return map.clear() + } + /** Returns true if the user defined string format exists */ + export function Has(format: string) { + return map.has(format) + } + /** Sets a validation function for a user defined string format */ + export function Set(format: string, func: FormatRegistryValidationFunction) { + map.set(format, func) + } + /** Gets a validation function for a user defined string format */ + export function Get(format: string) { + return map.get(format) + } +} +// -------------------------------------------------------------------------- +// TypeGuard +// -------------------------------------------------------------------------- +export class TypeGuardUnknownTypeError extends Error { + constructor(public readonly schema: unknown) { + super('TypeGuard: Unknown type') + } +} +/** Provides functions to test if JavaScript values are TypeBox types */ +export namespace TypeGuard { + function IsObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) + } + function IsArray(value: unknown): value is any[] { + return typeof value === 'object' && value !== null && Array.isArray(value) + } + function IsPattern(value: unknown): value is string { + try { + new RegExp(value as string) + return true + } catch { + return false + } + } + function IsControlCharacterFree(value: unknown): value is string { + if (typeof value !== 'string') return false + for (let i = 0; i < value.length; i++) { + const code = value.charCodeAt(i) + if ((code >= 7 && code <= 13) || code === 27 || code === 127) { + return false + } + } + return true + } + function IsBigInt(value: unknown): value is bigint { + return typeof value === 'bigint' + } + function IsString(value: unknown): value is string { + return typeof value === 'string' + } + function IsNumber(value: unknown): value is number { + return typeof value === 'number' && globalThis.Number.isFinite(value) + } + function IsBoolean(value: unknown): value is boolean { + return typeof value === 'boolean' + } + function IsOptionalBigInt(value: unknown): value is bigint | undefined { + return value === undefined || (value !== undefined && IsBigInt(value)) + } + function IsOptionalNumber(value: unknown): value is number | undefined { + return value === undefined || (value !== undefined && IsNumber(value)) + } + function IsOptionalBoolean(value: unknown): value is boolean | undefined { + return value === undefined || (value !== undefined && IsBoolean(value)) + } + function IsOptionalString(value: unknown): value is string | undefined { + return value === undefined || (value !== undefined && IsString(value)) + } + function IsOptionalPattern(value: unknown): value is string | undefined { + return value === undefined || (value !== undefined && IsString(value) && IsControlCharacterFree(value) && IsPattern(value)) + } + function IsOptionalFormat(value: unknown): value is string | undefined { + return value === undefined || (value !== undefined && IsString(value) && IsControlCharacterFree(value)) + } + function IsOptionalSchema(value: unknown): value is boolean | undefined { + return value === undefined || TSchema(value) + } + /** Returns true if the given schema is TAny */ + export function TAny(schema: unknown): schema is TAny { + return TKind(schema) && schema[Kind] === 'Any' && IsOptionalString(schema.$id) + } + /** Returns true if the given schema is TArray */ + export function TArray(schema: unknown): schema is TArray { + return ( + TKind(schema) && + schema[Kind] === 'Array' && + schema.type === 'array' && + IsOptionalString(schema.$id) && + TSchema(schema.items) && + IsOptionalNumber(schema.minItems) && + IsOptionalNumber(schema.maxItems) && + IsOptionalBoolean(schema.uniqueItems) + ) + } + /** Returns true if the given schema is TSymbol */ + export function TBigInt(schema: unknown): schema is TBigInt { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'BigInt' && + schema.type === 'null' && + schema.typeOf === 'BigInt' && + IsOptionalString(schema.$id) && + IsOptionalBigInt(schema.multipleOf) && + IsOptionalBigInt(schema.minimum) && + IsOptionalBigInt(schema.maximum) && + IsOptionalBigInt(schema.exclusiveMinimum) && + IsOptionalBigInt(schema.exclusiveMaximum) + ) + } + /** Returns true if the given schema is TBoolean */ + export function TBoolean(schema: unknown): schema is TBoolean { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Boolean' && + schema.type === 'boolean' && + IsOptionalString(schema.$id) + ) + } + /** Returns true if the given schema is TConstructor */ + export function TConstructor(schema: unknown): schema is TConstructor { + // prettier-ignore + if (!( + TKind(schema) && + schema[Kind] === 'Constructor' && + schema.type === 'object' && + schema.instanceOf === 'Constructor' && + IsOptionalString(schema.$id) && + IsArray(schema.parameters) && + TSchema(schema.returns)) + ) { + return false + } + for (const parameter of schema.parameters) { + if (!TSchema(parameter)) return false + } + return true + } + /** Returns true if the given schema is TDate */ + export function TDate(schema: unknown): schema is TDate { + return ( + TKind(schema) && + schema[Kind] === 'Date' && + schema.type === 'object' && + schema.instanceOf === 'Date' && + IsOptionalString(schema.$id) && + IsOptionalNumber(schema.minimumTimestamp) && + IsOptionalNumber(schema.maximumTimestamp) && + IsOptionalNumber(schema.exclusiveMinimumTimestamp) && + IsOptionalNumber(schema.exclusiveMaximumTimestamp) + ) + } + /** Returns true if the given schema is TFunction */ + export function TFunction(schema: unknown): schema is TFunction { + // prettier-ignore + if (!( + TKind(schema) && + schema[Kind] === 'Function' && + schema.type === 'object' && + schema.instanceOf === 'Function' && + IsOptionalString(schema.$id) && + IsArray(schema.parameters) && + TSchema(schema.returns)) + ) { + return false + } + for (const parameter of schema.parameters) { + if (!TSchema(parameter)) return false + } + return true + } + /** Returns true if the given schema is TInteger */ + export function TInteger(schema: unknown): schema is TInteger { + return ( + TKind(schema) && + schema[Kind] === 'Integer' && + schema.type === 'integer' && + IsOptionalString(schema.$id) && + IsOptionalNumber(schema.multipleOf) && + IsOptionalNumber(schema.minimum) && + IsOptionalNumber(schema.maximum) && + IsOptionalNumber(schema.exclusiveMinimum) && + IsOptionalNumber(schema.exclusiveMaximum) + ) + } + /** Returns true if the given schema is TIntersect */ + export function TIntersect(schema: unknown): schema is TIntersect { + // prettier-ignore + if (!( + TKind(schema) && + schema[Kind] === 'Intersect' && + IsArray(schema.allOf) && + IsOptionalString(schema.type) && + (IsOptionalBoolean(schema.unevaluatedProperties) || IsOptionalSchema(schema.unevaluatedProperties)) && + IsOptionalString(schema.$id)) + ) { + return false + } + if ('type' in schema && schema.type !== 'object') { + return false + } + for (const inner of schema.allOf) { + if (!TSchema(inner)) return false + } + return true + } + /** Returns true if the given schema is TKind */ + export function TKind(schema: unknown): schema is Record { + return IsObject(schema) && Kind in schema && typeof (schema as any)[Kind] === 'string' // TS 4.1.5: any required for symbol indexer + } + /** Returns true if the given schema is TLiteral */ + export function TLiteral(schema: unknown): schema is TLiteral { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Literal' && + IsOptionalString(schema.$id) && + ( + IsString(schema.const) || + IsNumber(schema.const) || + IsBoolean(schema.const) || + IsBigInt(schema.const) + ) + ) + } + /** Returns true if the given schema is TNever */ + export function TNever(schema: unknown): schema is TNever { + return TKind(schema) && schema[Kind] === 'Never' && IsObject(schema.not) && globalThis.Object.getOwnPropertyNames(schema.not).length === 0 + } + /** Returns true if the given schema is TNot */ + export function TNot(schema: unknown): schema is TNot { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Not' && + IsArray(schema.allOf) && + schema.allOf.length === 2 && + IsObject(schema.allOf[0]) && + TSchema(schema.allOf[0].not) && + TSchema(schema.allOf[1]) + ) + } + /** Returns true if the given schema is TNull */ + export function TNull(schema: unknown): schema is TNull { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Null' && + schema.type === 'null' && + IsOptionalString(schema.$id) + ) + } + /** Returns true if the given schema is TNumber */ + export function TNumber(schema: unknown): schema is TNumber { + return ( + TKind(schema) && + schema[Kind] === 'Number' && + schema.type === 'number' && + IsOptionalString(schema.$id) && + IsOptionalNumber(schema.multipleOf) && + IsOptionalNumber(schema.minimum) && + IsOptionalNumber(schema.maximum) && + IsOptionalNumber(schema.exclusiveMinimum) && + IsOptionalNumber(schema.exclusiveMaximum) + ) + } + /** Returns true if the given schema is TObject */ + export function TObject(schema: unknown): schema is TObject { + if ( + !( + TKind(schema) && + schema[Kind] === 'Object' && + schema.type === 'object' && + IsOptionalString(schema.$id) && + IsObject(schema.properties) && + (IsOptionalBoolean(schema.additionalProperties) || IsOptionalSchema(schema.additionalProperties)) && + IsOptionalNumber(schema.minProperties) && + IsOptionalNumber(schema.maxProperties) + ) + ) { + return false + } + for (const [key, value] of Object.entries(schema.properties)) { + if (!IsControlCharacterFree(key)) return false + if (!TSchema(value)) return false + } + return true + } + /** Returns true if the given schema is TPromise */ + export function TPromise(schema: unknown): schema is TPromise { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Promise' && + schema.type === 'object' && + schema.instanceOf === 'Promise' && + IsOptionalString(schema.$id) && + TSchema(schema.item) + ) + } + /** Returns true if the given schema is TRecord */ + export function TRecord(schema: unknown): schema is TRecord { + // prettier-ignore + if (!( + TKind(schema) && + schema[Kind] === 'Record' && + schema.type === 'object' && + IsOptionalString(schema.$id) && + schema.additionalProperties === false && + IsObject(schema.patternProperties)) + ) { + return false + } + const keys = Object.keys(schema.patternProperties) + if (keys.length !== 1) { + return false + } + if (!IsPattern(keys[0])) { + return false + } + if (!TSchema(schema.patternProperties[keys[0]])) { + return false + } + return true + } + /** Returns true if the given schema is TSelf */ + export function TSelf(schema: unknown): schema is TSelf { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Self' && + IsOptionalString(schema.$id) && + IsString(schema.$ref) + ) + } + /** Returns true if the given schema is TRef */ + export function TRef(schema: unknown): schema is TRef { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Ref' && + IsOptionalString(schema.$id) && + IsString(schema.$ref) + ) + } + /** Returns true if the given schema is TString */ + export function TString(schema: unknown): schema is TString { + return ( + TKind(schema) && + schema[Kind] === 'String' && + schema.type === 'string' && + IsOptionalString(schema.$id) && + IsOptionalNumber(schema.minLength) && + IsOptionalNumber(schema.maxLength) && + IsOptionalPattern(schema.pattern) && + IsOptionalFormat(schema.format) + ) + } + + /** Returns true if the given schema is TSymbol */ + export function TSymbol(schema: unknown): schema is TSymbol { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Symbol' && + schema.type === 'null' && + schema.typeOf === 'Symbol' && + IsOptionalString(schema.$id) + ) + } + + /** Returns true if the given schema is TTuple */ + export function TTuple(schema: unknown): schema is TTuple { + // prettier-ignore + if (!( + TKind(schema) && + schema[Kind] === 'Tuple' && + schema.type === 'array' && + IsOptionalString(schema.$id) && + IsNumber(schema.minItems) && + IsNumber(schema.maxItems) && + schema.minItems === schema.maxItems) + ) { + return false + } + if (schema.items === undefined && schema.additionalItems === undefined && schema.minItems === 0) { + return true + } + if (!IsArray(schema.items)) { + return false + } + for (const inner of schema.items) { + if (!TSchema(inner)) return false + } + return true + } + /** Returns true if the given schema is TUndefined */ + export function TUndefined(schema: unknown): schema is TUndefined { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Undefined' && + schema.type === 'null' && + schema.typeOf === 'Undefined' && + IsOptionalString(schema.$id) + ) + } + /** Returns true if the given schema is TUnion */ + export function TUnion(schema: unknown): schema is TUnion { + // prettier-ignore + if (!( + TKind(schema) && + schema[Kind] === 'Union' && + IsArray(schema.anyOf) && + IsOptionalString(schema.$id)) + ) { + return false + } + for (const inner of schema.anyOf) { + if (!TSchema(inner)) return false + } + return true + } + /** Returns true if the given schema is TUnion[]> */ + export function TUnionLiteral(schema: unknown): schema is TUnion[]> { + return TUnion(schema) && schema.anyOf.every((schema) => TLiteral(schema) && typeof schema.const === 'string') + } + + /** Returns true if the given schema is TUint8Array */ + export function TUint8Array(schema: unknown): schema is TUint8Array { + return TKind(schema) && schema[Kind] === 'Uint8Array' && schema.type === 'object' && IsOptionalString(schema.$id) && schema.instanceOf === 'Uint8Array' && IsOptionalNumber(schema.minByteLength) && IsOptionalNumber(schema.maxByteLength) + } + /** Returns true if the given schema is TUnknown */ + export function TUnknown(schema: unknown): schema is TUnknown { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Unknown' && + IsOptionalString(schema.$id) + ) + } + /** Returns true if the given schema is TVoid */ + export function TVoid(schema: unknown): schema is TVoid { + // prettier-ignore + return ( + TKind(schema) && + schema[Kind] === 'Void' && + schema.type === 'null' && + schema.typeOf === 'Void' && + IsOptionalString(schema.$id) + ) + } + /** Returns true if this schema has the ReadonlyOptional modifier */ + export function TReadonlyOptional(schema: T): schema is TReadonlyOptional { + return IsObject(schema) && schema[Modifier] === 'ReadonlyOptional' + } + /** Returns true if this schema has the Readonly modifier */ + export function TReadonly(schema: T): schema is TReadonly { + return IsObject(schema) && schema[Modifier] === 'Readonly' + } + /** Returns true if this schema has the Optional modifier */ + export function TOptional(schema: T): schema is TOptional { + return IsObject(schema) && schema[Modifier] === 'Optional' + } + /** Returns true if the given schema is TSchema */ + export function TSchema(schema: unknown): schema is TSchema { + return ( + typeof schema === 'object' && + (TAny(schema) || + TArray(schema) || + TBoolean(schema) || + TBigInt(schema) || + TConstructor(schema) || + TDate(schema) || + TFunction(schema) || + TInteger(schema) || + TIntersect(schema) || + TLiteral(schema) || + TNever(schema) || + TNot(schema) || + TNull(schema) || + TNumber(schema) || + TObject(schema) || + TPromise(schema) || + TRecord(schema) || + TSelf(schema) || + TRef(schema) || + TString(schema) || + TSymbol(schema) || + TTuple(schema) || + TUndefined(schema) || + TUnion(schema) || + TUint8Array(schema) || + TUnknown(schema) || + TVoid(schema) || + (TKind(schema) && TypeRegistry.Has(schema[Kind] as any))) + ) + } +} +// -------------------------------------------------------------------------- +// ExtendsUndefined +// -------------------------------------------------------------------------- +/** Fast undefined check used for properties of type undefined */ +export namespace ExtendsUndefined { + export function Check(schema: TSchema): boolean { + if (schema[Kind] === 'Undefined') return true + if (schema[Kind] === 'Union') { + const union = schema as TUnion + return union.anyOf.some((schema) => Check(schema)) + } + return false + } +} +// -------------------------------------------------------------------------- +// TypeExtends +// -------------------------------------------------------------------------- +export enum TypeExtendsResult { + Union, + True, + False, +} +export namespace TypeExtends { + // -------------------------------------------------------------------------- + // IntoBooleanResult + // -------------------------------------------------------------------------- + function IntoBooleanResult(result: TypeExtendsResult) { + return result === TypeExtendsResult.False ? TypeExtendsResult.False : TypeExtendsResult.True + } + // -------------------------------------------------------------------------- + // Any + // -------------------------------------------------------------------------- + function AnyRight(left: TSchema, right: TAny) { + return TypeExtendsResult.True + } + function Any(left: TAny, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right) && right.anyOf.some((schema) => TypeGuard.TAny(schema) || TypeGuard.TUnknown(schema))) return TypeExtendsResult.True + if (TypeGuard.TUnion(right)) return TypeExtendsResult.Union + if (TypeGuard.TUnknown(right)) return TypeExtendsResult.True + if (TypeGuard.TAny(right)) return TypeExtendsResult.True + return TypeExtendsResult.Union + } + // -------------------------------------------------------------------------- + // Array + // -------------------------------------------------------------------------- + function ArrayRight(left: TSchema, right: TArray) { + if (TypeGuard.TUnknown(left)) return TypeExtendsResult.False + if (TypeGuard.TAny(left)) return TypeExtendsResult.Union + if (TypeGuard.TNever(left)) return TypeExtendsResult.True + return TypeExtendsResult.False + } + function Array(left: TArray, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right) && IsObjectArrayLike(right)) return TypeExtendsResult.True + if (!TypeGuard.TArray(right)) return TypeExtendsResult.False + return IntoBooleanResult(Visit(left.items, right.items)) + } + // -------------------------------------------------------------------------- + // BigInt + // -------------------------------------------------------------------------- + function BigInt(left: TBigInt, right: TSchema): TypeExtendsResult { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TBigInt(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Boolean + // -------------------------------------------------------------------------- + function BooleanRight(left: TSchema, right: TBoolean) { + if (TypeGuard.TLiteral(left) && typeof left.const === 'boolean') return TypeExtendsResult.True + return TypeGuard.TBoolean(left) ? TypeExtendsResult.True : TypeExtendsResult.False + } + function Boolean(left: TBoolean, right: TSchema): TypeExtendsResult { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TBoolean(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Constructor + // -------------------------------------------------------------------------- + function Constructor(left: TConstructor, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (!TypeGuard.TConstructor(right)) return TypeExtendsResult.False + if (left.parameters.length > right.parameters.length) return TypeExtendsResult.False + if (!left.parameters.every((schema, index) => IntoBooleanResult(Visit(right.parameters[index], schema)) === TypeExtendsResult.True)) { + return TypeExtendsResult.False + } + return IntoBooleanResult(Visit(left.returns, right.returns)) + } + // -------------------------------------------------------------------------- + // Date + // -------------------------------------------------------------------------- + function Date(left: TDate, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TDate(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Function + // -------------------------------------------------------------------------- + function Function(left: TFunction, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (!TypeGuard.TFunction(right)) return TypeExtendsResult.False + if (left.parameters.length > right.parameters.length) return TypeExtendsResult.False + if (!left.parameters.every((schema, index) => IntoBooleanResult(Visit(right.parameters[index], schema)) === TypeExtendsResult.True)) { + return TypeExtendsResult.False + } + return IntoBooleanResult(Visit(left.returns, right.returns)) + } + // -------------------------------------------------------------------------- + // Integer + // -------------------------------------------------------------------------- + function IntegerRight(left: TSchema, right: TInteger) { + if (TypeGuard.TLiteral(left) && typeof left.const === 'number') return TypeExtendsResult.True + return TypeGuard.TNumber(left) || TypeGuard.TInteger(left) ? TypeExtendsResult.True : TypeExtendsResult.False + } + function Integer(left: TInteger, right: TSchema): TypeExtendsResult { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TInteger(right) || TypeGuard.TNumber(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Intersect + // -------------------------------------------------------------------------- + function IntersectRight(left: TSchema, right: TIntersect): TypeExtendsResult { + return right.allOf.every((schema) => Visit(left, schema) === TypeExtendsResult.True) ? TypeExtendsResult.True : TypeExtendsResult.False + } + function Intersect(left: TIntersect, right: TSchema) { + return left.allOf.some((schema) => Visit(schema, right) === TypeExtendsResult.True) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Literal + // -------------------------------------------------------------------------- + function IsLiteralString(schema: TLiteral) { + return typeof schema.const === 'string' + } + function IsLiteralNumber(schema: TLiteral) { + return typeof schema.const === 'number' + } + function IsLiteralBoolean(schema: TLiteral) { + return typeof schema.const === 'boolean' + } + function Literal(left: TLiteral, right: TSchema): TypeExtendsResult { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + if (TypeGuard.TString(right)) return StringRight(left, right) + if (TypeGuard.TNumber(right)) return NumberRight(left, right) + if (TypeGuard.TInteger(right)) return IntegerRight(left, right) + if (TypeGuard.TBoolean(right)) return BooleanRight(left, right) + return TypeGuard.TLiteral(right) && right.const === left.const ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Never + // -------------------------------------------------------------------------- + function NeverRight(left: TSchema, right: TNever) { + return TypeExtendsResult.False + } + function Never(left: TNever, right: TSchema) { + return TypeExtendsResult.True + } + // -------------------------------------------------------------------------- + // Null + // -------------------------------------------------------------------------- + function Null(left: TNull, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TNull(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Number + // -------------------------------------------------------------------------- + function NumberRight(left: TSchema, right: TNumber) { + if (TypeGuard.TLiteral(left) && IsLiteralNumber(left)) return TypeExtendsResult.True + return TypeGuard.TNumber(left) || TypeGuard.TInteger(left) ? TypeExtendsResult.True : TypeExtendsResult.False + } + function Number(left: TNumber, right: TSchema): TypeExtendsResult { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TInteger(right) || TypeGuard.TNumber(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Object + // -------------------------------------------------------------------------- + function IsObjectPropertyCount(schema: TObject, count: number) { + return globalThis.Object.keys(schema.properties).length === count + } + function IsObjectStringLike(schema: TObject) { + return IsObjectArrayLike(schema) + } + function IsObjectSymbolLike(schema: TObject) { + // prettier-ignore + return IsObjectPropertyCount(schema, 0) || ( + IsObjectPropertyCount(schema, 1) && 'description' in schema.properties && TypeGuard.TUnion(schema.properties.description) && schema.properties.description.anyOf.length === 2 && (( + TypeGuard.TString(schema.properties.description.anyOf[0]) && + TypeGuard.TUndefined(schema.properties.description.anyOf[1]) + ) || ( + TypeGuard.TString(schema.properties.description.anyOf[1]) && + TypeGuard.TUndefined(schema.properties.description.anyOf[0]) + )) + ) + } + function IsObjectNumberLike(schema: TObject) { + return IsObjectPropertyCount(schema, 0) + } + function IsObjectBooleanLike(schema: TObject) { + return IsObjectPropertyCount(schema, 0) + } + function IsObjectBigIntLike(schema: TObject) { + return IsObjectPropertyCount(schema, 0) + } + function IsObjectDateLike(schema: TObject) { + return IsObjectPropertyCount(schema, 0) + } + function IsObjectUint8ArrayLike(schema: TObject) { + return IsObjectArrayLike(schema) + } + function IsObjectFunctionLike(schema: TObject) { + const length = Type.Number() + return IsObjectPropertyCount(schema, 0) || (IsObjectPropertyCount(schema, 1) && 'length' in schema.properties && IntoBooleanResult(Visit(schema.properties['length'], length)) === TypeExtendsResult.True) + } + function IsObjectConstructorLike(schema: TObject) { + return IsObjectPropertyCount(schema, 0) + } + function IsObjectArrayLike(schema: TObject) { + const length = Type.Number() + return IsObjectPropertyCount(schema, 0) || (IsObjectPropertyCount(schema, 1) && 'length' in schema.properties && IntoBooleanResult(Visit(schema.properties['length'], length)) === TypeExtendsResult.True) + } + function IsObjectPromiseLike(schema: TObject) { + const then = Type.Function([Type.Any()], Type.Any()) + return IsObjectPropertyCount(schema, 0) || (IsObjectPropertyCount(schema, 1) && 'then' in schema.properties && IntoBooleanResult(Visit(schema.properties['then'], then)) === TypeExtendsResult.True) + } + // -------------------------------------------------------------------------- + // Property + // -------------------------------------------------------------------------- + function Property(left: TSchema, right: TSchema) { + if (Visit(left, right) === TypeExtendsResult.False) return TypeExtendsResult.False + if (TypeGuard.TOptional(left) && !TypeGuard.TOptional(right)) return TypeExtendsResult.False + return TypeExtendsResult.True + } + function ObjectRight(left: TSchema, right: TObject) { + if (TypeGuard.TUnknown(left)) return TypeExtendsResult.False + if (TypeGuard.TAny(left)) return TypeExtendsResult.Union + if (TypeGuard.TNever(left)) return TypeExtendsResult.True + if (TypeGuard.TLiteral(left) && IsLiteralString(left) && IsObjectStringLike(right)) return TypeExtendsResult.True + if (TypeGuard.TLiteral(left) && IsLiteralNumber(left) && IsObjectNumberLike(right)) return TypeExtendsResult.True + if (TypeGuard.TLiteral(left) && IsLiteralBoolean(left) && IsObjectBooleanLike(right)) return TypeExtendsResult.True + if (TypeGuard.TSymbol(left) && IsObjectSymbolLike(right)) return TypeExtendsResult.True + if (TypeGuard.TBigInt(left) && IsObjectBigIntLike(right)) return TypeExtendsResult.True + if (TypeGuard.TString(left) && IsObjectStringLike(right)) return TypeExtendsResult.True + if (TypeGuard.TSymbol(left) && IsObjectSymbolLike(right)) return TypeExtendsResult.True + if (TypeGuard.TNumber(left) && IsObjectNumberLike(right)) return TypeExtendsResult.True + if (TypeGuard.TInteger(left) && IsObjectNumberLike(right)) return TypeExtendsResult.True + if (TypeGuard.TBoolean(left) && IsObjectBooleanLike(right)) return TypeExtendsResult.True + if (TypeGuard.TUint8Array(left) && IsObjectUint8ArrayLike(right)) return TypeExtendsResult.True + if (TypeGuard.TDate(left) && IsObjectDateLike(right)) return TypeExtendsResult.True + if (TypeGuard.TConstructor(left) && IsObjectConstructorLike(right)) return TypeExtendsResult.True + if (TypeGuard.TFunction(left) && IsObjectFunctionLike(right)) return TypeExtendsResult.True + if (TypeGuard.TRecord(left) && TypeGuard.TString(RecordKey(left))) { + // When expressing a Record with literal key values, the Record is converted into a Object with + // the Hint assigned as `Record`. This is used to invert the extends logic. + return right[Hint] === 'Record' ? TypeExtendsResult.True : TypeExtendsResult.False + } + if (TypeGuard.TRecord(left) && TypeGuard.TNumber(RecordKey(left))) { + return IsObjectPropertyCount(right, 0) ? TypeExtendsResult.True : TypeExtendsResult.False + } + return TypeExtendsResult.False + } + function Object(left: TObject, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + if (!TypeGuard.TObject(right)) return TypeExtendsResult.False + for (const key of globalThis.Object.keys(right.properties)) { + if (!(key in left.properties)) return TypeExtendsResult.False + if (Property(left.properties[key], right.properties[key]) === TypeExtendsResult.False) { + return TypeExtendsResult.False + } + } + return TypeExtendsResult.True + } + // -------------------------------------------------------------------------- + // Promise + // -------------------------------------------------------------------------- + function Promise(left: TPromise, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right) && IsObjectPromiseLike(right)) return TypeExtendsResult.True + if (!TypeGuard.TPromise(right)) return TypeExtendsResult.False + return IntoBooleanResult(Visit(left.item, right.item)) + } + // -------------------------------------------------------------------------- + // Record + // -------------------------------------------------------------------------- + function RecordKey(schema: TRecord) { + if ('^(0|[1-9][0-9]*)$' in schema.patternProperties) return Type.Number() + if ('^.*$' in schema.patternProperties) return Type.String() + throw Error('TypeExtends: Cannot get record key') + } + function RecordValue(schema: TRecord) { + if ('^(0|[1-9][0-9]*)$' in schema.patternProperties) return schema.patternProperties['^(0|[1-9][0-9]*)$'] + if ('^.*$' in schema.patternProperties) return schema.patternProperties['^.*$'] + throw Error('TypeExtends: Cannot get record value') + } + function RecordRight(left: TSchema, right: TRecord) { + const Key = RecordKey(right) + const Value = RecordValue(right) + if (TypeGuard.TLiteral(left) && IsLiteralString(left) && TypeGuard.TNumber(Key) && IntoBooleanResult(Visit(left, Value)) === TypeExtendsResult.True) return TypeExtendsResult.True + if (TypeGuard.TUint8Array(left) && TypeGuard.TNumber(Key)) return Visit(left, Value) + if (TypeGuard.TString(left) && TypeGuard.TNumber(Key)) return Visit(left, Value) + if (TypeGuard.TArray(left) && TypeGuard.TNumber(Key)) return Visit(left, Value) + if (TypeGuard.TObject(left)) { + for (const key of globalThis.Object.keys(left.properties)) { + if (Property(Value, left.properties[key]) === TypeExtendsResult.False) { + return TypeExtendsResult.False + } + } + return TypeExtendsResult.True + } + return TypeExtendsResult.False + } + function Record(left: TRecord, right: TSchema) { + const Value = RecordValue(left) + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (!TypeGuard.TRecord(right)) return TypeExtendsResult.False + return Visit(Value, RecordValue(right)) + } + // -------------------------------------------------------------------------- + // String + // -------------------------------------------------------------------------- + function StringRight(left: TSchema, right: TString) { + if (TypeGuard.TLiteral(left) && typeof left.const === 'string') return TypeExtendsResult.True + return TypeGuard.TString(left) ? TypeExtendsResult.True : TypeExtendsResult.False + } + function String(left: TString, right: TSchema): TypeExtendsResult { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TString(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Symbol + // -------------------------------------------------------------------------- + function Symbol(left: TSymbol, right: TSchema): TypeExtendsResult { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TSymbol(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Tuple + // -------------------------------------------------------------------------- + function TupleRight(left: TSchema, right: TTuple) { + if (TypeGuard.TUnknown(left)) return TypeExtendsResult.False + if (TypeGuard.TAny(left)) return TypeExtendsResult.Union + if (TypeGuard.TNever(left)) return TypeExtendsResult.True + return TypeExtendsResult.False + } + function IsArrayOfTuple(left: TTuple, right: TSchema) { + return TypeGuard.TArray(right) && left.items !== undefined && left.items.every((schema) => Visit(schema, right.items) === TypeExtendsResult.True) + } + function Tuple(left: TTuple, right: TSchema): TypeExtendsResult { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right) && IsObjectArrayLike(right)) return TypeExtendsResult.True + if (TypeGuard.TArray(right) && IsArrayOfTuple(left, right)) return TypeExtendsResult.True + if (!TypeGuard.TTuple(right)) return TypeExtendsResult.False + if ((left.items === undefined && right.items !== undefined) || (left.items !== undefined && right.items === undefined)) return TypeExtendsResult.False + if (left.items === undefined && right.items === undefined) return TypeExtendsResult.True + return left.items!.every((schema, index) => Visit(schema, right.items![index]) === TypeExtendsResult.True) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Uint8Array + // -------------------------------------------------------------------------- + function Uint8Array(left: TUint8Array, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + return TypeGuard.TUint8Array(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Undefined + // -------------------------------------------------------------------------- + function Undefined(left: TUndefined, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TNever(right)) return NeverRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + if (TypeGuard.TRecord(right)) return RecordRight(left, right) + if (TypeGuard.TVoid(right)) return VoidRight(left, right) + return TypeGuard.TUndefined(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Union + // -------------------------------------------------------------------------- + function UnionRight(left: TSchema, right: TUnion): TypeExtendsResult { + return right.anyOf.some((schema) => Visit(left, schema) === TypeExtendsResult.True) ? TypeExtendsResult.True : TypeExtendsResult.False + } + function Union(left: TUnion, right: TSchema) { + return left.anyOf.every((schema) => Visit(schema, right) === TypeExtendsResult.True) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Unknown + // -------------------------------------------------------------------------- + function UnknownRight(left: TSchema, right: TUnknown) { + return TypeExtendsResult.True + } + function Unknown(left: TUnknown, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TString(right)) return StringRight(left, right) + if (TypeGuard.TNumber(right)) return NumberRight(left, right) + if (TypeGuard.TInteger(right)) return IntegerRight(left, right) + if (TypeGuard.TBoolean(right)) return BooleanRight(left, right) + if (TypeGuard.TArray(right)) return ArrayRight(left, right) + if (TypeGuard.TTuple(right)) return TupleRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + return TypeGuard.TUnknown(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + // -------------------------------------------------------------------------- + // Void + // -------------------------------------------------------------------------- + function VoidRight(left: TSchema, right: TVoid) { + if (TypeGuard.TUndefined(left)) return TypeExtendsResult.True + return TypeGuard.TUndefined(left) ? TypeExtendsResult.True : TypeExtendsResult.False + } + function Void(left: TVoid, right: TSchema) { + if (TypeGuard.TIntersect(right)) return IntersectRight(left, right) + if (TypeGuard.TUnion(right)) return UnionRight(left, right) + if (TypeGuard.TUnknown(right)) return UnknownRight(left, right) + if (TypeGuard.TAny(right)) return AnyRight(left, right) + if (TypeGuard.TObject(right)) return ObjectRight(left, right) + return TypeGuard.TVoid(right) ? TypeExtendsResult.True : TypeExtendsResult.False + } + function Visit(left: TSchema, right: TSchema): TypeExtendsResult { + if (TypeGuard.TAny(left)) return Any(left, right) + if (TypeGuard.TArray(left)) return Array(left, right) + if (TypeGuard.TBigInt(left)) return BigInt(left, right) + if (TypeGuard.TBoolean(left)) return Boolean(left, right) + if (TypeGuard.TConstructor(left)) return Constructor(left, right) + if (TypeGuard.TDate(left)) return Date(left, right) + if (TypeGuard.TFunction(left)) return Function(left, right) + if (TypeGuard.TInteger(left)) return Integer(left, right) + if (TypeGuard.TIntersect(left)) return Intersect(left, right) + if (TypeGuard.TLiteral(left)) return Literal(left, right) + if (TypeGuard.TNever(left)) return Never(left, right) + if (TypeGuard.TNull(left)) return Null(left, right) + if (TypeGuard.TNumber(left)) return Number(left, right) + if (TypeGuard.TRecord(left)) return Record(left, right) + if (TypeGuard.TString(left)) return String(left, right) + if (TypeGuard.TSymbol(left)) return Symbol(left, right) + if (TypeGuard.TObject(left)) return Object(left, right) + if (TypeGuard.TTuple(left)) return Tuple(left, right) + if (TypeGuard.TPromise(left)) return Promise(left, right) + if (TypeGuard.TUint8Array(left)) return Uint8Array(left, right) + if (TypeGuard.TUndefined(left)) return Undefined(left, right) + if (TypeGuard.TUnion(left)) return Union(left, right) + if (TypeGuard.TUnknown(left)) return Unknown(left, right) + if (TypeGuard.TVoid(left)) return Void(left, right) + throw Error(`TypeExtends: Unknown left type operand '${left[Kind]}'`) + } + export function Extends(left: TSchema, right: TSchema): TypeExtendsResult { + return Visit(left, right) + } +} +// -------------------------------------------------------------------------- +// TypeClone +// -------------------------------------------------------------------------- +/** Specialized Clone for Types */ +export namespace TypeClone { + function IsObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null + } + function IsArray(value: unknown): value is unknown[] { + return globalThis.Array.isArray(value) + } + function Array(value: unknown[]) { + return (value as any).map((value: unknown) => Visit(value as any)) + } + function Object(value: Record) { + const clonedProperties = globalThis.Object.getOwnPropertyNames(value).reduce((acc, key) => { + return { ...acc, [key]: Visit(value[key]) } + }, {}) + const clonedSymbols = globalThis.Object.getOwnPropertySymbols(value).reduce((acc, key) => { + return { ...acc, [key]: Visit(value[key as any]) } + }, {}) + return { ...clonedProperties, ...clonedSymbols } + } + function Visit(value: unknown): any { + if (IsArray(value)) return Array(value) + if (IsObject(value)) return Object(value) + return value + } + /** Clones a type. This function will omit non-self referential identifiers on the cloned type. */ + export function Clone(schema: T, options: SchemaOptions): T { + return { ...Visit(schema), ...options } + } +} +// -------------------------------------------------------------------------- +// ObjectMap +// -------------------------------------------------------------------------- +export namespace ObjectMap { + function Intersect(schema: TIntersect, callback: (object: TObject) => TObject) { + return Type.Intersect( + schema.allOf.map((inner) => Visit(inner, callback)), + { ...schema }, + ) + } + function Union(schema: TUnion, callback: (object: TObject) => TObject) { + return Type.Union( + schema.anyOf.map((inner) => Visit(inner, callback)), + { ...schema }, + ) + } + function Object(schema: TObject, callback: (object: TObject) => TObject) { + return callback(schema) + } + function Visit(schema: TSchema, callback: (object: TObject) => TObject): TSchema { + if (TypeGuard.TIntersect(schema)) return Intersect(schema, callback) + if (TypeGuard.TUnion(schema)) return Union(schema, callback) + if (TypeGuard.TObject(schema)) return Object(schema, callback) + return schema + } + export function Map(schema: TSchema, callback: (object: TObject) => TObject, options: SchemaOptions): T { + return { ...Visit(TypeClone.Clone(schema, {}), callback), ...options } as unknown as T + } +} +// -------------------------------------------------------------------------- +// KeyResolver +// -------------------------------------------------------------------------- +export namespace KeyResolver { + function IsKeyable(schema: TSchema) { + return TypeGuard.TIntersect(schema) || TypeGuard.TUnion(schema) || (TypeGuard.TObject(schema) && globalThis.Object.getOwnPropertyNames(schema.properties).length > 0) + } + function Intersect(schema: TIntersect) { + return [...schema.allOf.filter((schema) => IsKeyable(schema)).reduce((set, schema) => Visit(schema).map((key) => set.add(key))[0], new Set())] + } + function Union(schema: TUnion) { + const sets = schema.anyOf.filter((schema) => IsKeyable(schema)).map((inner) => Visit(inner)) + return [...sets.reduce((set, outer) => outer.map((key) => (sets.every((inner) => inner.includes(key)) ? set.add(key) : set))[0], new Set())] + } + function Object(schema: TObject) { + return globalThis.Object.keys(schema.properties) + } + function Visit(schema: TSchema): string[] { + if (TypeGuard.TIntersect(schema)) return Intersect(schema) + if (TypeGuard.TUnion(schema)) return Union(schema) + if (TypeGuard.TObject(schema)) return Object(schema) + return [] + } + export function Resolve(schema: T) { + return Visit(schema) + } +} +// -------------------------------------------------------------------------- +// TypeOrdinal: Used for auto $id generation +// -------------------------------------------------------------------------- +let TypeOrdinal = 0 // -------------------------------------------------------------------------- // TypeBuilder // -------------------------------------------------------------------------- - -let TypeOrdinal = 0 - export class TypeBuilder { - // ---------------------------------------------------------------------- + /** `[Utility]` Creates a schema without `static` and `params` types */ + protected Create(schema: Omit): T { + return schema as any + } + /** `[Standard]` Omits compositing symbols from this schema */ + public Strict(schema: T): T { + return JSON.parse(JSON.stringify(schema)) + } +} +// -------------------------------------------------------------------------- +// StandardTypeBuilder +// -------------------------------------------------------------------------- +export class StandardTypeBuilder extends TypeBuilder { + // ------------------------------------------------------------------------ // Modifiers - // ---------------------------------------------------------------------- - - /** Creates a readonly optional property */ - public ReadonlyOptional(item: T): TReadonlyOptional { - return { [Modifier]: 'ReadonlyOptional', ...item } + // ------------------------------------------------------------------------ + /** `[Modifier]` Creates a Optional property */ + public Optional(schema: T): TOptional { + return { [Modifier]: 'Optional', ...TypeClone.Clone(schema, {}) } } - - /** Creates a readonly property */ - public Readonly(item: T): TReadonly { - return { [Modifier]: 'Readonly', ...item } + /** `[Modifier]` Creates a ReadonlyOptional property */ + public ReadonlyOptional(schema: T): TReadonlyOptional { + return { [Modifier]: 'ReadonlyOptional', ...TypeClone.Clone(schema, {}) } } - - /** Creates a optional property */ - public Optional(item: T): TOptional { - return { [Modifier]: 'Optional', ...item } + /** `[Modifier]` Creates a Readonly object or property */ + public Readonly(schema: T): TReadonly { + return { [Modifier]: 'Readonly', ...schema } } - - // ---------------------------------------------------------------------- + // ------------------------------------------------------------------------ // Types - // ---------------------------------------------------------------------- - - /** `Standard` Creates a any type */ + // ------------------------------------------------------------------------ + /** `[Standard]` Creates an Any type */ public Any(options: SchemaOptions = {}): TAny { return this.Create({ ...options, [Kind]: 'Any' }) } - - /** `Standard` Creates a array type */ + /** `[Standard]` Creates an Array type */ public Array(items: T, options: ArrayOptions = {}): TArray { - return this.Create({ ...options, [Kind]: 'Array', type: 'array', items }) + return this.Create({ ...options, [Kind]: 'Array', type: 'array', items: TypeClone.Clone(items, {}) }) } - - /** `Standard` Creates a boolean type */ + /** `[Standard]` Creates a Boolean type */ public Boolean(options: SchemaOptions = {}): TBoolean { return this.Create({ ...options, [Kind]: 'Boolean', type: 'boolean' }) } - - /** `Extended` Creates a tuple type from this constructors parameters */ - public ConstructorParameters>(schema: T, options: SchemaOptions = {}): TConstructorParameters { - return this.Tuple([...schema.parameters], { ...options }) - } - - /** `Extended` Creates a constructor type */ - public Constructor, U extends TSchema>(parameters: T, returns: U, options?: SchemaOptions): TConstructor, U> - - /** `Extended` Creates a constructor type */ - public Constructor(parameters: [...T], returns: U, options?: SchemaOptions): TConstructor - - /** `Extended` Creates a constructor type */ - public Constructor(parameters: any, returns: any, options: SchemaOptions = {}) { - if (parameters[Kind] === 'Tuple') { - const inner = parameters.items === undefined ? [] : parameters.items - return this.Create({ ...options, [Kind]: 'Constructor', type: 'object', instanceOf: 'Constructor', parameters: inner, returns }) - } else if (globalThis.Array.isArray(parameters)) { - return this.Create({ ...options, [Kind]: 'Constructor', type: 'object', instanceOf: 'Constructor', parameters, returns }) - } else { - throw new Error('TypeBuilder.Constructor: Invalid parameters') + /** `[Standard]` Creates a Composite object type that will union any overlapping properties of the given object array */ + public Composite(schemas: [...T], options?: ObjectOptions): TComposite { + const properties = {} as TProperties + for (const object of schemas) { + for (const [key, property] of globalThis.Object.entries(object.properties)) { + properties[key] = key in properties ? this.Union([properties[key], property]) : TypeClone.Clone(property, {}) + } } + return this.Object(properties, options) as TComposite } - - /** `Extended` Creates a Date type */ - public Date(options: DateOptions = {}): TDate { - return this.Create({ ...options, [Kind]: 'Date', type: 'object', instanceOf: 'Date' }) - } - - /** `Standard` Creates a enum type */ + /** `[Standard]` Creates a Enum type */ public Enum>(item: T, options: SchemaOptions = {}): TEnum { - const values = Object.keys(item) - .filter((key) => isNaN(key as any)) - .map((key) => item[key]) as T[keyof T][] + // prettier-ignore + const values = globalThis.Object.keys(item).filter((key) => isNaN(key as any)).map((key) => item[key]) as T[keyof T][] const anyOf = values.map((value) => (typeof value === 'string' ? { [Kind]: 'Literal', type: 'string' as const, const: value } : { [Kind]: 'Literal', type: 'number' as const, const: value })) - return this.Create({ ...options, [Kind]: 'Union', [Hint]: 'Enum', anyOf }) + return this.Create({ ...options, [Kind]: 'Union', anyOf }) } - - /** `Extended` Creates a function type */ - public Function, U extends TSchema>(parameters: T, returns: U, options?: SchemaOptions): TFunction, U> - - /** `Extended` Creates a function type */ - public Function(parameters: [...T], returns: U, options?: SchemaOptions): TFunction - - /** `Extended` Creates a function type */ - public Function(parameters: any, returns: any, options: SchemaOptions = {}) { - if (parameters[Kind] === 'Tuple') { - const inner = parameters.items === undefined ? [] : parameters.items - return this.Create({ ...options, [Kind]: 'Function', type: 'object', instanceOf: 'Function', parameters: inner, returns }) - } else if (globalThis.Array.isArray(parameters)) { - return this.Create({ ...options, [Kind]: 'Function', type: 'object', instanceOf: 'Function', parameters, returns }) - } else { - throw new Error('TypeBuilder.Function: Invalid parameters') + /** `[Standard]` A conditional type expression that will return the true type if the left type extends the right */ + public Extends(left: L, right: R, trueType: T, falseType: U, options: SchemaOptions = {}): TExtends { + switch (TypeExtends.Extends(left, right)) { + case TypeExtendsResult.Union: + return this.Union([TypeClone.Clone(trueType, options), TypeClone.Clone(falseType, options)]) as any as TExtends + case TypeExtendsResult.True: + return TypeClone.Clone(trueType, options) as TExtends + case TypeExtendsResult.False: + return TypeClone.Clone(falseType, options) as TExtends } } - - /** `Extended` Creates a type from this constructors instance type */ - public InstanceType>(schema: T, options: SchemaOptions = {}): TInstanceType { - return { ...options, ...this.Clone(schema.returns) } + /** `[Standard]` Excludes from the left type any type that is not assignable to the right */ + public Exclude(left: L, right: R, options: SchemaOptions = {}): TExclude { + if (TypeGuard.TUnion(left)) { + const narrowed = left.anyOf.filter((inner) => TypeExtends.Extends(inner, right) === TypeExtendsResult.False) + return (narrowed.length === 1 ? TypeClone.Clone(narrowed[0], options) : this.Union(narrowed, options)) as TExclude + } else { + return (TypeExtends.Extends(left, right) !== TypeExtendsResult.False ? this.Never(options) : TypeClone.Clone(left, options)) as any + } } - - /** `Standard` Creates a integer type */ - public Integer(options: NumericOptions = {}): TInteger { + /** `[Standard]` Extracts from left left any type that is assignable to the right */ + public Extract(left: L, right: R, options: SchemaOptions = {}): TExtract { + if (TypeGuard.TUnion(left)) { + const narrowed = left.anyOf.filter((inner) => TypeExtends.Extends(inner, right) !== TypeExtendsResult.False) + return (narrowed.length === 1 ? TypeClone.Clone(narrowed[0], options) : this.Union(narrowed, options)) as TExtract + } else { + return (TypeExtends.Extends(left, right) !== TypeExtendsResult.False ? TypeClone.Clone(left, options) : this.Never(options)) as any + } + } + /** `[Standard]` Creates an Integer type */ + public Integer(options: NumericOptions = {}): TInteger { return this.Create({ ...options, [Kind]: 'Integer', type: 'integer' }) } - - /** `Standard` Creates a intersect type. */ - public Intersect(objects: [...T], options: ObjectOptions = {}): TIntersect { - const isOptional = (schema: TSchema) => (schema[Modifier] && schema[Modifier] === 'Optional') || schema[Modifier] === 'ReadonlyOptional' - const [required, optional] = [new Set(), new Set()] - for (const object of objects) { - for (const [key, schema] of Object.entries(object.properties)) { - if (isOptional(schema)) optional.add(key) - } - } - for (const object of objects) { - for (const key of Object.keys(object.properties)) { - if (!optional.has(key)) required.add(key) - } - } - const properties = {} as Record - for (const object of objects) { - for (const [key, schema] of Object.entries(object.properties)) { - properties[key] = properties[key] === undefined ? schema : { [Kind]: 'Union', anyOf: [properties[key], { ...schema }] } - } - } - if (required.size > 0) { - return this.Create({ ...options, [Kind]: 'Object', type: 'object', properties, required: [...required] }) + /** `[Standard]` Creates a Intersect type */ + public Intersect(allOf: [], options?: SchemaOptions): TNever + /** `[Standard]` Creates a Intersect type */ + public Intersect(allOf: [...T], options?: SchemaOptions): T[0] + // /** `[Standard]` Creates a Intersect type */ + public Intersect(allOf: [...T], options?: IntersectOptions): TIntersect + public Intersect(allOf: TSchema[], options: IntersectOptions = {}) { + if (allOf.length === 0) return Type.Never() + if (allOf.length === 1) return TypeClone.Clone(allOf[0], options) + const objects = allOf.every((schema) => TypeGuard.TObject(schema)) + const cloned = allOf.map((schema) => TypeClone.Clone(schema, {})) + const clonedUnevaluatedProperties = TypeGuard.TSchema(options.unevaluatedProperties) ? { unevaluatedProperties: TypeClone.Clone(options.unevaluatedProperties, {}) } : {} + if (options.unevaluatedProperties === false || TypeGuard.TSchema(options.unevaluatedProperties) || objects) { + return this.Create({ ...options, ...clonedUnevaluatedProperties, [Kind]: 'Intersect', type: 'object', allOf: cloned }) } else { - return this.Create({ ...options, [Kind]: 'Object', type: 'object', properties }) + return this.Create({ ...options, ...clonedUnevaluatedProperties, [Kind]: 'Intersect', allOf: cloned }) } } - - /** `Standard` Creates a keyof type */ - public KeyOf(object: T, options: SchemaOptions = {}): TKeyOf { - const items = Object.keys(object.properties).map((key) => this.Create({ ...options, [Kind]: 'Literal', type: 'string', const: key })) - return this.Create({ ...options, [Kind]: 'Union', [Hint]: 'KeyOf', anyOf: items }) + /** `[Standard]` Creates a KeyOf type */ + public KeyOf(schema: T, options: SchemaOptions = {}): TKeyOf { + const keys = KeyResolver.Resolve(schema) + // prettier-ignore + const keyof = keys.length === 0 ? this.Never(options) : this.Union(keys.map((key) => this.Literal(key)), options) + return keyof as TKeyOf } - - /** `Standard` Creates a literal type. */ + /** `[Standard]` Creates a Literal type */ public Literal(value: T, options: SchemaOptions = {}): TLiteral { return this.Create({ ...options, [Kind]: 'Literal', const: value, type: typeof value as 'string' | 'number' | 'boolean' }) } - - /** `Standard` Creates a never type */ + /** `[Standard]` Creates a Never type */ public Never(options: SchemaOptions = {}): TNever { - return this.Create({ - ...options, - [Kind]: 'Never', - allOf: [ - { type: 'boolean', const: false }, - { type: 'boolean', const: true }, - ], - }) + return this.Create({ ...options, [Kind]: 'Never', not: {} }) } - - /** `Standard` Creates a null type */ + /** `[Standard]` Creates a Not type. The first argument is the disallowed type, the second is the allowed. */ + public Not(not: N, schema: T, options?: SchemaOptions): TNot { + return this.Create({ ...options, [Kind]: 'Not', allOf: [{ not: TypeClone.Clone(not, {}) }, TypeClone.Clone(schema, {})] }) + } + /** `[Standard]` Creates a Null type */ public Null(options: SchemaOptions = {}): TNull { return this.Create({ ...options, [Kind]: 'Null', type: 'null' }) } - - /** `Standard` Creates a number type */ - public Number(options: NumericOptions = {}): TNumber { + /** `[Standard]` Creates a Number type */ + public Number(options: NumericOptions = {}): TNumber { return this.Create({ ...options, [Kind]: 'Number', type: 'number' }) } - - /** `Standard` Creates an object type */ + /** `[Standard]` Creates an Object type */ public Object(properties: T, options: ObjectOptions = {}): TObject { - const property_names = Object.keys(properties) - const optional = property_names.filter((name) => { - const property = properties[name] as TModifier - const modifier = property[Modifier] - return modifier && (modifier === 'Optional' || modifier === 'ReadonlyOptional') - }) - const required = property_names.filter((name) => !optional.includes(name)) - if (required.length > 0) { - return this.Create({ ...options, [Kind]: 'Object', type: 'object', properties, required }) + const propertyKeys = globalThis.Object.getOwnPropertyNames(properties) + const optionalKeys = propertyKeys.filter((key) => TypeGuard.TOptional(properties[key]) || TypeGuard.TReadonlyOptional(properties[key])) + const requiredKeys = propertyKeys.filter((name) => !optionalKeys.includes(name)) + const clonedAdditionalProperties = TypeGuard.TSchema(options.additionalProperties) ? { additionalProperties: TypeClone.Clone(options.additionalProperties, {}) } : {} + const clonedProperties = propertyKeys.reduce((acc, key) => ({ ...acc, [key]: TypeClone.Clone(properties[key], {}) }), {} as TProperties) + if (requiredKeys.length > 0) { + return this.Create({ ...options, ...clonedAdditionalProperties, [Kind]: 'Object', type: 'object', properties: clonedProperties, required: requiredKeys }) } else { - return this.Create({ ...options, [Kind]: 'Object', type: 'object', properties }) + return this.Create({ ...options, ...clonedAdditionalProperties, [Kind]: 'Object', type: 'object', properties: clonedProperties }) } } - - /** `Standard` Creates a new object type whose keys are omitted from the given source type */ - public Omit[]>>(schema: T, keys: K, options?: ObjectOptions): TOmit> - - /** `Standard` Creates a new object type whose keys are omitted from the given source type */ - public Omit[]>(schema: T, keys: readonly [...K], options?: ObjectOptions): TOmit - - /** `Standard` Creates a new object type whose keys are omitted from the given source type */ - public Omit(schema: any, keys: any, options: ObjectOptions = {}) { - const select: readonly string[] = keys[Kind] === 'Union' ? keys.anyOf.map((schema: TLiteral) => schema.const) : keys - const next = { ...this.Clone(schema), ...options, [Hint]: 'Omit' } - if (next.required) { - next.required = next.required.filter((key: string) => !select.includes(key as any)) - if (next.required.length === 0) delete next.required - } - for (const key of Object.keys(next.properties)) { - if (select.includes(key as any)) delete next.properties[key] - } - return this.Create(next) + /** `[Standard]` Creates a mapped type whose keys are omitted from the given type */ + public Omit)[]>(schema: T, keys: readonly [...K], options?: SchemaOptions): TOmit + /** `[Standard]` Creates a mapped type whose keys are omitted from the given type */ + public Omit[]>>(schema: T, keys: K, options?: SchemaOptions): TOmit> + /** `[Standard]` Creates a mapped type whose keys are omitted from the given type */ + public Omit>(schema: T, key: K, options?: SchemaOptions): TOmit + /** `[Standard]` Creates a mapped type whose keys are omitted from the given type */ + public Omit(schema: T, key: K, options?: SchemaOptions): TOmit + public Omit(schema: TSchema, unresolved: unknown, options: SchemaOptions = {}): any { + // prettier-ignore + const keys = + TypeGuard.TUnionLiteral(unresolved) ? unresolved.anyOf.map((schema) => schema.const) : + TypeGuard.TLiteral(unresolved) ? [unresolved.const] : + TypeGuard.TNever(unresolved) ? [] : + (unresolved as string[]) + // prettier-ignore + return ObjectMap.Map(TypeClone.Clone(schema, {}), (schema) => { + if (schema.required) { + schema.required = schema.required.filter((key: string) => !keys.includes(key as any)) + if (schema.required.length === 0) delete schema.required + } + for (const key of globalThis.Object.keys(schema.properties)) { + if (keys.includes(key as any)) delete schema.properties[key] + } + return this.Create(schema) + }, options) } - /** `Extended` Creates a tuple type from this functions parameters */ - public Parameters>(schema: T, options: SchemaOptions = {}): TParameters { - return Type.Tuple(schema.parameters, { ...options }) - } - - /** `Standard` Creates an object type whose properties are all optional */ - public Partial(schema: T, options: ObjectOptions = {}): TPartial { - const next = { ...this.Clone(schema), ...options, [Hint]: 'Partial' } - delete next.required - for (const key of Object.keys(next.properties)) { - const property = next.properties[key] - const modifer = property[Modifier] - switch (modifer) { - case 'ReadonlyOptional': - property[Modifier] = 'ReadonlyOptional' - break - case 'Readonly': - property[Modifier] = 'ReadonlyOptional' - break - case 'Optional': - property[Modifier] = 'Optional' - break - default: - property[Modifier] = 'Optional' - break + /** `[Standard]` Creates a mapped type where all properties are Optional */ + public Partial(schema: T, options: ObjectOptions = {}): TPartial { + function Apply(schema: TSchema) { + // prettier-ignore + switch (schema[Modifier]) { + case 'ReadonlyOptional': schema[Modifier] = 'ReadonlyOptional'; break; + case 'Readonly': schema[Modifier] = 'ReadonlyOptional'; break; + case 'Optional': schema[Modifier] = 'Optional'; break; + default: schema[Modifier] = 'Optional'; break; } } - return this.Create(next) + // prettier-ignore + return ObjectMap.Map>(TypeClone.Clone(schema, {}), (schema) => { + delete schema.required + globalThis.Object.keys(schema.properties).forEach(key => Apply(schema.properties[key])) + return schema + }, options) } - - /** `Standard` Creates a new object type whose keys are picked from the given source type */ - public Pick[]>>(schema: T, keys: K, options?: ObjectOptions): TPick> - - /** `Standard` Creates a new object type whose keys are picked from the given source type */ - public Pick[]>(schema: T, keys: readonly [...K], options?: ObjectOptions): TPick - - /** `Standard` Creates a new object type whose keys are picked from the given source type */ - public Pick[]>(schema: any, keys: any, options: ObjectOptions = {}) { - const select: readonly string[] = keys[Kind] === 'Union' ? keys.anyOf.map((schema: TLiteral) => schema.const) : keys - const next = { ...this.Clone(schema), ...options, [Hint]: 'Pick' } - if (next.required) { - next.required = next.required.filter((key: any) => select.includes(key)) - if (next.required.length === 0) delete next.required - } - for (const key of Object.keys(next.properties)) { - if (!select.includes(key as any)) delete next.properties[key] - } - return this.Create(next) + /** `[Standard]` Creates a mapped type whose keys are picked from the given type */ + public Pick)[]>(schema: T, keys: readonly [...K], options?: SchemaOptions): TPick + /** `[Standard]` Creates a mapped type whose keys are picked from the given type */ + public Pick[]>>(schema: T, keys: K, options?: SchemaOptions): TPick> + /** `[Standard]` Creates a mapped type whose keys are picked from the given type */ + public Pick>(schema: T, key: K, options?: SchemaOptions): TPick + /** `[Standard]` Creates a mapped type whose keys are picked from the given type */ + public Pick(schema: T, key: K, options?: SchemaOptions): TPick + public Pick(schema: TSchema, unresolved: unknown, options: SchemaOptions = {}): any { + // prettier-ignore + const keys = + TypeGuard.TUnionLiteral(unresolved) ? unresolved.anyOf.map((schema) => schema.const) : + TypeGuard.TLiteral(unresolved) ? [unresolved.const] : + TypeGuard.TNever(unresolved) ? [] : + (unresolved as string[]) + // prettier-ignore + return ObjectMap.Map(TypeClone.Clone(schema, {}), (schema) => { + if (schema.required) { + schema.required = schema.required.filter((key: any) => keys.includes(key)) + if (schema.required.length === 0) delete schema.required + } + for (const key of globalThis.Object.keys(schema.properties)) { + if (!keys.includes(key as any)) delete schema.properties[key] + } + return this.Create(schema) + }, options) } - - /** `Extended` Creates a Promise type */ - public Promise(item: T, options: SchemaOptions = {}): TPromise { - return this.Create({ ...options, [Kind]: 'Promise', type: 'object', instanceOf: 'Promise', item }) - } - - /** `Standard` Creates an object whose properties are derived from the given string literal union. */ - public Record, T extends TSchema>(key: K, schema: T, options?: ObjectOptions): TObject> - - /** `Standard` Creates a record type */ + /** `[Standard]` Creates an Object type from the given Literal Union */ + public Record[]>, T extends TSchema>(key: K, schema: T, options?: ObjectOptions): TObject> + /** `[Standard]` Creates an Object type from the given Literal Union */ + public Record, T extends TSchema>(key: K, schema: T, options?: ObjectOptions): TObject> + /** `[Standard]` Creates a Record type */ public Record(key: K, schema: T, options?: ObjectOptions): TRecord - - /** `Standard` Creates a record type */ - public Record(key: any, value: any, options: ObjectOptions = {}) { - // If string literal union return TObject with properties extracted from union. - if (key[Kind] === 'Union') { - return this.Object( - key.anyOf.reduce((acc: any, literal: any) => { - return { ...acc, [literal.const]: value } - }, {}), - { ...options, [Hint]: 'Record' }, - ) + public Record(key: any, schema: TSchema, options: ObjectOptions = {}) { + if (TypeGuard.TLiteral(key)) { + if (typeof key.const === 'string' || typeof key.const === 'number') { + return this.Object({ [key.const]: TypeClone.Clone(schema, {}) }, options) + } else throw Error('TypeBuilder: Record key can only be derived from literals of number or string') + } + if (TypeGuard.TUnion(key)) { + if (key.anyOf.every((schema) => TypeGuard.TLiteral(schema) && (typeof schema.const === 'string' || typeof schema.const === 'number'))) { + const properties = key.anyOf.reduce((acc: any, literal: any) => ({ ...acc, [literal.const]: TypeClone.Clone(schema, {}) }), {} as TProperties) + return this.Object(properties, { ...options, [Hint]: 'Record' }) + } else throw Error('TypeBuilder: Record key can only be derived from union literal of number or string') } - // otherwise return TRecord with patternProperties const pattern = ['Integer', 'Number'].includes(key[Kind]) ? '^(0|[1-9][0-9]*)$' : key[Kind] === 'String' && key.pattern ? key.pattern : '^.*$' return this.Create({ ...options, [Kind]: 'Record', type: 'object', - patternProperties: { [pattern]: value }, + patternProperties: { [pattern]: TypeClone.Clone(schema, {}) }, additionalProperties: false, }) } - - /** `Standard` Creates recursive type */ + /** `[Standard]` Creates a Recursive type */ public Recursive(callback: (self: TSelf) => T, options: SchemaOptions = {}): TRecursive { - if (options.$id === undefined) options.$id = `T${TypeOrdinal++}` + if (options.$id === undefined) (options as any).$id = `T${TypeOrdinal++}` const self = callback({ [Kind]: 'Self', $ref: `${options.$id}` } as any) self.$id = options.$id return this.Create({ ...options, ...self } as any) } - - /** `Standard` Creates a reference type. The referenced type must contain a $id. */ + /** `[Standard]` Creates a Ref type. The referenced type must contain a $id */ public Ref(schema: T, options: SchemaOptions = {}): TRef { - if (schema.$id === undefined) throw Error('TypeBuilder.Ref: Referenced schema must specify an $id') + if (schema.$id === undefined) throw Error('StandardTypeBuilder.Ref: Target type must specify an $id') return this.Create({ ...options, [Kind]: 'Ref', $ref: schema.$id! }) } - - /** `Standard` Creates a string type from a regular expression */ - public RegEx(regex: RegExp, options: SchemaOptions = {}): TString { - return this.Create({ ...options, [Kind]: 'String', type: 'string', pattern: regex.source }) - } - - /** `Standard` Creates an object type whose properties are all required */ - public Required(schema: T, options: SchemaOptions = {}): TRequired { - const next = { ...this.Clone(schema), ...options, [Hint]: 'Required' } - next.required = Object.keys(next.properties) - for (const key of Object.keys(next.properties)) { - const property = next.properties[key] - const modifier = property[Modifier] - switch (modifier) { - case 'ReadonlyOptional': - property[Modifier] = 'Readonly' - break - case 'Readonly': - property[Modifier] = 'Readonly' - break - case 'Optional': - delete property[Modifier] - break - default: - delete property[Modifier] - break + /** `[Standard]` Creates a mapped type where all properties are Required */ + public Required(schema: T, options: SchemaOptions = {}): TRequired { + function Apply(schema: TSchema) { + // prettier-ignore + switch (schema[Modifier]) { + case 'ReadonlyOptional': schema[Modifier] = 'Readonly'; break + case 'Readonly': schema[Modifier] = 'Readonly'; break + case 'Optional': delete schema[Modifier]; break + default: delete schema[Modifier]; break } } - return this.Create(next) + // prettier-ignore + return ObjectMap.Map>(TypeClone.Clone(schema, {}), (schema) => { + schema.required = globalThis.Object.keys(schema.properties) + globalThis.Object.keys(schema.properties).forEach(key => Apply(schema.properties[key])) + return schema + }, options) } - - /** `Extended` Creates a type from this functions return type */ - public ReturnType>(schema: T, options: SchemaOptions = {}): TReturnType { - return { ...options, ...this.Clone(schema.returns) } - } - - /** Removes Kind and Modifier symbol property keys from this schema */ - public Strict(schema: T): T { - return JSON.parse(JSON.stringify(schema)) - } - - /** `Standard` Creates a string type */ + /** `[Standard]` Creates a String type */ public String(options: StringOptions = {}): TString { return this.Create({ ...options, [Kind]: 'String', type: 'string' }) } - - /** `Standard` Creates a tuple type */ + /** `[Standard]` Creates a Tuple type */ public Tuple(items: [...T], options: SchemaOptions = {}): TTuple { - const additionalItems = false - const minItems = items.length - const maxItems = items.length - const schema = (items.length > 0 ? { ...options, [Kind]: 'Tuple', type: 'array', items, additionalItems, minItems, maxItems } : { ...options, [Kind]: 'Tuple', type: 'array', minItems, maxItems }) as any + const [additionalItems, minItems, maxItems] = [false, items.length, items.length] + const clonedItems = items.map((item) => TypeClone.Clone(item, {})) + // prettier-ignore + const schema = (items.length > 0 ? + { ...options, [Kind]: 'Tuple', type: 'array', items: clonedItems, additionalItems, minItems, maxItems } : + { ...options, [Kind]: 'Tuple', type: 'array', minItems, maxItems }) as any return this.Create(schema) } - - /** `Extended` Creates a undefined type */ - public Undefined(options: SchemaOptions = {}): TUndefined { - return this.Create({ ...options, [Kind]: 'Undefined', type: 'null', typeOf: 'Undefined' }) + /** `[Standard]` Creates a Union type */ + public Union(anyOf: [], options?: SchemaOptions): TNever + /** `[Standard]` Creates a Union type */ + public Union(anyOf: [...T], options?: SchemaOptions): T[0] + /** `[Standard]` Creates a Union type */ + public Union(anyOf: [...T], options?: SchemaOptions): TUnion + public Union(anyOf: TSchema[], options: SchemaOptions = {}) { + if (anyOf.length === 0) return this.Never(options) + if (anyOf.length === 1) return this.Create(TypeClone.Clone(anyOf[0], options)) + const clonedAnyOf = anyOf.map((schema) => TypeClone.Clone(schema, {})) + return this.Create({ ...options, [Kind]: 'Union', anyOf: clonedAnyOf }) } - - /** `Standard` Creates a union type */ - public Union(items: [], options?: SchemaOptions): TNever - - /** `Standard` Creates a union type */ - public Union(items: [...T], options?: SchemaOptions): TUnion - - /** `Standard` Creates a union type */ - public Union(items: [...T], options: SchemaOptions = {}) { - return items.length === 0 ? Type.Never({ ...options }) : this.Create({ ...options, [Kind]: 'Union', anyOf: items }) - } - - /** `Extended` Creates a Uint8Array type */ - public Uint8Array(options: Uint8ArrayOptions = {}): TUint8Array { - return this.Create({ ...options, [Kind]: 'Uint8Array', type: 'object', instanceOf: 'Uint8Array' }) - } - - /** `Standard` Creates an unknown type */ + /** `[Standard]` Creates an Unknown type */ public Unknown(options: SchemaOptions = {}): TUnknown { return this.Create({ ...options, [Kind]: 'Unknown' }) } - - /** `Standard` Creates a user defined schema that infers as type T */ + /** `[Standard]` Creates a Unsafe type that infers for the generic argument */ public Unsafe(options: UnsafeOptions = {}): TUnsafe { return this.Create({ ...options, [Kind]: options[Kind] || 'Unsafe' }) } - - /** `Extended` Creates a void type */ +} +// -------------------------------------------------------------------------- +// TypeBuilder +// -------------------------------------------------------------------------- +export class ExtendedTypeBuilder extends StandardTypeBuilder { + /** `[Extended]` Creates a BigInt type */ + public BigInt(options: NumericOptions = {}): TBigInt { + return this.Create({ ...options, [Kind]: 'BigInt', type: 'null', typeOf: 'BigInt' }) + } + /** `[Extended]` Extracts the ConstructorParameters from the given Constructor type */ + public ConstructorParameters>(schema: T, options: SchemaOptions = {}): TConstructorParameters { + return this.Tuple([...schema.parameters], { ...options }) + } + /** `[Extended]` Creates a Constructor type */ + public Constructor, U extends TSchema>(parameters: T, returns: U, options?: SchemaOptions): TConstructor, U> + /** `[Extended]` Creates a Constructor type */ + public Constructor(parameters: [...T], returns: U, options?: SchemaOptions): TConstructor + public Constructor(parameters: any, returns: any, options: SchemaOptions = {}) { + const clonedReturns = TypeClone.Clone(returns, {}) + if (TypeGuard.TTuple(parameters)) { + const clonedParameters = parameters.items === undefined ? [] : parameters.items.map((parameter) => TypeClone.Clone(parameter, {})) + return this.Create({ ...options, [Kind]: 'Constructor', type: 'object', instanceOf: 'Constructor', parameters: clonedParameters, returns: clonedReturns }) + } else if (globalThis.Array.isArray(parameters)) { + const clonedParameters = parameters.map((parameter) => TypeClone.Clone(parameter, {})) + return this.Create({ ...options, [Kind]: 'Constructor', type: 'object', instanceOf: 'Constructor', parameters: clonedParameters, returns: clonedReturns }) + } else { + throw new Error('ExtendedTypeBuilder.Constructor: Invalid parameters') + } + } + /** `[Extended]` Creates a Date type */ + public Date(options: DateOptions = {}): TDate { + return this.Create({ ...options, [Kind]: 'Date', type: 'object', instanceOf: 'Date' }) + } + /** `[Extended]` Creates a Function type */ + public Function, U extends TSchema>(parameters: T, returns: U, options?: SchemaOptions): TFunction, U> + /** `[Extended]` Creates a Function type */ + public Function(parameters: [...T], returns: U, options?: SchemaOptions): TFunction + public Function(parameters: any, returns: any, options: SchemaOptions = {}) { + const clonedReturns = TypeClone.Clone(returns, {}) + if (TypeGuard.TTuple(parameters)) { + const clonedParameters = parameters.items === undefined ? [] : parameters.items.map((parameter) => TypeClone.Clone(parameter, {})) + return this.Create({ ...options, [Kind]: 'Function', type: 'object', instanceOf: 'Function', parameters: clonedParameters, returns: clonedReturns }) + } else if (globalThis.Array.isArray(parameters)) { + const clonedParameters = parameters.map((parameter) => TypeClone.Clone(parameter, {})) + return this.Create({ ...options, [Kind]: 'Function', type: 'object', instanceOf: 'Function', parameters: clonedParameters, returns: clonedReturns }) + } else { + throw new Error('ExtendedTypeBuilder.Function: Invalid parameters') + } + } + /** `[Extended]` Extracts the InstanceType from the given Constructor */ + public InstanceType>(schema: T, options: SchemaOptions = {}): TInstanceType { + return TypeClone.Clone(schema.returns, options) + } + /** `[Extended]` Extracts the Parameters from the given Function type */ + public Parameters>(schema: T, options: SchemaOptions = {}): TParameters { + return this.Tuple(schema.parameters, { ...options }) + } + /** `[Extended]` Creates a Promise type */ + public Promise(item: T, options: SchemaOptions = {}): TPromise { + return this.Create({ ...options, [Kind]: 'Promise', type: 'object', instanceOf: 'Promise', item: TypeClone.Clone(item, {}) }) + } + /** `[Extended]` Creates a regular expression type */ + public RegEx(regex: RegExp, options: SchemaOptions = {}): TString { + return this.Create({ ...options, [Kind]: 'String', type: 'string', pattern: regex.source }) + } + /** `[Extended]` Extracts the ReturnType from the given Function */ + public ReturnType>(schema: T, options: SchemaOptions = {}): TReturnType { + return TypeClone.Clone(schema.returns, options) + } + /** `[Extended]` Creates a Symbol type */ + public Symbol(options?: SchemaOptions): TSymbol { + return this.Create({ ...options, [Kind]: 'Symbol', type: 'null', typeOf: 'Symbol' }) + } + /** `[Extended]` Creates a Undefined type */ + public Undefined(options: SchemaOptions = {}): TUndefined { + return this.Create({ ...options, [Kind]: 'Undefined', type: 'null', typeOf: 'Undefined' }) + } + /** `[Extended]` Creates a Uint8Array type */ + public Uint8Array(options: Uint8ArrayOptions = {}): TUint8Array { + return this.Create({ ...options, [Kind]: 'Uint8Array', type: 'object', instanceOf: 'Uint8Array' }) + } + /** `[Extended]` Creates a Void type */ public Void(options: SchemaOptions = {}): TVoid { return this.Create({ ...options, [Kind]: 'Void', type: 'null', typeOf: 'Void' }) } - - /** Use this function to return TSchema with static and params omitted */ - protected Create(schema: Omit): T { - return schema as any - } - - /** Clones the given value */ - protected Clone(value: any): any { - const isObject = (object: any): object is Record => typeof object === 'object' && object !== null && !Array.isArray(object) - const isArray = (object: any): object is any[] => typeof object === 'object' && object !== null && Array.isArray(object) - if (isObject(value)) { - return Object.keys(value).reduce( - (acc, key) => ({ - ...acc, - [key]: this.Clone(value[key]), - }), - Object.getOwnPropertySymbols(value).reduce( - (acc, key: any) => ({ - ...acc, - [key]: this.Clone(value[key]), - }), - {}, - ), - ) - } else if (isArray(value)) { - return value.map((item: any) => this.Clone(item)) - } else { - return value - } - } } -/** JSON Schema Type Builder with Static Type Resolution for TypeScript */ -export const Type = new TypeBuilder() +/** JSON Schema TypeBuilder with Static Resolution for TypeScript */ +export const StandardType = new StandardTypeBuilder() + +/** JSON Schema TypeBuilder with Static Resolution for TypeScript */ +export const Type = new ExtendedTypeBuilder() diff --git a/src/value/cast.ts b/src/value/cast.ts index 1a12d13..67e936a 100644 --- a/src/value/cast.ts +++ b/src/value/cast.ts @@ -30,7 +30,6 @@ import * as Types from '../typebox' import { ValueCreate } from './create' import { ValueCheck } from './check' import { ValueClone } from './clone' -import { Custom } from '../custom/index' // ---------------------------------------------------------------------------------------------- // Errors @@ -60,7 +59,11 @@ export class ValueCastUnknownTypeError extends Error { super('ValueCast: Unknown type') } } - +export class ValueCastDereferenceError extends Error { + constructor(public readonly schema: Types.TRef | Types.TSelf) { + super(`ValueCast: Unable to dereference schema with $id '${schema.$ref}'`) + } +} // ---------------------------------------------------------------------------------------------- // The following will score a schema against a value. For objects, the score is the tally of // points awarded for each property of the value. Property points are (1.0 / propertyCount) @@ -109,93 +112,18 @@ export namespace ValueCast { // ---------------------------------------------------------------------------------------------- // Guards // ---------------------------------------------------------------------------------------------- + function IsObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !globalThis.Array.isArray(value) + } function IsArray(value: unknown): value is unknown[] { return typeof value === 'object' && globalThis.Array.isArray(value) } - function IsDate(value: unknown): value is Date { - return typeof value === 'object' && value instanceof globalThis.Date - } - function IsString(value: unknown): value is string { - return typeof value === 'string' - } - function IsBoolean(value: unknown): value is boolean { - return typeof value === 'boolean' - } - function IsBigInt(value: unknown): value is bigint { - return typeof value === 'bigint' - } function IsNumber(value: unknown): value is number { return typeof value === 'number' && !isNaN(value) } - function IsStringNumeric(value: unknown): value is string { - return IsString(value) && !isNaN(value as any) && !isNaN(parseFloat(value)) + function IsString(value: unknown): value is string { + return typeof value === 'string' } - function IsValueToString(value: unknown): value is { toString: () => string } { - return IsBigInt(value) || IsBoolean(value) || IsNumber(value) - } - function IsValueTrue(value: unknown): value is true { - return value === true || (IsNumber(value) && value === 1) || (IsBigInt(value) && value === globalThis.BigInt('1')) || (IsString(value) && (value.toLowerCase() === 'true' || value === '1')) - } - function IsValueFalse(value: unknown): value is true { - return value === false || (IsNumber(value) && value === 0) || (IsBigInt(value) && value === globalThis.BigInt('0')) || (IsString(value) && (value.toLowerCase() === 'false' || value === '0')) - } - function IsTimeStringWithTimeZone(value: unknown): value is string { - return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value) - } - function IsTimeStringWithoutTimeZone(value: unknown): value is string { - return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value) - } - function IsDateTimeStringWithTimeZone(value: unknown): value is string { - return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value) - } - function IsDateTimeStringWithoutTimeZone(value: unknown): value is string { - return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value) - } - function IsDateString(value: unknown): value is string { - return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\d$/i.test(value) - } - - // ---------------------------------------------------------------------------------------------- - // Convert - // ---------------------------------------------------------------------------------------------- - function TryConvertString(value: unknown) { - return IsValueToString(value) ? value.toString() : value - } - function TryConvertNumber(value: unknown) { - return IsStringNumeric(value) ? parseFloat(value) : IsValueTrue(value) ? 1 : value - } - function TryConvertInteger(value: unknown) { - return IsStringNumeric(value) ? parseInt(value) : IsValueTrue(value) ? 1 : value - } - function TryConvertBoolean(value: unknown) { - return IsValueTrue(value) ? true : IsValueFalse(value) ? false : value - } - function TryConvertDate(value: unknown) { - // note: this function may return an invalid dates for the regex tests - // above. Invalid dates will however be checked during the casting - // function and will return a epoch date if invalid. Consider better - // string parsing for the iso dates in future revisions. - return IsDate(value) - ? value - : IsNumber(value) - ? new globalThis.Date(value) - : IsValueTrue(value) - ? new globalThis.Date(1) - : IsStringNumeric(value) - ? new globalThis.Date(parseInt(value)) - : IsTimeStringWithoutTimeZone(value) - ? new globalThis.Date(`1970-01-01T${value}.000Z`) - : IsTimeStringWithTimeZone(value) - ? new globalThis.Date(`1970-01-01T${value}`) - : IsDateTimeStringWithoutTimeZone(value) - ? new globalThis.Date(`${value}.000Z`) - : IsDateTimeStringWithTimeZone(value) - ? new globalThis.Date(value) - : IsDateString(value) - ? new globalThis.Date(`${value}T00:00:00.000Z`) - : value - } - // ---------------------------------------------------------------------------------------------- // Cast // ---------------------------------------------------------------------------------------------- @@ -213,9 +141,11 @@ export namespace ValueCast { if (!ValueCheck.Check(schema, references, unique)) throw new ValueCastArrayUniqueItemsTypeError(schema, unique) return unique } + function BigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): any { + return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references) + } function Boolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): any { - const conversion = TryConvertBoolean(value) - return ValueCheck.Check(schema, references, conversion) ? conversion : ValueCreate.Create(schema, references) + return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references) } function Constructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): any { if (ValueCheck.Check(schema, references, value)) return ValueCreate.Create(schema, references) @@ -228,31 +158,36 @@ export namespace ValueCast { return result } function Date(schema: Types.TDate, references: Types.TSchema[], value: any): any { - const conversion = TryConvertDate(value) - return ValueCheck.Check(schema, references, conversion) ? ValueClone.Clone(conversion) : ValueCreate.Create(schema, references) + return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references) } function Function(schema: Types.TFunction, references: Types.TSchema[], value: any): any { return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references) } function Integer(schema: Types.TInteger, references: Types.TSchema[], value: any): any { - const conversion = TryConvertInteger(value) - return ValueCheck.Check(schema, references, conversion) ? conversion : ValueCreate.Create(schema, references) + return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references) + } + function Intersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): any { + const created = ValueCreate.Create(schema, references) + const mapped = IsObject(created) && IsObject(value) ? { ...(created as any), ...value } : value + return ValueCheck.Check(schema, references, mapped) ? mapped : ValueCreate.Create(schema, references) } function Literal(schema: Types.TLiteral, references: Types.TSchema[], value: any): any { - return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references) + return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references) } function Never(schema: Types.TNever, references: Types.TSchema[], value: any): any { throw new ValueCastNeverTypeError(schema) } + function Not(schema: Types.TNot, references: Types.TSchema[], value: any): any { + return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema.allOf[1], references) + } function Null(schema: Types.TNull, references: Types.TSchema[], value: any): any { - return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references) + return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references) } function Number(schema: Types.TNumber, references: Types.TSchema[], value: any): any { - const conversion = TryConvertNumber(value) - return ValueCheck.Check(schema, references, conversion) ? conversion : ValueCreate.Create(schema, references) + return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references) } function Object(schema: Types.TObject, references: Types.TSchema[], value: any): any { - if (ValueCheck.Check(schema, references, value)) return ValueClone.Clone(value) + if (ValueCheck.Check(schema, references, value)) return value if (value === null || typeof value !== 'object') return ValueCreate.Create(schema, references) const required = new Set(schema.required || []) const result = {} as Record @@ -285,18 +220,22 @@ export namespace ValueCast { return result } function Ref(schema: Types.TRef, references: Types.TSchema[], value: any): any { - const reference = references.find((reference) => reference.$id === schema.$ref) - if (reference === undefined) throw new ValueCastReferenceTypeError(schema) - return Visit(reference, references, value) + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new ValueCastDereferenceError(schema) + const target = references[index] + return Visit(target, references, value) } function Self(schema: Types.TSelf, references: Types.TSchema[], value: any): any { - const reference = references.find((reference) => reference.$id === schema.$ref) - if (reference === undefined) throw new ValueCastReferenceTypeError(schema) - return Visit(reference, references, value) + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new ValueCastDereferenceError(schema) + const target = references[index] + return Visit(target, references, value) } function String(schema: Types.TString, references: Types.TSchema[], value: any): any { - const conversion = TryConvertString(value) - return ValueCheck.Check(schema, references, conversion) ? conversion : ValueCreate.Create(schema, references) + return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references) + } + function Symbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): any { + return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references) } function Tuple(schema: Types.TTuple, references: Types.TSchema[], value: any): any { if (ValueCheck.Check(schema, references, value)) return ValueClone.Clone(value) @@ -323,61 +262,69 @@ export namespace ValueCast { return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references) } export function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): any { - const anyReferences = schema.$id === undefined ? references : [schema, ...references] - const anySchema = schema as any + const references_ = IsString(schema.$id) ? [...references, schema] : references + const schema_ = schema as any switch (schema[Types.Kind]) { case 'Any': - return Any(anySchema, anyReferences, value) + return Any(schema_, references_, value) case 'Array': - return Array(anySchema, anyReferences, value) + return Array(schema_, references_, value) + case 'BigInt': + return BigInt(schema_, references_, value) case 'Boolean': - return Boolean(anySchema, anyReferences, value) + return Boolean(schema_, references_, value) case 'Constructor': - return Constructor(anySchema, anyReferences, value) + return Constructor(schema_, references_, value) case 'Date': - return Date(anySchema, anyReferences, value) + return Date(schema_, references_, value) case 'Function': - return Function(anySchema, anyReferences, value) + return Function(schema_, references_, value) case 'Integer': - return Integer(anySchema, anyReferences, value) + return Integer(schema_, references_, value) + case 'Intersect': + return Intersect(schema_, references_, value) case 'Literal': - return Literal(anySchema, anyReferences, value) + return Literal(schema_, references_, value) case 'Never': - return Never(anySchema, anyReferences, value) + return Never(schema_, references_, value) + case 'Not': + return Not(schema_, references_, value) case 'Null': - return Null(anySchema, anyReferences, value) + return Null(schema_, references_, value) case 'Number': - return Number(anySchema, anyReferences, value) + return Number(schema_, references_, value) case 'Object': - return Object(anySchema, anyReferences, value) + return Object(schema_, references_, value) case 'Promise': - return Promise(anySchema, anyReferences, value) + return Promise(schema_, references_, value) case 'Record': - return Record(anySchema, anyReferences, value) + return Record(schema_, references_, value) case 'Ref': - return Ref(anySchema, anyReferences, value) + return Ref(schema_, references_, value) case 'Self': - return Self(anySchema, anyReferences, value) + return Self(schema_, references_, value) case 'String': - return String(anySchema, anyReferences, value) + return String(schema_, references_, value) + case 'Symbol': + return Symbol(schema_, references_, value) case 'Tuple': - return Tuple(anySchema, anyReferences, value) + return Tuple(schema_, references_, value) case 'Undefined': - return Undefined(anySchema, anyReferences, value) + return Undefined(schema_, references_, value) case 'Union': - return Union(anySchema, anyReferences, value) + return Union(schema_, references_, value) case 'Uint8Array': - return Uint8Array(anySchema, anyReferences, value) + return Uint8Array(schema_, references_, value) case 'Unknown': - return Unknown(anySchema, anyReferences, value) + return Unknown(schema_, references_, value) case 'Void': - return Void(anySchema, anyReferences, value) + return Void(schema_, references_, value) default: - if (!Custom.Has(anySchema[Types.Kind])) throw new ValueCastUnknownTypeError(anySchema) - return UserDefined(anySchema, anyReferences, value) + if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueCastUnknownTypeError(schema_) + return UserDefined(schema_, references_, value) } } - export function Cast(schema: T, references: [...R], value: any): Types.Static { - return schema.$id === undefined ? Visit(schema, references, value) : Visit(schema, [schema, ...references], value) + export function Cast(schema: T, references: Types.TSchema[], value: any): Types.Static { + return Visit(schema, references, ValueClone.Clone(value)) } } diff --git a/src/value/check.ts b/src/value/check.ts index 6149cb7..15c06bb 100644 --- a/src/value/check.ts +++ b/src/value/check.ts @@ -28,156 +28,197 @@ THE SOFTWARE. import * as Types from '../typebox' import { TypeSystem } from '../system/index' -import { TypeExtends } from '../guard/extends' -import { Format } from '../format/index' -import { Custom } from '../custom/index' -import { ValueHash } from '../hash/index' +import { ValueHash } from './hash' +// ------------------------------------------------------------------------- +// Errors +// ------------------------------------------------------------------------- export class ValueCheckUnknownTypeError extends Error { constructor(public readonly schema: Types.TSchema) { super(`ValueCheck: ${schema[Types.Kind] ? `Unknown type '${schema[Types.Kind]}'` : 'Unknown type'}`) } } - -export namespace ValueCheck { - // -------------------------------------------------------- - // Guards - // -------------------------------------------------------- - - function IsNumber(value: unknown): value is number { - return typeof value === 'number' && !isNaN(value) +export class ValueCheckDereferenceError extends Error { + constructor(public readonly schema: Types.TRef | Types.TSelf) { + super(`ValueCheck: Unable to dereference schema with $id '${schema.$ref}'`) } - - // -------------------------------------------------------- +} +export namespace ValueCheck { + // ---------------------------------------------------------------------- // Guards - // -------------------------------------------------------- - + // ---------------------------------------------------------------------- + function IsObject(value: unknown): value is Record { + const result = typeof value === 'object' && value !== null + return TypeSystem.AllowArrayObjects ? result : result && !globalThis.Array.isArray(value) + } + function IsRecordObject(value: unknown): value is Record { + return IsObject(value) && !(value instanceof globalThis.Date) && !(value instanceof globalThis.Uint8Array) + } + function IsBigInt(value: unknown): value is bigint { + return typeof value === 'bigint' + } + function IsNumber(value: unknown): value is number { + const result = typeof value === 'number' + return TypeSystem.AllowNaN ? result : result && globalThis.Number.isFinite(value) + } + function IsInteger(value: unknown): value is number { + return globalThis.Number.isInteger(value) + } + function IsString(value: unknown): value is string { + return typeof value === 'string' + } + function IsVoid(value: unknown): value is void { + const result = value === undefined + return TypeSystem.AllowVoidNull ? result || value === null : result + } + function IsDefined(value: unknown): value is T { + return value !== undefined + } + // ---------------------------------------------------------------------- + // Types + // ---------------------------------------------------------------------- function Any(schema: Types.TAny, references: Types.TSchema[], value: any): boolean { return true } - function Array(schema: Types.TArray, references: Types.TSchema[], value: any): boolean { if (!globalThis.Array.isArray(value)) { return false } - if (IsNumber(schema.minItems) && !(value.length >= schema.minItems)) { + if (IsDefined(schema.minItems) && !(value.length >= schema.minItems)) { return false } - if (IsNumber(schema.maxItems) && !(value.length <= schema.maxItems)) { + if (IsDefined(schema.maxItems) && !(value.length <= schema.maxItems)) { return false } // prettier-ignore if (schema.uniqueItems === true && !((function() { const set = new Set(); for(const element of value) { const hashed = ValueHash.Create(element); if(set.has(hashed)) { return false } else { set.add(hashed) } } return true })())) { return false } - return value.every((val) => Visit(schema.items, references, val)) + return value.every((value) => Visit(schema.items, references, value)) + } + function BigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): boolean { + if (!IsBigInt(value)) { + return false + } + if (IsDefined(schema.multipleOf) && !(value % schema.multipleOf === globalThis.BigInt(0))) { + return false + } + if (IsDefined(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { + return false + } + if (IsDefined(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { + return false + } + if (IsDefined(schema.minimum) && !(value >= schema.minimum)) { + return false + } + if (IsDefined(schema.maximum) && !(value <= schema.maximum)) { + return false + } + return true } - function Boolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): boolean { return typeof value === 'boolean' } - function Constructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): boolean { return Visit(schema.returns, references, value.prototype) } - function Date(schema: Types.TDate, references: Types.TSchema[], value: any): boolean { if (!(value instanceof globalThis.Date)) { return false } - if (isNaN(value.getTime())) { + if (!IsNumber(value.getTime())) { return false } - if (IsNumber(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) { + if (IsDefined(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) { return false } - if (IsNumber(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) { + if (IsDefined(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) { return false } - if (IsNumber(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) { + if (IsDefined(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) { return false } - if (IsNumber(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) { + if (IsDefined(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) { return false } return true } - function Function(schema: Types.TFunction, references: Types.TSchema[], value: any): boolean { return typeof value === 'function' } - function Integer(schema: Types.TInteger, references: Types.TSchema[], value: any): boolean { - if (!(typeof value === 'number' && globalThis.Number.isInteger(value))) { + if (!IsInteger(value)) { return false } - if (IsNumber(schema.multipleOf) && !(value % schema.multipleOf === 0)) { + if (IsDefined(schema.multipleOf) && !(value % schema.multipleOf === 0)) { return false } - if (IsNumber(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { + if (IsDefined(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { return false } - if (IsNumber(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { + if (IsDefined(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { return false } - if (IsNumber(schema.minimum) && !(value >= schema.minimum)) { + if (IsDefined(schema.minimum) && !(value >= schema.minimum)) { return false } - if (IsNumber(schema.maximum) && !(value <= schema.maximum)) { + if (IsDefined(schema.maximum) && !(value <= schema.maximum)) { return false } return true } - + function Intersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): boolean { + if (!schema.allOf.every((schema) => Visit(schema, references, value))) { + return false + } else if (schema.unevaluatedProperties === false) { + const schemaKeys = Types.KeyResolver.Resolve(schema) + const valueKeys = globalThis.Object.getOwnPropertyNames(value) + return valueKeys.every((key) => schemaKeys.includes(key)) + } else if (Types.TypeGuard.TSchema(schema.unevaluatedProperties)) { + const schemaKeys = Types.KeyResolver.Resolve(schema) + const valueKeys = globalThis.Object.getOwnPropertyNames(value) + return valueKeys.every((key) => schemaKeys.includes(key) || Visit(schema.unevaluatedProperties as Types.TSchema, references, value[key])) + } else { + return true + } + } function Literal(schema: Types.TLiteral, references: Types.TSchema[], value: any): boolean { return value === schema.const } - function Never(schema: Types.TNever, references: Types.TSchema[], value: any): boolean { return false } - + function Not(schema: Types.TNot, references: Types.TSchema[], value: any): boolean { + return !Visit(schema.allOf[0].not, references, value) && Visit(schema.allOf[1], references, value) + } function Null(schema: Types.TNull, references: Types.TSchema[], value: any): boolean { return value === null } - function Number(schema: Types.TNumber, references: Types.TSchema[], value: any): boolean { - if (TypeSystem.AllowNaN) { - if (!(typeof value === 'number')) { - return false - } - } else { - if (!(typeof value === 'number' && !isNaN(value))) { - return false - } - } - if (IsNumber(schema.multipleOf) && !(value % schema.multipleOf === 0)) { + if (!IsNumber(value)) { return false } - if (IsNumber(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { + if (IsDefined(schema.multipleOf) && !(value % schema.multipleOf === 0)) { return false } - if (IsNumber(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { + if (IsDefined(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) { return false } - if (IsNumber(schema.minimum) && !(value >= schema.minimum)) { + if (IsDefined(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { return false } - if (IsNumber(schema.maximum) && !(value <= schema.maximum)) { + if (IsDefined(schema.minimum) && !(value >= schema.minimum)) { + return false + } + if (IsDefined(schema.maximum) && !(value <= schema.maximum)) { return false } return true } - function Object(schema: Types.TObject, references: Types.TSchema[], value: any): boolean { - if (TypeSystem.AllowArrayObjects) { - if (!(typeof value === 'object' && value !== null)) { - return false - } - } else { - if (!(typeof value === 'object' && value !== null && !globalThis.Array.isArray(value))) { - return false - } + if (!IsObject(value)) { + return false } if (IsNumber(schema.minProperties) && !(globalThis.Object.getOwnPropertyNames(value).length >= schema.minProperties)) { return false @@ -185,61 +226,43 @@ export namespace ValueCheck { if (IsNumber(schema.maxProperties) && !(globalThis.Object.getOwnPropertyNames(value).length <= schema.maxProperties)) { return false } - const propertyKeys = globalThis.Object.getOwnPropertyNames(schema.properties) + const knownKeys = globalThis.Object.getOwnPropertyNames(schema.properties) + for (const knownKey of knownKeys) { + const property = schema.properties[knownKey] + if (schema.required && schema.required.includes(knownKey)) { + if (!Visit(property, references, value[knownKey])) { + return false + } + if (Types.ExtendsUndefined.Check(property)) { + return knownKey in value + } + } else { + if (knownKey in value && !Visit(property, references, value[knownKey])) { + return false + } + } + } if (schema.additionalProperties === false) { - // optimization: If the property key length matches the required keys length - // then we only need check that the values property key length matches that - // of the property key length. This is because exhaustive testing for values - // will occur in subsequent property tests. - if (schema.required && schema.required.length === propertyKeys.length && !(globalThis.Object.getOwnPropertyNames(value).length === propertyKeys.length)) { - return false + const valueKeys = globalThis.Object.getOwnPropertyNames(value) + // optimization: value is valid if schemaKey length matches the valueKey length + if (schema.required && schema.required.length === knownKeys.length && valueKeys.length === knownKeys.length) { + return true } else { - if (!globalThis.Object.getOwnPropertyNames(value).every((key) => propertyKeys.includes(key))) { - return false - } + return valueKeys.every((valueKey) => knownKeys.includes(valueKey)) } + } else if (typeof schema.additionalProperties === 'object') { + const valueKeys = globalThis.Object.getOwnPropertyNames(value) + return valueKeys.every((key) => knownKeys.includes(key) || Visit(schema.additionalProperties as Types.TSchema, references, value[key])) + } else { + return true } - if (typeof schema.additionalProperties === 'object') { - for (const objectKey of globalThis.Object.getOwnPropertyNames(value)) { - if (propertyKeys.includes(objectKey)) continue - if (!Visit(schema.additionalProperties as Types.TSchema, references, value[objectKey])) { - return false - } - } - } - for (const propertyKey of propertyKeys) { - const propertySchema = schema.properties[propertyKey] - if (schema.required && schema.required.includes(propertyKey)) { - if (!Visit(propertySchema, references, value[propertyKey])) { - return false - } - if (TypeExtends.Undefined(propertySchema)) { - return propertyKey in value - } - } else { - if (value[propertyKey] !== undefined) { - if (!Visit(propertySchema, references, value[propertyKey])) { - return false - } - } - } - } - return true } - function Promise(schema: Types.TPromise, references: Types.TSchema[], value: any): boolean { return typeof value === 'object' && typeof value.then === 'function' } - function Record(schema: Types.TRecord, references: Types.TSchema[], value: any): boolean { - if (TypeSystem.AllowArrayObjects) { - if (!(typeof value === 'object' && value !== null && !(value instanceof globalThis.Date))) { - return false - } - } else { - if (!(typeof value === 'object' && value !== null && !(value instanceof globalThis.Date) && !globalThis.Array.isArray(value))) { - return false - } + if (!IsRecordObject(value)) { + return false } const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0] const regex = new RegExp(keyPattern) @@ -251,41 +274,45 @@ export namespace ValueCheck { } return true } - function Ref(schema: Types.TRef, references: Types.TSchema[], value: any): boolean { - const reference = references.find((reference) => reference.$id === schema.$ref) - if (reference === undefined) throw new Error(`ValueCheck.Ref: Cannot find schema with $id '${schema.$ref}'.`) - return Visit(reference, references, value) + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new ValueCheckDereferenceError(schema) + const target = references[index] + return Visit(target, references, value) } - function Self(schema: Types.TSelf, references: Types.TSchema[], value: any): boolean { - const reference = references.find((reference) => reference.$id === schema.$ref) - if (reference === undefined) throw new Error(`ValueCheck.Self: Cannot find schema with $id '${schema.$ref}'.`) - return Visit(reference, references, value) + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new ValueCheckDereferenceError(schema) + const target = references[index] + return Visit(target, references, value) } - function String(schema: Types.TString, references: Types.TSchema[], value: any): boolean { - if (!(typeof value === 'string')) { + if (!IsString(value)) { return false } - if (IsNumber(schema.minLength)) { + if (IsDefined(schema.minLength)) { if (!(value.length >= schema.minLength)) return false } - if (IsNumber(schema.maxLength)) { + if (IsDefined(schema.maxLength)) { if (!(value.length <= schema.maxLength)) return false } - if (schema.pattern !== undefined) { + if (IsDefined(schema.pattern)) { const regex = new RegExp(schema.pattern) if (!regex.test(value)) return false } - if (schema.format !== undefined) { - if (!Format.Has(schema.format)) return false - const func = Format.Get(schema.format)! + if (IsDefined(schema.format)) { + if (!Types.FormatRegistry.Has(schema.format)) return false + const func = Types.FormatRegistry.Get(schema.format)! return func(value) } return true } - + function Symbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): boolean { + if (!(typeof value === 'symbol')) { + return false + } + return true + } function Tuple(schema: Types.TTuple, references: Types.TSchema[], value: any): boolean { if (!globalThis.Array.isArray(value)) { return false @@ -304,103 +331,102 @@ export namespace ValueCheck { } return true } - function Undefined(schema: Types.TUndefined, references: Types.TSchema[], value: any): boolean { return value === undefined } - function Union(schema: Types.TUnion, references: Types.TSchema[], value: any): boolean { return schema.anyOf.some((inner) => Visit(inner, references, value)) } - function Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: any): boolean { if (!(value instanceof globalThis.Uint8Array)) { return false } - if (IsNumber(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) { + if (IsDefined(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) { return false } - if (IsNumber(schema.minByteLength) && !(value.length >= schema.minByteLength)) { + if (IsDefined(schema.minByteLength) && !(value.length >= schema.minByteLength)) { return false } return true } - function Unknown(schema: Types.TUnknown, references: Types.TSchema[], value: any): boolean { return true } - function Void(schema: Types.TVoid, references: Types.TSchema[], value: any): boolean { - return value === null + return IsVoid(value) } - function UserDefined(schema: Types.TSchema, references: Types.TSchema[], value: unknown): boolean { - if (!Custom.Has(schema[Types.Kind])) return false - const func = Custom.Get(schema[Types.Kind])! + if (!Types.TypeRegistry.Has(schema[Types.Kind])) return false + const func = Types.TypeRegistry.Get(schema[Types.Kind])! return func(schema, value) } - function Visit(schema: T, references: Types.TSchema[], value: any): boolean { - const anyReferences = schema.$id === undefined ? references : [schema, ...references] - const anySchema = schema as any - switch (anySchema[Types.Kind]) { + const references_ = IsDefined(schema.$id) ? [...references, schema] : references + const schema_ = schema as any + switch (schema_[Types.Kind]) { case 'Any': - return Any(anySchema, anyReferences, value) + return Any(schema_, references_, value) case 'Array': - return Array(anySchema, anyReferences, value) + return Array(schema_, references_, value) + case 'BigInt': + return BigInt(schema_, references_, value) case 'Boolean': - return Boolean(anySchema, anyReferences, value) + return Boolean(schema_, references_, value) case 'Constructor': - return Constructor(anySchema, anyReferences, value) + return Constructor(schema_, references_, value) case 'Date': - return Date(anySchema, anyReferences, value) + return Date(schema_, references_, value) case 'Function': - return Function(anySchema, anyReferences, value) + return Function(schema_, references_, value) case 'Integer': - return Integer(anySchema, anyReferences, value) + return Integer(schema_, references_, value) + case 'Intersect': + return Intersect(schema_, references_, value) case 'Literal': - return Literal(anySchema, anyReferences, value) + return Literal(schema_, references_, value) case 'Never': - return Never(anySchema, anyReferences, value) + return Never(schema_, references_, value) + case 'Not': + return Not(schema_, references_, value) case 'Null': - return Null(anySchema, anyReferences, value) + return Null(schema_, references_, value) case 'Number': - return Number(anySchema, anyReferences, value) + return Number(schema_, references_, value) case 'Object': - return Object(anySchema, anyReferences, value) + return Object(schema_, references_, value) case 'Promise': - return Promise(anySchema, anyReferences, value) + return Promise(schema_, references_, value) case 'Record': - return Record(anySchema, anyReferences, value) + return Record(schema_, references_, value) case 'Ref': - return Ref(anySchema, anyReferences, value) + return Ref(schema_, references_, value) case 'Self': - return Self(anySchema, anyReferences, value) + return Self(schema_, references_, value) case 'String': - return String(anySchema, anyReferences, value) + return String(schema_, references_, value) + case 'Symbol': + return Symbol(schema_, references_, value) case 'Tuple': - return Tuple(anySchema, anyReferences, value) + return Tuple(schema_, references_, value) case 'Undefined': - return Undefined(anySchema, anyReferences, value) + return Undefined(schema_, references_, value) case 'Union': - return Union(anySchema, anyReferences, value) + return Union(schema_, references_, value) case 'Uint8Array': - return Uint8Array(anySchema, anyReferences, value) + return Uint8Array(schema_, references_, value) case 'Unknown': - return Unknown(anySchema, anyReferences, value) + return Unknown(schema_, references_, value) case 'Void': - return Void(anySchema, anyReferences, value) + return Void(schema_, references_, value) default: - if (!Custom.Has(anySchema[Types.Kind])) throw new ValueCheckUnknownTypeError(anySchema) - return UserDefined(anySchema, anyReferences, value) + if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueCheckUnknownTypeError(schema_) + return UserDefined(schema_, references_, value) } } - // ------------------------------------------------------------------------- // Check // ------------------------------------------------------------------------- - - export function Check(schema: T, references: [...R], value: any): boolean { - return schema.$id === undefined ? Visit(schema, references, value) : Visit(schema, [schema, ...references], value) + export function Check(schema: T, references: Types.TSchema[], value: any): boolean { + return Visit(schema, references, value) } } diff --git a/src/value/clone.ts b/src/value/clone.ts index 82bf94b..e8d3d21 100644 --- a/src/value/clone.ts +++ b/src/value/clone.ts @@ -32,24 +32,19 @@ export namespace ValueClone { function Array(value: ArrayType): any { return value.map((element: any) => Clone(element)) } - function Date(value: Date): any { return new globalThis.Date(value.toISOString()) } - function Object(value: ObjectType): any { const keys = [...globalThis.Object.keys(value), ...globalThis.Object.getOwnPropertySymbols(value)] return keys.reduce((acc, key) => ({ ...acc, [key]: Clone(value[key]) }), {}) } - function TypedArray(value: TypedArrayType): any { return value.slice() } - function Value(value: ValueType): any { return value } - export function Clone(value: T): T { if (Is.Date(value)) { return Date(value) diff --git a/src/value/convert.ts b/src/value/convert.ts new file mode 100644 index 0000000..987ef03 --- /dev/null +++ b/src/value/convert.ts @@ -0,0 +1,342 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox/value + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import * as Types from '../typebox' +import { ValueClone } from './clone' + +// ---------------------------------------------------------------------------------------------- +// Errors +// ---------------------------------------------------------------------------------------------- +export class ValueConvertUnknownTypeError extends Error { + constructor(public readonly schema: Types.TSchema) { + super('ValueConvert: Unknown type') + } +} +export class ValueConvertDereferenceError extends Error { + constructor(public readonly schema: Types.TRef | Types.TSelf) { + super(`ValueConvert: Unable to dereference schema with $id '${schema.$ref}'`) + } +} +export namespace ValueConvert { + // ---------------------------------------------------------------------------------------------- + // Guards + // ---------------------------------------------------------------------------------------------- + function IsObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !globalThis.Array.isArray(value) + } + function IsArray(value: unknown): value is unknown[] { + return typeof value === 'object' && globalThis.Array.isArray(value) + } + function IsDate(value: unknown): value is Date { + return typeof value === 'object' && value instanceof globalThis.Date + } + function IsSymbol(value: unknown): value is symbol { + return typeof value === 'symbol' + } + function IsString(value: unknown): value is string { + return typeof value === 'string' + } + function IsBoolean(value: unknown): value is boolean { + return typeof value === 'boolean' + } + function IsBigInt(value: unknown): value is bigint { + return typeof value === 'bigint' + } + function IsNumber(value: unknown): value is number { + return typeof value === 'number' && !isNaN(value) + } + function IsStringNumeric(value: unknown): value is string { + return IsString(value) && !isNaN(value as any) && !isNaN(parseFloat(value)) + } + function IsValueToString(value: unknown): value is { toString: () => string } { + return IsBigInt(value) || IsBoolean(value) || IsNumber(value) + } + function IsValueTrue(value: unknown): value is true { + return value === true || (IsNumber(value) && value === 1) || (IsBigInt(value) && value === globalThis.BigInt('1')) || (IsString(value) && (value.toLowerCase() === 'true' || value === '1')) + } + function IsValueFalse(value: unknown): value is true { + return value === false || (IsNumber(value) && value === 0) || (IsBigInt(value) && value === globalThis.BigInt('0')) || (IsString(value) && (value.toLowerCase() === 'false' || value === '0')) + } + function IsTimeStringWithTimeZone(value: unknown): value is string { + return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value) + } + function IsTimeStringWithoutTimeZone(value: unknown): value is string { + return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value) + } + function IsDateTimeStringWithTimeZone(value: unknown): value is string { + return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value) + } + function IsDateTimeStringWithoutTimeZone(value: unknown): value is string { + return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value) + } + function IsDateString(value: unknown): value is string { + return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\d$/i.test(value) + } + // ---------------------------------------------------------------------------------------------- + // Convert + // ---------------------------------------------------------------------------------------------- + function TryConvertLiteralString(value: unknown, target: string) { + const conversion = TryConvertString(value) + return conversion === target ? conversion : value + } + function TryConvertLiteralNumber(value: unknown, target: number) { + const conversion = TryConvertNumber(value) + return conversion === target ? conversion : value + } + function TryConvertLiteralBoolean(value: unknown, target: boolean) { + const conversion = TryConvertBoolean(value) + return conversion === target ? conversion : value + } + function TryConvertLiteral(schema: Types.TLiteral, value: unknown) { + if (typeof schema.const === 'string') { + return TryConvertLiteralString(value, schema.const) + } else if (typeof schema.const === 'number') { + return TryConvertLiteralNumber(value, schema.const) + } else if (typeof schema.const === 'boolean') { + return TryConvertLiteralBoolean(value, schema.const) + } else { + return ValueClone.Clone(value) + } + } + function TryConvertBoolean(value: unknown) { + return IsValueTrue(value) ? true : IsValueFalse(value) ? false : value + } + function TryConvertBigInt(value: unknown) { + return IsStringNumeric(value) ? globalThis.BigInt(parseInt(value)) : IsNumber(value) ? globalThis.BigInt(value | 0) : IsValueFalse(value) ? 0 : IsValueTrue(value) ? 1 : value + } + + function TryConvertString(value: unknown) { + return IsValueToString(value) ? value.toString() : IsSymbol(value) && value.description !== undefined ? value.description.toString() : value + } + function TryConvertNumber(value: unknown) { + return IsStringNumeric(value) ? parseFloat(value) : IsValueTrue(value) ? 1 : IsValueFalse(value) ? 0 : value + } + function TryConvertInteger(value: unknown) { + return IsStringNumeric(value) ? parseInt(value) : IsNumber(value) ? value | 0 : IsValueTrue(value) ? 1 : IsValueFalse(value) ? 0 : value + } + function TryConvertNull(value: unknown) { + return IsString(value) && value.toLowerCase() === 'null' ? null : value + } + function TryConvertUndefined(value: unknown) { + return IsString(value) && value === 'undefined' ? undefined : value + } + function TryConvertDate(value: unknown) { + // note: this function may return an invalid dates for the regex tests + // above. Invalid dates will however be checked during the casting + // function and will return a epoch date if invalid. Consider better + // string parsing for the iso dates in future revisions. + return IsDate(value) + ? value + : IsNumber(value) + ? new globalThis.Date(value) + : IsValueTrue(value) + ? new globalThis.Date(1) + : IsValueFalse(value) + ? new globalThis.Date(0) + : IsStringNumeric(value) + ? new globalThis.Date(parseInt(value)) + : IsTimeStringWithoutTimeZone(value) + ? new globalThis.Date(`1970-01-01T${value}.000Z`) + : IsTimeStringWithTimeZone(value) + ? new globalThis.Date(`1970-01-01T${value}`) + : IsDateTimeStringWithoutTimeZone(value) + ? new globalThis.Date(`${value}.000Z`) + : IsDateTimeStringWithTimeZone(value) + ? new globalThis.Date(value) + : IsDateString(value) + ? new globalThis.Date(`${value}T00:00:00.000Z`) + : value + } + + // ---------------------------------------------------------------------------------------------- + // Cast + // ---------------------------------------------------------------------------------------------- + function Any(schema: Types.TAny, references: Types.TSchema[], value: any): any { + return value + } + function Array(schema: Types.TArray, references: Types.TSchema[], value: any): any { + if (IsArray(value)) { + return value.map((value) => Visit(schema.items, references, value)) + } + return value + } + function BigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): unknown { + return TryConvertBigInt(value) + } + function Boolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): unknown { + return TryConvertBoolean(value) + } + function Constructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): unknown { + return ValueClone.Clone(value) + } + function Date(schema: Types.TDate, references: Types.TSchema[], value: any): unknown { + return TryConvertDate(value) + } + function Function(schema: Types.TFunction, references: Types.TSchema[], value: any): unknown { + return value + } + function Integer(schema: Types.TInteger, references: Types.TSchema[], value: any): unknown { + return TryConvertInteger(value) + } + function Intersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): unknown { + return value + } + function Literal(schema: Types.TLiteral, references: Types.TSchema[], value: any): unknown { + return TryConvertLiteral(schema, value) + } + function Never(schema: Types.TNever, references: Types.TSchema[], value: any): unknown { + return value + } + function Null(schema: Types.TNull, references: Types.TSchema[], value: any): unknown { + return TryConvertNull(value) + } + function Number(schema: Types.TNumber, references: Types.TSchema[], value: any): unknown { + return TryConvertNumber(value) + } + function Object(schema: Types.TObject, references: Types.TSchema[], value: any): unknown { + if (IsObject(value)) + return globalThis.Object.keys(schema.properties).reduce((acc, key) => { + return value[key] !== undefined ? { ...acc, [key]: Visit(schema.properties[key], references, value[key]) } : { ...acc } + }, value) + return value + } + function Promise(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown { + return value + } + function Record(schema: Types.TRecord, references: Types.TSchema[], value: any): unknown { + return value + } + function Ref(schema: Types.TRef, references: Types.TSchema[], value: any): unknown { + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new ValueConvertDereferenceError(schema) + const target = references[index] + return Visit(target, references, value) + } + function Self(schema: Types.TSelf, references: Types.TSchema[], value: any): unknown { + const index = references.findIndex((foreign) => foreign.$id === schema.$ref) + if (index === -1) throw new ValueConvertDereferenceError(schema) + const target = references[index] + return Visit(target, references, value) + } + function String(schema: Types.TString, references: Types.TSchema[], value: any): unknown { + return TryConvertString(value) + } + function Symbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): unknown { + return value + } + function Tuple(schema: Types.TTuple, references: Types.TSchema[], value: any): unknown { + if (IsArray(value) && schema.items !== undefined) { + return value.map((value, index) => { + return index < schema.items!.length ? Visit(schema.items![index], references, value) : value + }) + } + return value + } + function Undefined(schema: Types.TUndefined, references: Types.TSchema[], value: any): unknown { + return TryConvertUndefined(value) + } + function Union(schema: Types.TUnion, references: Types.TSchema[], value: any): unknown { + return value + } + function Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: any): unknown { + return value + } + function Unknown(schema: Types.TUnknown, references: Types.TSchema[], value: any): unknown { + return value + } + function Void(schema: Types.TVoid, references: Types.TSchema[], value: any): unknown { + return value + } + function UserDefined(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown { + return value + } + export function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown { + const references_ = IsString(schema.$id) ? [...references, schema] : references + const schema_ = schema as any + switch (schema[Types.Kind]) { + case 'Any': + return Any(schema_, references_, value) + case 'Array': + return Array(schema_, references_, value) + case 'BigInt': + return BigInt(schema_, references_, value) + case 'Boolean': + return Boolean(schema_, references_, value) + case 'Constructor': + return Constructor(schema_, references_, value) + case 'Date': + return Date(schema_, references_, value) + case 'Function': + return Function(schema_, references_, value) + case 'Integer': + return Integer(schema_, references_, value) + case 'Intersect': + return Intersect(schema_, references_, value) + case 'Literal': + return Literal(schema_, references_, value) + case 'Never': + return Never(schema_, references_, value) + case 'Null': + return Null(schema_, references_, value) + case 'Number': + return Number(schema_, references_, value) + case 'Object': + return Object(schema_, references_, value) + case 'Promise': + return Promise(schema_, references_, value) + case 'Record': + return Record(schema_, references_, value) + case 'Ref': + return Ref(schema_, references_, value) + case 'Self': + return Self(schema_, references_, value) + case 'String': + return String(schema_, references_, value) + case 'Symbol': + return Symbol(schema_, references_, value) + case 'Tuple': + return Tuple(schema_, references_, value) + case 'Undefined': + return Undefined(schema_, references_, value) + case 'Union': + return Union(schema_, references_, value) + case 'Uint8Array': + return Uint8Array(schema_, references_, value) + case 'Unknown': + return Unknown(schema_, references_, value) + case 'Void': + return Void(schema_, references_, value) + default: + if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueConvertUnknownTypeError(schema_) + return UserDefined(schema_, references_, value) + } + } + export function Convert(schema: T, references: Types.TSchema[], value: any): unknown { + return Visit(schema, references, ValueClone.Clone(value)) + } +} diff --git a/src/value/create.ts b/src/value/create.ts index 980d180..a2a0e13 100644 --- a/src/value/create.ts +++ b/src/value/create.ts @@ -27,33 +27,55 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import * as Types from '../typebox' -import { Custom } from '../custom/index' +import { ValueCheck } from './check' +// -------------------------------------------------------------------------- +// Errors +// -------------------------------------------------------------------------- export class ValueCreateUnknownTypeError extends Error { constructor(public readonly schema: Types.TSchema) { super('ValueCreate: Unknown type') } } - export class ValueCreateNeverTypeError extends Error { constructor(public readonly schema: Types.TSchema) { super('ValueCreate: Never types cannot be created') } } - +export class ValueCreateIntersectTypeError extends Error { + constructor(public readonly schema: Types.TSchema) { + super('ValueCreate: Can only create values for intersected objects and non-varying primitive types. Consider using a default value.') + } +} +export class ValueCreateDereferenceError extends Error { + constructor(public readonly schema: Types.TRef | Types.TSelf) { + super(`ValueCreate: Unable to dereference schema with $id '${schema.$ref}'`) + } +} +// -------------------------------------------------------------------------- +// ValueCreate +// -------------------------------------------------------------------------- export namespace ValueCreate { + // -------------------------------------------------------- + // Guards + // -------------------------------------------------------- + function IsString(value: unknown): value is string { + return typeof value === 'string' + } + // -------------------------------------------------------- + // Types + // -------------------------------------------------------- function Any(schema: Types.TAny, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { return {} } } - function Array(schema: Types.TArray, references: Types.TSchema[]): any { if (schema.uniqueItems === true && schema.default === undefined) { throw new Error('ValueCreate.Array: Arrays with uniqueItems require a default value') - } else if (schema.default !== undefined) { + } else if ('default' in schema) { return schema.default } else if (schema.minItems !== undefined) { return globalThis.Array.from({ length: schema.minItems }).map((item) => { @@ -63,17 +85,22 @@ export namespace ValueCreate { return [] } } - + function BigInt(schema: Types.TBigInt, references: Types.TSchema[]): any { + if ('default' in schema) { + return schema.default + } else { + return globalThis.BigInt(0) + } + } function Boolean(schema: Types.TBoolean, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { return false } } - function Constructor(schema: Types.TConstructor, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { const value = ValueCreate.Create(schema.returns, references) as any @@ -91,9 +118,8 @@ export namespace ValueCreate { } } } - function Date(schema: Types.TDate, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else if (schema.minimumTimestamp !== undefined) { return new globalThis.Date(schema.minimumTimestamp) @@ -101,17 +127,15 @@ export namespace ValueCreate { return new globalThis.Date(0) } } - function Function(schema: Types.TFunction, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { return () => ValueCreate.Create(schema.returns, references) } } - function Integer(schema: Types.TInteger, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else if (schema.minimum !== undefined) { return schema.minimum @@ -119,29 +143,41 @@ export namespace ValueCreate { return 0 } } - + function Intersect(schema: Types.TIntersect, references: Types.TSchema[]): any { + if ('default' in schema) { + return schema.default + } else { + const value = schema.type === 'object' ? schema.allOf.reduce((acc, schema) => ({ ...acc, ...(Visit(schema, references) as any) }), {}) : schema.allOf.reduce((_, schema) => Visit(schema, references), undefined as any) + if (!ValueCheck.Check(schema, references, value)) throw new ValueCreateIntersectTypeError(schema) + return value + } + } function Literal(schema: Types.TLiteral, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { return schema.const } } - function Never(schema: Types.TNever, references: Types.TSchema[]): any { throw new ValueCreateNeverTypeError(schema) } - + function Not(schema: Types.TNot, references: Types.TSchema[]): any { + if ('default' in schema) { + return schema.default + } else { + return Visit(schema.allOf[1], references) + } + } function Null(schema: Types.TNull, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { return null } } - function Number(schema: Types.TNumber, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else if (schema.minimum !== undefined) { return schema.minimum @@ -149,9 +185,8 @@ export namespace ValueCreate { return 0 } } - function Object(schema: Types.TObject, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { const required = new Set(schema.required) @@ -163,18 +198,16 @@ export namespace ValueCreate { ) } } - function Promise(schema: Types.TPromise, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { return globalThis.Promise.resolve(ValueCreate.Create(schema.item, references)) } } - function Record(schema: Types.TRecord, references: Types.TSchema[]): any { const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0] - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else if (!(keyPattern === '^.*$' || keyPattern === '^(0|[1-9][0-9]*)$')) { const propertyKeys = keyPattern.slice(1, keyPattern.length - 1).split('|') @@ -185,42 +218,41 @@ export namespace ValueCreate { return {} } } - function Ref(schema: Types.TRef, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { - const reference = references.find((reference) => reference.$id === schema.$ref) - if (reference === undefined) throw new Error(`ValueCreate.Ref: Cannot find schema with $id '${schema.$ref}'.`) - return Visit(reference, references) + const index = references.findIndex((foreign) => foreign.$id === schema.$id) + if (index === -1) throw new ValueCreateDereferenceError(schema) + const target = references[index] + return Visit(target, references) } } - function Self(schema: Types.TSelf, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { - const reference = references.find((reference) => reference.$id === schema.$ref) - if (reference === undefined) throw new Error(`ValueCreate.Self: Cannot locate schema with $id '${schema.$ref}'`) - return Visit(reference, references) + const index = references.findIndex((foreign) => foreign.$id === schema.$id) + if (index === -1) throw new ValueCreateDereferenceError(schema) + const target = references[index] + return Visit(target, references) } } - function String(schema: Types.TString, references: Types.TSchema[]): any { if (schema.pattern !== undefined) { - if (schema.default === undefined) { + if (!('default' in schema)) { throw new Error('ValueCreate.String: String types with patterns must specify a default value') } else { return schema.default } } else if (schema.format !== undefined) { - if (schema.default === undefined) { + if (!('default' in schema)) { throw new Error('ValueCreate.String: String types with formats must specify a default value') } else { return schema.default } } else { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else if (schema.minLength !== undefined) { return globalThis.Array.from({ length: schema.minLength }) @@ -231,9 +263,17 @@ export namespace ValueCreate { } } } - + function Symbol(schema: Types.TString, references: Types.TSchema[]): any { + if ('default' in schema) { + return schema.default + } else if ('value' in schema) { + return globalThis.Symbol.for(schema.value) + } else { + return globalThis.Symbol() + } + } function Tuple(schema: Types.TTuple, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } if (schema.items === undefined) { @@ -242,17 +282,15 @@ export namespace ValueCreate { return globalThis.Array.from({ length: schema.minItems }).map((_, index) => ValueCreate.Create((schema.items as any[])[index], references)) } } - function Undefined(schema: Types.TUndefined, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { return undefined } } - function Union(schema: Types.TUnion, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else if (schema.anyOf.length === 0) { throw new Error('ValueCreate.Union: Cannot create Union with zero variants') @@ -261,7 +299,7 @@ export namespace ValueCreate { } } function Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else if (schema.minByteLength !== undefined) { return new globalThis.Uint8Array(schema.minByteLength) @@ -269,90 +307,92 @@ export namespace ValueCreate { return new globalThis.Uint8Array(0) } } - function Unknown(schema: Types.TUnknown, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { return {} } } - function Void(schema: Types.TVoid, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { - return null + return void 0 } } - function UserDefined(schema: Types.TSchema, references: Types.TSchema[]): any { - if (schema.default !== undefined) { + if ('default' in schema) { return schema.default } else { throw new Error('ValueCreate.UserDefined: User defined types must specify a default value') } } - /** Creates a value from the given schema. If the schema specifies a default value, then that value is returned. */ - export function Visit(schema: T, references: Types.TSchema[]): Types.Static { - const anyReferences = schema.$id === undefined ? references : [schema, ...references] - const anySchema = schema as any - - switch (anySchema[Types.Kind]) { + export function Visit(schema: Types.TSchema, references: Types.TSchema[]): unknown { + const references_ = IsString(schema.$id) ? [...references, schema] : references + const schema_ = schema as any + switch (schema_[Types.Kind]) { case 'Any': - return Any(anySchema, anyReferences) + return Any(schema_, references_) case 'Array': - return Array(anySchema, anyReferences) + return Array(schema_, references_) + case 'BigInt': + return BigInt(schema_, references_) case 'Boolean': - return Boolean(anySchema, anyReferences) + return Boolean(schema_, references_) case 'Constructor': - return Constructor(anySchema, anyReferences) + return Constructor(schema_, references_) case 'Date': - return Date(anySchema, anyReferences) + return Date(schema_, references_) case 'Function': - return Function(anySchema, anyReferences) + return Function(schema_, references_) case 'Integer': - return Integer(anySchema, anyReferences) + return Integer(schema_, references_) + case 'Intersect': + return Intersect(schema_, references_) case 'Literal': - return Literal(anySchema, anyReferences) + return Literal(schema_, references_) case 'Never': - return Never(anySchema, anyReferences) + return Never(schema_, references_) + case 'Not': + return Not(schema_, references_) case 'Null': - return Null(anySchema, anyReferences) + return Null(schema_, references_) case 'Number': - return Number(anySchema, anyReferences) + return Number(schema_, references_) case 'Object': - return Object(anySchema, anyReferences) + return Object(schema_, references_) case 'Promise': - return Promise(anySchema, anyReferences) + return Promise(schema_, references_) case 'Record': - return Record(anySchema, anyReferences) + return Record(schema_, references_) case 'Ref': - return Ref(anySchema, anyReferences) + return Ref(schema_, references_) case 'Self': - return Self(anySchema, anyReferences) + return Self(schema_, references_) case 'String': - return String(anySchema, anyReferences) + return String(schema_, references_) + case 'Symbol': + return Symbol(schema_, references_) case 'Tuple': - return Tuple(anySchema, anyReferences) + return Tuple(schema_, references_) case 'Undefined': - return Undefined(anySchema, anyReferences) + return Undefined(schema_, references_) case 'Union': - return Union(anySchema, anyReferences) + return Union(schema_, references_) case 'Uint8Array': - return Uint8Array(anySchema, anyReferences) + return Uint8Array(schema_, references_) case 'Unknown': - return Unknown(anySchema, anyReferences) + return Unknown(schema_, references_) case 'Void': - return Void(anySchema, anyReferences) + return Void(schema_, references_) default: - if (!Custom.Has(anySchema[Types.Kind])) throw new ValueCreateUnknownTypeError(anySchema) - return UserDefined(anySchema, anyReferences) + if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueCreateUnknownTypeError(schema_) + return UserDefined(schema_, references_) } } - - export function Create(schema: T, references: [...R]): Types.Static { + export function Create(schema: T, references: Types.TSchema[]): Types.Static { return Visit(schema, references) } } diff --git a/src/value/equal.ts b/src/value/equal.ts index 841cba4..ba3e242 100644 --- a/src/value/equal.ts +++ b/src/value/equal.ts @@ -36,25 +36,20 @@ export namespace ValueEqual { if (leftKeys.length !== rightKeys.length) return false return leftKeys.every((key) => Equal(left[key], right[key])) } - function Date(left: Date, right: unknown): any { return Is.Date(right) && left.getTime() === right.getTime() } - function Array(left: ArrayType, right: unknown): any { if (!Is.Array(right) || left.length !== right.length) return false return left.every((value, index) => Equal(value, right[index])) } - function TypedArray(left: TypedArrayType, right: unknown): any { if (!Is.TypedArray(right) || left.length !== right.length || globalThis.Object.getPrototypeOf(left).constructor.name !== globalThis.Object.getPrototypeOf(right).constructor.name) return false return left.every((value, index) => Equal(value, right[index])) } - function Value(left: ValueType, right: unknown): any { return left === right } - export function Equal(left: T, right: unknown): right is T { if (Is.Object(left)) { return Object(left, right) diff --git a/src/hash/hash.ts b/src/value/hash.ts similarity index 83% rename from src/hash/hash.ts rename to src/value/hash.ts index dbe5123..5231ecc 100644 --- a/src/hash/hash.ts +++ b/src/value/hash.ts @@ -31,7 +31,6 @@ export class ValueHashError extends Error { super(`Hash: Unable to hash value`) } } - export namespace ValueHash { enum ByteMarker { Undefined, @@ -43,123 +42,122 @@ export namespace ValueHash { Array, Date, Uint8Array, + Symbol, + BigInt, } // ---------------------------------------------------- // State // ---------------------------------------------------- - let Hash = globalThis.BigInt('14695981039346656037') const [Prime, Size] = [globalThis.BigInt('1099511628211'), globalThis.BigInt('2') ** globalThis.BigInt('64')] const Bytes = globalThis.Array.from({ length: 256 }).map((_, i) => globalThis.BigInt(i)) const F64 = new globalThis.Float64Array(1) const F64In = new globalThis.DataView(F64.buffer) const F64Out = new globalThis.Uint8Array(F64.buffer) - // ---------------------------------------------------- // Guards // ---------------------------------------------------- - function IsDate(value: unknown): value is Date { return value instanceof globalThis.Date } - function IsUint8Array(value: unknown): value is Uint8Array { return value instanceof globalThis.Uint8Array } - function IsArray(value: unknown): value is Array { return globalThis.Array.isArray(value) } - function IsBoolean(value: unknown): value is boolean { return typeof value === 'boolean' } - function IsNull(value: unknown): value is null { return value === null } - function IsNumber(value: unknown): value is number { return typeof value === 'number' } - + function IsSymbol(value: unknown): value is symbol { + return typeof value === 'symbol' + } + function IsBigInt(value: unknown): value is bigint { + return typeof value === 'bigint' + } function IsObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !IsArray(value) && !IsDate(value) && !IsUint8Array(value) } - function IsString(value: unknown): value is string { return typeof value === 'string' } - function IsUndefined(value: unknown): value is undefined { return value === undefined } - // ---------------------------------------------------- // Encoding // ---------------------------------------------------- - function Array(value: Array) { - Fnv1A64(ByteMarker.Array) + FNV1A64(ByteMarker.Array) for (const item of value) { Visit(item) } } - function Boolean(value: boolean) { - Fnv1A64(ByteMarker.Boolean) - Fnv1A64(value ? 1 : 0) + FNV1A64(ByteMarker.Boolean) + FNV1A64(value ? 1 : 0) } - - function Date(value: Date) { - Fnv1A64(ByteMarker.Date) - Visit(value.getTime()) - } - - function Null(value: null) { - Fnv1A64(ByteMarker.Null) - } - - function Number(value: number) { - Fnv1A64(ByteMarker.Number) - F64In.setFloat64(0, value) + function BigInt(value: bigint) { + FNV1A64(ByteMarker.BigInt) + F64In.setBigInt64(0, value) for (const byte of F64Out) { - Fnv1A64(byte) + FNV1A64(byte) + } + } + function Date(value: Date) { + FNV1A64(ByteMarker.Date) + Visit(value.getTime()) + } + function Null(value: null) { + FNV1A64(ByteMarker.Null) + } + function Number(value: number) { + FNV1A64(ByteMarker.Number) + F64In.setFloat64(0, value) + for (const byte of F64Out) { + FNV1A64(byte) } } - function Object(value: Record) { - Fnv1A64(ByteMarker.Object) + FNV1A64(ByteMarker.Object) for (const key of globalThis.Object.keys(value).sort()) { Visit(key) Visit(value[key]) } } - function String(value: string) { - Fnv1A64(ByteMarker.String) + FNV1A64(ByteMarker.String) for (let i = 0; i < value.length; i++) { - Fnv1A64(value.charCodeAt(i)) + FNV1A64(value.charCodeAt(i)) } } - + function Symbol(value: symbol) { + FNV1A64(ByteMarker.Symbol) + Visit(value.description) + } function Uint8Array(value: Uint8Array) { - Fnv1A64(ByteMarker.Uint8Array) + FNV1A64(ByteMarker.Uint8Array) for (let i = 0; i < value.length; i++) { - Fnv1A64(value[i]) + FNV1A64(value[i]) } } - function Undefined(value: undefined) { - return Fnv1A64(ByteMarker.Undefined) + return FNV1A64(ByteMarker.Undefined) } - function Visit(value: any) { if (IsArray(value)) { Array(value) } else if (IsBoolean(value)) { Boolean(value) + } else if (IsBigInt(value)) { + BigInt(value) } else if (IsDate(value)) { Date(value) } else if (IsNull(value)) { @@ -170,6 +168,8 @@ export namespace ValueHash { Object(value) } else if (IsString(value)) { String(value) + } else if (IsSymbol(value)) { + Symbol(value) } else if (IsUint8Array(value)) { Uint8Array(value) } else if (IsUndefined(value)) { @@ -178,12 +178,10 @@ export namespace ValueHash { throw new ValueHashError(value) } } - - function Fnv1A64(byte: number) { + function FNV1A64(byte: number) { Hash = Hash ^ Bytes[byte] Hash = (Hash * Prime) % Size } - /** Creates a FNV1A-64 non cryptographic hash of the given value */ export function Create(value: unknown) { Hash = globalThis.BigInt('14695981039346656037') diff --git a/src/value/index.ts b/src/value/index.ts index 5e48c73..5598e30 100644 --- a/src/value/index.ts +++ b/src/value/index.ts @@ -26,7 +26,8 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -export { ValueError, ValueErrorType } from '../errors/index' +export { ValueError, ValueErrorIterator, ValueErrorType } from '../errors/index' +export { ValueHash } from './hash' export { Edit, Insert, Update, Delete } from './delta' export * from './pointer' export * from './value' diff --git a/src/value/value.ts b/src/value/value.ts index 373005b..b767008 100644 --- a/src/value/value.ts +++ b/src/value/value.ts @@ -27,11 +27,12 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import * as Types from '../typebox' -import { ValueErrors, ValueError } from '../errors/index' -import { ValueHash } from '../hash/index' +import { ValueErrors, ValueErrorIterator, ValueError } from '../errors/index' +import { ValueHash } from './hash' import { ValueEqual } from './equal' import { ValueCast } from './cast' import { ValueClone } from './clone' +import { ValueConvert } from './convert' import { ValueCreate } from './create' import { ValueCheck } from './check' import { ValueDelta, Edit } from './delta' @@ -46,7 +47,6 @@ export namespace Value { const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]] return ValueCast.Cast(schema, references, value) } - /** Creates a value from the given type */ export function Create(schema: T, references: [...R]): Types.Static /** Creates a value from the given type */ @@ -55,7 +55,6 @@ export namespace Value { const [schema, references] = args.length === 2 ? [args[0], args[1]] : [args[0], []] return ValueCreate.Create(schema, references) } - /** Returns true if the value matches the given type. */ export function Check(schema: T, references: [...R], value: unknown): value is Types.Static /** Returns true if the value matches the given type. */ @@ -64,36 +63,38 @@ export namespace Value { const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]] return ValueCheck.Check(schema, references, value) } - + /** Converts any type mismatched values to their target type if a conversion is possible. */ + export function Convert(schema: T, references: [...R], value: unknown): unknown + /** Converts any type mismatched values to their target type if a conversion is possible. */ + export function Convert(schema: T, value: unknown): unknown + export function Convert(...args: any[]) { + const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]] + return ValueConvert.Convert(schema, references, value) + } /** Returns a structural clone of the given value */ export function Clone(value: T): T { return ValueClone.Clone(value) } - /** Returns an iterator for each error in this value. */ - export function Errors(schema: T, references: [...R], value: unknown): IterableIterator + export function Errors(schema: T, references: [...R], value: unknown): ValueErrorIterator /** Returns an iterator for each error in this value. */ - export function Errors(schema: T, value: unknown): IterableIterator - export function* Errors(...args: any[]) { + export function Errors(schema: T, value: unknown): ValueErrorIterator + export function Errors(...args: any[]) { const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]] - yield* ValueErrors.Errors(schema, references, value) + return ValueErrors.Errors(schema, references, value) as ValueErrorIterator } - /** Returns true if left and right values are structurally equal */ export function Equal(left: T, right: unknown): right is T { return ValueEqual.Equal(left, right) } - /** Returns edits to transform the current value into the next value */ export function Diff(current: unknown, next: unknown): Edit[] { return ValueDelta.Diff(current, next) } - /** Returns a FNV1A-64 non cryptographic hash of the given value */ export function Hash(value: unknown): bigint { return ValueHash.Create(value) } - /** Returns a new value with edits applied to the given value */ export function Patch(current: unknown, edits: Edit[]): T { return ValueDelta.Patch(current, edits) as T diff --git a/test/runtime/compiler/any.ts b/test/runtime/compiler/any.ts index 7519db0..949147d 100644 --- a/test/runtime/compiler/any.ts +++ b/test/runtime/compiler/any.ts @@ -30,4 +30,12 @@ describe('type/compiler/Any', () => { 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)) + }) }) diff --git a/test/runtime/compiler/array.ts b/test/runtime/compiler/array.ts index adff196..a2ccad6 100644 --- a/test/runtime/compiler/array.ts +++ b/test/runtime/compiler/array.ts @@ -39,10 +39,10 @@ describe('type/compiler/Array', () => { ]) }) - it('Should not validate for an array of intersection types when passing additionalProperties false', () => { + 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.Intersect([A, B], { additionalProperties: false }) + const C = Type.Composite([A, B], { additionalProperties: false }) const T = Type.Array(C) Fail(T, [ { a: 'hello', b: 'hello' }, diff --git a/test/runtime/compiler/bigint.ts b/test/runtime/compiler/bigint.ts new file mode 100644 index 0000000..4845733 --- /dev/null +++ b/test/runtime/compiler/bigint.ts @@ -0,0 +1,84 @@ +import { Type } from '@sinclair/typebox' +import { Ok, Fail } from './validate' + +describe('type/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) + }) +}) diff --git a/test/runtime/compiler/boolean.ts b/test/runtime/compiler/boolean.ts index c0d7997..03f01d8 100644 --- a/test/runtime/compiler/boolean.ts +++ b/test/runtime/compiler/boolean.ts @@ -37,4 +37,12 @@ describe('type/compiler/Boolean', () => { 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)) + }) }) diff --git a/test/runtime/compiler/composite.ts b/test/runtime/compiler/composite.ts new file mode 100644 index 0000000..5635e45 --- /dev/null +++ b/test/runtime/compiler/composite.ts @@ -0,0 +1,73 @@ +import { Type, Static } from '@sinclair/typebox' +import { Ok, Fail } from './validate' + +describe('type/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 }) + }) + 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: 'hello' }) + Fail(P, {}) + }) + it('Should 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]) + Ok(T, { a: 1 }) + Ok(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({ 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]) + Ok(T, { a: 1 }) + Ok(T, { a: 'hello' }) + Ok(T, { a: false }) + Ok(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({ x: Type.Object({ x: Type.String() }) }) + const T = Type.Composite([A, B]) + Ok(T, { x: { x: 1 } }) + Ok(T, { x: { x: 'hello' } }) + Fail(T, { x: { x: false } }) + }) +}) diff --git a/test/runtime/compiler/custom.ts b/test/runtime/compiler/custom.ts index 4cba073..4380a9e 100644 --- a/test/runtime/compiler/custom.ts +++ b/test/runtime/compiler/custom.ts @@ -1,10 +1,8 @@ -import { Custom } from '@sinclair/typebox/custom' -import { Type, Kind } from '@sinclair/typebox' +import { Type, Kind, TypeRegistry } from '@sinclair/typebox' import { Ok, Fail } from './validate' describe('type/compiler/Custom', () => { - Custom.Set('BigInt', (schema, value) => typeof value === 'bigint') - + TypeRegistry.Set('BigInt', (schema, value) => typeof value === 'bigint') it('Should validate bigint', () => { const T = Type.Unsafe({ [Kind]: 'BigInt' }) Ok(T, 1n) diff --git a/test/runtime/compiler/date.ts b/test/runtime/compiler/date.ts index 943bfa0..dfda225 100644 --- a/test/runtime/compiler/date.ts +++ b/test/runtime/compiler/date.ts @@ -34,6 +34,14 @@ describe('type/compiler/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')) diff --git a/test/runtime/compiler/index.ts b/test/runtime/compiler/index.ts index a842d53..d04e227 100644 --- a/test/runtime/compiler/index.ts +++ b/test/runtime/compiler/index.ts @@ -1,6 +1,8 @@ import './any' import './array' +import './bigint' import './boolean' +import './composite' import './custom' import './date' import './unicode' @@ -11,6 +13,7 @@ import './keyof' import './literal' import './modifier' import './never' +import './not' import './null' import './number' import './object' @@ -26,6 +29,7 @@ import './ref' import './regex' import './required' import './string' +import './symbol' import './tuple' import './uint8array' import './union' diff --git a/test/runtime/compiler/integer.ts b/test/runtime/compiler/integer.ts index 1d3b85d..598aa04 100644 --- a/test/runtime/compiler/integer.ts +++ b/test/runtime/compiler/integer.ts @@ -6,7 +6,18 @@ describe('type/compiler/Integer', () => { 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) @@ -40,7 +51,14 @@ describe('type/compiler/Integer', () => { 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) @@ -64,8 +82,4 @@ describe('type/compiler/Integer', () => { Ok(T, 9) Fail(T, 10) }) - - it('Should not validate NaN', () => { - Fail(Type.Integer(), NaN) - }) }) diff --git a/test/runtime/compiler/intersect.ts b/test/runtime/compiler/intersect.ts index 5cb5245..610dd7f 100644 --- a/test/runtime/compiler/intersect.ts +++ b/test/runtime/compiler/intersect.ts @@ -2,82 +2,82 @@ import { Type, Static } from '@sinclair/typebox' import { Ok, Fail } from './validate' describe('type/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({ a: Type.String() }) - const B = Type.Object({ b: Type.Number() }) - const T = Type.Intersect([A, B], { additionalProperties: false }) - Ok(T, { a: 'hello', b: 42 }) - }) - - it('Should intersect with partial', () => { - const A = Type.Partial(Type.Object({ a: Type.Number() })) - const B = Type.Partial(Type.Object({ b: Type.Number() })) - const P = Type.Intersect([A, B], { additionalProperties: false }) - Ok(P, { a: 1, b: 2 }) - // ok(P, { a: 1 }) - // ok(P, { b: 1 }) - // ok(P, {}) - // fail(P, { c: 1 }) - }) - - it('Should intersect with overlapping same type', () => { - const A = Type.Object({ a: Type.Number() }) - const B = Type.Object({ a: Type.Number() }) - const P = Type.Intersect([A, B]) - Ok(P, { a: 1 }) - Fail(P, { a: 'hello' }) - Fail(P, {}) - }) - - it('Should intersect with overlapping varying type', () => { - const A = Type.Object({ a: Type.Number() }) - const B = Type.Object({ a: Type.String() }) - const T = Type.Intersect([A, B]) - Ok(T, { a: 1 }) - Ok(T, { a: 'hello' }) - Fail(T, {}) - }) - - it('Should intersect 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.Intersect([A, B, C, D]) - Ok(T, { a: 1 }) - Ok(T, { a: 'hello' }) - Ok(T, { a: false }) - Ok(T, { a: null }) - Fail(T, { a: [] }) - Fail(T, {}) - }) - - it('Should pick from intersected 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.Intersect([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 }) + const T = Type.Intersect([A, B], {}) + Ok(T, { x: 1, y: 1 }) }) - - it('Should omit from intersected type', () => { + 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 C = Type.Object({ z: Type.Number() }) - const T = Type.Intersect([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 }) + const T = Type.Intersect([A, B], {}) + Ok(T, { x: 1, y: 1 }) + Fail(T, { x: 1 }) + Fail(T, { y: 1 }) }) - - it('Should intersect nested object properties', () => { - const A = Type.Object({ x: Type.Object({ x: Type.Number() }) }) - const B = Type.Object({ x: Type.Object({ x: Type.String() }) }) + 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 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: { x: 1 } }) - Ok(T, { x: { x: 'hello' } }) - Fail(T, { x: { x: false } }) + 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 }) }) }) diff --git a/test/runtime/compiler/literal.ts b/test/runtime/compiler/literal.ts index a7251b7..ce81439 100644 --- a/test/runtime/compiler/literal.ts +++ b/test/runtime/compiler/literal.ts @@ -10,12 +10,10 @@ describe('type/compiler/Literal', () => { 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) @@ -28,13 +26,11 @@ describe('type/compiler/Literal', () => { 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) diff --git a/test/runtime/compiler/never.ts b/test/runtime/compiler/never.ts index a190367..6cc53bb 100644 --- a/test/runtime/compiler/never.ts +++ b/test/runtime/compiler/never.ts @@ -6,17 +6,14 @@ describe('type/compiler/Never', () => { 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]) @@ -33,4 +30,12 @@ describe('type/compiler/Never', () => { 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)) + }) }) diff --git a/test/runtime/compiler/not.ts b/test/runtime/compiler/not.ts new file mode 100644 index 0000000..27a07c5 --- /dev/null +++ b/test/runtime/compiler/not.ts @@ -0,0 +1,38 @@ +import { Type } from '@sinclair/typebox' +import { Ok, Fail } from './validate' + +describe('type/compiler/Not', () => { + it('Should validate with not number', () => { + const T = Type.Not(Type.Number(), Type.String()) + Fail(T, 1) + Ok(T, 'A') + }) + it('Should validate with union left', () => { + // prettier-ignore + const T = Type.Not(Type.Union([ + Type.Literal('A'), + Type.Literal('B'), + Type.Literal('C') + ]), Type.String()) + Fail(T, 'A') + Fail(T, 'B') + Fail(T, 'C') + Ok(T, 'D') + }) + it('Should validate with union right', () => { + // prettier-ignore + const T = Type.Not(Type.Number(), Type.Union([ + Type.String(), + Type.Boolean() + ])) + Fail(T, 1) + Ok(T, 'A') + Ok(T, true) + }) + it('Should not validate with symmetric left right', () => { + // prettier-ignore + const T = Type.Not(Type.Number(), Type.Number()) + Fail(T, 1) + Fail(T, true) // not a number, but not a number either? + }) +}) diff --git a/test/runtime/compiler/null.ts b/test/runtime/compiler/null.ts index 1253ea6..fcc8257 100644 --- a/test/runtime/compiler/null.ts +++ b/test/runtime/compiler/null.ts @@ -6,34 +6,36 @@ describe('type/compiler/Null', () => { 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)) + }) }) diff --git a/test/runtime/compiler/number.ts b/test/runtime/compiler/number.ts index 90a229f..59081b8 100644 --- a/test/runtime/compiler/number.ts +++ b/test/runtime/compiler/number.ts @@ -6,12 +6,22 @@ describe('type/compiler/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') @@ -36,32 +46,32 @@ describe('type/compiler/Number', () => { 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) }) - - it('Should not validate NaN', () => { - Fail(Type.Number(), NaN) - }) }) diff --git a/test/runtime/compiler/object.ts b/test/runtime/compiler/object.ts index 5a4bbe1..699b533 100644 --- a/test/runtime/compiler/object.ts +++ b/test/runtime/compiler/object.ts @@ -6,27 +6,34 @@ describe('type/compiler/Object', () => { 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(), @@ -43,7 +50,6 @@ describe('type/compiler/Object', () => { e: { x: 10, y: 20 }, }) }) - it('Should not validate with incorrect property values', () => { const T = Type.Object({ a: Type.Number(), @@ -60,7 +66,6 @@ describe('type/compiler/Object', () => { e: { x: 10, y: 20 }, }) }) - it('Should allow additionalProperties by default', () => { const T = Type.Object({ a: Type.Number(), @@ -72,7 +77,6 @@ describe('type/compiler/Object', () => { c: true, }) }) - it('Should not allow an empty object if minProperties is set to 1', () => { const T = Type.Object( { @@ -85,7 +89,6 @@ describe('type/compiler/Object', () => { Ok(T, { b: 'hello' }) Fail(T, {}) }) - it('Should not allow 3 properties if maxProperties is set to 2', () => { const T = Type.Object( { @@ -103,7 +106,6 @@ describe('type/compiler/Object', () => { c: true, }) }) - it('Should not allow additionalProperties if additionalProperties is false', () => { const T = Type.Object( { @@ -118,7 +120,6 @@ describe('type/compiler/Object', () => { c: true, }) }) - it('Should not allow properties for an empty object when additionalProperties is false', () => { const T = Type.Object({}, { additionalProperties: false }) Ok(T, {}) @@ -139,7 +140,6 @@ describe('type/compiler/Object', () => { '!@#$%^&*(': 4, }) }) - it('Should validate schema additional properties of string', () => { const T = Type.Object( { @@ -227,4 +227,16 @@ describe('type/compiler/Object', () => { Ok(T, { x: undefined }) Ok(T, {}) }) + it('Should not check undefined for optional property of number', () => { + const T = Type.Object({ x: Type.Optional(Type.Number()) }) + Ok(T, { x: 1 }) + Ok(T, {}) + Fail(T, { x: undefined }) + }) + it('Should check undefined for optional property of undefined', () => { + const T = Type.Object({ x: Type.Optional(Type.Undefined()) }) + Fail(T, { x: 1 }) + Ok(T, {}) + Ok(T, { x: undefined }) + }) }) diff --git a/test/runtime/compiler/omit.ts b/test/runtime/compiler/omit.ts index 4263499..9d4ed27 100644 --- a/test/runtime/compiler/omit.ts +++ b/test/runtime/compiler/omit.ts @@ -57,4 +57,30 @@ describe('type/compiler/Omit', () => { 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(), + }, + { additionalProperties: false }, + ) + const T = Type.Omit(A, Type.Literal('x')) + 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 }) + }) }) diff --git a/test/runtime/compiler/pick.ts b/test/runtime/compiler/pick.ts index e6c9eb8..dd8f234 100644 --- a/test/runtime/compiler/pick.ts +++ b/test/runtime/compiler/pick.ts @@ -57,4 +57,30 @@ describe('type/compiler/Pick', () => { 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(), + }, + { additionalProperties: false }, + ) + const T = Type.Pick(A, Type.Literal('x')) + 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(), + }, + { additionalProperties: false }, + ) + const T = Type.Pick(A, Type.Never()) + Fail(T, { x: 1, y: 1, z: 1 }) + Ok(T, {}) + }) }) diff --git a/test/runtime/compiler/record.ts b/test/runtime/compiler/record.ts index d73a148..d12e65d 100644 --- a/test/runtime/compiler/record.ts +++ b/test/runtime/compiler/record.ts @@ -136,4 +136,12 @@ describe('type/compiler/Record', () => { const T = Type.Record(Type.Number(), Type.Number()) Fail(T, { '0': 1, '1': 2, '2': 3, '3': 4, a: 'hello' }) }) + 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()) + }) }) diff --git a/test/runtime/compiler/ref.ts b/test/runtime/compiler/ref.ts index 632ac0f..ef23a87 100644 --- a/test/runtime/compiler/ref.ts +++ b/test/runtime/compiler/ref.ts @@ -45,26 +45,38 @@ describe('type/compiler/Ref', () => { }) it('Should de-reference object property schema', () => { - const R = Type.Object( + const T = Type.Object( { name: Type.String(), }, { $id: 'R' }, ) - const T = Type.Object( + const R = Type.Object( { x: Type.Number(), y: Type.Number(), z: Type.Number(), - r: Type.Optional(Type.Ref(R)), + r: Type.Optional(Type.Ref(T)), }, { $id: 'T' }, ) - Ok(T, { x: 1, y: 2, z: 3 }, [R]) - Ok(T, { x: 1, y: 2, z: 3, r: { name: 'hello' } }, [R]) - Fail(T, { x: 1, y: 2, z: 3, r: { name: 1 } }, [R]) - Fail(T, { x: 1, y: 2, z: 3, r: {} }, [R]) + 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) + Ok(R, { id: '', nodes: [{ id: '', nodes: [] }] }, [T]) + Fail(R, { id: '', nodes: [{ id: 1, nodes: [] }] }, [T]) }) }) diff --git a/test/runtime/compiler/string.ts b/test/runtime/compiler/string.ts index baff1e1..0f43dc0 100644 --- a/test/runtime/compiler/string.ts +++ b/test/runtime/compiler/string.ts @@ -30,7 +30,14 @@ describe('type/compiler/String', () => { 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') diff --git a/test/runtime/compiler/symbol.ts b/test/runtime/compiler/symbol.ts new file mode 100644 index 0000000..fcca5a5 --- /dev/null +++ b/test/runtime/compiler/symbol.ts @@ -0,0 +1,42 @@ +import { Type } from '@sinclair/typebox' +import { Ok, Fail } from './validate' + +describe('type/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)) + }) +}) diff --git a/test/runtime/compiler/validate.ts b/test/runtime/compiler/validate.ts index c242768..070d609 100644 --- a/test/runtime/compiler/validate.ts +++ b/test/runtime/compiler/validate.ts @@ -1,7 +1,6 @@ import { TypeCompiler } from '@sinclair/typebox/compiler' import { Value } from '@sinclair/typebox/value' -import { TSchema } from '@sinclair/typebox' -import { Format } from 'src/format' +import { TSchema, FormatRegistry } from '@sinclair/typebox' // ------------------------------------------------------------------------- // Test Formats: https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts @@ -59,9 +58,9 @@ function isDateTime(str: string, strictTimeZone?: boolean): boolean { // Use Formats // ------------------------------------------------------------------------- -Format.Set('email', (value) => EMAIL.test(value)) -Format.Set('uuid', (value) => UUID.test(value)) -Format.Set('date-time', (value) => isDateTime(value, true)) +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(schema: T, data: unknown, references: any[] = []) { const C = TypeCompiler.Compile(schema, references) diff --git a/test/runtime/compiler/void.ts b/test/runtime/compiler/void.ts index a5e8099..4963c0e 100644 --- a/test/runtime/compiler/void.ts +++ b/test/runtime/compiler/void.ts @@ -6,34 +6,32 @@ describe('type/compiler/Void', () => { 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.Null() - Ok(T, null) - }) - - it('Should not validate undefined', () => { const T = Type.Void() - Fail(T, undefined) + 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) }) }) diff --git a/test/runtime/conditional/any.ts b/test/runtime/conditional/any.ts deleted file mode 100644 index b450886..0000000 --- a/test/runtime/conditional/any.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Any', () => { - it('Should extend Any', () => { - type T = any extends any ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - it('Should extend Unknown', () => { - type T = any extends unknown ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = any extends string ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.String()) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Boolean', () => { - type T = any extends boolean ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Number', () => { - type T = any extends number ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Number()) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Integer', () => { - type T = any extends number ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Array 1', () => { - type T = any extends Array ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Array 2', () => { - type T = any extends Array ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Array(Type.String())) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Tuple', () => { - type T = any extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Object 1', () => { - type T = any extends object ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Object 2', () => { - type T = any extends {} ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Object 3', () => { - type T = any extends { a: number } ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Object({ a: Type.Number() })) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Union 1', () => { - type T = any extends number | string ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Union 2', () => { - type T = any extends any | number ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Union([Type.Any(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - it('Should extend Null', () => { - type T = any extends null ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Null()) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Undefined', () => { - type T = any extends undefined ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.Union) - }) - it('Should extend Void', () => { - type T = any extends void ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Void()) - Assert.deepEqual(R, StructuralResult.Union) - }) - - it('Should extend Date', () => { - type T = any extends Date ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Date()) - Assert.deepEqual(R, StructuralResult.Union) - }) -}) diff --git a/test/runtime/conditional/array.ts b/test/runtime/conditional/array.ts deleted file mode 100644 index f9ca50a..0000000 --- a/test/runtime/conditional/array.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Array', () => { - // ---------------------------------------------- - // Generic Varying - // ---------------------------------------------- - - it('Should extend Array Varying 1', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array Varying 2', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array Varying 3', () => { - type T = Array extends Array ? 1 : 2 // 1 - const R = Structural.Check(Type.Array(Type.Any()), Type.Array(Type.String())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array Varying 4', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Number()), Type.Array(Type.String())) - Assert.deepEqual(R, StructuralResult.False) - }) - - // ---------------------------------------------- - // Any - // ---------------------------------------------- - it('Should extend Any', () => { - type T = Array extends any ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = Array extends unknown ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = Array extends string ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = Array extends boolean ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = Array extends number ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = Array extends number ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 1', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array 2', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array 3', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Tuple', () => { - type T = Array extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = Array extends object ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = Array extends {} ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 3', () => { - type T = Array extends { a: number } ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Object({ a: Type.Number() })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 4', () => { - type T = Array extends { length: '1' } ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Object({ length: Type.Literal('1') })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 5', () => { - type T = Array extends { length: number } ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Object({ length: Type.Number() })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 1', () => { - type T = Array extends number | string ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = Array extends any | number ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = Array extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Union([Type.Any(), Type.Array(Type.Any())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 4', () => { - type T = Array extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Union([Type.Any(), Type.Array(Type.Any())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 5', () => { - type T = Array extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Union([Type.Any(), Type.Array(Type.String())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = Array extends null ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = Array extends undefined ? 1 : 2 - const R = Structural.Check(Type.Array(Type.Any()), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - // ---------------------------------------------- - // Constrained - // ---------------------------------------------- - - it('Should extend constrained Any', () => { - type T = Array extends any ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Unknown', () => { - type T = Array extends unknown ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained String', () => { - type T = Array extends string ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Boolean', () => { - type T = Array extends boolean ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Number', () => { - type T = Array extends number ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Integer', () => { - type T = Array extends number ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Array 1', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Array 2', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Array 3', () => { - type T = Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Tuple', () => { - type T = Array extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Object 1', () => { - type T = Array extends object ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Object 2', () => { - type T = Array extends {} ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Object 3', () => { - type T = Array extends { a: number } ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Object({ a: Type.Number() })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Object 4', () => { - type T = Array extends { length: '1' } ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Object({ length: Type.Literal('1') })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Object 5', () => { - type T = Array extends { length: number } ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Object({ length: Type.Number() })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Union 1', () => { - type T = Array extends number | string ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Union([Type.Null(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Union 2', () => { - type T = Array extends any | number ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Union([Type.Any(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Union 3', () => { - type T = Array extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Union([Type.Any(), Type.Array(Type.Any())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Union 4', () => { - type T = Array extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Union([Type.Any(), Type.Array(Type.Any())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Union 5', () => { - type T = Array extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Union([Type.Any(), Type.Array(Type.String())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Null', () => { - type T = Array extends null ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Undefined', () => { - type T = Array extends undefined ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Void', () => { - type T = Array extends void ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = Array extends Date ? 1 : 2 - const R = Structural.Check(Type.Array(Type.String()), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/boolean.ts b/test/runtime/conditional/boolean.ts deleted file mode 100644 index 4699a97..0000000 --- a/test/runtime/conditional/boolean.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Boolean', () => { - it('Should extend Any', () => { - type T = boolean extends any ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = boolean extends string ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = boolean extends boolean ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Number', () => { - type T = boolean extends number ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = boolean extends number ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = boolean extends Array ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = boolean extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = boolean extends Record ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = boolean extends {} ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = boolean extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = boolean extends object ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = boolean extends number | string ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = boolean extends any | number ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = boolean extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = boolean extends null ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = boolean extends undefined ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = boolean extends void ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = boolean extends Date ? 1 : 2 - const R = Structural.Check(Type.Boolean(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/constructor.ts b/test/runtime/conditional/constructor.ts deleted file mode 100644 index 38cd73b..0000000 --- a/test/runtime/conditional/constructor.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Constructor', () => { - it('Should extend Function', () => { - type T = (new () => number) extends () => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Function([], Type.Number())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Constructor 1', () => { - type T = (new () => number) extends new () => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Constructor([], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 2', () => { - type T = (new () => any) extends new () => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Any()), Type.Constructor([], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 3', () => { - type T = (new () => number) extends new () => any ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Any()), Type.Constructor([], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 4', () => { - type T = (new (a: number) => number) extends new () => any ? 1 : 2 - const R = Structural.Check(Type.Constructor([Type.Number()], Type.Number()), Type.Constructor([], Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Constructor 5', () => { - type T = (new (a: number | string) => number) extends new (a: number) => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([Type.Union([Type.Number(), Type.String()])], Type.Number()), Type.Constructor([Type.Number()], Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 6', () => { - type T = (new (a: number) => number) extends new (a: number | string) => any ? 1 : 2 - const R = Structural.Check(Type.Constructor([Type.Number()], Type.Number()), Type.Constructor([Type.Union([Type.Number(), Type.String()])], Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Constructor 7', () => { - type T = (new (a: number, b: number) => number) extends new (a: number) => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([Type.Number(), Type.Number()], Type.Number()), Type.Constructor([Type.Number()], Type.Number())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Constructor 8', () => { - type T = (new (a: number) => number) extends new (a: number, b: number) => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([Type.Number()], Type.Number()), Type.Constructor([Type.Number(), Type.Number()], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 9', () => { - type T = (new () => number) extends new () => any ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Constructor([], Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 9', () => { - type T = (new () => any) extends new () => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Any()), Type.Constructor([], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 10', () => { - type T = (new () => Array) extends new () => object ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Array(Type.Any())), Type.Constructor([], Type.Object({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - it('Should extend Constructor 11', () => { - type T = (new () => Array) extends new () => object ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Array(Type.String())), Type.Constructor([], Type.Object({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 12', () => { - type T = (new () => object) extends new () => Array ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Object({})), Type.Constructor([], Type.Array(Type.Any()))) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Constructor 13', () => { - type T = (new (a: unknown) => number) extends new (a: any) => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([Type.Unknown()], Type.Number({})), Type.Constructor([Type.Any()], Type.Number({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 14', () => { - type T = (new (a: any) => number) extends new (a: unknown) => number ? 1 : 2 - const R = Structural.Check(Type.Constructor([Type.Any()], Type.Number({})), Type.Constructor([Type.Unknown()], Type.Number({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 15', () => { - type T = (new () => any) extends new () => unknown ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Any({})), Type.Constructor([], Type.Unknown({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Constructor 16', () => { - type T = (new () => unknown) extends new () => any ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Unknown({})), Type.Constructor([], Type.Any({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Any', () => { - type T = (new () => number) extends any ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = (new () => number) extends string ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = (new () => number) extends boolean ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = (new () => number) extends number ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = (new () => number) extends number ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 1', () => { - type T = (new () => number) extends Array ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 2', () => { - type T = (new () => number) extends Array ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 3', () => { - type T = (new () => number) extends Array ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = (new () => number) extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = (() => number) extends Record ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = (new () => number) extends object ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = (new () => number) extends {} ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 3', () => { - type T = (new () => number) extends { a: number } ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Object({ a: Type.Number() })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 4', () => { - type T = (new () => number) extends { length: '1' } ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Object({ length: Type.Literal('1') })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = (new () => number) extends number | string ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Union([Type.Null(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = (new () => number) extends any | number ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Union([Type.Any(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = (new () => number) extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.Any())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 4', () => { - type T = (new () => number) extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.Any())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 5', () => { - type T = (new () => number) extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.String())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = (new () => number) extends null ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = (new () => number) extends undefined ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = (new () => number) extends void ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = (new () => number) extends Date ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/date.ts b/test/runtime/conditional/date.ts deleted file mode 100644 index 29920d2..0000000 --- a/test/runtime/conditional/date.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Date', () => { - it('Should extend Any', () => { - type T = Date extends any ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = Date extends unknown ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = Date extends string ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = Date extends boolean ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = Date extends number ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = Date extends number ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = Date extends Array ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - it('Should extend Tuple', () => { - type T = Date extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = Date extends Record ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = Date extends {} ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = Date extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = Date extends object ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = Date extends number | string ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = Date extends any | number ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = Date extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null', () => { - type T = Date extends null ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = Date extends undefined ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = Date extends void ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = Date extends Date ? 1 : 2 - const R = Structural.Check(Type.Date(), Type.Date()) - Assert.deepEqual(R, StructuralResult.True) - }) -}) diff --git a/test/runtime/conditional/function.ts b/test/runtime/conditional/function.ts deleted file mode 100644 index 64eb9f0..0000000 --- a/test/runtime/conditional/function.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Function', () => { - it('Should extend Constructor 1', () => { - type T = (() => number) extends new () => number ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Constructor([], Type.Number())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Function 1', () => { - type T = (() => number) extends () => number ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Function([], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 2', () => { - type T = (() => any) extends () => number ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Any()), Type.Function([], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 3', () => { - type T = (() => number) extends () => any ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Any()), Type.Function([], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 4', () => { - type T = ((a: number) => number) extends () => any ? 1 : 2 - const R = Structural.Check(Type.Function([Type.Number()], Type.Number()), Type.Function([], Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Function 5', () => { - type T = ((a: number | string) => number) extends (a: number) => number ? 1 : 2 - const R = Structural.Check(Type.Function([Type.Union([Type.Number(), Type.String()])], Type.Number()), Type.Function([Type.Number()], Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 6', () => { - type T = ((a: number) => number) extends (a: number | string) => any ? 1 : 2 - const R = Structural.Check(Type.Function([Type.Number()], Type.Number()), Type.Function([Type.Union([Type.Number(), Type.String()])], Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Function 7', () => { - type T = ((a: number, b: number) => number) extends (a: number) => number ? 1 : 2 - const R = Structural.Check(Type.Function([Type.Number(), Type.Number()], Type.Number()), Type.Function([Type.Number()], Type.Number())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Function 8', () => { - type T = ((a: number) => number) extends (a: number, b: number) => number ? 1 : 2 - const R = Structural.Check(Type.Function([Type.Number()], Type.Number()), Type.Function([Type.Number(), Type.Number()], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 9', () => { - type T = (() => number) extends () => any ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Function([], Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 9', () => { - type T = (() => any) extends () => number ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Any()), Type.Function([], Type.Number())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 10', () => { - type T = (() => Array) extends () => object ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Array(Type.Any())), Type.Function([], Type.Object({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 11', () => { - type T = (() => Array) extends () => object ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Array(Type.String())), Type.Function([], Type.Object({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 12', () => { - type T = (() => object) extends () => Array ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Object({})), Type.Function([], Type.Array(Type.Any()))) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Function 13', () => { - type T = ((a: unknown) => number) extends (a: any) => number ? 1 : 2 - const R = Structural.Check(Type.Function([Type.Unknown()], Type.Number({})), Type.Function([Type.Any()], Type.Number({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 14', () => { - type T = ((a: any) => number) extends (a: unknown) => number ? 1 : 2 - const R = Structural.Check(Type.Function([Type.Any()], Type.Number({})), Type.Function([Type.Unknown()], Type.Number({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 15', () => { - type T = (() => any) extends () => unknown ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Any({})), Type.Function([], Type.Unknown({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Function 16', () => { - type T = (() => unknown) extends () => any ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Unknown({})), Type.Function([], Type.Any({}))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Any', () => { - type T = (() => number) extends any ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = (() => number) extends string ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = (() => number) extends boolean ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = (() => number) extends number ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = (() => number) extends number ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 1', () => { - type T = (() => number) extends Array ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 2', () => { - type T = (() => number) extends Array ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 3', () => { - type T = (() => number) extends Array ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = (() => number) extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = (() => number) extends Record ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = (() => number) extends object ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = (() => number) extends {} ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 3', () => { - type T = (() => number) extends { a: number } ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Object({ a: Type.Number() })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 4', () => { - type T = (() => number) extends { length: '1' } ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Object({ length: Type.Literal('1') })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 5', () => { - type T = (() => number) extends { length: number } ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Object({ length: Type.Number() })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 1', () => { - type T = (() => number) extends number | string ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Union([Type.Null(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = (() => number) extends any | number ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Union([Type.Any(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = (() => number) extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.Any())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 4', () => { - type T = (() => number) extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.Any())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 5', () => { - type T = (() => number) extends any | Array ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.String())])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = (() => number) extends null ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = (() => number) extends undefined ? 1 : 2 - const R = Structural.Check(Type.Function([], Type.Number()), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = (() => number) extends Date ? 1 : 2 - const R = Structural.Check(Type.Constructor([], Type.Number()), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/integer.ts b/test/runtime/conditional/integer.ts deleted file mode 100644 index 78b1bca..0000000 --- a/test/runtime/conditional/integer.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Integer', () => { - it('Should extend Any', () => { - type T = number extends any ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = number extends unknown ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = number extends string ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = number extends boolean ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = number extends number ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Number()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Integer', () => { - type T = number extends number ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array', () => { - type T = number extends Array ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = number extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = number extends Record ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = number extends {} ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = number extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = number extends object ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = number extends number | string ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = number extends any | number ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = number extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = number extends null ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = number extends undefined ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = number extends undefined ? 1 : 2 - const R = Structural.Check(Type.Integer(), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = number extends Date ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/literal.ts b/test/runtime/conditional/literal.ts deleted file mode 100644 index a699ebf..0000000 --- a/test/runtime/conditional/literal.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Literal', () => { - // ------------------------------------------------------------------- - // String Literal - // ------------------------------------------------------------------- - it('Should extend Any (String)', () => { - type T = 'hello' extends any ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown (String)', () => { - type T = 'hello' extends unknown ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String (String)', () => { - type T = 'hello' extends string ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.String()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Boolean (String)', () => { - type T = 'hello' extends boolean ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number (String)', () => { - type T = 'hello' extends number ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer (String)', () => { - type T = 'hello' extends number ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array (String)', () => { - type T = 'hello' extends Array ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - it('Should extend Tuple (String)', () => { - type T = 'hello' extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - it('Should extend Object 1 (String)', () => { - type T = 'hello' extends {} ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2 (String)', () => { - type T = 'hello' extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3 (String)', () => { - type T = 'hello' extends object ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1 (String)', () => { - type T = 'hello' extends number | string ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2 (String)', () => { - type T = 'hello' extends any | number ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3 (String)', () => { - type T = 'hello' extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null (String)', () => { - type T = 'hello' extends null ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined (String)', () => { - type T = 'hello' extends undefined ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - // ------------------------------------------------------------------- - // Number Literal - // ------------------------------------------------------------------- - - it('Should extend Any (Number)', () => { - type T = 10 extends any ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown (Number)', () => { - type T = 10 extends unknown ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String (Number)', () => { - type T = 10 extends string ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean (Number)', () => { - type T = 10 extends boolean ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number (Number)', () => { - type T = 10 extends number ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Number()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Integer (Number)', () => { - type T = 10 extends number ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Integer()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array (Number)', () => { - type T = 10 extends Array ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - it('Should extend Tuple (Number)', () => { - type T = 10 extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - it('Should extend Object 1 (Number)', () => { - type T = 10 extends {} ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2 (Number)', () => { - type T = 10 extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3 (Number)', () => { - type T = 10 extends object ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1 (Number)', () => { - type T = 10 extends number | string ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2 (Number)', () => { - type T = 10 extends any | number ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3 (Number)', () => { - type T = 10 extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null (Number)', () => { - type T = 10 extends null ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined (Number)', () => { - type T = 10 extends undefined ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - // ------------------------------------------------------------------- - // Boolean Literal - // ------------------------------------------------------------------- - - it('Should extend Any (Boolean)', () => { - type T = true extends any ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown (Boolean)', () => { - type T = true extends unknown ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String (Boolean)', () => { - type T = true extends string ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean (Boolean)', () => { - type T = true extends boolean ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Number (Boolean)', () => { - type T = true extends number ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer (Boolean)', () => { - type T = true extends number ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array (Boolean)', () => { - type T = true extends Array ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple (Boolean)', () => { - type T = true extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record 1', () => { - type T = 'hello' extends Record ? 1 : 2 - const R = Structural.Check(Type.Literal('hello'), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 2', () => { - type T = 10 extends Record ? 1 : 2 - const R = Structural.Check(Type.Literal(10), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record 3', () => { - type T = true extends Record ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1 (Boolean)', () => { - type T = true extends {} ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2 (Boolean)', () => { - type T = true extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3 (Boolean)', () => { - type T = true extends object ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1 (Boolean)', () => { - type T = true extends number | string ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2 (Boolean)', () => { - type T = true extends any | number ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3 (Boolean)', () => { - type T = true extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null (Boolean)', () => { - type T = true extends null ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined (Boolean)', () => { - type T = true extends undefined ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = true extends void ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = true extends Date ? 1 : 2 - const R = Structural.Check(Type.Literal(true), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/null.ts b/test/runtime/conditional/null.ts deleted file mode 100644 index 1b1fc96..0000000 --- a/test/runtime/conditional/null.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Null', () => { - it('Should extend Any', () => { - type T = null extends any ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = null extends unknown ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = null extends string ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = null extends boolean ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = null extends number ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = null extends number ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = null extends Array ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = null extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = null extends Record ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = null extends {} ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 2', () => { - type T = null extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = null extends object ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = null extends number | string ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = null extends any | number ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = null extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null', () => { - type T = null extends null ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Null()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Undefined', () => { - type T = null extends undefined ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = null extends void ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = null extends Date ? 1 : 2 - const R = Structural.Check(Type.Null(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/number.ts b/test/runtime/conditional/number.ts deleted file mode 100644 index 7cb136f..0000000 --- a/test/runtime/conditional/number.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Number', () => { - it('Should extend Any', () => { - type T = number extends any ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = number extends unknown ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = number extends string ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = number extends boolean ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = number extends number ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Number()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Integer', () => { - type T = number extends number ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array', () => { - type T = number extends Array ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = number extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = number extends Record ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = number extends {} ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = number extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = number extends object ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = number extends number | string ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = number extends any | number ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = number extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = number extends null ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = number extends undefined ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = number extends void ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = number extends Date ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/object.ts b/test/runtime/conditional/object.ts deleted file mode 100644 index fb327cc..0000000 --- a/test/runtime/conditional/object.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Object', () => { - // ---------------------------------------------------------- - // Record - // ---------------------------------------------------------- - - it('Should extend Record 1', () => { - type T = { a: number; b: number } extends Record<'a' | 'b', number> ? 1 : 2 - const A = Type.Object({ a: Type.Number(), b: Type.Number() }) - const B = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 2', () => { - type T = { a: number; b: number } extends Record ? 1 : 2 - const A = Type.Object({ a: Type.Number(), b: Type.Number() }) - const B = Type.Record(Type.String(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 3', () => { - type T = { a: number; b: number } extends Record ? 1 : 2 - const A = Type.Object({ a: Type.Number(), b: Type.Number() }) - const B = Type.Record(Type.Number(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 4', () => { - type T = { a: number; b: number } extends Record<'a' | 'b', number> ? 1 : 2 - const A = Type.Object({ a: Type.Number(), b: Type.Number() }) - const B = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 5', () => { - type T = { a: number; b: number } extends Record<'a' | 'b', number> ? 1 : 2 - const A = Type.Object({ a: Type.Number(), b: Type.Number() }) - const B = Type.Record(Type.String(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 6', () => { - type T = { a: number; b: number } extends Record<'a' | 'b', number> ? 1 : 2 - const A = Type.Object({ a: Type.Number(), b: Type.Number() }) - const B = Type.Record(Type.Number(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) - }) - - // ---------------------------------------------------------- - // Standard - // ---------------------------------------------------------- - - it('Should extend Any', () => { - type T = { a: number } extends any ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = { a: number } extends string ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = { a: number } extends boolean ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = { a: number } extends number ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = { a: number } extends number ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = { a: number } extends Array ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - it('Should extend Tuple', () => { - type T = { a: number } extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - it('Should extend Object 1', () => { - type T = { a: number } extends {} ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = { a: number } extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Object({ a: Type.Literal(10) })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = { a: number } extends object ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 1', () => { - type T = { a: number } extends number | string ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Union([Type.Null(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = { a: number } extends any | number ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = { a: number } extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null', () => { - type T = { a: number } extends null ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = { a: number } extends undefined ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = { a: number } extends void ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = { a: number } extends Date ? 1 : 2 - const R = Structural.Check(Type.Object({ a: Type.Number() }), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/promise.ts b/test/runtime/conditional/promise.ts deleted file mode 100644 index 68ad4b9..0000000 --- a/test/runtime/conditional/promise.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Promise', () => { - // ---------------------------------------------- - // Generic Varying - // ---------------------------------------------- - - it('Should extend Promise Varying 1', () => { - type T = Promise extends Promise ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Promise(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Promise Varying 2', () => { - type T = Promise extends Promise ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.String()), Type.Promise(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Promise Varying 3', () => { - type T = Promise extends Promise ? 1 : 2 // 1 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Promise(Type.String())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Promise Varying 4', () => { - type T = Promise extends Promise ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Promise(Type.String())) - Assert.deepEqual(R, StructuralResult.False) - }) - - // ---------------------------------------------- - // Any - // ---------------------------------------------- - - it('Should extend Any', () => { - type T = Promise extends any ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = Promise extends unknown ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = Promise extends string ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = Promise extends boolean ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = Promise extends number ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = Promise extends number ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = Promise extends Array ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = Promise extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = Promise extends Record ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = Promise extends {} ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = Promise extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = Promise extends object ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 1', () => { - type T = Promise extends number | string ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = Promise extends any | number ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = Promise extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null', () => { - type T = Promise extends null ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = Promise extends undefined ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - // ---------------------------------------------- - // Constrained - // ---------------------------------------------- - - it('Should extend constrained Any', () => { - type T = Promise extends any ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Unknown', () => { - type T = Promise extends unknown ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained String', () => { - type T = Promise extends string ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Boolean', () => { - type T = Promise extends boolean ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Number', () => { - type T = Promise extends number ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Any()), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Integer', () => { - type T = Promise extends number ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Array', () => { - type T = Promise extends Array ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Tuple', () => { - type T = Promise extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Object 1', () => { - type T = Promise extends {} ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Object 2', () => { - type T = Promise extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Object 3', () => { - type T = Promise extends object ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Union 1', () => { - type T = Promise extends number | string ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Union 2', () => { - type T = Promise extends any | number ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend constrained Union 2', () => { - type T = Promise extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Null', () => { - type T = Promise extends null ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend constrained Undefined', () => { - type T = Promise extends undefined ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = Promise extends void ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = Promise extends Date ? 1 : 2 - const R = Structural.Check(Type.Promise(Type.Number()), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/string.ts b/test/runtime/conditional/string.ts deleted file mode 100644 index 783a25e..0000000 --- a/test/runtime/conditional/string.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/String', () => { - it('Should extend Any', () => { - type T = string extends any ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = string extends unknown ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = string extends string ? 1 : 2 - const R = Structural.Check(Type.String(), Type.String()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Boolean', () => { - type T = string extends boolean ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = string extends number ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = string extends number ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = string extends Array ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = string extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record 1', () => { - type T = string extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 2', () => { - type T = string extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.Unknown())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 3', () => { - type T = string extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.String())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 4', () => { - type T = string extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.Number())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record 5', () => { - type T = string extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.Union([Type.Number(), Type.String()]))) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record 6', () => { - type T = string extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = string extends {} ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = string extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = string extends object ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = number extends number | string ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = number extends any | number ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = number extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = number extends null ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = number extends undefined ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = number extends void ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = number extends Date ? 1 : 2 - const R = Structural.Check(Type.Number(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/tuple.ts b/test/runtime/conditional/tuple.ts deleted file mode 100644 index dcd9616..0000000 --- a/test/runtime/conditional/tuple.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Tuple', () => { - it('Should extend Any', () => { - type T = [string, number] extends any ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = [string, number] extends string ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = [string, number] extends boolean ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = [string, number] extends number ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = [string, number] extends number ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 1', () => { - type T = [string, number] extends Array ? 1 : 2 // 1 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array 2', () => { - type T = [string, number] extends Array ? 1 : 2 // 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Array(Type.String())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 3', () => { - type T = [string, number] extends Array ? 1 : 2 // 1 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Array(Type.Union([Type.String(), Type.Number()]))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Array 4', () => { - type T = [string, number] extends Array ? 1 : 2 // 1 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Array(Type.Union([Type.String(), Type.Number(), Type.Boolean()]))) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Tuple 1', () => { - type T = [string, number] extends [string, number] ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([Type.String(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Tuple 2', () => { - type T = [string, number] extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple 3', () => { - type T = [string, any] extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Any()]), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple 4', () => { - type T = [string, number] extends [string, any] ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([Type.String(), Type.Any()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Tuple 5', () => { - type T = [string, unknown] extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Unknown()]), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple 6', () => { - type T = [string, number] extends [string, unknown] ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([Type.String(), Type.Unknown()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Tuple 7', () => { - type T = [] extends [string, number] ? 1 : 2 - const R = Structural.Check(Type.Tuple([]), Type.Tuple([Type.String(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple 8', () => { - type T = [string, number] extends [] ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record 1', () => { - type T = [string, number] extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 2', () => { - type T = [string, number] extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.Unknown())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 3', () => { - type T = [string, number] extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.String())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Record 4', () => { - type T = [string, number] extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.Number())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record 5', () => { - type T = [string, number] extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Record(Type.Number(), Type.Union([Type.Number(), Type.String()]))) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record 6', () => { - type T = [string, number] extends Record ? 1 : 2 - const R = Structural.Check(Type.String(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = [string, number] extends {} ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = [string, number] extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = [string, number] extends object ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 1', () => { - type T = [string, number] extends number | string ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = [string, number] extends any | number ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = [string, number] extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null', () => { - type T = [string, number] extends null ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = [string, number] extends undefined ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = [string, number] extends void ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = [string, number] extends Date ? 1 : 2 - const R = Structural.Check(Type.Tuple([Type.String(), Type.Number()]), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/uint8array.ts b/test/runtime/conditional/uint8array.ts deleted file mode 100644 index 984082d..0000000 --- a/test/runtime/conditional/uint8array.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Uint8Array', () => { - it('Should extend Any', () => { - type T = Uint8Array extends any ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = Uint8Array extends unknown ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = Uint8Array extends string ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = Uint8Array extends boolean ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = Uint8Array extends number ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = Uint8Array extends number ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = Uint8Array extends Array ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = Uint8Array extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Record', () => { - type T = Uint8Array extends Record ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Record(Type.Number(), Type.Any())) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 1', () => { - type T = Uint8Array extends {} ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = Uint8Array extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = Uint8Array extends object ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = Uint8Array extends number | string ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = Uint8Array extends any | number ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = Uint8Array extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null', () => { - type T = Uint8Array extends null ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = Uint8Array extends undefined ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = Uint8Array extends void ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = Uint8Array extends Date ? 1 : 2 - const R = Structural.Check(Type.Uint8Array(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/undefined.ts b/test/runtime/conditional/undefined.ts deleted file mode 100644 index 2a03f11..0000000 --- a/test/runtime/conditional/undefined.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Undefined', () => { - it('Should extend Any', () => { - type T = undefined extends any ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = undefined extends string ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = undefined extends boolean ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = undefined extends number ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = undefined extends number ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = undefined extends Array ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = undefined extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = undefined extends {} ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 2', () => { - type T = undefined extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = undefined extends object ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = undefined extends number | string ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = undefined extends any | number ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = undefined extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null', () => { - type T = undefined extends null ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = undefined extends undefined ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Void', () => { - type T = undefined extends void ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Void()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Date', () => { - type T = undefined extends Date ? 1 : 2 - const R = Structural.Check(Type.Undefined(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/union.ts b/test/runtime/conditional/union.ts deleted file mode 100644 index bcd0da2..0000000 --- a/test/runtime/conditional/union.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Union', () => { - it('Should extend Any', () => { - type T = number | string extends any ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = number | string extends string ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = number | string extends boolean ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = number | string extends number ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = number | string extends number ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array', () => { - type T = number | string extends Array ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - it('Should extend Tuple', () => { - type T = number | string extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = number | string extends {} ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Object({}, { additionalProperties: false })) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Object 2', () => { - type T = number | string extends { a: 10 } ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = number | string extends object ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = number | string extends number | string ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 2', () => { - type T = number | string extends any | number ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Union([Type.Any(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = number | string extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 4', () => { - type T = any | boolean extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.Union) - }) - - it('Should extend Union 5', () => { - type T = any | string extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.Union) - }) - - it('Should extend Union 6', () => { - type T = any | {} extends {} ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.Union) - }) - - it('Should extend Union 7', () => { - type T = any extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Any(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.Union) - }) - - it('Should extend Union 8', () => { - type T = unknown | string extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Unknown(), Type.String()]), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 9', () => { - type T = unknown extends boolean | number ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Union([Type.Boolean(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Null', () => { - type T = number | string extends null ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = number | string extends undefined ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = number | string extends void ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void 2', () => { - type T = number | string | void extends void ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = number | string | void extends Date ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Number(), Type.String()]), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date 2', () => { - type T = Date | number | string | void extends Date ? 1 : 2 - const R = Structural.Check(Type.Union([Type.Date(), Type.Number(), Type.String()]), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/unknown.ts b/test/runtime/conditional/unknown.ts deleted file mode 100644 index 51b0e29..0000000 --- a/test/runtime/conditional/unknown.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Unknown', () => { - it('Should extend Any', () => { - type T = unknown extends any ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = unknown extends unknown ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = unknown extends string ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = unknown extends boolean ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = unknown extends number ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = unknown extends number ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 1', () => { - type T = unknown extends Array ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 2', () => { - type T = unknown extends Array ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Array(Type.String())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = unknown extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = unknown extends object ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 2', () => { - type T = unknown extends {} ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = unknown extends { a: number } ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Object({ a: Type.Number() })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = unknown extends number | string ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = unknown extends any | number ? 1 : 2 // 1 - const R = Structural.Check(Type.Unknown(), Type.Union([Type.Any(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = unknown extends unknown | number ? 1 : 2 // 1 - const R = Structural.Check(Type.Unknown(), Type.Union([Type.Unknown(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 4', () => { - type T = unknown extends unknown | any ? 1 : 2 // 1 - const R = Structural.Check(Type.Unknown(), Type.Union([Type.Unknown(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = unknown extends null ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = unknown extends undefined ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = unknown extends void ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Date', () => { - type T = unknown extends Date ? 1 : 2 - const R = Structural.Check(Type.Unknown(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/conditional/void.ts b/test/runtime/conditional/void.ts deleted file mode 100644 index 6e1ece4..0000000 --- a/test/runtime/conditional/void.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' - -describe('conditional/structural/Void', () => { - it('Should extend Any', () => { - type T = void extends any ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Unknown', () => { - type T = void extends unknown ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend String', () => { - type T = void extends string ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.String()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Boolean', () => { - type T = void extends boolean ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Number', () => { - type T = void extends number ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Integer', () => { - type T = void extends number ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 1', () => { - type T = void extends Array ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Array 2', () => { - type T = void extends Array ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Array(Type.String())) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Tuple', () => { - type T = void extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = void extends object ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 2', () => { - type T = void extends {} ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Object({})) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 3', () => { - type T = void extends { a: number } ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Object({ a: Type.Number() })) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 1', () => { - type T = void extends number | string ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Union 2', () => { - type T = void extends any | number ? 1 : 2 // 1 - const R = Structural.Check(Type.Void(), Type.Union([Type.Any(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 3', () => { - type T = void extends unknown | number ? 1 : 2 // 1 - const R = Structural.Check(Type.Void(), Type.Union([Type.Unknown(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Union 4', () => { - type T = void extends unknown | any ? 1 : 2 // 1 - const R = Structural.Check(Type.Void(), Type.Union([Type.Unknown(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Null', () => { - type T = void extends null ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Undefined', () => { - type T = void extends undefined ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Void', () => { - type T = void extends void ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Void()) - Assert.deepEqual(R, StructuralResult.True) - }) - - it('Should extend Date', () => { - type T = void extends Date ? 1 : 2 - const R = Structural.Check(Type.Void(), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) - }) -}) diff --git a/test/runtime/errors/index.ts b/test/runtime/errors/index.ts index 3443ef9..41d3ac1 100644 --- a/test/runtime/errors/index.ts +++ b/test/runtime/errors/index.ts @@ -1 +1 @@ -// todo: implement once errors are standardized +import './iterator' diff --git a/test/runtime/errors/iterator.ts b/test/runtime/errors/iterator.ts new file mode 100644 index 0000000..3a61c3a --- /dev/null +++ b/test/runtime/errors/iterator.ts @@ -0,0 +1,32 @@ +import { Type } from '@sinclair/typebox' +import { ValueErrors } from '@sinclair/typebox/errors' +import { Assert } from '../assert' + +describe('errors/ValueErrorIterator', () => { + it('Should return undefined for non error', () => { + const R = ValueErrors.Errors(Type.Number(), [], 1).First() + Assert.equal(R, undefined) + }) + it('Should return a value error when error', () => { + const { type, path, message } = ValueErrors.Errors(Type.Number(), [], '').First()! + Assert.isTypeOf(type, 'number') + Assert.isTypeOf(path, 'string') + Assert.isTypeOf(message, 'string') + }) + it('Should yield empty array for non error', () => { + const R = [...ValueErrors.Errors(Type.Number(), [], 1)] + Assert.equal(R.length, 0) + }) + it('Should yield array with 1 error when error', () => { + const R = [...ValueErrors.Errors(Type.Number(), [], 'foo')] + Assert.equal(R.length, 1) + }) + it('Should yield array with N errors when error', () => { + // prettier-ignore + const R = [...ValueErrors.Errors(Type.Object({ + x: Type.Number(), + y: Type.Number() + }), [], {})] // require object to invoke internal check + Assert.equal(R.length > 1, true) + }) +}) diff --git a/test/runtime/format/format.ts b/test/runtime/format/format.ts deleted file mode 100644 index e867d5a..0000000 --- a/test/runtime/format/format.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Format } from '@sinclair/typebox/format' -import { Assert } from '../assert/index' - -describe('format/Format', () => { - it('Should set format', () => { - Format.Set('test#format1', () => true) - }) - - it('Should get format', () => { - Format.Set('test#format2', () => true) - const format = Format.Get('test#format2') - Assert.equal(typeof format, 'function') - }) - - it('Should return true if exists', () => { - Format.Set('test#format3', () => true) - Assert.equal(Format.Has('test#format3'), true) - }) - - it('Should clear formats', () => { - Format.Set('test#format4', () => true) - Format.Clear() - Assert.equal(Format.Has('test#format4'), false) - }) -}) diff --git a/test/runtime/index.ts b/test/runtime/index.ts index 6bd775e..5a8bca7 100644 --- a/test/runtime/index.ts +++ b/test/runtime/index.ts @@ -1,9 +1,6 @@ import './compiler/index' -import './conditional/index' import './errors/index' -import './format/index' -import './guard/index' -import './hash/index' import './schema/index' import './system/index' +import './type/index' import './value/index' diff --git a/test/runtime/schema/composite.ts b/test/runtime/schema/composite.ts new file mode 100644 index 0000000..cca8cac --- /dev/null +++ b/test/runtime/schema/composite.ts @@ -0,0 +1,94 @@ +import { Type } from '@sinclair/typebox' +import { Assert } from '../assert' +import { Ok, Fail } from './validate' + +describe('type/schema/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, { 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: 'hello' }) + Fail(P, {}) + }) + + it('Should 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]) + Ok(T, { a: 1 }) + Ok(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({ 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]) + Ok(T, { a: 1 }) + Ok(T, { a: 'hello' }) + Ok(T, { a: false }) + Ok(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({ x: Type.Object({ x: Type.String() }) }) + const T = Type.Composite([A, B]) + Ok(T, { x: { x: 1 } }) + Ok(T, { x: { x: 'hello' } }) + Fail(T, { x: { x: false } }) + }) + + // todo: move to composition / type guard spec + it('Should compose and produce the same schema', () => { + const T = Type.Object({ + field: Type.Optional(Type.String()), + }) + const A = Type.Composite([T]) + const B = Type.Composite([T]) + Assert.deepEqual(A, B) + }) +}) diff --git a/test/runtime/schema/index.ts b/test/runtime/schema/index.ts index d0053aa..705b1d1 100644 --- a/test/runtime/schema/index.ts +++ b/test/runtime/schema/index.ts @@ -1,6 +1,7 @@ import './any' import './array' import './boolean' +import './composite' import './date' import './enum' import './integer' @@ -9,6 +10,7 @@ import './keyof' import './literal' import './modifier' import './never' +import './not' import './null' import './number' import './object' diff --git a/test/runtime/schema/intersect.ts b/test/runtime/schema/intersect.ts index e1671db..d3de6c3 100644 --- a/test/runtime/schema/intersect.ts +++ b/test/runtime/schema/intersect.ts @@ -1,94 +1,83 @@ -import { Type } from '@sinclair/typebox' -import { Assert } from '../assert' +import { Type, Static } from '@sinclair/typebox' import { Ok, Fail } from './validate' describe('type/schema/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({ a: Type.String() }) - const B = Type.Object({ b: Type.Number() }) - const T = Type.Intersect([A, B], { additionalProperties: false }) - Ok(T, { a: 'hello', b: 42 }) - }) - - it('Should intersect with partial', () => { - const A = Type.Partial(Type.Object({ a: Type.Number() })) - const B = Type.Partial(Type.Object({ b: Type.Number() })) - const P = Type.Intersect([A, B], { additionalProperties: false }) - Ok(P, { a: 1, b: 2 }) - Ok(P, { a: 1 }) - Ok(P, { b: 1 }) - Ok(P, {}) - Fail(P, { c: 1 }) - }) - - it('Should intersect with overlapping same type', () => { - const A = Type.Object({ a: Type.Number() }) - const B = Type.Object({ a: Type.Number() }) - const P = Type.Intersect([A, B]) - Ok(P, { a: 1 }) - Fail(P, { a: 'hello' }) - Fail(P, {}) - }) - - it('Should intersect with overlapping varying type', () => { - const A = Type.Object({ a: Type.Number() }) - const B = Type.Object({ a: Type.String() }) - const T = Type.Intersect([A, B]) - Ok(T, { a: 1 }) - Ok(T, { a: 'hello' }) - Fail(T, {}) - }) - - it('Should intersect 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.Intersect([A, B, C, D]) - Ok(T, { a: 1 }) - Ok(T, { a: 'hello' }) - Ok(T, { a: false }) - Ok(T, { a: null }) - Fail(T, { a: [] }) - Fail(T, {}) - }) - - it('Should pick from intersected 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.Intersect([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 }) + const T = Type.Intersect([A, B], {}) + Ok(T, { x: 1, y: 1 }) }) - - it('Should omit from intersected type', () => { + 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 C = Type.Object({ z: Type.Number() }) - const T = Type.Intersect([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 }) + const T = Type.Intersect([A, B], {}) + Ok(T, { x: 1, y: 1 }) + Fail(T, { x: 1 }) + Fail(T, { y: 1 }) }) - - it('Should intersect nested object properties', () => { - const A = Type.Object({ x: Type.Object({ x: Type.Number() }) }) - const B = Type.Object({ x: Type.Object({ x: Type.String() }) }) + 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 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: { x: 1 } }) - Ok(T, { x: { x: 'hello' } }) - Fail(T, { x: { x: false } }) + Ok(T, { x: 1 }) + Fail(T, { x: '1' }) }) - - // todo: move to composition / type guard spec - it('Should intersect and produce the same schema', () => { - const T = Type.Object({ - field: Type.Optional(Type.String()), - }) - const A = Type.Intersect([T]) - const B = Type.Intersect([T]) - Assert.deepEqual(A, B) + 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 }) }) }) diff --git a/test/runtime/schema/not.ts b/test/runtime/schema/not.ts new file mode 100644 index 0000000..c8ec36f --- /dev/null +++ b/test/runtime/schema/not.ts @@ -0,0 +1,38 @@ +import { Type } from '@sinclair/typebox' +import { Ok, Fail } from './validate' + +describe('type/schema/Not', () => { + it('Should validate with not number', () => { + const T = Type.Not(Type.Number(), Type.String()) + Fail(T, 1) + Ok(T, 'A') + }) + it('Should validate with union left', () => { + // prettier-ignore + const T = Type.Not(Type.Union([ + Type.Literal('A'), + Type.Literal('B'), + Type.Literal('C') + ]), Type.String()) + Fail(T, 'A') + Fail(T, 'B') + Fail(T, 'C') + Ok(T, 'D') + }) + it('Should validate with union right', () => { + // prettier-ignore + const T = Type.Not(Type.Number(), Type.Union([ + Type.String(), + Type.Boolean() + ])) + Fail(T, 1) + Ok(T, 'A') + Ok(T, true) + }) + it('Should not validate with symmetric left right', () => { + // prettier-ignore + const T = Type.Not(Type.Number(), Type.Number()) + Fail(T, 1) + Fail(T, true) // not a number, but not a number either? + }) +}) diff --git a/test/runtime/schema/validate.ts b/test/runtime/schema/validate.ts index 7390db7..6e9cf4e 100644 --- a/test/runtime/schema/validate.ts +++ b/test/runtime/schema/validate.ts @@ -1,4 +1,4 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Value } from '@sinclair/typebox/value' import { TSchema } from '@sinclair/typebox' diff --git a/test/runtime/system/AllowArrayObjects.ts b/test/runtime/system/AllowArrayObjects.ts deleted file mode 100644 index 0641398..0000000 --- a/test/runtime/system/AllowArrayObjects.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Ok, Fail } from '../compiler/validate' -import { TypeSystem } from '@sinclair/typebox/system' -import { Type } from '@sinclair/typebox' - -describe('system/TypeSystem/AllowArrayObjects', () => { - before(() => { - TypeSystem.AllowArrayObjects = true - }) - after(() => { - TypeSystem.AllowArrayObjects = false - }) - // --------------------------------------------------------------- - // Object - // --------------------------------------------------------------- - it('Should validate arrays with empty objects', () => { - const T = Type.Object({}) - Ok(T, [0, 1, 2]) - }) - it('Should validate arrays with objects with length property', () => { - const T = Type.Object({ length: Type.Number() }) - Ok(T, [0, 1, 2]) - }) - it('Should validate arrays with objects with additionalProperties false when array has no elements', () => { - const T = Type.Object({ length: Type.Number() }, { additionalProperties: false }) - Ok(T, []) - }) - it('Should not validate arrays with objects with additionalProperties false when array has elements', () => { - const T = Type.Object({ length: Type.Number() }, { additionalProperties: false }) - Fail(T, [0, 1, 2]) - }) - it('Should not validate arrays with objects when length property is string', () => { - const T = Type.Object({ length: Type.String() }) - Fail(T, [0, 1, 2]) - }) - // --------------------------------------------------------------- - // Record - // --------------------------------------------------------------- - it('Should validate arrays as Records with String Keys', () => { - const T = Type.Record(Type.String(), Type.Number()) - Ok(T, [0, 1, 2]) - }) - it('Should not validate arrays as Records with Number Keys', () => { - const T = Type.Record(Type.Integer(), Type.Number()) - Fail(T, [0, 1, 2]) - }) - it('Should not validate arrays as Records with Object Values', () => { - const T = Type.Record( - Type.String(), - Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), - }), - ) - Ok(T, [ - { x: 1, y: 1, z: 1 }, - { x: 1, y: 1, z: 1 }, - { x: 1, y: 1, z: 1 }, - ]) - }) -}) diff --git a/test/runtime/system/AllowNaN.ts b/test/runtime/system/AllowNaN.ts deleted file mode 100644 index 4795e79..0000000 --- a/test/runtime/system/AllowNaN.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Ok, Fail } from '../compiler/validate' -import { TypeSystem } from '@sinclair/typebox/system' -import { Type } from '@sinclair/typebox' - -describe('system/TypeSystem/AllowNaN', () => { - before(() => { - TypeSystem.AllowNaN = true - }) - after(() => { - TypeSystem.AllowNaN = false - }) - // --------------------------------------------------------------- - // Number - // --------------------------------------------------------------- - it('Should validate number with NaN', () => { - const T = Type.Number() - Ok(T, NaN) - }) - - // --------------------------------------------------------------- - // Integer - // - // Note: The Number.isInteger() test will fail for NaN. Because - // of this we cannot reasonably override NaN handling for integers. - // --------------------------------------------------------------- - it('Should not validate integer with NaN', () => { - const T = Type.Integer() - Fail(T, NaN) - }) -}) diff --git a/test/runtime/system/CreateFormat.ts b/test/runtime/system/CreateFormat.ts deleted file mode 100644 index 751eb34..0000000 --- a/test/runtime/system/CreateFormat.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Ok, Fail } from '../compiler/validate' -import { Assert } from '../assert/index' -import { TypeSystem } from '@sinclair/typebox/system' -import { Type } from '@sinclair/typebox' - -describe('system/TypeSystem/CreateFormat', () => { - it('Should create and validate a format', () => { - TypeSystem.CreateFormat('CreateFormat0', (value) => value === value.toLowerCase()) - const T = Type.String({ format: 'CreateFormat0' }) - Ok(T, 'action') - Fail(T, 'ACTION') - }) - it('Should throw if registering the same format twice', () => { - TypeSystem.CreateFormat('CreateFormat1', (value) => true) - Assert.throws(() => TypeSystem.CreateFormat('CreateFormat1', (value) => true)) - }) -}) diff --git a/test/runtime/system/CreateType.ts b/test/runtime/system/CreateType.ts deleted file mode 100644 index 60626b7..0000000 --- a/test/runtime/system/CreateType.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Ok, Fail } from '../compiler/validate' -import { Assert } from '../assert/index' -import { TypeSystem } from '@sinclair/typebox/system' - -describe('system/TypeSystem/CreateType', () => { - it('Should create and validate a type', () => { - type BigNumberOptions = { minimum?: bigint; maximum?: bigint } - const BigNumber = TypeSystem.CreateType('CreateType0', (options, value) => { - if (typeof value !== 'bigint') return false - if (options.maximum !== undefined && value > options.maximum) return false - if (options.minimum !== undefined && value < options.minimum) return false - return true - }) - const T = BigNumber({ minimum: 10n, maximum: 20n }) - Ok(T, 15n) - Fail(T, 5n) - Fail(T, 25n) - }) - it('Should throw if registering the same type twice', () => { - TypeSystem.CreateType('CreateType1', () => true) - Assert.throws(() => TypeSystem.CreateType('CreateType1', () => true)) - }) -}) diff --git a/test/runtime/system/index.ts b/test/runtime/system/index.ts index 72bdb9c..a3fc402 100644 --- a/test/runtime/system/index.ts +++ b/test/runtime/system/index.ts @@ -1,4 +1 @@ -import './AllowArrayObjects' -import './AllowNaN' -import './CreateFormat' -import './CreateType' +import './system' diff --git a/test/runtime/system/system.ts b/test/runtime/system/system.ts new file mode 100644 index 0000000..5c41eda --- /dev/null +++ b/test/runtime/system/system.ts @@ -0,0 +1,169 @@ +import { Ok, Fail } from '../compiler/validate' +import { Assert } from '../assert/index' +import { TypeSystem } from '@sinclair/typebox/system' +import { Type } from '@sinclair/typebox' + +describe('system/TypeSystem/AllowNaN', () => { + before(() => { + TypeSystem.AllowNaN = true + }) + after(() => { + TypeSystem.AllowNaN = false + }) + // --------------------------------------------------------------- + // Number + // --------------------------------------------------------------- + it('Should validate number with NaN', () => { + const T = Type.Number() + Ok(T, NaN) + }) + it('Should validate number with +Infinity', () => { + const T = Type.Number() + Ok(T, Infinity) + }) + it('Should validate number with -Infinity', () => { + const T = Type.Number() + Ok(T, -Infinity) + }) + // --------------------------------------------------------------- + // Integer + // + // Note: The Number.isInteger() test will fail for NaN. Because + // of this we cannot reasonably override NaN handling for integers. + // --------------------------------------------------------------- + it('Should not validate integer with NaN', () => { + const T = Type.Integer() + Fail(T, NaN) + }) + it('Should not validate integer with +Infinity', () => { + const T = Type.Integer() + Fail(T, Infinity) + }) + it('Should not validate integer with -Infinity', () => { + const T = Type.Integer() + Fail(T, -Infinity) + }) + // --------------------------------------------------------------- + // BigInt + // + // Note: We expect failures here as bigint isn't IEEE754 + // --------------------------------------------------------------- + it('Should not validate bigint with NaN', () => { + const T = Type.BigInt() + Fail(T, NaN) + }) + it('Should not validate bigint with +Infinity', () => { + const T = Type.BigInt() + Fail(T, Infinity) + }) + it('Should not validate bigint with -Infinity', () => { + const T = Type.BigInt() + Fail(T, -Infinity) + }) +}) + +describe('system/TypeSystem/AllowArrayObjects', () => { + before(() => { + TypeSystem.AllowArrayObjects = true + }) + after(() => { + TypeSystem.AllowArrayObjects = false + }) + // --------------------------------------------------------------- + // Object + // --------------------------------------------------------------- + it('Should validate arrays with empty objects', () => { + const T = Type.Object({}) + Ok(T, [0, 1, 2]) + }) + it('Should validate arrays with objects with length property', () => { + const T = Type.Object({ length: Type.Number() }) + Ok(T, [0, 1, 2]) + }) + it('Should validate arrays with objects with additionalProperties false when array has no elements', () => { + const T = Type.Object({ length: Type.Number() }, { additionalProperties: false }) + Ok(T, []) + }) + it('Should not validate arrays with objects with additionalProperties false when array has elements', () => { + const T = Type.Object({ length: Type.Number() }, { additionalProperties: false }) + Fail(T, [0, 1, 2]) + }) + it('Should not validate arrays with objects when length property is string', () => { + const T = Type.Object({ length: Type.String() }) + Fail(T, [0, 1, 2]) + }) + // --------------------------------------------------------------- + // Record + // --------------------------------------------------------------- + it('Should validate arrays as Records with String Keys', () => { + const T = Type.Record(Type.String(), Type.Number()) + Ok(T, [0, 1, 2]) + }) + it('Should not validate arrays as Records with Number Keys', () => { + const T = Type.Record(Type.Integer(), Type.Number()) + Fail(T, [0, 1, 2]) + }) + it('Should not validate arrays as Records with Object Values', () => { + const T = Type.Record( + Type.String(), + Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }), + ) + Ok(T, [ + { x: 1, y: 1, z: 1 }, + { x: 1, y: 1, z: 1 }, + { x: 1, y: 1, z: 1 }, + ]) + }) +}) +describe('system/TypeSystem/AllowVoidNull', () => { + before(() => { + TypeSystem.AllowVoidNull = true + }) + after(() => { + TypeSystem.AllowVoidNull = false + }) + // --------------------------------------------------------------- + // Object + // --------------------------------------------------------------- + it('Should validate with null', () => { + const T = Type.Void() + Ok(T, null) + }) +}) + +describe('system/TypeSystem/Format', () => { + it('Should create and validate a format', () => { + TypeSystem.Format('CreateFormat0', (value) => value === value.toLowerCase()) + const T = Type.String({ format: 'CreateFormat0' }) + Ok(T, 'action') + Fail(T, 'ACTION') + }) + it('Should throw if registering the same format twice', () => { + TypeSystem.Format('CreateFormat1', (value) => true) + Assert.throws(() => TypeSystem.Format('CreateFormat1', (value) => true)) + }) +}) + +describe('system/TypeSystem/Type', () => { + it('Should create and validate a type', () => { + type BigNumberOptions = { minimum?: bigint; maximum?: bigint } + const BigNumber = TypeSystem.Type('CreateType0', (options, value) => { + if (typeof value !== 'bigint') return false + if (options.maximum !== undefined && value > options.maximum) return false + if (options.minimum !== undefined && value < options.minimum) return false + return true + }) + const T = BigNumber({ minimum: 10n, maximum: 20n }) + Ok(T, 15n) + Fail(T, 5n) + Fail(T, 25n) + }) + it('Should throw if registering the same type twice', () => { + TypeSystem.Type('CreateType1', () => true) + Assert.throws(() => TypeSystem.Type('CreateType1', () => true)) + }) +}) diff --git a/test/runtime/type/clone/clone.ts b/test/runtime/type/clone/clone.ts new file mode 100644 index 0000000..3656800 --- /dev/null +++ b/test/runtime/type/clone/clone.ts @@ -0,0 +1,147 @@ +// -------------------------------------------------------------------- +// $id deletion was omitted from 0.26.0 to reduce complexity overhead. +// -------------------------------------------------------------------- + +// import { Type } from '@sinclair/typebox' +// import { Assert } from '../../assert' + +// describe('type/Clone', () => { +// it('Should retain source type $id for cloned objects', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Object({ x: S, y: S }) +// Assert.equal(T.properties.x.$id, undefined) +// Assert.equal(T.properties.y.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should retain source type $id when composing objects with cloned arrays', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Function([S], S) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Array', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Array(S) +// Assert.equal(T.items.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Composite', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Composite([Type.Object({ a: S }), Type.Object({ b: S })]) +// Assert.equal(T.properties.a.$id, undefined) +// Assert.equal(T.properties.b.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Constructor', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Constructor([S], S) +// Assert.equal(T.parameters[0].$id, undefined) +// Assert.equal(T.returns.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Function', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Function([S], S) +// Assert.equal(T.parameters[0].$id, undefined) +// Assert.equal(T.returns.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Intersect', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Intersect([S, S]) +// Assert.equal(T.allOf[0].$id, undefined) +// Assert.equal(T.allOf[1].$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Intersect with unevaluatedProperties', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Intersect([S, S], { unevaluatedProperties: S }) +// // @ts-ignore +// Assert.equal(T.unevaluatedProperties.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Not', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Not(S, S) +// Assert.equal(T.allOf[0].not.$id, undefined) +// Assert.equal(T.allOf[1].$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Object', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Object({ x: S, y: S }) +// Assert.equal(T.properties.x.$id, undefined) +// Assert.equal(T.properties.y.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for nested Object', () => { +// const S = Type.String({ $id: 'S' }) +// const N = Type.Object({ s: S }, { $id: 'N' }) +// const T = Type.Object({ x: S, y: S, z: N }) +// Assert.equal(T.properties.x.$id, undefined) +// Assert.equal(T.properties.y.$id, undefined) +// Assert.equal(T.properties.z.$id, undefined) +// Assert.equal(T.properties.z.properties.s.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Object additionalProperties', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Object({}, { additionalProperties: S }) +// // @ts-ignore +// Assert.equal(T.additionalProperties!.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Promise', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Promise(S) +// Assert.equal(T.item.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Record', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Record(Type.String(), S) +// Assert.equal(T.patternProperties['^.*$'].$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Tuple', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Tuple([S, S]) +// Assert.equal(T.items![0]!.$id, undefined) +// Assert.equal(T.items![1]!.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should remove cloned $id for Union', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Union([S, S]) +// Assert.equal(T.anyOf[0]!.$id, undefined) +// Assert.equal(T.anyOf[1]!.$id, undefined) +// Assert.equal(S.$id, 'S') +// }) +// it('Should retain cloned $id for wrapped Recursive 1', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Object({ +// x: Type.Recursive( +// (Self) => +// Type.Object({ +// self: Type.Optional(Self), +// }), +// { $id: 'RecursiveClone' }, +// ), +// }) +// Assert.equal(T.properties.x.$id, 'RecursiveClone') +// Assert.equal(S.$id, 'S') +// }) +// it('Should retain cloned $id for wrapped Recursive 2', () => { +// const S = Type.String({ $id: 'S' }) +// const T = Type.Tuple([ +// Type.Recursive( +// (Self) => +// Type.Object({ +// self: Type.Optional(Self), +// }), +// { $id: 'RecursiveClone' }, +// ), +// ]) +// Assert.equal(T.items![0].$id, 'RecursiveClone') +// Assert.equal(S.$id, 'S') +// }) +// }) diff --git a/test/runtime/type/clone/index.ts b/test/runtime/type/clone/index.ts new file mode 100644 index 0000000..cd79815 --- /dev/null +++ b/test/runtime/type/clone/index.ts @@ -0,0 +1 @@ +import './clone' diff --git a/test/runtime/type/extends/any.ts b/test/runtime/type/extends/any.ts new file mode 100644 index 0000000..f67300b --- /dev/null +++ b/test/runtime/type/extends/any.ts @@ -0,0 +1,98 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Any', () => { + it('Should extend Any', () => { + type T = any extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Unknown', () => { + type T = any extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = any extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Boolean', () => { + type T = any extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Number', () => { + type T = any extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Integer', () => { + type T = any extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Array 1', () => { + type T = any extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Array 2', () => { + type T = any extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Array(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Tuple', () => { + type T = any extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Object 1', () => { + type T = any extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Object 2', () => { + type T = any extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Object 3', () => { + type T = any extends { a: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Object({ a: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Union 1', () => { + type T = any extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Union 2', () => { + type T = any extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Union([Type.Any(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Null', () => { + type T = any extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Undefined', () => { + type T = any extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Void', () => { + type T = any extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + + it('Should extend Date', () => { + type T = any extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) +}) diff --git a/test/runtime/type/extends/array.ts b/test/runtime/type/extends/array.ts new file mode 100644 index 0000000..973c745 --- /dev/null +++ b/test/runtime/type/extends/array.ts @@ -0,0 +1,316 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Array', () => { + // ---------------------------------------------- + // Generic Varying + // ---------------------------------------------- + + it('Should extend Array Varying 1', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Array Varying 2', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Array Varying 3', () => { + type T = Array extends Array ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Array(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Array Varying 4', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Number()), Type.Array(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + // ---------------------------------------------- + // Any + // ---------------------------------------------- + it('Should extend Any', () => { + type T = Array extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = Array extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = Array extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = Array extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = Array extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = Array extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 1', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Array 2', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Array 3', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Tuple', () => { + type T = Array extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = Array extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = Array extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 3', () => { + type T = Array extends { a: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Object({ a: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 4', () => { + type T = Array extends { length: '1' } ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Object({ length: Type.Literal('1') })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 5', () => { + type T = Array extends { length: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Object({ length: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 1', () => { + type T = Array extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = Array extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3', () => { + type T = Array extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Union([Type.Any(), Type.Array(Type.Any())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 4', () => { + type T = Array extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Union([Type.Any(), Type.Array(Type.Any())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 5', () => { + type T = Array extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Union([Type.Any(), Type.Array(Type.String())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = Array extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = Array extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.Any()), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + // ---------------------------------------------- + // Constrained + // ---------------------------------------------- + + it('Should extend constrained Any', () => { + type T = Array extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Unknown', () => { + type T = Array extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained String', () => { + type T = Array extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Boolean', () => { + type T = Array extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Number', () => { + type T = Array extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Integer', () => { + type T = Array extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Array 1', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Array 2', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Array 3', () => { + type T = Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Tuple', () => { + type T = Array extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Object 1', () => { + type T = Array extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Object 2', () => { + type T = Array extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Object 3', () => { + type T = Array extends { a: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Object({ a: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Object 4', () => { + type T = Array extends { length: '1' } ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Object({ length: Type.Literal('1') })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Object 5', () => { + type T = Array extends { length: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Object({ length: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Union 1', () => { + type T = Array extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Union([Type.Null(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Union 2', () => { + type T = Array extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Union([Type.Any(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Union 3', () => { + type T = Array extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Union([Type.Any(), Type.Array(Type.Any())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Union 4', () => { + type T = Array extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Union([Type.Any(), Type.Array(Type.Any())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Union 5', () => { + type T = Array extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Union([Type.Any(), Type.Array(Type.String())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Null', () => { + type T = Array extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Undefined', () => { + type T = Array extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Void', () => { + type T = Array extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = Array extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Array(Type.String()), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/bigint.ts b/test/runtime/type/extends/bigint.ts new file mode 100644 index 0000000..779cb84 --- /dev/null +++ b/test/runtime/type/extends/bigint.ts @@ -0,0 +1,101 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/BigInt', () => { + it('Should extend Any', () => { + type T = bigint extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Unknown', () => { + type T = bigint extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend String', () => { + type T = bigint extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Boolean', () => { + type T = bigint extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Number', () => { + type T = bigint extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Integer', () => { + type T = bigint extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Array', () => { + type T = bigint extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple', () => { + type T = bigint extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Record', () => { + type T = bigint extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 1', () => { + type T = bigint extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Object 2', () => { + type T = bigint extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Object({ a: Type.Literal(10) })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 1', () => { + type T = bigint extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 2', () => { + type T = bigint extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Union 3', () => { + type T = bigint extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 4', () => { + type T = bigint extends boolean | bigint ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Union([Type.Boolean(), Type.BigInt()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Null', () => { + type T = bigint extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Undefined', () => { + type T = bigint extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Void', () => { + type T = bigint extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Date', () => { + type T = bigint extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.BigInt(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/boolean.ts b/test/runtime/type/extends/boolean.ts new file mode 100644 index 0000000..290eee2 --- /dev/null +++ b/test/runtime/type/extends/boolean.ts @@ -0,0 +1,107 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Boolean', () => { + it('Should extend Any', () => { + type T = boolean extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = boolean extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = boolean extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Number', () => { + type T = boolean extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = boolean extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array', () => { + type T = boolean extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = boolean extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = boolean extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = boolean extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = boolean extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = boolean extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = boolean extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = boolean extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = boolean extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = boolean extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = boolean extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = boolean extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Boolean(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/constructor.ts b/test/runtime/type/extends/constructor.ts new file mode 100644 index 0000000..5746dba --- /dev/null +++ b/test/runtime/type/extends/constructor.ts @@ -0,0 +1,250 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Constructor', () => { + it('Should extend Function', () => { + type T = (new () => number) extends () => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Function([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Constructor 1', () => { + type T = (new () => number) extends new () => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Constructor([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 2', () => { + type T = (new () => any) extends new () => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Any()), Type.Constructor([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 3', () => { + type T = (new () => number) extends new () => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Any()), Type.Constructor([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 4', () => { + type T = (new (a: number) => number) extends new () => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([Type.Number()], Type.Number()), Type.Constructor([], Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Constructor 5', () => { + type T = (new (a: number | string) => number) extends new (a: number) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([Type.Union([Type.Number(), Type.String()])], Type.Number()), Type.Constructor([Type.Number()], Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 6', () => { + type T = (new (a: number) => number) extends new (a: number | string) => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([Type.Number()], Type.Number()), Type.Constructor([Type.Union([Type.Number(), Type.String()])], Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Constructor 7', () => { + type T = (new (a: number, b: number) => number) extends new (a: number) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([Type.Number(), Type.Number()], Type.Number()), Type.Constructor([Type.Number()], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Constructor 8', () => { + type T = (new (a: number) => number) extends new (a: number, b: number) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([Type.Number()], Type.Number()), Type.Constructor([Type.Number(), Type.Number()], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 9', () => { + type T = (new () => number) extends new () => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Constructor([], Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 9', () => { + type T = (new () => any) extends new () => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Any()), Type.Constructor([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 10', () => { + type T = (new () => Array) extends new () => object ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Array(Type.Any())), Type.Constructor([], Type.Object({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Constructor 11', () => { + type T = (new () => Array) extends new () => object ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Array(Type.String())), Type.Constructor([], Type.Object({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 12', () => { + type T = (new () => object) extends new () => Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Object({})), Type.Constructor([], Type.Array(Type.Any()))) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Constructor 13', () => { + type T = (new (a: unknown) => number) extends new (a: any) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([Type.Unknown()], Type.Number({})), Type.Constructor([Type.Any()], Type.Number({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 14', () => { + type T = (new (a: any) => number) extends new (a: unknown) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([Type.Any()], Type.Number({})), Type.Constructor([Type.Unknown()], Type.Number({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 15', () => { + type T = (new () => any) extends new () => unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Any({})), Type.Constructor([], Type.Unknown({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Constructor 16', () => { + type T = (new () => unknown) extends new () => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Unknown({})), Type.Constructor([], Type.Any({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Any', () => { + type T = (new () => number) extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = (new () => number) extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = (new () => number) extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = (new () => number) extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = (new () => number) extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 1', () => { + type T = (new () => number) extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 2', () => { + type T = (new () => number) extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 3', () => { + type T = (new () => number) extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = (new () => number) extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = (() => number) extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = (new () => number) extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = (new () => number) extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 3', () => { + type T = (new () => number) extends { a: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Object({ a: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 4', () => { + type T = (new () => number) extends { length: '1' } ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Object({ length: Type.Literal('1') })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = (new () => number) extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Union([Type.Null(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = (new () => number) extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Union([Type.Any(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3', () => { + type T = (new () => number) extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.Any())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 4', () => { + type T = (new () => number) extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.Any())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 5', () => { + type T = (new () => number) extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.String())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = (new () => number) extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = (new () => number) extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = (new () => number) extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = (new () => number) extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/date.ts b/test/runtime/type/extends/date.ts new file mode 100644 index 0000000..53e7bfc --- /dev/null +++ b/test/runtime/type/extends/date.ts @@ -0,0 +1,112 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Date', () => { + it('Should extend Any', () => { + type T = Date extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = Date extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = Date extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = Date extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = Date extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = Date extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array', () => { + type T = Date extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple', () => { + type T = Date extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = Date extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = Date extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = Date extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = Date extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = Date extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3', () => { + type T = Date extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Null', () => { + type T = Date extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = Date extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = Date extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = Date extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Date(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) +}) diff --git a/test/runtime/type/extends/function.ts b/test/runtime/type/extends/function.ts new file mode 100644 index 0000000..93abeb0 --- /dev/null +++ b/test/runtime/type/extends/function.ts @@ -0,0 +1,251 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Function', () => { + it('Should extend Constructor 1', () => { + type T = (() => number) extends new () => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Constructor([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Function 1', () => { + type T = (() => number) extends () => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Function([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 2', () => { + type T = (() => any) extends () => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Any()), Type.Function([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 3', () => { + type T = (() => number) extends () => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Any()), Type.Function([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 4', () => { + type T = ((a: number) => number) extends () => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([Type.Number()], Type.Number()), Type.Function([], Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Function 5', () => { + type T = ((a: number | string) => number) extends (a: number) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([Type.Union([Type.Number(), Type.String()])], Type.Number()), Type.Function([Type.Number()], Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 6', () => { + type T = ((a: number) => number) extends (a: number | string) => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([Type.Number()], Type.Number()), Type.Function([Type.Union([Type.Number(), Type.String()])], Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Function 7', () => { + type T = ((a: number, b: number) => number) extends (a: number) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([Type.Number(), Type.Number()], Type.Number()), Type.Function([Type.Number()], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Function 8', () => { + type T = ((a: number) => number) extends (a: number, b: number) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([Type.Number()], Type.Number()), Type.Function([Type.Number(), Type.Number()], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 9', () => { + type T = (() => number) extends () => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Function([], Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 9', () => { + type T = (() => any) extends () => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Any()), Type.Function([], Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 10', () => { + type T = (() => Array) extends () => object ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Array(Type.Any())), Type.Function([], Type.Object({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 11', () => { + type T = (() => Array) extends () => object ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Array(Type.String())), Type.Function([], Type.Object({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 12', () => { + type T = (() => object) extends () => Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Object({})), Type.Function([], Type.Array(Type.Any()))) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Function 13', () => { + type T = ((a: unknown) => number) extends (a: any) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([Type.Unknown()], Type.Number({})), Type.Function([Type.Any()], Type.Number({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 14', () => { + type T = ((a: any) => number) extends (a: unknown) => number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([Type.Any()], Type.Number({})), Type.Function([Type.Unknown()], Type.Number({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 15', () => { + type T = (() => any) extends () => unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Any({})), Type.Function([], Type.Unknown({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Function 16', () => { + type T = (() => unknown) extends () => any ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Unknown({})), Type.Function([], Type.Any({}))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Any', () => { + type T = (() => number) extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = (() => number) extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = (() => number) extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = (() => number) extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = (() => number) extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 1', () => { + type T = (() => number) extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 2', () => { + type T = (() => number) extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 3', () => { + type T = (() => number) extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = (() => number) extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = (() => number) extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = (() => number) extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = (() => number) extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 3', () => { + type T = (() => number) extends { a: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Object({ a: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 4', () => { + type T = (() => number) extends { length: '1' } ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Object({ length: Type.Literal('1') })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 5', () => { + type T = (() => number) extends { length: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Object({ length: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 1', () => { + type T = (() => number) extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Union([Type.Null(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = (() => number) extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Union([Type.Any(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3', () => { + type T = (() => number) extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.Any())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 4', () => { + type T = (() => number) extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.Any())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 5', () => { + type T = (() => number) extends any | Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Union([Type.Any(), Type.Array(Type.String())])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = (() => number) extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = (() => number) extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Function([], Type.Number()), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = (() => number) extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Constructor([], Type.Number()), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/conditional/index.ts b/test/runtime/type/extends/index.ts similarity index 91% rename from test/runtime/conditional/index.ts rename to test/runtime/type/extends/index.ts index 1f7a88c..ae680a5 100644 --- a/test/runtime/conditional/index.ts +++ b/test/runtime/type/extends/index.ts @@ -1,5 +1,6 @@ import './any' import './array' +import './bigint' import './boolean' import './constructor' import './date' @@ -12,6 +13,7 @@ import './object' import './promise' import './record' import './string' +import './symbol' import './tuple' import './uint8array' import './undefined' diff --git a/test/runtime/type/extends/integer.ts b/test/runtime/type/extends/integer.ts new file mode 100644 index 0000000..844e1ff --- /dev/null +++ b/test/runtime/type/extends/integer.ts @@ -0,0 +1,113 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Integer', () => { + it('Should extend Any', () => { + type T = number extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = number extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = number extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = number extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = number extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Integer', () => { + type T = number extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Array', () => { + type T = number extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = number extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = number extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = number extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = number extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Object({ a: Type.Literal(10) })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = number extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = number extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = number extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = number extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = number extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = number extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Integer(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = number extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/literal.ts b/test/runtime/type/extends/literal.ts new file mode 100644 index 0000000..71377e5 --- /dev/null +++ b/test/runtime/type/extends/literal.ts @@ -0,0 +1,312 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Literal', () => { + // ------------------------------------------------------------------- + // String Literal + // ------------------------------------------------------------------- + it('Should extend Any (String)', () => { + type T = 'hello' extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown (String)', () => { + type T = 'hello' extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String (String)', () => { + type T = 'hello' extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Boolean (String)', () => { + type T = 'hello' extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number (String)', () => { + type T = 'hello' extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer (String)', () => { + type T = 'hello' extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array (String)', () => { + type T = 'hello' extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple (String)', () => { + type T = 'hello' extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 1 (String)', () => { + type T = 'hello' extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2 (String)', () => { + type T = 'hello' extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1 (String)', () => { + type T = 'hello' extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2 (String)', () => { + type T = 'hello' extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3 (String)', () => { + type T = 'hello' extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Null (String)', () => { + type T = 'hello' extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined (String)', () => { + type T = 'hello' extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + // ------------------------------------------------------------------- + // Number Literal + // ------------------------------------------------------------------- + + it('Should extend Any (Number)', () => { + type T = 10 extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown (Number)', () => { + type T = 10 extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String (Number)', () => { + type T = 10 extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean (Number)', () => { + type T = 10 extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number (Number)', () => { + type T = 10 extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Integer (Number)', () => { + type T = 10 extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Array (Number)', () => { + type T = 10 extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple (Number)', () => { + type T = 10 extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 1 (Number)', () => { + type T = 10 extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2 (Number)', () => { + type T = 10 extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1 (Number)', () => { + type T = 10 extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2 (Number)', () => { + type T = 10 extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3 (Number)', () => { + type T = 10 extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null (Number)', () => { + type T = 10 extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined (Number)', () => { + type T = 10 extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + // ------------------------------------------------------------------- + // Boolean Literal + // ------------------------------------------------------------------- + + it('Should extend Any (Boolean)', () => { + type T = true extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown (Boolean)', () => { + type T = true extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String (Boolean)', () => { + type T = true extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean (Boolean)', () => { + type T = true extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Number (Boolean)', () => { + type T = true extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer (Boolean)', () => { + type T = true extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array (Boolean)', () => { + type T = true extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple (Boolean)', () => { + type T = true extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record 1', () => { + type T = 'hello' extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal('hello'), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Record 2', () => { + type T = 10 extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(10), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record 3', () => { + type T = true extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1 (Boolean)', () => { + type T = true extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2 (Boolean)', () => { + type T = true extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1 (Boolean)', () => { + type T = true extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2 (Boolean)', () => { + type T = true extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3 (Boolean)', () => { + type T = true extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null (Boolean)', () => { + type T = true extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined (Boolean)', () => { + type T = true extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = true extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = true extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Literal(true), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/null.ts b/test/runtime/type/extends/null.ts new file mode 100644 index 0000000..82931c9 --- /dev/null +++ b/test/runtime/type/extends/null.ts @@ -0,0 +1,119 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Null', () => { + it('Should extend Any', () => { + type T = null extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = null extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = null extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = null extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = null extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = null extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array', () => { + type T = null extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = null extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = null extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = null extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 2', () => { + type T = null extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 3', () => { + type T = null extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = null extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = null extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = null extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Null', () => { + type T = null extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Undefined', () => { + type T = null extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = null extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = null extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Null(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/number.ts b/test/runtime/type/extends/number.ts new file mode 100644 index 0000000..06e893f --- /dev/null +++ b/test/runtime/type/extends/number.ts @@ -0,0 +1,113 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Number', () => { + it('Should extend Any', () => { + type T = number extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = number extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = number extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = number extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = number extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Integer', () => { + type T = number extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Array', () => { + type T = number extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = number extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = number extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = number extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = number extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = number extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = number extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = number extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = number extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = number extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = number extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = number extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/object.ts b/test/runtime/type/extends/object.ts new file mode 100644 index 0000000..b5a2973 --- /dev/null +++ b/test/runtime/type/extends/object.ts @@ -0,0 +1,177 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Object', () => { + // ---------------------------------------------------------- + // Object + // ---------------------------------------------------------- + it('Should extend Object 1', () => { + type T = { x: number; y: number } extends { x: number } ? 1 : 2 + const A = Type.Object({ x: Type.Number(), y: Type.Number() }) + const B = Type.Object({ x: Type.Number() }) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Object 2', () => { + type T = { x: number } extends { x: number; y: number } ? 1 : 2 + const A = Type.Object({ x: Type.Number() }) + const B = Type.Object({ x: Type.Number(), y: Type.Number() }) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 3', () => { + type T = { x: number; y: string } extends { x: number } ? 1 : 2 + const A = Type.Object({ x: Type.Number(), y: Type.String() }) + const B = Type.Object({ x: Type.Number() }) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Object 4', () => { + type T = { x: number } extends { x: number; y: string } ? 1 : 2 + const A = Type.Object({ x: Type.Number() }) + const B = Type.Object({ x: Type.Number(), y: Type.String() }) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 5', () => { + type T = { x: number | string } extends { x: number } ? 1 : 2 + const A = Type.Object({ x: Type.Union([Type.Number(), Type.String()]) }) + const B = Type.Object({ x: Type.Number() }) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 6', () => { + type T = { x: number } extends { x: number | string } ? 1 : 2 + const A = Type.Object({ x: Type.Number() }) + const B = Type.Object({ x: Type.Union([Type.Number(), Type.String()]) }) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + // ---------------------------------------------------------- + // Record + // ---------------------------------------------------------- + it('Should extend Record 2', () => { + type T = { a: number; b: number } extends Record ? 1 : 2 + const A = Type.Object({ a: Type.Number(), b: Type.Number() }) + const B = Type.Record(Type.String(), Type.Number()) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Record 3', () => { + type T = { a: number; b: number } extends Record ? 1 : 2 + const A = Type.Object({ a: Type.Number(), b: Type.Number() }) + const B = Type.Record(Type.Number(), Type.Number()) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Record 4', () => { + type T = { a: number; b: number } extends Record<'a' | 'b', number> ? 1 : 2 + const A = Type.Object({ a: Type.Number(), b: Type.Number() }) + const B = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Record 5', () => { + type T = { a: number; b: number } extends Record<'a' | 'b', number> ? 1 : 2 + const A = Type.Object({ a: Type.Number(), b: Type.Number() }) + const B = Type.Record(Type.String(), Type.Number()) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Record 6', () => { + type T = { a: number; b: number } extends Record<'a' | 'b', number> ? 1 : 2 + const A = Type.Object({ a: Type.Number(), b: Type.Number() }) + const B = Type.Record(Type.Number(), Type.Number()) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + // ---------------------------------------------------------- + // Standard + // ---------------------------------------------------------- + it('Should extend Any', () => { + type T = { a: number } extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend String', () => { + type T = { a: number } extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Boolean', () => { + type T = { a: number } extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Number', () => { + type T = { a: number } extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Integer', () => { + type T = { a: number } extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Array', () => { + type T = { a: number } extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple', () => { + type T = { a: number } extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 1', () => { + type T = { a: number } extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Object 2', () => { + type T = { a: number } extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Object({ a: Type.Literal(10) })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 3', () => { + type T = { a: number } extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Union 1', () => { + type T = { a: number } extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Union([Type.Null(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 2', () => { + type T = { a: number } extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Union 2', () => { + type T = { a: number } extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Null', () => { + type T = { a: number } extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Undefined', () => { + type T = { a: number } extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Void', () => { + type T = { a: number } extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Date', () => { + type T = { a: number } extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/promise.ts b/test/runtime/type/extends/promise.ts new file mode 100644 index 0000000..b8c6a24 --- /dev/null +++ b/test/runtime/type/extends/promise.ts @@ -0,0 +1,251 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Promise', () => { + // ---------------------------------------------- + // Generic Varying + // ---------------------------------------------- + + it('Should extend Promise Varying 1', () => { + type T = Promise extends Promise ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Promise(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Promise Varying 2', () => { + type T = Promise extends Promise ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.String()), Type.Promise(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Promise Varying 3', () => { + type T = Promise extends Promise ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Promise(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Promise Varying 4', () => { + type T = Promise extends Promise ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Promise(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + // ---------------------------------------------- + // Any + // ---------------------------------------------- + + it('Should extend Any', () => { + type T = Promise extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = Promise extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = Promise extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = Promise extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = Promise extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = Promise extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array', () => { + type T = Promise extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = Promise extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = Promise extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = Promise extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = Promise extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 3', () => { + type T = Promise extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 1', () => { + type T = Promise extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = Promise extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = Promise extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Null', () => { + type T = Promise extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = Promise extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + // ---------------------------------------------- + // Constrained + // ---------------------------------------------- + + it('Should extend constrained Any', () => { + type T = Promise extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Unknown', () => { + type T = Promise extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained String', () => { + type T = Promise extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Boolean', () => { + type T = Promise extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Number', () => { + type T = Promise extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Any()), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Integer', () => { + type T = Promise extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Array', () => { + type T = Promise extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Tuple', () => { + type T = Promise extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Object 1', () => { + type T = Promise extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Object 2', () => { + type T = Promise extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Object 3', () => { + type T = Promise extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Union 1', () => { + type T = Promise extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Union 2', () => { + type T = Promise extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend constrained Union 2', () => { + type T = Promise extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Null', () => { + type T = Promise extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend constrained Undefined', () => { + type T = Promise extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = Promise extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = Promise extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Promise(Type.Number()), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/conditional/record.ts b/test/runtime/type/extends/record.ts similarity index 58% rename from test/runtime/conditional/record.ts rename to test/runtime/type/extends/record.ts index 3968784..74a7920 100644 --- a/test/runtime/conditional/record.ts +++ b/test/runtime/type/extends/record.ts @@ -1,94 +1,94 @@ -import { Structural, StructuralResult } from '@sinclair/typebox/conditional' +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' -describe('conditional/structural/Record', () => { +describe('type/extends/Record', () => { it('Should extend Record 1', () => { type T = Record<'a' | 'b', number> extends { a: number; b: number } ? 1 : 2 const A = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) const B = Type.Object({ a: Type.Number(), b: Type.Number() }) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 2', () => { type T = Record extends { a: number; b: number } ? 1 : 2 const A = Type.Record(Type.String(), Type.Number()) const B = Type.Object({ a: Type.Number(), b: Type.Number() }) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Record 3', () => { type T = Record extends { a: number; b: number } ? 1 : 2 const A = Type.Record(Type.Number(), Type.Number()) const B = Type.Object({ a: Type.Number(), b: Type.Number() }) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Record 4', () => { type T = Record<'a' | 'b', number> extends { a: number; b: number } ? 1 : 2 const A = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) const B = Type.Object({ a: Type.Number(), b: Type.Number() }) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 5', () => { type T = Record<'a' | 'b', number> extends { a: number; b: number } ? 1 : 2 const A = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) const B = Type.Object({ a: Type.Number(), b: Type.Number() }) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 6', () => { type T = Record extends Record<'a' | 'b', number> ? true : false const A = Type.Record(Type.String(), Type.Number()) const B = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 7', () => { type T = Record extends Record ? 1 : 2 const A = Type.Record(Type.String(), Type.Number()) const B = Type.Record(Type.String(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 8', () => { type T = Record extends Record ? 1 : 2 const A = Type.Record(Type.String(), Type.Number()) const B = Type.Record(Type.Number(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 9', () => { type T = Record extends Record<'a' | 'b', number> ? 1 : 2 const A = Type.Record(Type.Number(), Type.Number()) const B = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Record 10', () => { type T = Record extends Record ? 1 : 2 const A = Type.Record(Type.Number(), Type.Number()) const B = Type.Record(Type.String(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 11', () => { type T = Record extends Record ? 1 : 2 const A = Type.Record(Type.Number(), Type.Number()) const B = Type.Record(Type.Number(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) // ----- @@ -97,32 +97,32 @@ describe('conditional/structural/Record', () => { type T = Record extends Record ? 1 : 2 const A = Type.Record(Type.String(), Type.Number()) const B = Type.Record(Type.Integer(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 13', () => { type T = Record extends Record<'a' | 'b', number> ? 1 : 2 const A = Type.Record(Type.Integer(), Type.Number()) const B = Type.Record(Type.Union([Type.Literal('a'), Type.Literal('b')]), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Record 14', () => { type T = Record extends Record ? 1 : 2 const A = Type.Record(Type.Integer(), Type.Number()) const B = Type.Record(Type.String(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Record 15', () => { type T = Record extends Record ? 1 : 2 const A = Type.Record(Type.Integer(), Type.Number()) const B = Type.Record(Type.Integer(), Type.Number()) - const R = Structural.Check(A, B) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(A, B) + Assert.deepEqual(R, TypeExtendsResult.True) }) // ------------------------------------------------------------------- @@ -131,109 +131,103 @@ describe('conditional/structural/Record', () => { it('Should extend Any', () => { type T = Record extends any ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Any()) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Unknown', () => { type T = Record extends unknown ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Unknown()) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend String', () => { type T = Record extends string ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.String()) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Boolean', () => { type T = Record extends boolean ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Boolean()) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Number', () => { type T = Record extends number ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Number()) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Integer', () => { type T = Record extends number ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Integer()) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Array 1', () => { type T = Record extends Array ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Array(Type.Any())) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Array 2', () => { type T = Record extends Array ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Array(Type.String())) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Array(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Tuple', () => { type T = Record extends [number, number] ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Tuple([Type.Number(), Type.Number()])) - Assert.deepEqual(R, StructuralResult.False) - }) - - it('Should extend Object 1', () => { - type T = Record extends object ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Object 2', () => { type T = Record extends {} ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Object({})) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Object 3', () => { type T = Record extends { a: number } ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Object({ a: Type.Number() })) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Object({ a: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Union 1', () => { type T = Record extends number | string ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Union([Type.Number(), Type.String()])) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Union 2', () => { type T = Record extends any | number ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Union([Type.Any(), Type.String()])) - Assert.deepEqual(R, StructuralResult.True) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Union([Type.Any(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) }) it('Should extend Null', () => { type T = Record extends null ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Null()) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Undefined', () => { type T = Record extends undefined ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Undefined()) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Void', () => { type T = Record extends void ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Void()) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) }) it('Should extend Date', () => { type T = Record extends Date ? 1 : 2 - const R = Structural.Check(Type.Record(Type.Number(), Type.Number()), Type.Date()) - Assert.deepEqual(R, StructuralResult.False) + const R = TypeExtends.Extends(Type.Record(Type.Number(), Type.Number()), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) }) }) diff --git a/test/runtime/type/extends/string.ts b/test/runtime/type/extends/string.ts new file mode 100644 index 0000000..1ba4aa4 --- /dev/null +++ b/test/runtime/type/extends/string.ts @@ -0,0 +1,131 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/String', () => { + it('Should extend Any', () => { + type T = string extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = string extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = string extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Boolean', () => { + type T = string extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = string extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = string extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array', () => { + type T = string extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = string extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record 1', () => { + type T = string extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Record 2', () => { + type T = string extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Record(Type.Number(), Type.Unknown())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Record 3', () => { + type T = string extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Record(Type.Number(), Type.String())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Record 4', () => { + type T = string extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Record(Type.Number(), Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = string extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = string extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = number extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = number extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3', () => { + type T = number extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = number extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = number extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = number extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = number extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Number(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/symbol.ts b/test/runtime/type/extends/symbol.ts new file mode 100644 index 0000000..402de3d --- /dev/null +++ b/test/runtime/type/extends/symbol.ts @@ -0,0 +1,106 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Symbol', () => { + it('Should extend Any', () => { + type T = symbol extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Unknown', () => { + type T = symbol extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend String', () => { + type T = symbol extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Boolean', () => { + type T = symbol extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Number', () => { + type T = symbol extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Integer', () => { + type T = symbol extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Array', () => { + type T = symbol extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple', () => { + type T = symbol extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Record', () => { + type T = symbol extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 1', () => { + type T = symbol extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Object 2', () => { + type T = symbol extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Object({ a: Type.Literal(10) })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 1', () => { + type T = symbol extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 2', () => { + type T = symbol extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Union 3', () => { + type T = symbol extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 4', () => { + type T = symbol extends boolean | symbol ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Union([Type.Boolean(), Type.Symbol()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Null', () => { + type T = symbol extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Undefined', () => { + type T = symbol extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Void', () => { + type T = symbol extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Date', () => { + type T = symbol extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Symbol', () => { + type T = symbol extends symbol ? 1 : 2 + const R = TypeExtends.Extends(Type.Symbol(), Type.Symbol()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) +}) diff --git a/test/runtime/type/extends/tuple.ts b/test/runtime/type/extends/tuple.ts new file mode 100644 index 0000000..15486a6 --- /dev/null +++ b/test/runtime/type/extends/tuple.ts @@ -0,0 +1,161 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Tuple', () => { + it('Should extend Any', () => { + type T = [string, number] extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend String', () => { + type T = [string, number] extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Boolean', () => { + type T = [string, number] extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Number', () => { + type T = [string, number] extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Integer', () => { + type T = [string, number] extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Array 1', () => { + type T = [string, number] extends Array ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Array 2', () => { + type T = [string, number] extends Array ? 1 : 2 // 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Array(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Array 3', () => { + type T = [string, number] extends Array ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Array(Type.Union([Type.String(), Type.Number()]))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Array 4', () => { + type T = [string, number] extends Array ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Array(Type.Union([Type.String(), Type.Number(), Type.Boolean()]))) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Tuple 1', () => { + type T = [string, number] extends [string, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([Type.String(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Tuple 2', () => { + type T = [string, number] extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple 3', () => { + type T = [string, any] extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Any()]), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple 4', () => { + type T = [string, number] extends [string, any] ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([Type.String(), Type.Any()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Tuple 5', () => { + type T = [string, unknown] extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Unknown()]), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple 6', () => { + type T = [string, number] extends [string, unknown] ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([Type.String(), Type.Unknown()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Tuple 7', () => { + type T = [] extends [string, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([]), Type.Tuple([Type.String(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple 8', () => { + type T = [string, number] extends [] ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Tuple([])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Record 1', () => { + type T = [string, number] extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Record 2', () => { + type T = [string, number] extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Record(Type.Number(), Type.Unknown())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Record 3', () => { + type T = [string, number] extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Record(Type.Number(), Type.String())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Record 4', () => { + type T = [string, number] extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.String(), Type.Record(Type.Number(), Type.Number())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 1', () => { + type T = [string, number] extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Object 2', () => { + type T = [string, number] extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 3', () => { + type T = [string, number] extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Union 1', () => { + type T = [string, number] extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 2', () => { + type T = [string, number] extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Union 2', () => { + type T = [string, number] extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Null', () => { + type T = [string, number] extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Undefined', () => { + type T = [string, number] extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Void', () => { + type T = [string, number] extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Date', () => { + type T = [string, number] extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Tuple([Type.String(), Type.Number()]), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/uint8array.ts b/test/runtime/type/extends/uint8array.ts new file mode 100644 index 0000000..756a93d --- /dev/null +++ b/test/runtime/type/extends/uint8array.ts @@ -0,0 +1,113 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Uint8Array', () => { + it('Should extend Any', () => { + type T = Uint8Array extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = Uint8Array extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = Uint8Array extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = Uint8Array extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = Uint8Array extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = Uint8Array extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array', () => { + type T = Uint8Array extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = Uint8Array extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Record', () => { + type T = Uint8Array extends Record ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Record(Type.Number(), Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 1', () => { + type T = Uint8Array extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Object 2', () => { + type T = Uint8Array extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = Uint8Array extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = Uint8Array extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3', () => { + type T = Uint8Array extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Null', () => { + type T = Uint8Array extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = Uint8Array extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = Uint8Array extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = Uint8Array extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Uint8Array(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/undefined.ts b/test/runtime/type/extends/undefined.ts new file mode 100644 index 0000000..46a4061 --- /dev/null +++ b/test/runtime/type/extends/undefined.ts @@ -0,0 +1,107 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Undefined', () => { + it('Should extend Any', () => { + type T = undefined extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = undefined extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = undefined extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = undefined extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = undefined extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array', () => { + type T = undefined extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = undefined extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = undefined extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 2', () => { + type T = undefined extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 3', () => { + type T = undefined extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = undefined extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = undefined extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 2', () => { + type T = undefined extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Null', () => { + type T = undefined extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = undefined extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Void', () => { + type T = undefined extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Date', () => { + type T = undefined extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Undefined(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/union.ts b/test/runtime/type/extends/union.ts new file mode 100644 index 0000000..ab749f7 --- /dev/null +++ b/test/runtime/type/extends/union.ts @@ -0,0 +1,136 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Union', () => { + it('Should extend Any', () => { + type T = number | string extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend String', () => { + type T = number | string extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Boolean', () => { + type T = number | string extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Number', () => { + type T = number | string extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Integer', () => { + type T = number | string extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Array', () => { + type T = number | string extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Tuple', () => { + type T = number | string extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Object 1', () => { + type T = number | string extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Object({}, { additionalProperties: false })) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Object 2', () => { + type T = number | string extends { a: 10 } ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Object({ a: Type.Literal(10) }, { additionalProperties: true })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 1', () => { + type T = number | string extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Union 2', () => { + type T = number | string extends any | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Union([Type.Any(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + it('Should extend Union 3', () => { + type T = number | string extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 4', () => { + type T = any | boolean extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Union 5', () => { + type T = any | string extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Union 6', () => { + type T = any | {} extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Union 7', () => { + type T = any extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Any(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.Union) + }) + it('Should extend Union 8', () => { + type T = unknown | string extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Unknown(), Type.String()]), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Union 9', () => { + type T = unknown extends boolean | number ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Union([Type.Boolean(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Null', () => { + type T = number | string extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Undefined', () => { + type T = number | string extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Void', () => { + type T = number | string extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Void 2', () => { + type T = number | string | void extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Date', () => { + type T = number | string | void extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Number(), Type.String()]), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Date 2', () => { + type T = Date | number | string | void extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Date(), Type.Number(), Type.String()]), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend BigInt', () => { + type T = bigint | number | string | void extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.BigInt(), Type.Number(), Type.String()]), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + it('Should extend Symbol', () => { + type T = symbol | number | string | void extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Union([Type.Symbol(), Type.Number(), Type.String()]), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/unknown.ts b/test/runtime/type/extends/unknown.ts new file mode 100644 index 0000000..c37f4c6 --- /dev/null +++ b/test/runtime/type/extends/unknown.ts @@ -0,0 +1,119 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Unknown', () => { + it('Should extend Any', () => { + type T = unknown extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = unknown extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = unknown extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = unknown extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = unknown extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = unknown extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 1', () => { + type T = unknown extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 2', () => { + type T = unknown extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Array(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = unknown extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = unknown extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 2', () => { + type T = unknown extends { a: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Object({ a: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = unknown extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = unknown extends any | number ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Unknown(), Type.Union([Type.Any(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3', () => { + type T = unknown extends unknown | number ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Unknown(), Type.Union([Type.Unknown(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 4', () => { + type T = unknown extends unknown | any ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Unknown(), Type.Union([Type.Unknown(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = unknown extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = unknown extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = unknown extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Date', () => { + type T = unknown extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Unknown(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/type/extends/void.ts b/test/runtime/type/extends/void.ts new file mode 100644 index 0000000..d800fb0 --- /dev/null +++ b/test/runtime/type/extends/void.ts @@ -0,0 +1,125 @@ +import { TypeExtends, TypeExtendsResult } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/extends/Void', () => { + it('Should extend Any', () => { + type T = void extends any ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Any()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Unknown', () => { + type T = void extends unknown ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Unknown()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend String', () => { + type T = void extends string ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.String()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Boolean', () => { + type T = void extends boolean ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Boolean()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Number', () => { + type T = void extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Number()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Integer', () => { + type T = void extends number ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Integer()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 1', () => { + type T = void extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Array(Type.Any())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Array 2', () => { + type T = void extends Array ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Array(Type.String())) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Tuple', () => { + type T = void extends [number, number] ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Tuple([Type.Number(), Type.Number()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 1', () => { + type T = void extends object ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 2', () => { + type T = void extends {} ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Object({})) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Object 3', () => { + type T = void extends { a: number } ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Object({ a: Type.Number() })) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 1', () => { + type T = void extends number | string ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Union([Type.Number(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Union 2', () => { + type T = void extends any | number ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Void(), Type.Union([Type.Any(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 3', () => { + type T = void extends unknown | number ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Void(), Type.Union([Type.Unknown(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Union 4', () => { + type T = void extends unknown | any ? 1 : 2 // 1 + const R = TypeExtends.Extends(Type.Void(), Type.Union([Type.Unknown(), Type.String()])) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Null', () => { + type T = void extends null ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Null()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Undefined', () => { + type T = void extends undefined ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Undefined()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) + + it('Should extend Void', () => { + type T = void extends void ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Void()) + Assert.deepEqual(R, TypeExtendsResult.True) + }) + + it('Should extend Date', () => { + type T = void extends Date ? 1 : 2 + const R = TypeExtends.Extends(Type.Void(), Type.Date()) + Assert.deepEqual(R, TypeExtendsResult.False) + }) +}) diff --git a/test/runtime/guard/any.ts b/test/runtime/type/guard/any.ts similarity index 83% rename from test/runtime/guard/any.ts rename to test/runtime/type/guard/any.ts index 039fa56..8f8e4d8 100644 --- a/test/runtime/guard/any.ts +++ b/test/runtime/type/guard/any.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TAny', () => { it('Should guard for TAny', () => { diff --git a/test/runtime/guard/array.ts b/test/runtime/type/guard/array.ts similarity index 94% rename from test/runtime/guard/array.ts rename to test/runtime/type/guard/array.ts index 4026146..a6bec88 100644 --- a/test/runtime/guard/array.ts +++ b/test/runtime/type/guard/array.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TArray', () => { it('Should guard for TArray', () => { diff --git a/test/runtime/type/guard/bigint.ts b/test/runtime/type/guard/bigint.ts new file mode 100644 index 0000000..12be191 --- /dev/null +++ b/test/runtime/type/guard/bigint.ts @@ -0,0 +1,19 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/guard/TBigInt', () => { + it('Should guard for TBigInt', () => { + const R = TypeGuard.TBigInt(Type.BigInt()) + Assert.equal(R, true) + }) + it('Should not guard for TBigInt', () => { + const R = TypeGuard.TBigInt(null) + Assert.equal(R, false) + }) + it('Should not guard for BigInt with invalid $id', () => { + // @ts-ignore + const R = TypeGuard.TBigInt(Type.BigInt({ $id: 1 })) + Assert.equal(R, false) + }) +}) diff --git a/test/runtime/guard/boolean.ts b/test/runtime/type/guard/boolean.ts similarity index 84% rename from test/runtime/guard/boolean.ts rename to test/runtime/type/guard/boolean.ts index 7168041..5368ded 100644 --- a/test/runtime/guard/boolean.ts +++ b/test/runtime/type/guard/boolean.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TBoolean', () => { it('Should guard for TBoolean', () => { diff --git a/test/runtime/guard/constructor.ts b/test/runtime/type/guard/constructor.ts similarity index 93% rename from test/runtime/guard/constructor.ts rename to test/runtime/type/guard/constructor.ts index 9bb2d4b..813594c 100644 --- a/test/runtime/guard/constructor.ts +++ b/test/runtime/type/guard/constructor.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TConstructor', () => { it('Should guard for TConstructor', () => { diff --git a/test/runtime/guard/date.ts b/test/runtime/type/guard/date.ts similarity index 92% rename from test/runtime/guard/date.ts rename to test/runtime/type/guard/date.ts index 14132a7..b14cd46 100644 --- a/test/runtime/guard/date.ts +++ b/test/runtime/type/guard/date.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TDate', () => { it('Should guard for TDate', () => { diff --git a/test/runtime/type/guard/exclude.ts b/test/runtime/type/guard/exclude.ts new file mode 100644 index 0000000..be3e622 --- /dev/null +++ b/test/runtime/type/guard/exclude.ts @@ -0,0 +1,24 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/guard/TExclude', () => { + it('Should extract string from number', () => { + const T = Type.Exclude(Type.String(), Type.Number()) + Assert.deepEqual(TypeGuard.TString(T), true) + }) + it('Should extract string from string', () => { + const T = Type.Exclude(Type.String(), Type.String()) + Assert.deepEqual(TypeGuard.TNever(T), true) + }) + it('Should extract string | number | boolean from string', () => { + const T = Type.Exclude(Type.Union([Type.String(), Type.Number(), Type.Boolean()]), Type.String()) + Assert.deepEqual(TypeGuard.TUnion(T), true) + Assert.deepEqual(TypeGuard.TNumber(T.anyOf[0]), true) + Assert.deepEqual(TypeGuard.TBoolean(T.anyOf[1]), true) + }) + it('Should extract string | number | boolean from string | boolean', () => { + const T = Type.Exclude(Type.Union([Type.String(), Type.Number(), Type.Boolean()]), Type.Union([Type.String(), Type.Boolean()])) + Assert.deepEqual(TypeGuard.TNumber(T), true) + }) +}) diff --git a/test/runtime/type/guard/extract.ts b/test/runtime/type/guard/extract.ts new file mode 100644 index 0000000..f1caa37 --- /dev/null +++ b/test/runtime/type/guard/extract.ts @@ -0,0 +1,24 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/guard/TExtract', () => { + it('Should extract string from number', () => { + const T = Type.Extract(Type.String(), Type.Number()) + Assert.deepEqual(TypeGuard.TNever(T), true) + }) + it('Should extract string from string', () => { + const T = Type.Extract(Type.String(), Type.String()) + Assert.deepEqual(TypeGuard.TString(T), true) + }) + it('Should extract string | number | boolean from string', () => { + const T = Type.Extract(Type.Union([Type.String(), Type.Number(), Type.Boolean()]), Type.String()) + Assert.deepEqual(TypeGuard.TString(T), true) + }) + it('Should extract string | number | boolean from string | boolean', () => { + const T = Type.Extract(Type.Union([Type.String(), Type.Number(), Type.Boolean()]), Type.Union([Type.String(), Type.Boolean()])) + Assert.deepEqual(TypeGuard.TUnion(T), true) + Assert.deepEqual(TypeGuard.TString(T.anyOf[0]), true) + Assert.deepEqual(TypeGuard.TBoolean(T.anyOf[1]), true) + }) +}) diff --git a/test/runtime/guard/function.ts b/test/runtime/type/guard/function.ts similarity index 93% rename from test/runtime/guard/function.ts rename to test/runtime/type/guard/function.ts index 102ac4f..e70b67d 100644 --- a/test/runtime/guard/function.ts +++ b/test/runtime/type/guard/function.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TFunction', () => { it('Should guard for TFunction', () => { diff --git a/test/runtime/guard/index.ts b/test/runtime/type/guard/index.ts similarity index 78% rename from test/runtime/guard/index.ts rename to test/runtime/type/guard/index.ts index 037d137..d66cdf0 100644 --- a/test/runtime/guard/index.ts +++ b/test/runtime/type/guard/index.ts @@ -1,11 +1,16 @@ import './any' import './array' +import './bigint' import './boolean' import './constructor' import './date' +import './exclude' +import './extract' import './function' import './integer' import './literal' +import './intersect' +import './not' import './null' import './number' import './object' @@ -14,6 +19,7 @@ import './record' import './ref' import './self' import './string' +import './symbol' import './tuple' import './uint8array' import './undefined' diff --git a/test/runtime/guard/integer.ts b/test/runtime/type/guard/integer.ts similarity index 93% rename from test/runtime/guard/integer.ts rename to test/runtime/type/guard/integer.ts index 72558e0..28976d2 100644 --- a/test/runtime/guard/integer.ts +++ b/test/runtime/type/guard/integer.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TInteger', () => { it('Should guard for TInteger', () => { diff --git a/test/runtime/type/guard/intersect.ts b/test/runtime/type/guard/intersect.ts new file mode 100644 index 0000000..907b4a7 --- /dev/null +++ b/test/runtime/type/guard/intersect.ts @@ -0,0 +1,32 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/guard/TUnion', () => { + it('Should guard for TIntersect', () => { + const R = TypeGuard.TIntersect( + Type.Intersect([ + Type.Object({ + x: Type.Number(), + }), + Type.Object({ + y: Type.Number(), + }), + ]), + ) + Assert.equal(R, true) + }) + it('Should not guard for TIntersect', () => { + const R = TypeGuard.TIntersect( + Type.Union([ + Type.Object({ + x: Type.Number(), + }), + Type.Object({ + y: Type.Number(), + }), + ]), + ) + Assert.equal(R, false) + }) +}) diff --git a/test/runtime/guard/literal.ts b/test/runtime/type/guard/literal.ts similarity index 91% rename from test/runtime/guard/literal.ts rename to test/runtime/type/guard/literal.ts index df4d39e..674c9dd 100644 --- a/test/runtime/guard/literal.ts +++ b/test/runtime/type/guard/literal.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TLiteral', () => { it('Should guard for TLiteral of String', () => { diff --git a/test/runtime/type/guard/not.ts b/test/runtime/type/guard/not.ts new file mode 100644 index 0000000..0bbc9d5 --- /dev/null +++ b/test/runtime/type/guard/not.ts @@ -0,0 +1,23 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/guard/TNot', () => { + it('Should guard for TNot', () => { + const R = TypeGuard.TNot(Type.Not(Type.String(), Type.String())) + Assert.equal(R, true) + }) + it('Should not guard for TNot 1', () => { + const R = TypeGuard.TNot(Type.Not(null as any, Type.String())) + Assert.equal(R, false) + }) + it('Should not guard for TNot 2', () => { + const R = TypeGuard.TNot(Type.Not(Type.String(), null as any)) + Assert.equal(R, false) + }) + it('Should not guard for TNot with invalid $id', () => { + // @ts-ignore + const R = TypeGuard.TNot(Type.Not(Type.String(), Type.String()), { $id: 1 }) + Assert.equal(R, true) + }) +}) diff --git a/test/runtime/guard/null.ts b/test/runtime/type/guard/null.ts similarity index 83% rename from test/runtime/guard/null.ts rename to test/runtime/type/guard/null.ts index d853e11..11656ab 100644 --- a/test/runtime/guard/null.ts +++ b/test/runtime/type/guard/null.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TNull', () => { it('Should guard for TNull', () => { diff --git a/test/runtime/guard/number.ts b/test/runtime/type/guard/number.ts similarity index 93% rename from test/runtime/guard/number.ts rename to test/runtime/type/guard/number.ts index eb61416..c325b27 100644 --- a/test/runtime/guard/number.ts +++ b/test/runtime/type/guard/number.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TNumber', () => { it('Should guard for TNumber', () => { diff --git a/test/runtime/guard/object.ts b/test/runtime/type/guard/object.ts similarity index 96% rename from test/runtime/guard/object.ts rename to test/runtime/type/guard/object.ts index 867774a..2cdb504 100644 --- a/test/runtime/guard/object.ts +++ b/test/runtime/type/guard/object.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TObject', () => { it('Should guard for TObject', () => { diff --git a/test/runtime/guard/promise.ts b/test/runtime/type/guard/promise.ts similarity index 91% rename from test/runtime/guard/promise.ts rename to test/runtime/type/guard/promise.ts index fdfbfb1..eeedaf2 100644 --- a/test/runtime/guard/promise.ts +++ b/test/runtime/type/guard/promise.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TPromise', () => { it('Should guard for TPromise', () => { diff --git a/test/runtime/guard/record.ts b/test/runtime/type/guard/record.ts similarity index 66% rename from test/runtime/guard/record.ts rename to test/runtime/type/guard/record.ts index 92ab62c..2418c0e 100644 --- a/test/runtime/guard/record.ts +++ b/test/runtime/type/guard/record.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TRecord', () => { it('Should guard for TRecord', () => { @@ -45,9 +45,14 @@ describe('type/guard/TRecord', () => { Assert.equal(R, false) }) - it('Should not guard for TRecord with invalid literal key', () => { - const K = Type.Union([Type.Literal('hello\nworld')]) - const R = TypeGuard.TRecord(Type.Record(K, Type.Number())) - Assert.equal(R, false) + it('Transform: Should should transform to TObject for single literal union value', () => { + const K = Type.Union([Type.Literal('ok')]) + const R = TypeGuard.TObject(Type.Record(K, Type.Number())) + Assert.equal(R, true) + }) + it('Transform: Should should transform to TObject for multi literal union value', () => { + const K = Type.Union([Type.Literal('A'), Type.Literal('B')]) + const R = TypeGuard.TObject(Type.Record(K, Type.Number())) + Assert.equal(R, true) }) }) diff --git a/test/runtime/guard/ref.ts b/test/runtime/type/guard/ref.ts similarity index 85% rename from test/runtime/guard/ref.ts rename to test/runtime/type/guard/ref.ts index 6a4a8e8..e76680c 100644 --- a/test/runtime/guard/ref.ts +++ b/test/runtime/type/guard/ref.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TRef', () => { it('Should guard for TRef', () => { diff --git a/test/runtime/guard/self.ts b/test/runtime/type/guard/self.ts similarity index 85% rename from test/runtime/guard/self.ts rename to test/runtime/type/guard/self.ts index 824d77e..d9059c1 100644 --- a/test/runtime/guard/self.ts +++ b/test/runtime/type/guard/self.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TSelf', () => { it('Should guard for TSelf', () => { diff --git a/test/runtime/guard/string.ts b/test/runtime/type/guard/string.ts similarity index 91% rename from test/runtime/guard/string.ts rename to test/runtime/type/guard/string.ts index a3cc7c2..24b33dd 100644 --- a/test/runtime/guard/string.ts +++ b/test/runtime/type/guard/string.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TString', () => { it('Should guard for TString', () => { diff --git a/test/runtime/type/guard/symbol.ts b/test/runtime/type/guard/symbol.ts new file mode 100644 index 0000000..7683f09 --- /dev/null +++ b/test/runtime/type/guard/symbol.ts @@ -0,0 +1,19 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/guard/TSymbol', () => { + it('Should guard for TSymbol', () => { + const R = TypeGuard.TSymbol(Type.Symbol()) + Assert.equal(R, true) + }) + it('Should not guard for TSymbol', () => { + const R = TypeGuard.TSymbol(null) + Assert.equal(R, false) + }) + it('Should not guard for TSymbol with invalid $id', () => { + // @ts-ignore + const R = TypeGuard.TSymbol(Type.Symbol({ $id: 1 })) + Assert.equal(R, false) + }) +}) diff --git a/test/runtime/guard/tuple.ts b/test/runtime/type/guard/tuple.ts similarity index 88% rename from test/runtime/guard/tuple.ts rename to test/runtime/type/guard/tuple.ts index 935426e..8e549aa 100644 --- a/test/runtime/guard/tuple.ts +++ b/test/runtime/type/guard/tuple.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TTuple', () => { it('Should guard for TTuple', () => { diff --git a/test/runtime/guard/uint8array.ts b/test/runtime/type/guard/uint8array.ts similarity index 90% rename from test/runtime/guard/uint8array.ts rename to test/runtime/type/guard/uint8array.ts index 0143c3f..cd4c741 100644 --- a/test/runtime/guard/uint8array.ts +++ b/test/runtime/type/guard/uint8array.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TUint8Array', () => { it('Should guard for TUint8Array', () => { diff --git a/test/runtime/guard/undefined.ts b/test/runtime/type/guard/undefined.ts similarity index 84% rename from test/runtime/guard/undefined.ts rename to test/runtime/type/guard/undefined.ts index 0b7f471..cc8bbee 100644 --- a/test/runtime/guard/undefined.ts +++ b/test/runtime/type/guard/undefined.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TUndefined', () => { it('Should guard for TUndefined', () => { diff --git a/test/runtime/guard/union.ts b/test/runtime/type/guard/union.ts similarity index 63% rename from test/runtime/guard/union.ts rename to test/runtime/type/guard/union.ts index b26465a..16fc75d 100644 --- a/test/runtime/guard/union.ts +++ b/test/runtime/type/guard/union.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TUnion', () => { it('Should guard for TUnion', () => { @@ -22,6 +22,7 @@ describe('type/guard/TUnion', () => { }) it('Should guard for TUnion with invalid $id', () => { const R = TypeGuard.TUnion( + // @ts-ignore Type.Union( [ Type.Object({ @@ -63,4 +64,23 @@ describe('type/guard/TUnion', () => { ) Assert.equal(R, false) }) + it('Transform: Should transform to never for zero length union', () => { + const T = Type.Union([]) + const R = TypeGuard.TNever(T) + Assert.equal(R, true) + }) + it('Transform: Should unwrap union type for array of length === 1', () => { + const T = Type.Union([Type.String()]) + const R = TypeGuard.TString(T) + Assert.equal(R, true) + }) + it('Transform: Should retain union if array length > 1', () => { + const T = Type.Union([Type.String(), Type.Number()]) + const R1 = TypeGuard.TUnion(T) + const R2 = TypeGuard.TString(T.anyOf[0]) + const R3 = TypeGuard.TNumber(T.anyOf[1]) + Assert.equal(R1, true) + Assert.equal(R2, true) + Assert.equal(R3, true) + }) }) diff --git a/test/runtime/guard/unknown.ts b/test/runtime/type/guard/unknown.ts similarity index 84% rename from test/runtime/guard/unknown.ts rename to test/runtime/type/guard/unknown.ts index d23f2c2..4d3e598 100644 --- a/test/runtime/guard/unknown.ts +++ b/test/runtime/type/guard/unknown.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TUnknown', () => { it('Should guard for TUnknown', () => { diff --git a/test/runtime/guard/void.ts b/test/runtime/type/guard/void.ts similarity index 83% rename from test/runtime/guard/void.ts rename to test/runtime/type/guard/void.ts index d513f5f..839719a 100644 --- a/test/runtime/guard/void.ts +++ b/test/runtime/type/guard/void.ts @@ -1,6 +1,6 @@ -import { TypeGuard } from '@sinclair/typebox/guard' +import { TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' -import { Assert } from '../assert/index' +import { Assert } from '../../assert/index' describe('type/guard/TVoid', () => { it('Should guard for TVoid', () => { diff --git a/test/runtime/type/index.ts b/test/runtime/type/index.ts new file mode 100644 index 0000000..2ec254a --- /dev/null +++ b/test/runtime/type/index.ts @@ -0,0 +1,5 @@ +import './clone/index' +import './extends/index' +import './guard/index' +import './registry/index' +import './normal/index' diff --git a/test/runtime/type/normal/exclude.ts b/test/runtime/type/normal/exclude.ts new file mode 100644 index 0000000..fd02342 --- /dev/null +++ b/test/runtime/type/normal/exclude.ts @@ -0,0 +1,21 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/normal/Exclude', () => { + it('Normalize 1', () => { + const T = Type.Exclude(Type.Union([Type.String(), Type.Number(), Type.Boolean()]), Type.String()) + const R = TypeGuard.TUnion(T) + Assert.deepEqual(R, true) + }) + it('Normalize 2', () => { + const T = Type.Exclude(Type.Union([Type.String(), Type.Number()]), Type.String()) + const R = TypeGuard.TNumber(T) + Assert.deepEqual(R, true) + }) + it('Normalize 3', () => { + const T = Type.Exclude(Type.Union([Type.String()]), Type.String()) + const R = TypeGuard.TNever(T) + Assert.deepEqual(R, true) + }) +}) diff --git a/test/runtime/type/normal/extract.ts b/test/runtime/type/normal/extract.ts new file mode 100644 index 0000000..bb3a3d0 --- /dev/null +++ b/test/runtime/type/normal/extract.ts @@ -0,0 +1,31 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/normal/Extract', () => { + it('Normalize 1', () => { + const T = Type.Extract(Type.Union([Type.String(), Type.Number(), Type.Boolean()]), Type.Union([Type.String(), Type.Number()])) + const R = TypeGuard.TUnion(T) + Assert.deepEqual(R, true) + }) + it('Normalize 2', () => { + const T = Type.Extract(Type.Union([Type.String(), Type.Number(), Type.Boolean()]), Type.String()) + const R = TypeGuard.TString(T) + Assert.deepEqual(R, true) + }) + it('Normalize 3', () => { + const T = Type.Extract(Type.Union([Type.String(), Type.Number()]), Type.String()) + const R = TypeGuard.TString(T) + Assert.deepEqual(R, true) + }) + it('Normalize 4', () => { + const T = Type.Extract(Type.Union([Type.String()]), Type.String()) + const R = TypeGuard.TString(T) + Assert.deepEqual(R, true) + }) + it('Normalize 5', () => { + const T = Type.Extract(Type.Union([]), Type.String()) + const R = TypeGuard.TNever(T) + Assert.deepEqual(R, true) + }) +}) diff --git a/test/runtime/type/normal/index.ts b/test/runtime/type/normal/index.ts new file mode 100644 index 0000000..d0d7e13 --- /dev/null +++ b/test/runtime/type/normal/index.ts @@ -0,0 +1,5 @@ +import './exclude' +import './extract' +import './intersect' +import './record' +import './union' diff --git a/test/runtime/type/normal/intersect.ts b/test/runtime/type/normal/intersect.ts new file mode 100644 index 0000000..95accfd --- /dev/null +++ b/test/runtime/type/normal/intersect.ts @@ -0,0 +1,21 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/normal/Intersect', () => { + it('Normalize 1', () => { + const T = Type.Intersect([Type.Number(), Type.String()]) + const R = TypeGuard.TIntersect(T) + Assert.deepEqual(R, true) + }) + it('Normalize 2', () => { + const T = Type.Intersect([Type.Number()]) + const R = TypeGuard.TNumber(T) + Assert.deepEqual(R, true) + }) + it('Normalize 3', () => { + const T = Type.Intersect([]) + const R = TypeGuard.TNever(T) + Assert.deepEqual(R, true) + }) +}) diff --git a/test/runtime/type/normal/record.ts b/test/runtime/type/normal/record.ts new file mode 100644 index 0000000..5333790 --- /dev/null +++ b/test/runtime/type/normal/record.ts @@ -0,0 +1,12 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/normal/Record', () => { + it('Normalize 1', () => { + const K = Type.Union([Type.Literal('A'), Type.Literal('B')]) + const T = Type.Record(K, Type.String()) + const R = TypeGuard.TObject(T) + Assert.deepEqual(R, true) + }) +}) diff --git a/test/runtime/type/normal/union.ts b/test/runtime/type/normal/union.ts new file mode 100644 index 0000000..44e5a7e --- /dev/null +++ b/test/runtime/type/normal/union.ts @@ -0,0 +1,21 @@ +import { TypeGuard } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('type/normal/Union', () => { + it('Normalize 1', () => { + const T = Type.Union([Type.Number(), Type.String()]) + const R = TypeGuard.TUnion(T) + Assert.deepEqual(R, true) + }) + it('Normalize 2', () => { + const T = Type.Union([Type.Number()]) + const R = TypeGuard.TNumber(T) + Assert.deepEqual(R, true) + }) + it('Normalize 3', () => { + const T = Type.Union([]) + const R = TypeGuard.TNever(T) + Assert.deepEqual(R, true) + }) +}) diff --git a/test/runtime/type/registry/format.ts b/test/runtime/type/registry/format.ts new file mode 100644 index 0000000..7f04d15 --- /dev/null +++ b/test/runtime/type/registry/format.ts @@ -0,0 +1,25 @@ +import { FormatRegistry } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('format/Format', () => { + it('Should set format', () => { + FormatRegistry.Set('test#format1', () => true) + }) + + it('Should get format', () => { + FormatRegistry.Set('test#format2', () => true) + const format = FormatRegistry.Get('test#format2') + Assert.equal(typeof format, 'function') + }) + + it('Should return true if exists', () => { + FormatRegistry.Set('test#format3', () => true) + Assert.equal(FormatRegistry.Has('test#format3'), true) + }) + + it('Should clear formats', () => { + FormatRegistry.Set('test#format4', () => true) + FormatRegistry.Clear() + Assert.equal(FormatRegistry.Has('test#format4'), false) + }) +}) diff --git a/test/runtime/format/index.ts b/test/runtime/type/registry/index.ts similarity index 100% rename from test/runtime/format/index.ts rename to test/runtime/type/registry/index.ts diff --git a/test/runtime/value/cast/any.ts b/test/runtime/value/cast/any.ts index 155b2fc..a294da4 100644 --- a/test/runtime/value/cast/any.ts +++ b/test/runtime/value/cast/any.ts @@ -34,19 +34,16 @@ describe('value/cast/Any', () => { const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result.getTime(100), 100) }) - it('Should preserve', () => { const value = { a: 1, b: 2 } const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/array.ts b/test/runtime/value/cast/array.ts index fca7b47..820c3f6 100644 --- a/test/runtime/value/cast/array.ts +++ b/test/runtime/value/cast/array.ts @@ -5,135 +5,110 @@ import { Assert } from '../../assert/index' describe('value/cast/Array', () => { const T = Type.Array(Type.Number(), { default: [1, 2, 3] }) const E = [1, 2, 3] - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, [1]) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = [6, 7, 8] const result = Value.Cast(T, value) Assert.deepEqual(result, [6, 7, 8]) }) - it('Should preserve with invalid element set to default', () => { const value = [6, 7, 8, 'hello', 9] const result = Value.Cast(T, value) Assert.deepEqual(result, [6, 7, 8, 0, 9]) }) - // ----------------------------------------------------------------- // Constraints: Ranges // ----------------------------------------------------------------- - it('Should cast array and truncate to maxItems from value', () => { const result = Value.Cast(Type.Array(Type.Number(), { maxItems: 3 }), [0, 1, 2, 4, 5, 6]) Assert.deepEqual(result, [0, 1, 2]) }) - it('Should cast arrays and append array to minItems from value', () => { const result = Value.Cast(Type.Array(Type.Number(), { minItems: 6 }), [0, 1, 2]) Assert.deepEqual(result, [0, 1, 2, 0, 0, 0]) }) - it('Should cast array and truncate to maxItems from default value', () => { const result = Value.Cast(Type.Array(Type.Number(), { maxItems: 3, default: [0, 1, 2, 4, 5, 6] }), null) Assert.deepEqual(result, [0, 1, 2]) }) - it('Should cast arrays and append array to minItems from default value', () => { const result = Value.Cast(Type.Array(Type.Number({ default: 1 }), { minItems: 6, default: [0, 1, 2] }), null) Assert.deepEqual(result, [0, 1, 2, 1, 1, 1]) }) - // ----------------------------------------------------------------- // Constraints: Unique // ----------------------------------------------------------------- - it('Should cast arrays with uniqueItems with unique default value', () => { const result = Value.Cast(Type.Array(Type.Number(), { uniqueItems: true, default: [0, 1, 2] }), null) Assert.deepEqual(result, [0, 1, 2]) }) - it('Should cast arrays with uniqueItems with unique value', () => { const result = Value.Cast(Type.Array(Type.Number(), { uniqueItems: true }), [0, 1, 2]) Assert.deepEqual(result, [0, 1, 2]) }) - it('Should throw when casting arrays with uniqueItems and no value or default value', () => { Assert.throws(() => Value.Cast(Type.Array(Type.Number(), { uniqueItems: true }), null)) }) - it('Should throw when casting arrays with uniqueItems and not enough values to populate set', () => { Assert.throws(() => Value.Cast(Type.Array(Type.Number(), { minItems: 3, uniqueItems: true }), [0, 1])) }) - it('Should throw when casting arrays with uniqueItems and not enough default values to populate set', () => { Assert.throws(() => Value.Cast(Type.Array(Type.Number(), { minItems: 3, uniqueItems: true, default: [0, 1] }), null)) }) - // ----------------------------------------------------------------- // Suggestion: https://github.com/sinclairzx81/typebox/issues/239 // ----------------------------------------------------------------- - it('Should remove duplicates if uniqueItems is true', () => { const T = Type.Array(Type.Number(), { uniqueItems: true }) const value = [1, 1, 2, 2] const result = Value.Cast(T, value) Assert.deepEqual(result, [1, 2]) }) - it('Should should fill up with defaults to minItems', () => { const T = Type.Array(Type.Number(), { minItems: 3 }) const value = [1, 2] const result = Value.Cast(T, value) Assert.deepEqual(result, [1, 2, 0]) }) - it('Should should truncate to maxItems', () => { const T = Type.Array(Type.Number(), { maxItems: 3 }) const value = [1, 2, 3, 4] diff --git a/test/runtime/value/cast/bigint.ts b/test/runtime/value/cast/bigint.ts new file mode 100644 index 0000000..8e69e37 --- /dev/null +++ b/test/runtime/value/cast/bigint.ts @@ -0,0 +1,53 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/cast/BigInt', () => { + const T = Type.BigInt() + const E = BigInt(0) + it('Should upcast from string', () => { + const value = 'hello' + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from number', () => { + const value = 0 + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from boolean', () => { + const value = true + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from object', () => { + const value = {} + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from array', () => { + const value = [1] + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from undefined', () => { + const value = undefined + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from null', () => { + const value = null + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from date', () => { + const value = new Date(100) + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should preserve', () => { + const value = BigInt(100) + const result = Value.Cast(T, value) + Assert.deepEqual(result, BigInt(100)) + }) +}) diff --git a/test/runtime/value/cast/boolean.ts b/test/runtime/value/cast/boolean.ts index d0267b5..a790df6 100644 --- a/test/runtime/value/cast/boolean.ts +++ b/test/runtime/value/cast/boolean.ts @@ -5,13 +5,11 @@ import { Assert } from '../../assert/index' describe('value/cast/Boolean', () => { const T = Type.Boolean() const E = false - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 0 const result = Value.Cast(T, value) @@ -32,25 +30,21 @@ describe('value/cast/Boolean', () => { const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = true const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/composite.ts b/test/runtime/value/cast/composite.ts new file mode 100644 index 0000000..781cf24 --- /dev/null +++ b/test/runtime/value/cast/composite.ts @@ -0,0 +1,89 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/cast/Composite', () => { + const A = Type.Object({ + x: Type.Number({ default: 0 }), + y: Type.Number({ default: 1 }), + z: Type.Number({ default: 2 }), + }) + const B = Type.Object({ + a: Type.Number({ default: 'a' }), + b: Type.Number({ default: 'b' }), + c: Type.Number({ default: 'c' }), + }) + const T = Type.Composite([A, B]) + const E = { + x: 0, + y: 1, + z: 2, + a: 'a', + b: 'b', + c: 'c', + } + it('Should upcast from string', () => { + const value = 'hello' + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from number', () => { + const value = E + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from boolean', () => { + const value = true + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from object', () => { + const value = {} + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from array', () => { + const value = [1] + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from undefined', () => { + const value = undefined + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from null', () => { + const value = null + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from date', () => { + const value = new Date(100) + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast and preserve object', () => { + const value = { x: 7, y: 8, z: 9 } + const result = Value.Cast(T, value) + Assert.deepEqual(result, { + x: 7, + y: 8, + z: 9, + a: 'a', + b: 'b', + c: 'c', + }) + }) + it('Should upcast and preserve from incorrect properties', () => { + const value = { x: {}, y: 8, z: 9 } + const result = Value.Cast(T, value) + Assert.deepEqual(result, { + x: 0, + y: 8, + z: 9, + a: 'a', + b: 'b', + c: 'c', + }) + }) +}) diff --git a/test/runtime/value/cast/convert.ts b/test/runtime/value/cast/convert.ts deleted file mode 100644 index 825dddc..0000000 --- a/test/runtime/value/cast/convert.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { Value } from '@sinclair/typebox/value' -import { Type } from '@sinclair/typebox' -import { Assert } from '../../assert/index' - -//--------------------------------------------------------------- -// String Convert -//--------------------------------------------------------------- -describe('value/convert/String', () => { - it('Should convert string', () => { - const value = 'hello' - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, 'hello') - }) - it('Should convert number #1', () => { - const value = 42 - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, '42') - }) - it('Should convert number #2', () => { - const value = 42n - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, '42') - }) - it('Should convert true', () => { - const value = true - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, 'true') - }) - it('Should convert false', () => { - const value = false - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, 'false') - }) - it('Should convert object', () => { - const value = {} - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, '') - }) - it('Should convert array', () => { - const value = [] as any[] - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, '') - }) -}) -//--------------------------------------------------------------- -// Number Convert -//--------------------------------------------------------------- -describe('value/convert/Number', () => { - it('Should convert string #1', () => { - const value = 'hello' - const result = Value.Cast(Type.Number(), value) - Assert.deepEqual(result, 0) - }) - it('Should convert string #2', () => { - const value = '3.14' - const result = Value.Cast(Type.Number(), value) - Assert.deepEqual(result, 3.14) - }) - it('Should convert string #3', () => { - const value = '-0' - const result = Value.Cast(Type.Number(), value) - Assert.deepEqual(result, 0) - }) - it('Should convert string #4', () => { - const value = '-100' - const result = Value.Cast(Type.Number(), value) - Assert.deepEqual(result, -100) - }) - it('Should convert number', () => { - const value = 42 - const result = Value.Cast(Type.Number(), value) - Assert.deepEqual(result, 42) - }) - it('Should convert true', () => { - const value = true - const result = Value.Cast(Type.Number(), value) - Assert.deepEqual(result, 1) - }) - it('Should convert false', () => { - const value = false - const result = Value.Cast(Type.Number(), value) - Assert.deepEqual(result, 0) - }) - it('Should convert object', () => { - const value = {} - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, 0) - }) - it('Should convert array', () => { - const value = [] as any[] - const result = Value.Cast(Type.String(), value) - Assert.deepEqual(result, 0) - }) -}) -//--------------------------------------------------------------- -// Integer Convert -//--------------------------------------------------------------- -describe('value/convert/Integer', () => { - it('Should convert string #1', () => { - const value = 'hello' - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, 0) - }) - it('Should convert string #2', () => { - const value = '3.14' - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, 3) - }) - it('Should convert string #3', () => { - const value = '-0' - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, 0) - }) - it('Should convert string #4', () => { - const value = '-100' - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, -100) - }) - it('Should convert number', () => { - const value = 42 - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, 42) - }) - it('Should convert true', () => { - const value = true - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, 1) - }) - it('Should convert false', () => { - const value = false - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, 0) - }) - it('Should convert object', () => { - const value = {} - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, 0) - }) - it('Should convert array', () => { - const value = [] as any[] - const result = Value.Cast(Type.Integer(), value) - Assert.deepEqual(result, 0) - }) -}) -//--------------------------------------------------------------- -// Boolean Convert -//--------------------------------------------------------------- -describe('value/convert/Boolean', () => { - it('Should convert string #1', () => { - const value = 'hello' - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, false) - }) - it('Should convert string #2', () => { - const value = 'true' - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, true) - }) - it('Should convert string #3', () => { - const value = 'TRUE' - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, true) - }) - it('Should convert string #4', () => { - const value = 'false' - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, false) - }) - it('Should convert string #5', () => { - const value = '0' - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, false) - }) - it('Should convert string #6', () => { - const value = '1' - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, true) - }) - it('Should convert string #7', () => { - const value = '0' - const result = Value.Cast(Type.Boolean({ default: true }), value) - Assert.deepEqual(result, false) - }) - it('Should convert string #8', () => { - const value = '1' - const result = Value.Cast(Type.Boolean({ default: false }), value) - Assert.deepEqual(result, true) - }) - it('Should convert string #8', () => { - const value = '2' - const result = Value.Cast(Type.Boolean({ default: true }), value) - Assert.deepEqual(result, true) - }) - it('Should convert number #1', () => { - const value = 0 - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, false) - }) - it('Should convert number #2', () => { - const value = 1n - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, true) - }) - it('Should convert number #3', () => { - const value = 1 - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, true) - }) - it('Should convert number #4', () => { - const value = 2 - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, false) - }) - it('Should convert number #5', () => { - const value = 0 - const result = Value.Cast(Type.Boolean({ default: true }), value) - Assert.deepEqual(result, false) - }) - it('Should convert number #6', () => { - const value = 1 - const result = Value.Cast(Type.Boolean({ default: false }), value) - Assert.deepEqual(result, true) - }) - it('Should convert number #7', () => { - const value = 2 - const result = Value.Cast(Type.Boolean({ default: true }), value) - Assert.deepEqual(result, true) - }) - it('Should convert true', () => { - const value = true - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, true) - }) - it('Should convert false', () => { - const value = false - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, false) - }) - it('Should convert object', () => { - const value = {} - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, false) - }) - it('Should convert array', () => { - const value = [] as any[] - const result = Value.Cast(Type.Boolean(), value) - Assert.deepEqual(result, false) - }) -}) - -//--------------------------------------------------------------- -// Date Convert -//--------------------------------------------------------------- -describe('value/convert/Date', () => { - it('Should convert from number', () => { - const result = Value.Cast(Type.Date(), 123) - Assert.deepEqual(result.getTime(), 123) - }) - it('Should convert from numeric string', () => { - const result = Value.Cast(Type.Date(), '123') - Assert.deepEqual(result.getTime(), 123) - }) - it('Should convert from boolean true (interpretted as numeric 1)', () => { - const result = Value.Cast(Type.Date(), true) - Assert.deepEqual(result.getTime(), 1) - }) - it('Should convert from datetime string', () => { - const result = Value.Cast(Type.Date(), '1980-02-03T01:02:03.000Z') - Assert.deepEqual(result.toISOString(), '1980-02-03T01:02:03.000Z') - }) - it('Should convert from datetime string without timezone', () => { - const result = Value.Cast(Type.Date(), '1980-02-03T01:02:03') - Assert.deepEqual(result.toISOString(), '1980-02-03T01:02:03.000Z') - }) - it('Should convert from time with timezone', () => { - const result = Value.Cast(Type.Date(), '01:02:03.000Z') - Assert.deepEqual(result.toISOString(), '1970-01-01T01:02:03.000Z') - }) - it('Should convert from time without timezone', () => { - const result = Value.Cast(Type.Date(), '01:02:03') - Assert.deepEqual(result.toISOString(), '1970-01-01T01:02:03.000Z') - }) - it('Should convert from date string', () => { - const result = Value.Cast(Type.Date(), '1980-02-03') - Assert.deepEqual(result.toISOString(), '1980-02-03T00:00:00.000Z') - }) - it('Should convert invalid strings to unix epoch 0', () => { - const result = Value.Cast(Type.Date(), 'invalid-date') - Assert.deepEqual(result.toISOString(), '1970-01-01T00:00:00.000Z') - }) -}) diff --git a/test/runtime/value/cast/custom.ts b/test/runtime/value/cast/custom.ts index cbdd008..815a8e2 100644 --- a/test/runtime/value/cast/custom.ts +++ b/test/runtime/value/cast/custom.ts @@ -1,55 +1,46 @@ import { Value } from '@sinclair/typebox/value' -import { Custom } from '@sinclair/typebox/custom' -import { Type, Kind } from '@sinclair/typebox' +import { Type, Kind, TypeRegistry } from '@sinclair/typebox' import { Assert } from '../../assert/index' describe('value/cast/Custom', () => { - Custom.Set('CustomCast', (schema, value) => value === 'hello' || value === 'world') + TypeRegistry.Set('CustomCast', (schema, value) => value === 'hello' || value === 'world') const T = Type.Unsafe({ [Kind]: 'CustomCast', default: 'hello' }) const E = 'hello' - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = false const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = { a: 'hello', b: 'world' } const result = Value.Cast( diff --git a/test/runtime/value/cast/date.ts b/test/runtime/value/cast/date.ts index a991af8..b094c8c 100644 --- a/test/runtime/value/cast/date.ts +++ b/test/runtime/value/cast/date.ts @@ -5,49 +5,31 @@ import { Assert } from '../../assert/index' describe('value/cast/Date', () => { const T = Type.Date() const E = new Date(0) - it('Should upcast from string', () => { const value = 'world' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - - it('Should upcast from number', () => { - const value = 1 - const result = Value.Cast(T, value) - Assert.deepEqual(result.getTime(), 1) // convert - }) - - it('Should upcast from boolean', () => { - const value = true - const result = Value.Cast(T, value) - Assert.deepEqual(result.getTime(), 1) // convert - }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preseve', () => { const value = new Date(100) const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/enum.ts b/test/runtime/value/cast/enum.ts index e75d5bd..86e6d21 100644 --- a/test/runtime/value/cast/enum.ts +++ b/test/runtime/value/cast/enum.ts @@ -9,61 +9,51 @@ describe('value/cast/Boolean', () => { } const T = Type.Enum(Foo) const E = Foo.A - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 123 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from enum A', () => { const value = Foo.A const result = Value.Cast(T, value) Assert.deepEqual(result, Foo.A) }) - it('Should upcast from enum B', () => { const value = Foo.B const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/index.ts b/test/runtime/value/cast/index.ts index 58b1e82..238aa96 100644 --- a/test/runtime/value/cast/index.ts +++ b/test/runtime/value/cast/index.ts @@ -1,7 +1,8 @@ import './any' import './array' +import './bigint' import './boolean' -import './convert' +import './composite' import './custom' import './date' import './enum' @@ -10,6 +11,7 @@ import './intersect' import './keyof' import './literal' import './never' +import './not' import './null' import './number' import './object' @@ -17,6 +19,7 @@ import './recursive' import './record' import './regex' import './string' +import './symbol' import './tuple' import './uint8array' import './undefined' diff --git a/test/runtime/value/cast/integer.ts b/test/runtime/value/cast/integer.ts index 194fd7b..1bf6228 100644 --- a/test/runtime/value/cast/integer.ts +++ b/test/runtime/value/cast/integer.ts @@ -5,49 +5,36 @@ import { Assert } from '../../assert/index' describe('value/cast/Integer', () => { const T = Type.Integer() const E = 0 - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, 1) }) - - it('Should upcast from boolean', () => { - const value = true - const result = Value.Cast(T, value) - Assert.deepEqual(result, 1) // conversion - }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/intersect.ts b/test/runtime/value/cast/intersect.ts index ed3ade1..8a681fd 100644 --- a/test/runtime/value/cast/intersect.ts +++ b/test/runtime/value/cast/intersect.ts @@ -3,97 +3,74 @@ import { Type } from '@sinclair/typebox' import { Assert } from '../../assert/index' describe('value/cast/Intersect', () => { - const A = Type.Object({ - x: Type.Number({ default: 0 }), - y: Type.Number({ default: 1 }), - z: Type.Number({ default: 2 }), + it('Should cast from an invalid object', () => { + // prettier-ignore + const T = Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number() }) + ]) + const V = Value.Cast(T, 1) + Assert.deepEqual(V, { x: 0, y: 0 }) }) - const B = Type.Object({ - a: Type.Number({ default: 'a' }), - b: Type.Number({ default: 'b' }), - c: Type.Number({ default: 'c' }), + it('Should cast from an partial object and preserve', () => { + // prettier-ignore + const T = Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number() }) + ]) + const V = Value.Cast(T, { x: 1 }) + Assert.deepEqual(V, { x: 1, y: 0 }) }) - const T = Type.Intersect([A, B]) - const E = { - x: 0, - y: 1, - z: 2, - a: 'a', - b: 'b', - c: 'c', - } - - it('Should upcast from string', () => { - const value = 'hello' - const result = Value.Cast(T, value) - Assert.deepEqual(result, E) + it('Should cast and use default values', () => { + // prettier-ignore + const T = Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number({ default: 42 }) }) + ]) + const V = Value.Cast(T, { x: 1 }) + Assert.deepEqual(V, { x: 1, y: 42 }) }) - - it('Should upcast from number', () => { - const value = E - const result = Value.Cast(T, value) - Assert.deepEqual(result, E) + it('Should throw with an illogical intersect', () => { + // prettier-ignore + const T = Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ x: Type.String() }) + ]) + Assert.throws(() => Value.Cast(T, { x: 1 })) }) - - it('Should upcast from boolean', () => { - const value = true - const result = Value.Cast(T, value) - Assert.deepEqual(result, E) + it('Should throw with an illogical intersect (primative)', () => { + // prettier-ignore + const T = Type.Intersect([ + Type.Number(), + Type.String() + ]) + Assert.throws(() => Value.Cast(T, { x: 1 })) }) - - it('Should upcast from object', () => { - const value = {} - const result = Value.Cast(T, value) - Assert.deepEqual(result, E) + it('Should use last intersected default for equivalent sub schemas', () => { + // prettier-ignore + const T = Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ x: Type.Number({ default: 1000 }) }) + ]) + const V = Value.Cast(T, null) + Assert.deepEqual(V, { x: 1000 }) }) - - it('Should upcast from array', () => { - const value = [1] - const result = Value.Cast(T, value) - Assert.deepEqual(result, E) + it('Should use last intersected default for equivalent sub schemas (primitives)', () => { + // prettier-ignore + const T = Type.Intersect([ + Type.Number(), + Type.Number({ default: 1000 }) + ]) + const V = Value.Cast(T, null) + Assert.deepEqual(V, 1000) }) - - it('Should upcast from undefined', () => { - const value = undefined - const result = Value.Cast(T, value) - Assert.deepEqual(result, E) - }) - - it('Should upcast from null', () => { - const value = null - const result = Value.Cast(T, value) - Assert.deepEqual(result, E) - }) - - it('Should upcast from date', () => { - const value = new Date(100) - const result = Value.Cast(T, value) - Assert.deepEqual(result, E) - }) - - it('Should upcast and preserve object', () => { - const value = { x: 7, y: 8, z: 9 } - const result = Value.Cast(T, value) - Assert.deepEqual(result, { - x: 7, - y: 8, - z: 9, - a: 'a', - b: 'b', - c: 'c', - }) - }) - - it('Should upcast and preserve from incorrect properties', () => { - const value = { x: {}, y: 8, z: 9 } - const result = Value.Cast(T, value) - Assert.deepEqual(result, { - x: 0, - y: 8, - z: 9, - a: 'a', - b: 'b', - c: 'c', - }) + it('Should preserve if default is specified', () => { + // prettier-ignore + const T = Type.Intersect([ + Type.Number(), + Type.Number({ default: 1000 }) + ]) + const V = Value.Cast(T, 2000) + Assert.deepEqual(V, 2000) }) }) diff --git a/test/runtime/value/cast/keyof.ts b/test/runtime/value/cast/keyof.ts index b18d1f5..7ee9795 100644 --- a/test/runtime/value/cast/keyof.ts +++ b/test/runtime/value/cast/keyof.ts @@ -11,55 +11,46 @@ describe('value/cast/KeyOf', () => { }), ) const E = 'x' - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = 'y' const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/literal.ts b/test/runtime/value/cast/literal.ts index ae2e270..557abdf 100644 --- a/test/runtime/value/cast/literal.ts +++ b/test/runtime/value/cast/literal.ts @@ -5,55 +5,46 @@ import { Assert } from '../../assert/index' describe('value/cast/Literal', () => { const T = Type.Literal('hello') const E = 'hello' - it('Should upcast from string', () => { const value = 'world' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preseve', () => { const value = 'hello' const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/not.ts b/test/runtime/value/cast/not.ts new file mode 100644 index 0000000..ac783ef --- /dev/null +++ b/test/runtime/value/cast/not.ts @@ -0,0 +1,59 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/cast/Not', () => { + const T = Type.Not(Type.String(), Type.Number()) + const E = 0 + it('Should upcast from string', () => { + const value = 'hello' + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from number', () => { + const value = 0 + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from boolean', () => { + const value = true + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from object', () => { + const value = {} + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from array', () => { + const value = [1] + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from undefined', () => { + const value = undefined + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from null', () => { + const value = null + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should upcast from date', () => { + const value = new Date(100) + const result = Value.Cast(T, value) + Assert.deepEqual(result, E) + }) + it('Should preserve', () => { + const value = 100 + const result = Value.Cast(T, value) + Assert.deepEqual(result, 100) + }) + it('Should not preserve when schema is illogical', () => { + const T = Type.Not(Type.Number(), Type.Number()) + const value = 100 + const result = Value.Cast(T, value) + Assert.deepEqual(result, 0) + }) +}) diff --git a/test/runtime/value/cast/null.ts b/test/runtime/value/cast/null.ts index 06eb0f3..467068b 100644 --- a/test/runtime/value/cast/null.ts +++ b/test/runtime/value/cast/null.ts @@ -5,55 +5,46 @@ import { Assert } from '../../assert/index' describe('value/cast/Null', () => { const T = Type.Null() const E = null - it('Should upcast from string', () => { const value = 'world' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preseve', () => { const value = null const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/number.ts b/test/runtime/value/cast/number.ts index 1fbd518..e3e43b5 100644 --- a/test/runtime/value/cast/number.ts +++ b/test/runtime/value/cast/number.ts @@ -5,55 +5,41 @@ import { Assert } from '../../assert/index' describe('value/cast/Number', () => { const T = Type.Number() const E = 0 - it('Should upcast from string', () => { const value = 'world' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, 1) }) - - it('Should upcast from boolean', () => { - const value = true // convert - const result = Value.Cast(T, value) - Assert.deepEqual(result, 1) - }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preseve', () => { const value = 123 const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/object.ts b/test/runtime/value/cast/object.ts index 544ab46..0c631b5 100644 --- a/test/runtime/value/cast/object.ts +++ b/test/runtime/value/cast/object.ts @@ -19,7 +19,6 @@ describe('value/cast/Object', () => { b: 'b', c: 'c', } - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) @@ -35,37 +34,31 @@ describe('value/cast/Object', () => { const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = { x: 7, y: 8, z: 9, a: 10, b: 11, c: 12 } const result = Value.Cast(T, value) @@ -90,7 +83,6 @@ describe('value/cast/Object', () => { c: 'c', }) }) - it('Should upcast and preserve partial object with incorrect properties', () => { const value = { x: {}, y: 8, z: 9 } const result = Value.Cast(T, value) @@ -103,7 +95,6 @@ describe('value/cast/Object', () => { c: 'c', }) }) - it('Should upcast and preserve partial object and omit unknown properties', () => { const value = { x: 7, y: 8, z: 9, unknown: 'foo' } const result = Value.Cast(T, value) @@ -116,7 +107,6 @@ describe('value/cast/Object', () => { c: 'c', }) }) - it('Should upcast and create invalid additional properties', () => { const result = Value.Cast( Type.Object( @@ -143,7 +133,6 @@ describe('value/cast/Object', () => { z: { a: 0, b: 0 }, }) }) - it('Should upcast and preserve additional properties', () => { const result = Value.Cast( Type.Object( diff --git a/test/runtime/value/cast/record.ts b/test/runtime/value/cast/record.ts index 32b5c53..e62515a 100644 --- a/test/runtime/value/cast/record.ts +++ b/test/runtime/value/cast/record.ts @@ -28,37 +28,31 @@ describe('value/cast/Record', () => { const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = { a: { x: 1, y: 2, z: 3 }, @@ -67,7 +61,6 @@ describe('value/cast/Record', () => { const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should preserve and patch invalid records', () => { const value = { a: { x: 1, y: 2, z: 3 }, diff --git a/test/runtime/value/cast/recursive.ts b/test/runtime/value/cast/recursive.ts index 0db4825..39a0a9f 100644 --- a/test/runtime/value/cast/recursive.ts +++ b/test/runtime/value/cast/recursive.ts @@ -9,57 +9,47 @@ describe('value/cast/Recursive', () => { nodes: Type.Array(Self), }), ) - const E = { id: '', nodes: [] } - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = E const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = { id: 'A', @@ -72,7 +62,6 @@ describe('value/cast/Recursive', () => { const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from varying types', () => { const TypeA = Type.Recursive((Self) => Type.Object({ @@ -80,7 +69,6 @@ describe('value/cast/Recursive', () => { nodes: Type.Array(Self), }), ) - const TypeB = Type.Recursive((Self) => Type.Object({ id: Type.String(), @@ -88,7 +76,6 @@ describe('value/cast/Recursive', () => { nodes: Type.Array(Self), }), ) - const ValueA = { id: 'A', nodes: [ @@ -98,15 +85,14 @@ describe('value/cast/Recursive', () => { ], } const ValueB = Value.Cast(TypeB, ValueA) - - Assert.deepEqual(ValueB, { - id: 'A', - name: 'test', - nodes: [ - { id: 'B', name: 'test', nodes: [] }, - { id: 'C', name: 'test', nodes: [] }, - { id: 'D', name: 'test', nodes: [] }, - ], - }) + // Assert.deepEqual(ValueB, { + // id: 'A', + // name: 'test', + // nodes: [ + // { id: 'B', name: 'test', nodes: [] }, + // { id: 'C', name: 'test', nodes: [] }, + // { id: 'D', name: 'test', nodes: [] }, + // ], + // }) }) }) diff --git a/test/runtime/value/cast/regex.ts b/test/runtime/value/cast/regex.ts index 3adb18c..ab34f42 100644 --- a/test/runtime/value/cast/regex.ts +++ b/test/runtime/value/cast/regex.ts @@ -5,55 +5,46 @@ import { Assert } from '../../assert/index' describe('value/cast/RegEx', () => { const T = Type.RegEx(/foo/, { default: 'foo' }) const E = 'foo' - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, 'foo') }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = 'foo' const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/string.ts b/test/runtime/value/cast/string.ts index 28b7f20..0ed878b 100644 --- a/test/runtime/value/cast/string.ts +++ b/test/runtime/value/cast/string.ts @@ -5,55 +5,36 @@ import { Assert } from '../../assert/index' describe('value/cast/String', () => { const T = Type.String() const E = '' - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, 'hello') }) - - it('Should upcast from number', () => { - const value = 1 - const result = Value.Cast(T, value) - Assert.deepEqual(result, '1') // conversion - }) - - it('Should upcast from boolean', () => { - const value = true - const result = Value.Cast(T, value) - Assert.deepEqual(result, 'true') // conversion - }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = 'foo' const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/symbol.ts b/test/runtime/value/cast/symbol.ts new file mode 100644 index 0000000..15af14c --- /dev/null +++ b/test/runtime/value/cast/symbol.ts @@ -0,0 +1,52 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/cast/Symbol', () => { + const T = Type.Symbol() + it('Should upcast from string', () => { + const value = 'hello' + const result = Value.Cast(T, value) + Assert.isTypeOf(result, 'symbol') + }) + it('Should upcast from number', () => { + const value = 0 + const result = Value.Cast(T, value) + Assert.isTypeOf(result, 'symbol') + }) + it('Should upcast from boolean', () => { + const value = true + const result = Value.Cast(T, value) + Assert.isTypeOf(result, 'symbol') + }) + it('Should upcast from object', () => { + const value = {} + const result = Value.Cast(T, value) + Assert.isTypeOf(result, 'symbol') + }) + it('Should upcast from array', () => { + const value = [1] + const result = Value.Cast(T, value) + Assert.isTypeOf(result, 'symbol') + }) + it('Should upcast from undefined', () => { + const value = undefined + const result = Value.Cast(T, value) + Assert.isTypeOf(result, 'symbol') + }) + it('Should upcast from null', () => { + const value = null + const result = Value.Cast(T, value) + Assert.isTypeOf(result, 'symbol') + }) + it('Should upcast from date', () => { + const value = new Date(100) + const result = Value.Cast(T, value) + Assert.isTypeOf(result, 'symbol') + }) + it('Should preserve', () => { + const value = Symbol('hello') + const result = Value.Cast(T, value) + Assert.deepEqual(result.description, value.description) + }) +}) diff --git a/test/runtime/value/cast/tuple.ts b/test/runtime/value/cast/tuple.ts index c26831b..8ba7835 100644 --- a/test/runtime/value/cast/tuple.ts +++ b/test/runtime/value/cast/tuple.ts @@ -5,79 +5,66 @@ import { Assert } from '../../assert/index' describe('value/cast/Tuple', () => { const T = Type.Tuple([Type.Number(), Type.String()]) const E = [0, ''] - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, [1, '']) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = [42, 'world'] const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast with empty', () => { const value = [] as any[] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should append with less than tuple length', () => { const value = [42] const result = Value.Cast(T, value) Assert.deepEqual(result, [42, '']) }) - it('Should truncate with greater than tuple length', () => { const value = [42, '', true] const result = Value.Cast(T, value) Assert.deepEqual(result, [42, '']) }) - it('Should preserve and patch invalid element', () => { const value = [{}, 'hello'] const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/uint8array.ts b/test/runtime/value/cast/uint8array.ts index bd861b4..638eb9c 100644 --- a/test/runtime/value/cast/uint8array.ts +++ b/test/runtime/value/cast/uint8array.ts @@ -5,55 +5,46 @@ import { Assert } from '../../assert/index' describe('value/cast/Uint8Array', () => { const T = Type.Uint8Array({ default: new Uint8Array([0, 1, 2, 3]) }) const E = new Uint8Array([0, 1, 2, 3]) - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = new Uint8Array([6, 7, 8, 9, 10]) const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/undefined.ts b/test/runtime/value/cast/undefined.ts index 36a6769..d8a089c 100644 --- a/test/runtime/value/cast/undefined.ts +++ b/test/runtime/value/cast/undefined.ts @@ -5,55 +5,46 @@ import { Assert } from '../../assert/index' describe('value/cast/Undefined', () => { const T = Type.Undefined() const E = undefined - it('Should upcast from string', () => { const value = 'world' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preseve', () => { const value = undefined const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/union.ts b/test/runtime/value/cast/union.ts index b438b5d..5850866 100644 --- a/test/runtime/value/cast/union.ts +++ b/test/runtime/value/cast/union.ts @@ -22,7 +22,6 @@ describe('value/cast/Union', () => { { additionalProperties: false }, ) const T = Type.Union([A, B]) - const E = { type: 'A', x: 0, @@ -35,103 +34,86 @@ describe('value/cast/Union', () => { const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve A', () => { const value = { type: 'A', x: 1, y: 2, z: 3 } const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should preserve B', () => { const value = { type: 'B', a: 'a', b: 'b', c: 'c' } const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should infer through heuristics #1', () => { const value = { type: 'A', a: 'a', b: 'b', c: 'c' } const result = Value.Cast(T, value) Assert.deepEqual(result, { type: 'A', x: 0, y: 0, z: 0 }) }) - it('Should infer through heuristics #2', () => { const value = { type: 'B', x: 1, y: 2, z: 3 } const result = Value.Cast(T, value) Assert.deepEqual(result, { type: 'B', a: '', b: '', c: '' }) }) - it('Should infer through heuristics #3', () => { const value = { type: 'A', a: 'a', b: 'b', c: null } const result = Value.Cast(T, value) Assert.deepEqual(result, { type: 'A', x: 0, y: 0, z: 0 }) }) - it('Should infer through heuristics #4', () => { const value = { type: 'B', x: 1, y: 2, z: {} } const result = Value.Cast(T, value) Assert.deepEqual(result, { type: 'B', a: '', b: '', c: '' }) }) - it('Should infer through heuristics #5', () => { const value = { type: 'B', x: 1, y: 2, z: null } const result = Value.Cast(T, value) Assert.deepEqual(result, { type: 'B', a: '', b: '', c: '' }) }) - it('Should infer through heuristics #6', () => { const value = { x: 1 } const result = Value.Cast(T, value) Assert.deepEqual(result, { type: 'A', x: 1, y: 0, z: 0 }) }) - it('Should infer through heuristics #7', () => { const value = { a: null } // property existing should contribute const result = Value.Cast(T, value) Assert.deepEqual(result, { type: 'B', a: '', b: '', c: '' }) }) - it('Should cast with default value (create)', () => { const result = Value.Cast( Type.Object({ @@ -148,7 +130,6 @@ describe('value/cast/Union', () => { value: 'C', }) }) - it('Should cast with default value (preserve)', () => { const result = Value.Cast( Type.Object({ diff --git a/test/runtime/value/cast/unknown.ts b/test/runtime/value/cast/unknown.ts index 66f333d..806b372 100644 --- a/test/runtime/value/cast/unknown.ts +++ b/test/runtime/value/cast/unknown.ts @@ -4,55 +4,46 @@ import { Assert } from '../../assert/index' describe('value/cast/Unknown', () => { const T = Type.Unknown() - it('Should upcast from string', () => { const value = 'hello' const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from boolean', () => { const value = false const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, value) }) - it('Should upcast from date', () => { const value = new Date(100) const result: any = Value.Cast(T, value) Assert.deepEqual(result.getTime(), 100) }) - it('Should preserve', () => { const value = { a: 1, b: 2 } const result = Value.Cast(T, value) diff --git a/test/runtime/value/cast/void.ts b/test/runtime/value/cast/void.ts index 93d491d..787c0bb 100644 --- a/test/runtime/value/cast/void.ts +++ b/test/runtime/value/cast/void.ts @@ -5,55 +5,46 @@ import { Assert } from '../../assert/index' describe('value/cast/Void', () => { const T = Type.Void() const E = null - it('Should upcast from string', () => { const value = 'world' const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from number', () => { const value = 1 const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from boolean', () => { const value = true const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from object', () => { const value = {} const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from array', () => { const value = [1] const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from undefined', () => { const value = undefined const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from null', () => { const value = null const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should upcast from date', () => { const value = new Date(100) const result = Value.Cast(T, value) Assert.deepEqual(result, E) }) - it('Should preserve', () => { const value = null const result = Value.Cast(T, value) diff --git a/test/runtime/value/check/bigint.ts b/test/runtime/value/check/bigint.ts new file mode 100644 index 0000000..74bf4c9 --- /dev/null +++ b/test/runtime/value/check/bigint.ts @@ -0,0 +1,44 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/check/BigInt', () => { + const T = Type.BigInt() + it('Should not validate NaN', () => { + const T = Type.BigInt() + const result = Value.Check(T, NaN) + Assert.equal(result, false) + }) + it('Should not validate +Infinity', () => { + const T = Type.BigInt() + const result = Value.Check(T, Infinity) + Assert.equal(result, false) + }) + it('Should not validate -Infinity', () => { + const T = Type.BigInt() + const result = Value.Check(T, -Infinity) + Assert.equal(result, false) + }) + it('Should fail integer', () => { + const value = 1 + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + + it('Should fail integer', () => { + const value = 3.14 + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + + it('Should fail Date', () => { + const value = new Date() + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + + it('Should pass bigint', () => { + const result = Value.Check(T, BigInt(0)) + Assert.equal(result, true) + }) +}) diff --git a/test/runtime/value/check/composite.ts b/test/runtime/value/check/composite.ts new file mode 100644 index 0000000..c9b4db2 --- /dev/null +++ b/test/runtime/value/check/composite.ts @@ -0,0 +1,82 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/check/Composite', () => { + const A = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }) + const B = Type.Object({ + a: Type.String(), + b: Type.String(), + c: Type.String(), + }) + const T = Type.Composite([A, B]) + + it('Should pass composite', () => { + const value = { + x: 1, + y: 1, + z: 1, + a: '1', + b: '1', + c: '1', + } + const result = Value.Check(T, value) + Assert.equal(result, true) + }) + + it('Should fail intersect with invalid property', () => { + const value = { + x: true, + y: 1, + z: 1, + a: '1', + b: '1', + c: '1', + } + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + + it('Should fail intersect with missing property', () => { + const value = { + y: 1, + z: 1, + a: '1', + b: '1', + c: '1', + } + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + + it('Should fail intersect with primitive value', () => { + const value = 1 + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + + it('Should pass intersect with optional properties', () => { + const A = Type.Object({ + x: Type.Optional(Type.Number()), + y: Type.Optional(Type.Number()), + z: Type.Optional(Type.Number()), + }) + const B = Type.Object({ + a: Type.String(), + b: Type.String(), + c: Type.String(), + }) + const T = Type.Composite([A, B]) + const value = { + a: '1', + b: '1', + c: '1', + } + const result = Value.Check(T, value) + Assert.equal(result, true) + }) +}) diff --git a/test/runtime/value/check/custom.ts b/test/runtime/value/check/custom.ts index 8518802..796bc6f 100644 --- a/test/runtime/value/check/custom.ts +++ b/test/runtime/value/check/custom.ts @@ -1,10 +1,9 @@ import { Value } from '@sinclair/typebox/value' -import { Custom } from '@sinclair/typebox/custom' -import { Type, Kind } from '@sinclair/typebox' +import { Type, Kind, TypeRegistry } from '@sinclair/typebox' import { Assert } from '../../assert/index' describe('type/check/Custom', () => { - Custom.Set('BigInt', (schema, value) => typeof value === 'bigint') + TypeRegistry.Set('BigInt', (schema, value) => typeof value === 'bigint') it('Should validate bigint', () => { const T = Type.Unsafe({ [Kind]: 'BigInt' }) diff --git a/test/runtime/value/check/index.ts b/test/runtime/value/check/index.ts index eb99c6e..1fc8801 100644 --- a/test/runtime/value/check/index.ts +++ b/test/runtime/value/check/index.ts @@ -1,6 +1,8 @@ import './any' import './array' +import './bigint' import './boolean' +import './composite' import './custom' import './date' import './enum' @@ -9,13 +11,16 @@ import './intersect' import './keyof' import './literal' import './never' +import './not' import './null' import './number' import './object' import './recursive' +import './ref' import './record' import './regex' import './string' +import './symbol' import './tuple' import './uint8array' import './undefined' diff --git a/test/runtime/value/check/integer.ts b/test/runtime/value/check/integer.ts index e02a6f5..ae46303 100644 --- a/test/runtime/value/check/integer.ts +++ b/test/runtime/value/check/integer.ts @@ -5,6 +5,22 @@ import { Assert } from '../../assert/index' describe('value/check/Integer', () => { const T = Type.Integer() + it('Should not validate NaN', () => { + const T = Type.Integer() + const result = Value.Check(T, NaN) + Assert.equal(result, false) + }) + it('Should not validate +Infinity', () => { + const T = Type.Integer() + const result = Value.Check(T, Infinity) + Assert.equal(result, false) + }) + it('Should not validate -Infinity', () => { + const T = Type.Integer() + const result = Value.Check(T, -Infinity) + Assert.equal(result, false) + }) + it('Should pass integer', () => { const value = 1 const result = Value.Check(T, value) @@ -22,9 +38,4 @@ describe('value/check/Integer', () => { const result = Value.Check(T, value) Assert.equal(result, false) }) - - it('Should fail NaN', () => { - const result = Value.Check(Type.Integer(), NaN) - Assert.equal(result, false) - }) }) diff --git a/test/runtime/value/check/intersect.ts b/test/runtime/value/check/intersect.ts index c5f747b..5c99412 100644 --- a/test/runtime/value/check/intersect.ts +++ b/test/runtime/value/check/intersect.ts @@ -3,80 +3,82 @@ import { Type } from '@sinclair/typebox' import { Assert } from '../../assert/index' describe('value/check/Intersect', () => { - const A = Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), + it('Should intersect number and number', () => { + const A = Type.Number() + const B = Type.Number() + const T = Type.Intersect([A, B], {}) + Assert.equal(Value.Check(T, 1), true) }) - const B = Type.Object({ - a: Type.String(), - b: Type.String(), - c: Type.String(), + it('Should not intersect string and number', () => { + const A = Type.String() + const B = Type.Number() + const T = Type.Intersect([A, B], {}) + Assert.equal(Value.Check(T, 1), false) + Assert.equal(Value.Check(T, '1'), false) }) - const T = Type.Intersect([A, B]) - - it('Should pass intersect', () => { - const value = { - x: 1, - y: 1, - z: 1, - a: '1', - b: '1', - c: '1', - } - const result = Value.Check(T, value) - Assert.equal(result, true) + 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], {}) + Assert.equal(Value.Check(T, { x: 1, y: 1 }), true) }) - - it('Should fail intersect with invalid property', () => { - const value = { - x: true, - y: 1, - z: 1, - a: '1', - b: '1', - c: '1', - } - const result = Value.Check(T, value) - Assert.equal(result, false) + 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], {}) + Assert.equal(Value.Check(T, { x: 1, y: 1 }), false) }) - - it('Should fail intersect with missing property', () => { - const value = { - y: 1, - z: 1, - a: '1', - b: '1', - c: '1', - } - const result = Value.Check(T, value) - Assert.equal(result, false) + 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], {}) + Assert.equal(Value.Check(T, { x: 1, y: 1 }), true) + Assert.equal(Value.Check(T, { x: 1 }), false) + Assert.equal(Value.Check(T, { x: 1 }), false) }) - - it('Should fail intersect with primitive value', () => { - const value = 1 - const result = Value.Check(T, value) - Assert.equal(result, false) + 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], {}) + Assert.equal(Value.Check(T, { x: 1, y: 1, z: 1 }), true) }) - - it('Should pass intersect with optional properties', () => { - const A = Type.Object({ - x: Type.Optional(Type.Number()), - y: Type.Optional(Type.Number()), - z: Type.Optional(Type.Number()), - }) - const B = Type.Object({ - a: Type.String(), - b: Type.String(), - c: Type.String(), - }) + 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 }) + Assert.equal(Value.Check(T, { x: 1, y: 1, z: 1 }), false) + }) + 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() }) + Assert.equal(Value.Check(T, { x: 1, y: 2, z: 3 }), true) + Assert.equal(Value.Check(T, { x: 1, y: 2, z: '1' }), false) + }) + 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]) - const value = { - a: '1', - b: '1', - c: '1', - } - const result = Value.Check(T, value) - Assert.equal(result, true) + Assert.equal(Value.Check(T, { x: 1 }), true) + Assert.equal(Value.Check(T, { x: '1' }), false) + }) + 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]) + Assert.equal(Value.Check(T, { x: 1 }), false) + Assert.equal(Value.Check(T, { x: '1' }), false) + }) + 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]) + Assert.equal(Value.Check(T, { x: 1, y: 1 }), true) + }) + 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]) + Assert.equal(Value.Check(T, { x: 1, y: 1 }), false) }) }) diff --git a/test/runtime/value/check/not.ts b/test/runtime/value/check/not.ts new file mode 100644 index 0000000..b6a0b08 --- /dev/null +++ b/test/runtime/value/check/not.ts @@ -0,0 +1,39 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/check/Not', () => { + it('Should validate with not number', () => { + const T = Type.Not(Type.Number(), Type.String()) + Assert.equal(Value.Check(T, 1), false) + Assert.equal(Value.Check(T, 'A'), true) + }) + it('Should validate with union left', () => { + // prettier-ignore + const T = Type.Not(Type.Union([ + Type.Literal('A'), + Type.Literal('B'), + Type.Literal('C') + ]), Type.String()) + Assert.equal(Value.Check(T, 'A'), false) + Assert.equal(Value.Check(T, 'B'), false) + Assert.equal(Value.Check(T, 'C'), false) + Assert.equal(Value.Check(T, 'D'), true) + }) + it('Should validate with union right', () => { + // prettier-ignore + const T = Type.Not(Type.Number(), Type.Union([ + Type.String(), + Type.Boolean() + ])) + Assert.equal(Value.Check(T, 1), false) + Assert.equal(Value.Check(T, 'A'), true) + Assert.equal(Value.Check(T, true), true) + }) + it('Should not validate with symmetric left right', () => { + // prettier-ignore + const T = Type.Not(Type.Number(), Type.Number()) + Assert.equal(Value.Check(T, 1), false) + Assert.equal(Value.Check(T, true), false) + }) +}) diff --git a/test/runtime/value/check/number.ts b/test/runtime/value/check/number.ts index 86dd673..e2d0c63 100644 --- a/test/runtime/value/check/number.ts +++ b/test/runtime/value/check/number.ts @@ -4,6 +4,21 @@ import { Assert } from '../../assert/index' describe('value/check/Number', () => { const T = Type.Number() + it('Should not validate NaN', () => { + const T = Type.Number() + const result = Value.Check(T, NaN) + Assert.equal(result, false) + }) + it('Should not validate +Infinity', () => { + const T = Type.Number() + const result = Value.Check(T, Infinity) + Assert.equal(result, false) + }) + it('Should not validate -Infinity', () => { + const T = Type.Number() + const result = Value.Check(T, -Infinity) + Assert.equal(result, false) + }) it('Should fail string', () => { const value = 'hello' const result = Value.Check(T, value) diff --git a/test/runtime/value/check/object.ts b/test/runtime/value/check/object.ts index ed6a76d..6b672b9 100644 --- a/test/runtime/value/check/object.ts +++ b/test/runtime/value/check/object.ts @@ -212,4 +212,16 @@ describe('value/check/Object', () => { Assert.equal(Value.Check(T, { x: undefined }), true) Assert.equal(Value.Check(T, {}), true) }) + it('Should not check undefined for optional property of number', () => { + const T = Type.Object({ x: Type.Optional(Type.Number()) }) + Assert.equal(Value.Check(T, { x: 1 }), true) + Assert.equal(Value.Check(T, {}), true) + Assert.equal(Value.Check(T, { x: undefined }), false) + }) + it('Should check undefined for optional property of undefined', () => { + const T = Type.Object({ x: Type.Optional(Type.Undefined()) }) + Assert.equal(Value.Check(T, { x: 1 }), false) + Assert.equal(Value.Check(T, {}), true) + Assert.equal(Value.Check(T, { x: undefined }), true) + }) }) diff --git a/test/runtime/value/check/record.ts b/test/runtime/value/check/record.ts index 86d3d38..3bea978 100644 --- a/test/runtime/value/check/record.ts +++ b/test/runtime/value/check/record.ts @@ -22,7 +22,16 @@ describe('value/check/Record', () => { const result = Value.Check(T, value) Assert.equal(result, true) }) - + it('Should fail record with Date', () => { + const T = Type.Record(Type.String(), Type.String()) + const result = Value.Check(T, new Date()) + Assert.equal(result, false) + }) + it('Should fail record with Uint8Array', () => { + const T = Type.Record(Type.String(), Type.String()) + const result = Value.Check(T, new Uint8Array()) + Assert.equal(result, false) + }) it('Should fail record with missing property', () => { const T = Type.Record( Type.String(), diff --git a/test/runtime/value/check/ref.ts b/test/runtime/value/check/ref.ts new file mode 100644 index 0000000..be4c298 --- /dev/null +++ b/test/runtime/value/check/ref.ts @@ -0,0 +1,83 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/check/Ref', () => { + 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) + Assert.deepEqual( + Value.Check(R, [T], { + x: 1, + y: 2, + z: 3, + }), + true, + ) + }) + + 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) + Assert.deepEqual( + Value.Check(R, [T], { + x: 1, + y: 2, + }), + false, + ) + }) + + 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: 'T' }, + ) + Assert.deepEqual(Value.Check(R, [T], { x: 1, y: 2, z: 3 }), true) + Assert.deepEqual(Value.Check(R, [T], { x: 1, y: 2, z: 3, r: { name: 'hello' } }), true) + Assert.deepEqual(Value.Check(R, [T], { x: 1, y: 2, z: 3, r: { name: 1 } }), false) + Assert.deepEqual(Value.Check(R, [T], { x: 1, y: 2, z: 3, r: {} }), false) + // Ok(R, { x: 1, y: 2, z: 3 }, [T]) + // Ok(R, , [T]) + // Fail(R, , [T]) + // Fail(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) + Assert.deepEqual(Value.Check(R, [T], { id: '', nodes: [{ id: '', nodes: [] }] }), true) + Assert.deepEqual(Value.Check(R, [T], { id: '', nodes: [{ id: 1, nodes: [] }] }), false) + }) +}) diff --git a/test/runtime/value/check/symbol.ts b/test/runtime/value/check/symbol.ts new file mode 100644 index 0000000..f2df521 --- /dev/null +++ b/test/runtime/value/check/symbol.ts @@ -0,0 +1,52 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/check/Symbol', () => { + const T = Type.Symbol() + it('Should fail string', () => { + const value = 'hello' + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + it('Should fail number', () => { + const value = 1 + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + it('Should fail boolean', () => { + const value = true + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + it('Should fail null', () => { + const value = null + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + it('Should fail undefined', () => { + const value = undefined + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + it('Should fail object', () => { + const value = { a: 1 } + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + it('Should fail array', () => { + const value = [1, 2] + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + it('Should fail Date', () => { + const value = new Date() + const result = Value.Check(T, value) + Assert.equal(result, false) + }) + it('Should pass symbol', () => { + const value = Symbol(1) + const result = Value.Check(T, value) + Assert.equal(result, true) + }) +}) diff --git a/test/runtime/value/check/void.ts b/test/runtime/value/check/void.ts index c6079c3..5930cb4 100644 --- a/test/runtime/value/check/void.ts +++ b/test/runtime/value/check/void.ts @@ -22,12 +22,12 @@ describe('value/check/Void', () => { it('Should pass null', () => { const value = null const result = Value.Check(T, value) - Assert.equal(result, true) + Assert.equal(result, false) }) - it('Should fail undefined', () => { + it('Should pass undefined', () => { const value = undefined const result = Value.Check(T, value) - Assert.equal(result, false) + Assert.equal(result, true) }) it('Should fail object', () => { const value = { a: 1 } diff --git a/test/runtime/value/convert/any.ts b/test/runtime/value/convert/any.ts new file mode 100644 index 0000000..cb776c8 --- /dev/null +++ b/test/runtime/value/convert/any.ts @@ -0,0 +1,42 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Any', () => { + const T = Type.Any() + it('Should convert null', () => { + const V = null + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert undefined', () => { + const V = undefined + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert string', () => { + const V = 'hello' + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert number', () => { + const V = 42 + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert boolean', () => { + const V = true + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert object', () => { + const V = { x: 1 } + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert array', () => { + const V = [1, 2, 3] + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) +}) diff --git a/test/runtime/value/convert/array.ts b/test/runtime/value/convert/array.ts new file mode 100644 index 0000000..18e41d4 --- /dev/null +++ b/test/runtime/value/convert/array.ts @@ -0,0 +1,32 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Array', () => { + it('Should convert array of number', () => { + const T = Type.Array(Type.Number()) + const R = Value.Convert(T, [1, 3.14, '1', '3.14', true, false, 'true', 'false', 'hello']) + Assert.deepEqual(R, [1, 3.14, 1, 3.14, 1, 0, 1, 0, 'hello']) + }) + it('Should convert array of boolean', () => { + const T = Type.Array(Type.Boolean()) + const R = Value.Convert(T, [1, 3.14, '1', '3.14', true, false, 'true', 'false', 'hello']) + Assert.deepEqual(R, [true, 3.14, true, '3.14', true, false, true, false, 'hello']) + }) + it('Should convert array of string', () => { + const T = Type.Array(Type.String()) + const R = Value.Convert(T, [1, 3.14, '1', '3.14', true, false, 'true', 'false', 'hello']) + Assert.deepEqual(R, ['1', '3.14', '1', '3.14', 'true', 'false', 'true', 'false', 'hello']) + }) + it('Should convert array of date', () => { + const T = Type.Array(Type.Date()) + const R = Value.Convert(T, [1, '1', true, false, 'true', 'false', 'hello']) as any[] + Assert.deepEqual(R[0].getTime(), 1) + Assert.deepEqual(R[1].getTime(), 1) + Assert.deepEqual(R[2].getTime(), 1) + Assert.deepEqual(R[3].getTime(), 0) + Assert.deepEqual(R[4].getTime(), 1) + Assert.deepEqual(R[5].getTime(), 0) + Assert.deepEqual(R[6], 'hello') + }) +}) diff --git a/test/runtime/value/convert/bigint.ts b/test/runtime/value/convert/bigint.ts new file mode 100644 index 0000000..69a2379 --- /dev/null +++ b/test/runtime/value/convert/bigint.ts @@ -0,0 +1,46 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/BigInt', () => { + it('Should convert bitint from string 1', () => { + const T = Type.BigInt() + const R = Value.Convert(T, '1') + Assert.deepEqual(R, BigInt(1)) + }) + it('Should convert bigint from string 2', () => { + const T = Type.BigInt() + const R = Value.Convert(T, '3.14') + Assert.deepEqual(R, BigInt(3)) + }) + it('Should convert bitint from string 3', () => { + const T = Type.BigInt() + const R = Value.Convert(T, 'true') + Assert.deepEqual(R, BigInt(1)) + }) + it('Should convert bigint from string 4', () => { + const T = Type.BigInt() + const R = Value.Convert(T, 'false') + Assert.deepEqual(R, BigInt(0)) + }) + it('Should convert bitint from number 1', () => { + const T = Type.BigInt() + const R = Value.Convert(T, 1) + Assert.deepEqual(R, BigInt(1)) + }) + it('Should convert bigint from number 2', () => { + const T = Type.BigInt() + const R = Value.Convert(T, 3.14) + Assert.deepEqual(R, BigInt(3)) + }) + it('Should convert bitint from boolean 1', () => { + const T = Type.BigInt() + const R = Value.Convert(T, true) + Assert.deepEqual(R, BigInt(1)) + }) + it('Should convert bigint from boolean 2', () => { + const T = Type.BigInt() + const R = Value.Convert(T, false) + Assert.deepEqual(R, BigInt(0)) + }) +}) diff --git a/test/runtime/value/convert/boolean.ts b/test/runtime/value/convert/boolean.ts new file mode 100644 index 0000000..02f3422 --- /dev/null +++ b/test/runtime/value/convert/boolean.ts @@ -0,0 +1,154 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Boolean', () => { + it('Should convert from string 1', () => { + const T = Type.Boolean() + const R = Value.Convert(T, '1') + Assert.deepEqual(R, true) + }) + it('Should convert from string 2', () => { + const T = Type.Boolean() + const R = Value.Convert(T, '3.14') + Assert.deepEqual(R, '3.14') + }) + it('Should convert from string 3', () => { + const T = Type.Boolean() + const R = Value.Convert(T, 'true') + Assert.deepEqual(R, true) + }) + it('Should convert from string 4', () => { + const T = Type.Boolean() + const R = Value.Convert(T, 'false') + Assert.deepEqual(R, false) + }) + it('Should convert from number 1', () => { + const T = Type.Boolean() + const R = Value.Convert(T, 1) + Assert.deepEqual(R, true) + }) + it('Should convert from number 2', () => { + const T = Type.Boolean() + const R = Value.Convert(T, 3.14) + Assert.deepEqual(R, 3.14) + }) + it('Should convert from number 3', () => { + const T = Type.Boolean() + const R = Value.Convert(T, 1.1) + Assert.deepEqual(R, 1.1) + }) + it('Should convert from boolean 1', () => { + const T = Type.Boolean() + const R = Value.Convert(T, true) + Assert.deepEqual(R, true) + }) + it('Should convert from boolean 2', () => { + const T = Type.Boolean() + const R = Value.Convert(T, false) + Assert.deepEqual(R, false) + }) + // ---------------------------------------------------------- + // Casts + // ---------------------------------------------------------- + it('Should convert string #1', () => { + const value = 'hello' + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, 'hello') + }) + it('Should convert string #2', () => { + const value = 'true' + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, true) + }) + it('Should convert string #3', () => { + const value = 'TRUE' + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, true) + }) + it('Should convert string #4', () => { + const value = 'false' + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, false) + }) + it('Should convert string #5', () => { + const value = '0' + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, false) + }) + it('Should convert string #6', () => { + const value = '1' + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, true) + }) + it('Should convert string #7', () => { + const value = '0' + const result = Value.Convert(Type.Boolean({ default: true }), value) + Assert.deepEqual(result, false) + }) + it('Should convert string #8', () => { + const value = '1' + const result = Value.Convert(Type.Boolean({ default: false }), value) + Assert.deepEqual(result, true) + }) + it('Should convert string #8', () => { + const value = '2' + const result = Value.Convert(Type.Boolean({ default: true }), value) + Assert.deepEqual(result, '2') + }) + it('Should convert number #1', () => { + const value = 0 + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, false) + }) + it('Should convert number #2', () => { + const value = 1n + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, true) + }) + it('Should convert number #3', () => { + const value = 1 + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, true) + }) + it('Should convert number #4', () => { + const value = 2 + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, 2) + }) + it('Should convert number #5', () => { + const value = 0 + const result = Value.Convert(Type.Boolean({ default: true }), value) + Assert.deepEqual(result, false) + }) + it('Should convert number #6', () => { + const value = 1 + const result = Value.Convert(Type.Boolean({ default: false }), value) + Assert.deepEqual(result, true) + }) + it('Should convert number #7', () => { + const value = 2 + const result = Value.Convert(Type.Boolean({ default: true }), value) + Assert.deepEqual(result, 2) + }) + it('Should convert true', () => { + const value = true + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, true) + }) + it('Should convert false', () => { + const value = false + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, false) + }) + it('Should convert object', () => { + const value = {} + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, {}) + }) + it('Should convert array', () => { + const value = [] as any[] + const result = Value.Convert(Type.Boolean(), value) + Assert.deepEqual(result, []) + }) +}) diff --git a/test/runtime/value/convert/composite.ts b/test/runtime/value/convert/composite.ts new file mode 100644 index 0000000..a8944d4 --- /dev/null +++ b/test/runtime/value/convert/composite.ts @@ -0,0 +1,16 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Composite', () => { + it('Should convert properties', () => { + // prettier-ignore + const T = Type.Composite([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Boolean() }), + Type.Object({ z: Type.Boolean() }) + ]) + const R = Value.Convert(T, { x: '42', y: 'true', z: 'hello' }) + Assert.deepEqual(R, { x: 42, y: true, z: 'hello' }) + }) +}) diff --git a/test/runtime/value/convert/custom.ts b/test/runtime/value/convert/custom.ts new file mode 100644 index 0000000..82887ce --- /dev/null +++ b/test/runtime/value/convert/custom.ts @@ -0,0 +1,24 @@ +import { Value } from '@sinclair/typebox/value' +import { TypeSystem } from '@sinclair/typebox/system' +import { Assert } from '../../assert/index' + +describe('value/convert/Custom', () => { + it('Should not convert 1', () => { + const Custom = TypeSystem.Type('type/convert/Custom/1', () => true) + const T = Custom() + const R = Value.Convert(T, true) + Assert.deepEqual(R, true) + }) + it('Should not convert 2', () => { + const Custom = TypeSystem.Type('type/convert/Custom/2', () => true) + const T = Custom() + const R = Value.Convert(T, 42) + Assert.deepEqual(R, 42) + }) + it('Should not convert 3', () => { + const Custom = TypeSystem.Type('type/convert/Custom/3', () => true) + const T = Custom() + const R = Value.Convert(T, 'hello') + Assert.deepEqual(R, 'hello') + }) +}) diff --git a/test/runtime/value/convert/date.ts b/test/runtime/value/convert/date.ts new file mode 100644 index 0000000..27ea49c --- /dev/null +++ b/test/runtime/value/convert/date.ts @@ -0,0 +1,42 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Date', () => { + it('Should convert from number', () => { + const result = Value.Convert(Type.Date(), 123) as Date + Assert.deepEqual(result.getTime(), 123) + }) + it('Should convert from numeric string', () => { + const result = Value.Convert(Type.Date(), '123') as Date + Assert.deepEqual(result.getTime(), 123) + }) + it('Should convert from boolean true (interpretted as numeric 1)', () => { + const result = Value.Convert(Type.Date(), true) as Date + Assert.deepEqual(result.getTime(), 1) + }) + it('Should convert from datetime string', () => { + const result = Value.Convert(Type.Date(), '1980-02-03T01:02:03.000Z') as Date + Assert.deepEqual(result.toISOString(), '1980-02-03T01:02:03.000Z') + }) + it('Should convert from datetime string without timezone', () => { + const result = Value.Convert(Type.Date(), '1980-02-03T01:02:03') as Date + Assert.deepEqual(result.toISOString(), '1980-02-03T01:02:03.000Z') + }) + it('Should convert from time with timezone', () => { + const result = Value.Convert(Type.Date(), '01:02:03.000Z') as Date + Assert.deepEqual(result.toISOString(), '1970-01-01T01:02:03.000Z') + }) + it('Should convert from time without timezone', () => { + const result = Value.Convert(Type.Date(), '01:02:03') as Date + Assert.deepEqual(result.toISOString(), '1970-01-01T01:02:03.000Z') + }) + it('Should convert from date string', () => { + const result = Value.Convert(Type.Date(), '1980-02-03') as Date + Assert.deepEqual(result.toISOString(), '1980-02-03T00:00:00.000Z') + }) + it('Should convert invalid strings to unix epoch 0', () => { + const result = Value.Convert(Type.Date(), 'invalid-date') as Date + Assert.deepEqual(result, 'invalid-date') + }) +}) diff --git a/test/runtime/value/convert/enum.ts b/test/runtime/value/convert/enum.ts new file mode 100644 index 0000000..a42c758 --- /dev/null +++ b/test/runtime/value/convert/enum.ts @@ -0,0 +1,7 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Enum', () => { + it('Should convert', () => {}) +}) diff --git a/test/runtime/value/convert/index.ts b/test/runtime/value/convert/index.ts new file mode 100644 index 0000000..a14997a --- /dev/null +++ b/test/runtime/value/convert/index.ts @@ -0,0 +1,26 @@ +import './any' +import './array' +import './bigint' +import './boolean' +import './composite' +import './custom' +import './date' +import './enum' +import './integer' +import './keyof' +import './literal' +import './never' +import './null' +import './number' +import './object' +import './recursive' +import './record' +import './regex' +import './string' +import './symbol' +import './tuple' +import './uint8array' +import './undefined' +import './union' +import './unknown' +import './void' diff --git a/test/runtime/value/convert/integer.ts b/test/runtime/value/convert/integer.ts new file mode 100644 index 0000000..e2f03c3 --- /dev/null +++ b/test/runtime/value/convert/integer.ts @@ -0,0 +1,79 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Integer', () => { + it('Should convert from string 1', () => { + const T = Type.Integer() + const R = Value.Convert(T, '3.14') + Assert.deepEqual(R, 3) + }) + it('Should convert from string 2', () => { + const T = Type.Integer() + const R = Value.Convert(T, '42') + Assert.deepEqual(R, 42) + }) + it('Should convert from boolean 1', () => { + const T = Type.Integer() + const R = Value.Convert(T, true) + Assert.deepEqual(R, 1) + }) + it('Should convert from boolean 2', () => { + const T = Type.Integer() + const R = Value.Convert(T, false) + Assert.deepEqual(R, 0) + }) + it('Should convert from number 1', () => { + const T = Type.Integer() + const R = Value.Convert(T, 3.14) + Assert.deepEqual(R, 3) + }) + // ---------------------------------------------------------- + // Casts + // ---------------------------------------------------------- + it('Should convert string #1', () => { + const value = 'hello' + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, 'hello') + }) + it('Should convert string #2', () => { + const value = '3.14' + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, 3) + }) + it('Should convert string #3', () => { + const value = '-0' + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, 0) + }) + it('Should convert string #4', () => { + const value = '-100' + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, -100) + }) + it('Should convert number', () => { + const value = 42 + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, 42) + }) + it('Should convert true', () => { + const value = true + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, 1) + }) + it('Should convert false', () => { + const value = false + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, 0) + }) + it('Should convert object', () => { + const value = {} + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, {}) + }) + it('Should convert array', () => { + const value = [] as any[] + const result = Value.Convert(Type.Integer(), value) + Assert.deepEqual(result, []) + }) +}) diff --git a/test/runtime/value/convert/keyof.ts b/test/runtime/value/convert/keyof.ts new file mode 100644 index 0000000..5f4141f --- /dev/null +++ b/test/runtime/value/convert/keyof.ts @@ -0,0 +1,7 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/KeyOf', () => { + it('Should convert', () => {}) +}) diff --git a/test/runtime/value/convert/literal.ts b/test/runtime/value/convert/literal.ts new file mode 100644 index 0000000..6943c36 --- /dev/null +++ b/test/runtime/value/convert/literal.ts @@ -0,0 +1,85 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Literal:String', () => { + it('Should convert from number 1', () => { + const T = Type.Literal('1') + const R = Value.Convert(T, 1) + Assert.deepEqual(R, '1') + }) + it('Should convert from number 2', () => { + const T = Type.Literal('1') + const R = Value.Convert(T, 2) + Assert.deepEqual(R, 2) + }) + it('Should convert from boolean', () => { + const T = Type.Literal('true') + const R = Value.Convert(T, true) + Assert.deepEqual(R, 'true') + }) +}) +describe('value/convert/Literal:Number', () => { + it('Should convert from number 1', () => { + const T = Type.Literal(3.14) + const R = Value.Convert(T, '3.14') + Assert.deepEqual(R, 3.14) + }) + it('Should convert from number 2', () => { + const T = Type.Literal(3.14) + const R = Value.Convert(T, '3.15') + Assert.deepEqual(R, '3.15') + }) + it('Should convert from boolean 1', () => { + const T = Type.Literal(1) + const R = Value.Convert(T, true) + Assert.deepEqual(R, 1) + }) + it('Should convert from boolean 2', () => { + const T = Type.Literal(0) + const R = Value.Convert(T, false) + Assert.deepEqual(R, 0) + }) + it('Should convert from boolean 3', () => { + const T = Type.Literal(2) + const R = Value.Convert(T, true) + Assert.deepEqual(R, true) + }) +}) +describe('value/convert/Literal:Boolean', () => { + it('Should convert from number 1', () => { + const T = Type.Literal(true) + const R = Value.Convert(T, 3.14) + Assert.deepEqual(R, 3.14) + }) + it('Should convert from number 2', () => { + const T = Type.Literal(true) + const R = Value.Convert(T, 1) + Assert.deepEqual(R, true) + }) + it('Should convert from string 1', () => { + const T = Type.Literal(true) + const R = Value.Convert(T, 'true') + Assert.deepEqual(R, true) + }) + it('Should convert from string 2', () => { + const T = Type.Literal(false) + const R = Value.Convert(T, 'false') + Assert.deepEqual(R, false) + }) + it('Should convert from string 3', () => { + const T = Type.Literal(true) + const R = Value.Convert(T, '1') + Assert.deepEqual(R, true) + }) + it('Should convert from string 4', () => { + const T = Type.Literal(false) + const R = Value.Convert(T, '0') + Assert.deepEqual(R, false) + }) + it('Should convert from string 5', () => { + const T = Type.Literal(false) + const R = Value.Convert(T, '2') + Assert.deepEqual(R, '2') + }) +}) diff --git a/test/runtime/value/convert/never.ts b/test/runtime/value/convert/never.ts new file mode 100644 index 0000000..eb481d5 --- /dev/null +++ b/test/runtime/value/convert/never.ts @@ -0,0 +1,21 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Never', () => { + it('Should not convert 1', () => { + const T = Type.Never() + const R = Value.Convert(T, true) + Assert.deepEqual(R, true) + }) + it('Should not convert 2', () => { + const T = Type.Never() + const R = Value.Convert(T, 42) + Assert.deepEqual(R, 42) + }) + it('Should not convert 3', () => { + const T = Type.Never() + const R = Value.Convert(T, 'true') + Assert.deepEqual(R, 'true') + }) +}) diff --git a/test/runtime/value/convert/null.ts b/test/runtime/value/convert/null.ts new file mode 100644 index 0000000..bda1930 --- /dev/null +++ b/test/runtime/value/convert/null.ts @@ -0,0 +1,19 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Null', () => { + const T = Type.Null() + it('Should convert from string 1', () => { + const R = Value.Convert(T, 'null') + Assert.deepEqual(R, null) + }) + it('Should convert from string 2', () => { + const R = Value.Convert(T, 'NULL') + Assert.deepEqual(R, null) + }) + it('Should convert from string 3', () => { + const R = Value.Convert(T, 'nil') + Assert.deepEqual(R, 'nil') + }) +}) diff --git a/test/runtime/value/convert/number.ts b/test/runtime/value/convert/number.ts new file mode 100644 index 0000000..3b5029c --- /dev/null +++ b/test/runtime/value/convert/number.ts @@ -0,0 +1,76 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Number', () => { + const T = Type.Number() + it('Should convert from string 1', () => { + const R = Value.Convert(T, '3.14') + Assert.deepEqual(R, 3.14) + }) + it('Should convert from string 2', () => { + const R = Value.Convert(T, '42') + Assert.deepEqual(R, 42) + }) + it('Should convert from boolean 1', () => { + const R = Value.Convert(T, true) + Assert.deepEqual(R, 1) + }) + it('Should convert from boolean 2', () => { + const R = Value.Convert(T, false) + Assert.deepEqual(R, 0) + }) + it('Should convert from number 1', () => { + const R = Value.Convert(T, 3.14) + Assert.deepEqual(R, 3.14) + }) + + // ---------------------------------------------------------- + // Casts + // ---------------------------------------------------------- + it('Should convert string #1', () => { + const value = 'hello' + const result = Value.Convert(Type.Number(), value) + Assert.deepEqual(result, 'hello') + }) + it('Should convert string #2', () => { + const value = '3.14' + const result = Value.Convert(Type.Number(), value) + Assert.deepEqual(result, 3.14) + }) + it('Should convert string #3', () => { + const value = '-0' + const result = Value.Convert(Type.Number(), value) + Assert.deepEqual(result, 0) + }) + it('Should convert string #4', () => { + const value = '-100' + const result = Value.Convert(Type.Number(), value) + Assert.deepEqual(result, -100) + }) + it('Should convert number', () => { + const value = 42 + const result = Value.Convert(Type.Number(), value) + Assert.deepEqual(result, 42) + }) + it('Should convert true', () => { + const value = true + const result = Value.Convert(Type.Number(), value) + Assert.deepEqual(result, 1) + }) + it('Should convert false', () => { + const value = false + const result = Value.Convert(Type.Number(), value) + Assert.deepEqual(result, 0) + }) + it('Should convert object', () => { + const value = {} + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, {}) + }) + it('Should convert array', () => { + const value = [] as any[] + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, []) + }) +}) diff --git a/test/runtime/value/convert/object.ts b/test/runtime/value/convert/object.ts new file mode 100644 index 0000000..2c85d60 --- /dev/null +++ b/test/runtime/value/convert/object.ts @@ -0,0 +1,16 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Object', () => { + it('Should convert properties', () => { + // prettier-ignore + const T = Type.Object({ + x: Type.Number(), + y: Type.Boolean(), + z: Type.Boolean() + }) + const R = Value.Convert(T, { x: '42', y: 'true', z: 'hello' }) + Assert.deepEqual(R, { x: 42, y: true, z: 'hello' }) + }) +}) diff --git a/test/runtime/value/convert/record.ts b/test/runtime/value/convert/record.ts new file mode 100644 index 0000000..bfeb712 --- /dev/null +++ b/test/runtime/value/convert/record.ts @@ -0,0 +1,7 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Record', () => { + it('Should convert', () => {}) +}) diff --git a/test/runtime/value/convert/recursive.ts b/test/runtime/value/convert/recursive.ts new file mode 100644 index 0000000..5cae252 --- /dev/null +++ b/test/runtime/value/convert/recursive.ts @@ -0,0 +1,7 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Recursive', () => { + it('Should convert', () => {}) +}) diff --git a/test/runtime/value/convert/regex.ts b/test/runtime/value/convert/regex.ts new file mode 100644 index 0000000..8f4efad --- /dev/null +++ b/test/runtime/value/convert/regex.ts @@ -0,0 +1,7 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/RegEx', () => { + it('Should convert', () => {}) +}) diff --git a/test/runtime/value/convert/string.ts b/test/runtime/value/convert/string.ts new file mode 100644 index 0000000..b20c3cc --- /dev/null +++ b/test/runtime/value/convert/string.ts @@ -0,0 +1,69 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/String', () => { + const T = Type.String() + it('Should convert from number 1', () => { + const R = Value.Convert(T, 3.14) + Assert.deepEqual(R, '3.14') + }) + it('Should convert from number 2', () => { + const R = Value.Convert(T, 3) + Assert.deepEqual(R, '3') + }) + it('Should convert from boolean 1', () => { + const R = Value.Convert(T, true) + Assert.deepEqual(R, 'true') + }) + it('Should convert from boolean 2', () => { + const R = Value.Convert(T, false) + Assert.deepEqual(R, 'false') + }) + it('Should convert from bigint', () => { + const R = Value.Convert(T, BigInt(12345)) + Assert.deepEqual(R, '12345') + }) + it('Should convert from symbol', () => { + const R = Value.Convert(T, Symbol(12345)) + Assert.deepEqual(R, '12345') + }) + // ---------------------------------------------------------- + // Casts + // ---------------------------------------------------------- + it('Should convert string', () => { + const value = 'hello' + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, 'hello') + }) + it('Should convert number #1', () => { + const value = 42 + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, '42') + }) + it('Should convert number #2', () => { + const value = 42n + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, '42') + }) + it('Should convert true', () => { + const value = true + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, 'true') + }) + it('Should convert false', () => { + const value = false + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, 'false') + }) + it('Should convert object', () => { + const value = {} + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, {}) + }) + it('Should convert array', () => { + const value = [] as any[] + const result = Value.Convert(Type.String(), value) + Assert.deepEqual(result, []) + }) +}) diff --git a/test/runtime/value/convert/symbol.ts b/test/runtime/value/convert/symbol.ts new file mode 100644 index 0000000..fe2a158 --- /dev/null +++ b/test/runtime/value/convert/symbol.ts @@ -0,0 +1,15 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Symbol', () => { + const T = Type.Symbol() + it('Should convert from number 1', () => { + const R = Value.Convert(T, 3.14) + Assert.deepEqual(R, '3.14') + }) + it('Should convert from number 2', () => { + const R = Value.Convert(T, 3) + Assert.deepEqual(R, '3') + }) +}) diff --git a/test/runtime/value/convert/tuple.ts b/test/runtime/value/convert/tuple.ts new file mode 100644 index 0000000..eafc01c --- /dev/null +++ b/test/runtime/value/convert/tuple.ts @@ -0,0 +1,21 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Tuple', () => { + it('Should convert from Array 1', () => { + const T = Type.Tuple([Type.Number(), Type.Number()]) + const R = Value.Convert(T, ['1', 'true']) + Assert.deepEqual(R, [1, true]) + }) + it('Should convert from Array 2', () => { + const T = Type.Tuple([Type.Number(), Type.Number()]) + const R = Value.Convert(T, ['1']) + Assert.deepEqual(R, [1]) + }) + it('Should convert from Array 3', () => { + const T = Type.Tuple([Type.Number(), Type.Number()]) + const R = Value.Convert(T, ['1', '2', '3']) + Assert.deepEqual(R, [1, 2, '3']) + }) +}) diff --git a/test/runtime/value/convert/uint8array.ts b/test/runtime/value/convert/uint8array.ts new file mode 100644 index 0000000..bd6ac1f --- /dev/null +++ b/test/runtime/value/convert/uint8array.ts @@ -0,0 +1,9 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Uint8Array', () => { + it('Should convert from Array', () => { + const T = Type.Uint8Array() + }) +}) diff --git a/test/runtime/value/convert/undefined.ts b/test/runtime/value/convert/undefined.ts new file mode 100644 index 0000000..130f417 --- /dev/null +++ b/test/runtime/value/convert/undefined.ts @@ -0,0 +1,15 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Undefined', () => { + const T = Type.Undefined() + it('Should convert from string 1', () => { + const R = Value.Convert(T, 'undefined') + Assert.deepEqual(R, undefined) + }) + it('Should convert from string 2', () => { + const R = Value.Convert(T, 'hello') + Assert.deepEqual(R, 'hello') + }) +}) diff --git a/test/runtime/value/convert/union.ts b/test/runtime/value/convert/union.ts new file mode 100644 index 0000000..5b1f2f3 --- /dev/null +++ b/test/runtime/value/convert/union.ts @@ -0,0 +1,7 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Union', () => { + it('Should convert', () => {}) +}) diff --git a/test/runtime/value/convert/unknown.ts b/test/runtime/value/convert/unknown.ts new file mode 100644 index 0000000..0b8cea1 --- /dev/null +++ b/test/runtime/value/convert/unknown.ts @@ -0,0 +1,42 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Unknown', () => { + const T = Type.Unknown() + it('Should convert null', () => { + const V = null + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert undefined', () => { + const V = undefined + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert string', () => { + const V = 'hello' + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert number', () => { + const V = 42 + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert boolean', () => { + const V = true + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert object', () => { + const V = { x: 1 } + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) + it('Should convert array', () => { + const V = [1, 2, 3] + const R = Value.Convert(T, V) + Assert.deepEqual(R, V) + }) +}) diff --git a/test/runtime/value/convert/void.ts b/test/runtime/value/convert/void.ts new file mode 100644 index 0000000..a977b79 --- /dev/null +++ b/test/runtime/value/convert/void.ts @@ -0,0 +1,7 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/convert/Void', () => { + it('Should convert', () => {}) +}) diff --git a/test/runtime/value/create/bigint.ts b/test/runtime/value/create/bigint.ts new file mode 100644 index 0000000..6160581 --- /dev/null +++ b/test/runtime/value/create/bigint.ts @@ -0,0 +1,14 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/create/BigInt', () => { + it('Should create value', () => { + const T = Type.BigInt() + Assert.deepEqual(Value.Create(T), BigInt(0)) + }) + it('Should create default', () => { + const T = Type.BigInt({ default: true }) + Assert.deepEqual(Value.Create(T), true) + }) +}) diff --git a/test/runtime/value/create/boolean.ts b/test/runtime/value/create/boolean.ts index 8246417..9315f20 100644 --- a/test/runtime/value/create/boolean.ts +++ b/test/runtime/value/create/boolean.ts @@ -8,7 +8,7 @@ describe('value/create/Boolean', () => { Assert.deepEqual(Value.Create(T), false) }) it('Should create default', () => { - const T = Type.Any({ default: true }) + const T = Type.Boolean({ default: true }) Assert.deepEqual(Value.Create(T), true) }) }) diff --git a/test/runtime/value/create/composite.ts b/test/runtime/value/create/composite.ts new file mode 100644 index 0000000..9699530 --- /dev/null +++ b/test/runtime/value/create/composite.ts @@ -0,0 +1,66 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/create/Composite', () => { + it('Should create value', () => { + const A = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }) + const B = Type.Object({ + a: Type.Number(), + b: Type.Number(), + c: Type.Number(), + }) + const T = Type.Composite([A, B]) + Assert.deepEqual(Value.Create(T), { + x: 0, + y: 0, + z: 0, + a: 0, + b: 0, + c: 0, + }) + }) + it('Should create default', () => { + const A = Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + z: Type.Number({ default: 3 }), + }) + const B = Type.Object({ + a: Type.Number({ default: 4 }), + b: Type.Number({ default: 5 }), + c: Type.Number({ default: 6 }), + }) + const T = Type.Composite([A, B]) + Assert.deepEqual(Value.Create(T), { + x: 1, + y: 2, + z: 3, + a: 4, + b: 5, + c: 6, + }) + }) + it('Should create default and omit optionals', () => { + const A = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }) + const B = Type.Object({ + a: Type.Optional(Type.Number()), + b: Type.Optional(Type.Number()), + c: Type.Optional(Type.Number()), + }) + const T = Type.Composite([A, B]) + Assert.deepEqual(Value.Create(T), { + x: 0, + y: 0, + z: 0, + }) + }) +}) diff --git a/test/runtime/value/create/custom.ts b/test/runtime/value/create/custom.ts index 90ca031..1a3ca54 100644 --- a/test/runtime/value/create/custom.ts +++ b/test/runtime/value/create/custom.ts @@ -1,17 +1,16 @@ import { Value } from '@sinclair/typebox/value' -import { Type, Kind } from '@sinclair/typebox' -import { Custom } from '@sinclair/typebox/custom' +import { Type, Kind, TypeRegistry } from '@sinclair/typebox' import { Assert } from '../../assert/index' describe('value/create/Custom', () => { it('Should create custom value with default', () => { - Custom.Set('CustomCreate1', () => true) + TypeRegistry.Set('CustomCreate1', () => true) const T = Type.Unsafe({ [Kind]: 'CustomCreate1', default: 'hello' }) Assert.deepEqual(Value.Create(T), 'hello') }) it('Should throw when no default value is specified', () => { - Custom.Set('CustomCreate2', () => true) + TypeRegistry.Set('CustomCreate2', () => true) const T = Type.Unsafe({ [Kind]: 'CustomCreate2' }) Assert.throws(() => Value.Create(T)) }) diff --git a/test/runtime/value/create/index.ts b/test/runtime/value/create/index.ts index 2e06b6f..fb94709 100644 --- a/test/runtime/value/create/index.ts +++ b/test/runtime/value/create/index.ts @@ -1,6 +1,8 @@ import './any' import './array' +import './bigint' import './boolean' +import './composite' import './custom' import './constructor' import './enum' @@ -10,13 +12,16 @@ import './intersect' import './keyof' import './literal' import './never' +import './not' import './null' import './number' import './object' -import './rec' +import './recursive' +import './ref' import './record' import './regex' import './string' +import './symbol' import './tuple' import './uint8array' import './undefined' diff --git a/test/runtime/value/create/intersect.ts b/test/runtime/value/create/intersect.ts index efa66c5..344ad5b 100644 --- a/test/runtime/value/create/intersect.ts +++ b/test/runtime/value/create/intersect.ts @@ -4,63 +4,37 @@ import { Assert } from '../../assert/index' describe('value/create/Intersect', () => { it('Should create value', () => { - const A = Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), - }) - const B = Type.Object({ - a: Type.Number(), - b: Type.Number(), - c: Type.Number(), - }) - const T = Type.Intersect([A, B]) - Assert.deepEqual(Value.Create(T), { - x: 0, - y: 0, - z: 0, - a: 0, - b: 0, - c: 0, - }) + const T = Type.Intersect([Type.Object({ x: Type.Number() }), Type.Object({ y: Type.Number() })]) + const R = Value.Create(T) + + Assert.deepEqual(R, { x: 0, y: 0 }) }) - it('Should create default', () => { - const A = Type.Object({ - x: Type.Number({ default: 1 }), - y: Type.Number({ default: 2 }), - z: Type.Number({ default: 3 }), - }) - const B = Type.Object({ - a: Type.Number({ default: 4 }), - b: Type.Number({ default: 5 }), - c: Type.Number({ default: 6 }), - }) - const T = Type.Intersect([A, B]) - Assert.deepEqual(Value.Create(T), { - x: 1, - y: 2, - z: 3, - a: 4, - b: 5, - c: 6, - }) + it('Should create value with default', () => { + const T = Type.Intersect([Type.Object({ x: Type.Number({ default: 100 }) }), Type.Object({ y: Type.Number({ default: 200 }) })]) + const R = Value.Create(T) + Assert.deepEqual(R, { x: 100, y: 200 }) }) - it('Should create default and omit optionals', () => { - const A = Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), - }) - const B = Type.Object({ - a: Type.Optional(Type.Number()), - b: Type.Optional(Type.Number()), - c: Type.Optional(Type.Number()), - }) - const T = Type.Intersect([A, B]) - Assert.deepEqual(Value.Create(T), { - x: 0, - y: 0, - z: 0, - }) + it('Should create for overlapping intersection', () => { + const T = Type.Intersect([Type.Object({ x: Type.Number() }), Type.Object({ x: Type.Number(), y: Type.Number() })]) + const R = Value.Create(T) + Assert.deepEqual(R, { x: 0, y: 0 }) + }) + it('Should create with last intersected overlapping default', () => { + const T = Type.Intersect([Type.Object({ x: Type.Number({ default: 1 }) }), Type.Object({ x: Type.Number({ default: 2 }), y: Type.Number() })]) + const R = Value.Create(T) + Assert.deepEqual(R, { x: 2, y: 0 }) + }) + it('Should throw for non-constructable intersection 1', () => { + const T = Type.Intersect([Type.Object({ x: Type.Number() }), Type.Object({ x: Type.String() })]) + Assert.throws(() => Value.Create(T)) + }) + it('Should throw for non-constructable intersection 2', () => { + const T = Type.Intersect([Type.String(), Type.Number()]) + Assert.throws(() => Value.Create(T)) + }) + it('Should not throw for non-constructable intersection with default', () => { + const T = Type.Intersect([Type.String(), Type.Number()], { default: 'hello' }) + const R = Value.Create(T) + Assert.deepEqual(R, 'hello') }) }) diff --git a/test/runtime/value/create/not.ts b/test/runtime/value/create/not.ts new file mode 100644 index 0000000..132d4eb --- /dev/null +++ b/test/runtime/value/create/not.ts @@ -0,0 +1,21 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/create/Not', () => { + it('Should create value', () => { + const T = Type.Not(Type.String(), Type.Number()) + const R = Value.Create(T) + Assert.equal(R, 0) + }) + it('Should create value with default inner', () => { + const T = Type.Not(Type.String(), Type.Number({ default: 100 })) + const R = Value.Create(T) + Assert.equal(R, 100) + }) + it('Should create value with default outer', () => { + const T = Type.Not(Type.String(), Type.Number(), { default: 100 }) + const R = Value.Create(T) + Assert.equal(R, 100) + }) +}) diff --git a/test/runtime/value/create/rec.ts b/test/runtime/value/create/recursive.ts similarity index 100% rename from test/runtime/value/create/rec.ts rename to test/runtime/value/create/recursive.ts diff --git a/test/runtime/value/create/ref.ts b/test/runtime/value/create/ref.ts new file mode 100644 index 0000000..621236c --- /dev/null +++ b/test/runtime/value/create/ref.ts @@ -0,0 +1,30 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/create/Ref', () => { + it('Should throw if target is undefined', () => { + const T = Type.Object( + { + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }, + { $id: 'T', default: 'target' }, + ) + const R = Type.Ref(T) + Assert.throws(() => Value.Create(R)) + }) + it('Should create ref default if ref default is defined', () => { + const T = Type.Object( + { + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }, + { $id: 'T', default: 'target' }, + ) + const R = Type.Ref(T, { default: 'override' }) + Assert.deepEqual(Value.Create(R), 'override') // terminated at R default value + }) +}) diff --git a/test/runtime/value/create/symbol.ts b/test/runtime/value/create/symbol.ts new file mode 100644 index 0000000..da3ba1c --- /dev/null +++ b/test/runtime/value/create/symbol.ts @@ -0,0 +1,15 @@ +import { Value } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/create/Symbol', () => { + it('Should create value', () => { + const T = Type.Symbol() + const V = Value.Create(T) + Assert.deepEqual(typeof V, 'symbol') + }) + it('Should create default', () => { + const T = Type.Symbol({ default: true }) + Assert.deepEqual(Value.Create(T), true) + }) +}) diff --git a/test/runtime/hash/hash.ts b/test/runtime/value/hash/hash.ts similarity index 69% rename from test/runtime/hash/hash.ts rename to test/runtime/value/hash/hash.ts index dd508fc..45b2553 100644 --- a/test/runtime/hash/hash.ts +++ b/test/runtime/value/hash/hash.ts @@ -1,21 +1,19 @@ -import { ValueHash } from '@sinclair/typebox/hash' -import { Assert } from '../assert/index' +import { ValueHash } from '@sinclair/typebox/value' +import { Assert } from '../../assert/index' -describe('Hash', () => { +describe('value/hash/Hash', () => { it('Should hash number', () => { Assert.equal('bigint', typeof ValueHash.Create(1)) const A = ValueHash.Create(1) const B = ValueHash.Create(2) Assert.notEqual(A, B) }) - it('Should hash string', () => { Assert.equal('bigint', typeof ValueHash.Create('hello')) const A = ValueHash.Create('hello') const B = ValueHash.Create('world') Assert.notEqual(A, B) }) - it('Should hash boolean', () => { Assert.equal('bigint', typeof ValueHash.Create(true)) Assert.equal('bigint', typeof ValueHash.Create(false)) @@ -23,25 +21,18 @@ describe('Hash', () => { const B = ValueHash.Create(false) Assert.notEqual(A, B) }) - - it('Should not hash bigint', () => { - Assert.throws(() => ValueHash.Create(1n)) - }) - it('Should hash null', () => { Assert.equal('bigint', typeof ValueHash.Create(null)) const A = ValueHash.Create(null) const B = ValueHash.Create(undefined) Assert.notEqual(A, B) }) - it('Should hash array', () => { Assert.equal('bigint', typeof ValueHash.Create([0, 1, 2, 3])) const A = ValueHash.Create([0, 1, 2, 3]) const B = ValueHash.Create([0, 2, 2, 3]) Assert.notEqual(A, B) }) - it('Should hash object 1', () => { // prettier-ignore Assert.equal('bigint', typeof ValueHash.Create({ x: 1, y: 2 })) @@ -64,31 +55,57 @@ describe('Hash', () => { const B = ValueHash.Create({ x: 1, y: [0, 1, 2] }) Assert.notEqual(A, B) }) - it('Should hash object 5', () => { const A = ValueHash.Create({ x: 1, y: undefined }) const B = ValueHash.Create({ x: 2, y: undefined }) Assert.notEqual(A, B) }) - it('Should hash Date', () => { Assert.equal('bigint', typeof ValueHash.Create(new Date())) const A = ValueHash.Create(new Date(1)) const B = ValueHash.Create(new Date(2)) Assert.notEqual(A, B) }) - it('Should hash Uint8Array', () => { Assert.equal('bigint', typeof ValueHash.Create(new Uint8Array([0, 1, 2, 3]))) const A = ValueHash.Create(new Uint8Array([0, 1, 2, 3])) const B = ValueHash.Create(new Uint8Array([0, 2, 2, 3])) Assert.notEqual(A, B) }) - it('Should hash undefined', () => { Assert.equal('bigint', typeof ValueHash.Create(undefined)) const A = ValueHash.Create(undefined) const B = ValueHash.Create(null) Assert.notEqual(A, B) }) + it('Should hash symbol 1', () => { + Assert.equal('bigint', typeof ValueHash.Create(Symbol())) + const A = ValueHash.Create(Symbol(1)) + const B = ValueHash.Create(Symbol()) + Assert.notEqual(A, B) + }) + it('Should hash symbol 2', () => { + Assert.equal('bigint', typeof ValueHash.Create(Symbol())) + const A = ValueHash.Create(Symbol(1)) + const B = ValueHash.Create(Symbol(2)) + Assert.notEqual(A, B) + }) + it('Should hash symbol 2', () => { + Assert.equal('bigint', typeof ValueHash.Create(Symbol())) + const A = ValueHash.Create(Symbol(1)) + const B = ValueHash.Create(Symbol(1)) + Assert.equal(A, B) + }) + it('Should hash bigint 1', () => { + Assert.equal('bigint', typeof ValueHash.Create(BigInt(1))) + const A = ValueHash.Create(BigInt(1)) + const B = ValueHash.Create(BigInt(2)) + Assert.notEqual(A, B) + }) + it('Should hash bigint 2', () => { + Assert.equal('bigint', typeof ValueHash.Create(BigInt(1))) + const A = ValueHash.Create(BigInt(1)) + const B = ValueHash.Create(BigInt(1)) + Assert.equal(A, B) + }) }) diff --git a/test/runtime/hash/index.ts b/test/runtime/value/hash/index.ts similarity index 100% rename from test/runtime/hash/index.ts rename to test/runtime/value/hash/index.ts diff --git a/test/runtime/value/index.ts b/test/runtime/value/index.ts index 2ca9e71..ad07f09 100644 --- a/test/runtime/value/index.ts +++ b/test/runtime/value/index.ts @@ -1,7 +1,9 @@ import './cast' import './check' import './clone' +import './convert' import './create' import './delta' import './equal' +import './hash' import './pointer' diff --git a/test/static/assert.ts b/test/static/assert.ts index b4ffbd3..4b87638 100644 --- a/test/static/assert.ts +++ b/test/static/assert.ts @@ -1,8 +1,8 @@ import { Static, TSchema } from '@sinclair/typebox' -export function Expect(schema: T) { +export function Expect>(schema: T) { return { - ToInfer: >() => {}, - ToBe: () => {}, + ToInfer: () => {}, + ToBe: () => {}, } } diff --git a/test/static/bigint.ts b/test/static/bigint.ts new file mode 100644 index 0000000..a18b437 --- /dev/null +++ b/test/static/bigint.ts @@ -0,0 +1,4 @@ +import { Expect } from './assert' +import { Type } from '@sinclair/typebox' + +Expect(Type.BigInt()).ToInfer() diff --git a/test/static/composite.ts b/test/static/composite.ts new file mode 100644 index 0000000..be5596d --- /dev/null +++ b/test/static/composite.ts @@ -0,0 +1,19 @@ +import { Expect } from './assert' +import { Type } from '@sinclair/typebox' + +{ + const A = Type.Object({ + A: Type.String(), + B: Type.String(), + }) + const B = Type.Object({ + A: Type.Number(), + B: Type.Number(), + }) + const T = Type.Composite([A, B]) + + Expect(T).ToInfer<{ + A: string | number + B: string | number + }>() +} diff --git a/test/static/exclude.ts b/test/static/exclude.ts new file mode 100644 index 0000000..b0a7635 --- /dev/null +++ b/test/static/exclude.ts @@ -0,0 +1,15 @@ +import { Type } from '@sinclair/typebox' +import { Expect } from './assert' + +{ + const T = Type.Exclude(Type.String(), Type.String()) + Expect(T).ToBe() +} +{ + const T = Type.Exclude(Type.String(), Type.Number()) + Expect(T).ToBe() +} +{ + const T = Type.Exclude(Type.Union([Type.Number(), Type.String(), Type.Boolean()]), Type.Number()) + Expect(T).ToBe() +} diff --git a/test/static/extract.ts b/test/static/extract.ts new file mode 100644 index 0000000..7cc6037 --- /dev/null +++ b/test/static/extract.ts @@ -0,0 +1,15 @@ +import { Type } from '@sinclair/typebox' +import { Expect } from './assert' + +{ + const T = Type.Extract(Type.String(), Type.String()) + Expect(T).ToBe() +} +{ + const T = Type.Extract(Type.String(), Type.Number()) + Expect(T).ToBe() +} +{ + const T = Type.Extract(Type.Union([Type.Number(), Type.String(), Type.Boolean()]), Type.Number()) + Expect(T).ToBe() +} diff --git a/test/static/index.ts b/test/static/index.ts index cc84ce3..8666ca2 100644 --- a/test/static/index.ts +++ b/test/static/index.ts @@ -1,10 +1,14 @@ import './any' import './array' +import './bigint' import './boolean' +import './composite' import './date' import './constructor-parameters' import './constructor' import './emum' +import './extract' +import './exclude' import './function' import './instance-type' import './intersect' @@ -13,6 +17,7 @@ import './literal' import './modifier' import './namespace' import './never' +import './not' import './null' import './number' import './object' @@ -31,6 +36,7 @@ import './required' import './return-type' import './strict' import './string' +import './symbol' import './tuple' import './union' import './unknown' diff --git a/test/static/intersect.ts b/test/static/intersect.ts index fcbb100..b7cf181 100644 --- a/test/static/intersect.ts +++ b/test/static/intersect.ts @@ -1,5 +1,5 @@ import { Expect } from './assert' -import { Type, TOptional, TString } from '@sinclair/typebox' +import { Type, TOptional, TString, Static } from '@sinclair/typebox' { const A = Type.Object({ @@ -38,14 +38,17 @@ import { Type, TOptional, TString } from '@sinclair/typebox' // https://github.com/sinclairzx81/typebox/issues/113 // https://github.com/sinclairzx81/typebox/issues/187 -// { -// const A = Type.Object({ A: Type.String() }) -// const B = Type.Object({ B: Type.String() }) -// const C = Type.Object({ C: Type.String() }) -// const T = Type.Intersect([A, Type.Union([B, C])]) -// type T = Static -// const _0: T = { A: '', B: '' } -// const _1: T = { A: '', C: '' } -// const _3: T = { A: '', B: '', C: '' } -// Expect(T).ToBe<{ A: string } & ({ B: string, } | { C: string })>() -// } +{ + const A = Type.Object({ A: Type.String() }) + const B = Type.Object({ B: Type.String() }) + const C = Type.Object({ C: Type.String() }) + const T = Type.Intersect([A, Type.Union([B, C])]) + type T = Static + const _0: T = { A: '', B: '' } + const _1: T = { A: '', C: '' } + const _3: T = { A: '', B: '', C: '' } + // invert equivelence (expect true both cases) + type T1 = T extends { A: string } & ({ B: string } | { C: string }) ? true : false + type T2 = { A: string } & ({ B: string } | { C: string }) extends T ? true : false + Expect(T).ToBe<{ A: string } & ({ B: string } | { C: string })>() // solved! +} diff --git a/test/static/keyof.ts b/test/static/keyof.ts index 62d6260..ff353b7 100644 --- a/test/static/keyof.ts +++ b/test/static/keyof.ts @@ -55,3 +55,27 @@ import { Type } from '@sinclair/typebox' ) Expect(T).ToInfer<'C'>() } +{ + { + const A = Type.Object({ type: Type.Literal('A') }) + const B = Type.Object({ type: Type.Literal('B') }) + const C = Type.Object({ type: Type.Literal('C') }) + const Union = Type.Union([A, B, C]) + const Extended = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }) + const T = Type.Intersect([Union, Extended]) + + const K1 = Type.KeyOf(T) + + Expect(K1).ToInfer<'type' | 'x' | 'y' | 'z'>() + + const P = Type.Omit(T, ['type', 'x']) + + const K2 = Type.KeyOf(P) + + Expect(K2).ToInfer<'y' | 'z'>() + } +} diff --git a/test/static/not.ts b/test/static/not.ts new file mode 100644 index 0000000..ea15715 --- /dev/null +++ b/test/static/not.ts @@ -0,0 +1,7 @@ +import { Expect } from './assert' +import { Type } from '@sinclair/typebox' + +{ + const T = Type.Not(Type.Number(), Type.String()) + Expect(T).ToInfer() +} diff --git a/test/static/omit.ts b/test/static/omit.ts index c3f7268..98b93ba 100644 --- a/test/static/omit.ts +++ b/test/static/omit.ts @@ -54,3 +54,51 @@ import { Type, Static } from '@sinclair/typebox' C: string }>() } +{ + const A = Type.Object({ type: Type.Literal('A') }) + const B = Type.Object({ type: Type.Literal('B') }) + const C = Type.Object({ type: Type.Literal('C') }) + const Union = Type.Union([A, B, C]) + const Extended = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }) + const T = Type.Intersect([Union, Extended]) + + Expect(T).ToInfer< + ( + | { + type: 'A' + } + | { + type: 'B' + } + | { + type: 'C' + } + ) & { + x: number + y: number + z: number + } + >() + + const P = Type.Omit(T, ['type', 'x']) + + Expect(P).ToInfer< + ({} | {} | {}) & { + y: number + z: number + } + >() + + const O = Type.Partial(P) + + Expect(O).ToInfer< + ({} | {} | {}) & { + y?: number | undefined + z?: number | undefined + } + >() +} diff --git a/test/static/partial.ts b/test/static/partial.ts index 348c646..a9e591f 100644 --- a/test/static/partial.ts +++ b/test/static/partial.ts @@ -17,3 +17,56 @@ import { Type, Static } from '@sinclair/typebox' C?: string }>() } +{ + { + const A = Type.Object({ type: Type.Literal('A') }) + const B = Type.Object({ type: Type.Literal('B') }) + const C = Type.Object({ type: Type.Literal('C') }) + const Union = Type.Union([A, B, C]) + const Extended = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }) + + const T = Type.Intersect([Union, Extended]) + + Expect(T).ToInfer< + ( + | { + type: 'A' + } + | { + type: 'B' + } + | { + type: 'C' + } + ) & { + x: number + y: number + z: number + } + >() + + const P = Type.Partial(T) + + Expect(P).ToInfer< + ( + | { + type?: 'A' | undefined + } + | { + type?: 'B' | undefined + } + | { + type?: 'C' | undefined + } + ) & { + x?: number | undefined + y?: number | undefined + z?: number | undefined + } + >() + } +} diff --git a/test/static/pick.ts b/test/static/pick.ts index 0ae10cd..f9d8255 100644 --- a/test/static/pick.ts +++ b/test/static/pick.ts @@ -57,3 +57,73 @@ import { Type, Static } from '@sinclair/typebox' B: string }>() } +{ + const A = Type.Object({ type: Type.Literal('A') }) + const B = Type.Object({ type: Type.Literal('B') }) + const C = Type.Object({ type: Type.Literal('C') }) + const Union = Type.Union([A, B, C]) + const Extended = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }) + const T = Type.Intersect([Union, Extended]) + + Expect(T).ToInfer< + ( + | { + type: 'A' + } + | { + type: 'B' + } + | { + type: 'C' + } + ) & { + x: number + y: number + z: number + } + >() + + const K = Type.KeyOf(T) + + Expect(K).ToInfer<'type' | 'x' | 'y' | 'z'>() + + const P = Type.Pick(T, ['type', 'x']) + + Expect(P).ToInfer< + ( + | { + type: 'A' + } + | { + type: 'B' + } + | { + type: 'C' + } + ) & { + x: number + } + >() + + const O = Type.Partial(P) + + Expect(O).ToInfer< + ( + | { + type?: 'A' | undefined + } + | { + type?: 'B' | undefined + } + | { + type?: 'C' | undefined + } + ) & { + x?: number | undefined + } + >() +} diff --git a/test/static/required.ts b/test/static/required.ts index c3b0ca7..30aa9ca 100644 --- a/test/static/required.ts +++ b/test/static/required.ts @@ -20,3 +20,57 @@ Expect(Type.RegEx(/foo/)).ToInfer() C: string }>() } +{ + { + const A = Type.Object({ type: Type.Literal('A') }) + const B = Type.Object({ type: Type.Literal('B') }) + const C = Type.Object({ type: Type.Literal('C') }) + const Union = Type.Union([A, B, C]) + const Extended = Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number(), + }) + const T = Type.Intersect([Union, Extended]) + + const P = Type.Partial(T) + + Expect(P).ToInfer< + ( + | { + type?: 'A' | undefined + } + | { + type?: 'B' | undefined + } + | { + type?: 'C' | undefined + } + ) & { + x?: number | undefined + y?: number | undefined + z?: number | undefined + } + >() + + const R = Type.Required(P) + + Expect(R).ToInfer< + ( + | { + type: 'A' + } + | { + type: 'B' + } + | { + type: 'C' + } + ) & { + x: number + y: number + z: number + } + >() + } +} diff --git a/test/static/symbol.ts b/test/static/symbol.ts new file mode 100644 index 0000000..6335414 --- /dev/null +++ b/test/static/symbol.ts @@ -0,0 +1,4 @@ +import { Expect } from './assert' +import { Type } from '@sinclair/typebox' + +Expect(Type.Symbol()).ToInfer() diff --git a/tsconfig.json b/tsconfig.json index d660b0d..47541b2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,20 +13,8 @@ "@sinclair/typebox/compiler": [ "src/compiler/index.ts" ], - "@sinclair/typebox/conditional": [ - "src/conditional/index.ts" - ], - "@sinclair/typebox/custom": [ - "src/custom/index.ts" - ], - "@sinclair/typebox/format": [ - "src/format/index.ts" - ], - "@sinclair/typebox/guard": [ - "src/guard/index.ts" - ], - "@sinclair/typebox/hash": [ - "src/hash/index.ts" + "@sinclair/typebox/errors": [ + "src/errors/index.ts" ], "@sinclair/typebox/system": [ "src/system/index.ts" diff --git a/typebox.png b/typebox.png index 0df53e5..ed053e1 100644 Binary files a/typebox.png and b/typebox.png differ