mirror of
https://github.com/zoriya/typebox.git
synced 2025-12-06 06:46:10 +00:00
Revision 0.31.0 (#525)
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
import * as ValueErrors from '@sinclair/typebox/errors'
|
||||
import * as Errors from '@sinclair/typebox/errors'
|
||||
|
||||
console.log(ValueErrors)
|
||||
console.log(Errors)
|
||||
|
||||
331
changelog/0.31.0.md
Normal file
331
changelog/0.31.0.md
Normal file
@@ -0,0 +1,331 @@
|
||||
## [0.31.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.31.0)
|
||||
|
||||
## Overview
|
||||
|
||||
Revision 0.31.0 is a subsequent milestone revision for the TypeBox library and a direct continuation of the work carried out in 0.30.0 to optimize and prepare TypeBox for a 1.0 release candidate. This revision implements a new codec system with Transform types, provides configurable error message generation for i18n support, adds a library wide exception type named TypeBoxError and generalizes the Rest type to enable richer composition. This revision also finalizes optimization work to reduce the TypeBox package size.
|
||||
|
||||
This revision contains relatively minor breaking changes due to internal type renaming. A minor semver revision is required.
|
||||
|
||||
## Contents
|
||||
|
||||
- Enhancements
|
||||
- [Transform Types](#Transform-Types)
|
||||
- [Encode and Decode](#Encode-and-Decode)
|
||||
- [StaticEncode and StaticDecode](#StaticEncode-and-StaticDecode)
|
||||
- [Rest Types](#Rest-Types)
|
||||
- [Record Key](#Record-Key)
|
||||
- [TypeBoxError](#TypeBoxError)
|
||||
- [TypeSystemErrorFunction](#TypeSystemErrorFunction)
|
||||
- [Reduce Package Size](#Reduce-Package-Size)
|
||||
- Breaking
|
||||
- [JsonTypeBuilder and JavaScriptTypeBuilder](#JsonTypeBuilder-and-JavaScriptTypeBuilder)
|
||||
- [TypeSystemPolicy](#TypeSystemPolicy)
|
||||
|
||||
<a name="Transform-Types"></a>
|
||||
|
||||
## Transform Types
|
||||
|
||||
Revision 0.31.0 includes a new codec system referred to as Transform types. A Transform type is used to augment a regular TypeBox type with Encode and Decode functions. These functions are invoked via the new Encode and Decode functions available on both Value and TypeCompiler modules.
|
||||
|
||||
The following shows a Transform type which increments and decrements a number.
|
||||
|
||||
```typescript
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
const T = Type.Transform(Type.Number()) // const T = {
|
||||
.Decode(value => value + 1) // type: 'number',
|
||||
.Encode(value => value - 1) // [Symbol(TypeBox.Kind)]: 'Number',
|
||||
// [Symbol(TypeBox.Transform)]: {
|
||||
// Decode: [Function: Decode],
|
||||
// Encode: [Function: Encode]
|
||||
// }
|
||||
// }
|
||||
|
||||
const A = Value.Decode(T, 0) // const A: number = 1
|
||||
|
||||
const B = Value.Encode(T, 1) // const B: number = 0
|
||||
```
|
||||
|
||||
<a name="Encode-and-Decode"></a>
|
||||
|
||||
## Encode and Decode
|
||||
|
||||
Revision 0.31.0 includes new functions to Decode and Encode values. These functions are written in service to Transform types, but can be used equally well without them. These functions return a typed value that matches the type being transformed. TypeBox will infer decode and encode differently, yielding the correct type as derived from the codec implementation.
|
||||
|
||||
The following shows decoding and encoding between number to Date. Note these functions will throw if the value is invalid.
|
||||
|
||||
```typescript
|
||||
const T = Type.Transform(Type.Number())
|
||||
.Decode(value => new Date(value)) // number to Date
|
||||
.Encode(value => value.getTime()) // Date to number
|
||||
|
||||
// Ok
|
||||
//
|
||||
const A = Value.Decode(T, 42) // const A = new Date(42)
|
||||
|
||||
const B = Value.Encode(T, new Date(42)) // const B = 42
|
||||
|
||||
// Error
|
||||
//
|
||||
const C = Value.Decode(T, true) // Error: Expected number
|
||||
|
||||
const D = Value.Encode(T, 'not a date') // Error: getTime is not a function
|
||||
```
|
||||
The Decode function is extremely fast when decoding regular TypeBox types; and TypeBox will by pass codec execution if the type being decoded contains no interior Transforms (and will only use Check). When using Transforms however, these functions may incur a performance penelty due to codecs operating structurally on values using dynamic techniques (as would be the case for applications manually decoding values). As such the Decode design is built to be general and opt in, but not necessarily high performance.
|
||||
|
||||
<a name="StaticEncode-and-StaticDecode"></a>
|
||||
|
||||
## StaticEncode and StaticDecode
|
||||
|
||||
Revision 0.31.0 includes new inference types `StaticEncode` and `StaticDecode`. These types can be used to infer the encoded and decoded states of a Transform as well as regular TypeBox types. These types can be used to replace `Static` for `Request` and `Response` inference pipelines.
|
||||
|
||||
The following shows an example `Route` function that uses Transform inference via `StaticDecode`.
|
||||
|
||||
```typescript
|
||||
// Route
|
||||
//
|
||||
export type RouteCallback<TRequest extends TSchema, TResponse extends TSchema> =
|
||||
(request: StaticDecode<TRequest>) => StaticDecode<TResponse> // replace Static with StaticDecode
|
||||
|
||||
export function Route<TPath extends string, TRequest extends TSchema, TResponse extends TSchema>(
|
||||
path: TPath,
|
||||
requestType: TRequest,
|
||||
responseType: TResponse,
|
||||
callback: RouteCallback<TRequest, TResponse>
|
||||
) {
|
||||
// route handling here ...
|
||||
|
||||
const input = null // receive input
|
||||
const request = Value.Decode(requestType, input)
|
||||
const response = callback(request)
|
||||
const output = Value.Encode(responseType, response)
|
||||
// send output
|
||||
}
|
||||
|
||||
// Route: Without Transform
|
||||
//
|
||||
const Timestamp = Type.Number()
|
||||
|
||||
Route('/exampleA', Timestamp, Timestamp, (value) => {
|
||||
return value // value observed as number
|
||||
})
|
||||
|
||||
// Route: With Transform
|
||||
//
|
||||
const Timestamp = Type.Transform(Type.Number())
|
||||
.Decode(value => new Date(value))
|
||||
.Encode(value => value.getTime())
|
||||
|
||||
Route('/exampleB', Timestamp, Timestamp, (value) => {
|
||||
return value // value observed as Date
|
||||
})
|
||||
```
|
||||
|
||||
<a name="Rest-Types"></a>
|
||||
|
||||
## Rest Types
|
||||
|
||||
Revision 0.31.0 updates the Rest type to support variadic tuple extraction from Union, Intersection and Tuple types. Previously the Rest type was limited to Tuple types only, but has been extended to other types to allow uniform remapping without having to extract types from specific schema representations.
|
||||
|
||||
The following remaps a Tuple into a Union.
|
||||
|
||||
```typescript
|
||||
const T = Type.Tuple([ // const T = {
|
||||
Type.String(), // type: 'array',
|
||||
Type.Number() // items: [
|
||||
]) // { type: 'string' },
|
||||
// { type: 'number' }
|
||||
// ],
|
||||
// additionalItems: false,
|
||||
// minItems: 2,
|
||||
// maxItems: 2,
|
||||
// }
|
||||
|
||||
const R = Type.Rest(T) // const R = [
|
||||
// { type: 'string' },
|
||||
// { type: 'number' }
|
||||
// ]
|
||||
|
||||
const U = Type.Union(R) // const U = {
|
||||
// anyOf: [
|
||||
// { type: 'string' },
|
||||
// { type: 'number' }
|
||||
// ]
|
||||
// }
|
||||
```
|
||||
This type can be used to remap Intersect a Composite
|
||||
|
||||
```typescript
|
||||
const I = Type.Intersect([ // const I = {
|
||||
Type.Object({ x: Type.Number() }), // allOf: [{
|
||||
Type.Object({ y: Type.Number() }) // type: 'object',
|
||||
]) // required: ['x'],
|
||||
// properties: {
|
||||
// x: { type: 'number' }
|
||||
// }
|
||||
// }, {
|
||||
// type: 'object',
|
||||
// required: ['y'],
|
||||
// properties: {
|
||||
// y: { type: 'number' }
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
|
||||
const C = Type.Composite(Type.Rest(I)) // const C = {
|
||||
// type: 'object',
|
||||
// required: ['x', 'y'],
|
||||
// properties: {
|
||||
// 'x': { type: 'number' },
|
||||
// 'y': { type: 'number' }
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
<a name="Record-Key"></a>
|
||||
|
||||
## Record Key
|
||||
|
||||
Revision 0.31.0 updates the inference strategy for Record types and generalizes RecordKey to TSchema. This update aims to help Record types compose better when used with generic functions. The update also removes the overloaded Record factory methods, opting for a full conditional inference path. It also removes the `RecordKey` type which would type error when used with Record overloads. The return type of Record will be TNever if passing an invalid key. Valid Record key types include TNumber, TString, TInteger, TTemplateLiteral, TLiteralString, TLiteralNumber and TUnion.
|
||||
|
||||
```typescript
|
||||
// 0.30.0
|
||||
//
|
||||
import { RecordKey, TSchema } from '@sinclair/typebox'
|
||||
|
||||
function StrictRecord<K extends RecordKey, T extends TSchema>(K: K, T: T) {
|
||||
return Type.Record(K, T, { additionalProperties: false }) // Error: RecordKey unresolvable to overload
|
||||
}
|
||||
// 0.31.0
|
||||
//
|
||||
import { TSchema } from '@sinclair/typebox'
|
||||
|
||||
function StrictRecord<K extends TSchema, T extends TSchema>(K: K, T: T) {
|
||||
return Type.Record(K, T, { additionalProperties: false }) // Ok: dynamically mapped
|
||||
}
|
||||
|
||||
const A = StrictRecord(Type.String(), Type.Null()) // const A: TRecord<TString, TNull>
|
||||
|
||||
const B = StrictRecord(Type.Literal('A'), Type.Null()) // const B: TObject<{ A: TNull }>
|
||||
|
||||
const C = StrictRecord(Type.BigInt(), Type.Null()) // const C: TNever
|
||||
```
|
||||
|
||||
<a name="TypeBoxError"></a>
|
||||
|
||||
## TypeBoxError
|
||||
|
||||
Revision 0.31.0 updates all errors thrown by TypeBox to extend the sub type `TypeBoxError`. This can be used to help narrow down the source of errors in `try/catch` blocks.
|
||||
|
||||
```typescript
|
||||
import { Type, TypeBoxError } from '@sinclair/typebox'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
try {
|
||||
const A = Value.Decode(Type.Number(), 'hello')
|
||||
} catch(error) {
|
||||
if(error instanceof TypeBoxError) {
|
||||
// typebox threw this error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="TypeSystemErrorFunction"></a>
|
||||
|
||||
## TypeSystemErrorFunction
|
||||
|
||||
Revision 0.31.0 adds functionality to remap error messages with the TypeSystemErrorFunction. This function is invoked whenever a validation error is generated in TypeBox. The following is an example of a custom TypeSystemErrorFunction using some of the messages TypeBox generates by default. TypeBox also provides the DefaultErrorFunction which can be used for fallthrough cases.
|
||||
|
||||
```typescript
|
||||
import { TypeSystemErrorFunction, DefaultErrorFunction } from '@sinclair/typebox/system'
|
||||
|
||||
// Example CustomErrorFunction
|
||||
export function CustomErrorFunction(schema: Types.TSchema, errorType: ValueErrorType) {
|
||||
switch (errorType) {
|
||||
case ValueErrorType.ArrayContains:
|
||||
return 'Expected array to contain at least one matching value'
|
||||
case ValueErrorType.ArrayMaxContains:
|
||||
return `Expected array to contain no more than ${schema.maxContains} matching values`
|
||||
case ValueErrorType.ArrayMinContains:
|
||||
return `Expected array to contain at least ${schema.minContains} matching values`
|
||||
...
|
||||
default: return DefaultErrorFunction(schema, errorType)
|
||||
}
|
||||
}
|
||||
// Sets the CustomErrorFunction
|
||||
TypeSystemErrorFunction.Set(CustomErrorFunction)
|
||||
```
|
||||
It is possible to call `.Set()` on the TypeSystemErrorFunction module prior to each call to `.Errors()`. This can be useful for applications that require i18n support in their validation pipelines.
|
||||
|
||||
<a name="Reduce Package Size"></a>
|
||||
|
||||
## Reduce Package Size
|
||||
|
||||
Revision 0.31.0 completes a full sweep of code optimizations and modularization to reduce package bundle size. The following table shows the bundle sizes inclusive of the new 0.31.0 functionality against 0.30.0.
|
||||
|
||||
```typescript
|
||||
// Revision 0.30.0
|
||||
//
|
||||
┌──────────────────────┬────────────┬────────────┬─────────────┐
|
||||
│ (index) │ Compiled │ Minified │ Compression │
|
||||
├──────────────────────┼────────────┼────────────┼─────────────┤
|
||||
│ typebox/compiler │ '131.4 kb' │ ' 59.4 kb' │ '2.21 x' │
|
||||
│ typebox/errors │ '113.6 kb' │ ' 50.9 kb' │ '2.23 x' │
|
||||
│ typebox/system │ ' 78.5 kb' │ ' 32.5 kb' │ '2.42 x' │
|
||||
│ typebox/value │ '182.8 kb' │ ' 80.0 kb' │ '2.28 x' │
|
||||
│ typebox │ ' 77.4 kb' │ ' 32.0 kb' │ '2.42 x' │
|
||||
└──────────────────────┴────────────┴────────────┴─────────────┘
|
||||
|
||||
// Revision 0.31.0
|
||||
//
|
||||
┌──────────────────────┬────────────┬────────────┬─────────────┐
|
||||
│ (index) │ Compiled │ Minified │ Compression │
|
||||
├──────────────────────┼────────────┼────────────┼─────────────┤
|
||||
│ typebox/compiler │ '149.5 kb' │ ' 66.1 kb' │ '2.26 x' │
|
||||
│ typebox/errors │ '112.1 kb' │ ' 49.4 kb' │ '2.27 x' │
|
||||
│ typebox/system │ ' 83.2 kb' │ ' 37.1 kb' │ '2.24 x' │
|
||||
│ typebox/value │ '191.1 kb' │ ' 82.7 kb' │ '2.31 x' │
|
||||
│ typebox │ ' 73.0 kb' │ ' 31.9 kb' │ '2.29 x' │
|
||||
└──────────────────────┴────────────┴────────────┴─────────────┘
|
||||
```
|
||||
|
||||
Additional code reductions may not be possible without implicating code maintainability. The `typebox` module may however be broken down into sub modules in later revisions to further bolster modularity, but is retained as a single file on this revision for historical reasons (not necessarily technical ones).
|
||||
|
||||
<a name="JsonTypeBuilder-and-JavaScriptTypeBuilder"></a>
|
||||
|
||||
## JsonTypeBuilder and JavaScriptTypeBuilder
|
||||
|
||||
Revision 0.31.0 renames the `StandardTypeBuilder` and `ExtendedTypeBuilder` to `JsonTypeBuilder` and `JavaScriptTypeBuilder` respectively. Applications that extend TypeBox's TypeBuilders will need to update to these names.
|
||||
|
||||
```typescript
|
||||
// 0.30.0
|
||||
//
|
||||
export class ApplicationTypeBuilder extends ExtendedTypeBuilder {}
|
||||
|
||||
// 0.31.0
|
||||
//
|
||||
export class ApplicationTypeBuilder extends JavaScriptTypeBuilder {}
|
||||
```
|
||||
These builders also update the jsdoc comment to `[Json]` and `[JavaScript]` inline with this new naming convention.
|
||||
|
||||
<a name="TypeSystemPolicy"></a>
|
||||
|
||||
## TypeSystemPolicy
|
||||
|
||||
Revision 0.31.0 moves the `TypeSystem.Policy` configurations into a new type named `TypeSystemPolicy`. This change was done to unify internal policy checks used by the Value and Error modules during bundle size optimization; as well as to keep policy configurations contextually separate from the Type and Format API on the TypeSystem module.
|
||||
|
||||
```typescript
|
||||
// Revision 0.30.0
|
||||
//
|
||||
import { TypeSystem } from '@sinclair/typebox/system'
|
||||
|
||||
TypeSystem.AllowNaN = true
|
||||
|
||||
// Revision 0.31.0
|
||||
//
|
||||
import { TypeSystemPolicy } from '@sinclair/typebox/system'
|
||||
|
||||
TypeSystemPolicy.AllowNaN = true
|
||||
|
||||
TypeSystemPolicy.IsNumberLike(NaN) // true
|
||||
```
|
||||
@@ -27,18 +27,18 @@ THE SOFTWARE.
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
|
||||
import { TSchema, Static } from '@sinclair/typebox'
|
||||
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// TypeArrayError
|
||||
// ----------------------------------------------------------------
|
||||
export class TypeArrayError extends Error {
|
||||
export class TypeArrayError extends TypeBoxError {
|
||||
constructor(message: string) {
|
||||
super(`${message}`)
|
||||
}
|
||||
}
|
||||
export class TypeArrayLengthError extends Error {
|
||||
export class TypeArrayLengthError extends TypeBoxError {
|
||||
constructor() {
|
||||
super('arrayLength not a number')
|
||||
}
|
||||
|
||||
@@ -27,18 +27,18 @@ THE SOFTWARE.
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
|
||||
import { TSchema, Static } from '@sinclair/typebox'
|
||||
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// TypeMapKeyError
|
||||
// ----------------------------------------------------------------
|
||||
export class TypeMapKeyError extends Error {
|
||||
export class TypeMapKeyError extends TypeBoxError {
|
||||
constructor(message: string) {
|
||||
super(`${message} for key`)
|
||||
}
|
||||
}
|
||||
export class TypeMapValueError extends Error {
|
||||
export class TypeMapValueError extends TypeBoxError {
|
||||
constructor(key: unknown, message: string) {
|
||||
super(`${message} for key ${JSON.stringify(key)}`)
|
||||
}
|
||||
|
||||
@@ -27,13 +27,13 @@ THE SOFTWARE.
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
|
||||
import { TSchema, Static } from '@sinclair/typebox'
|
||||
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Errors
|
||||
// ----------------------------------------------------------------
|
||||
export class TypeSetError extends Error {
|
||||
export class TypeSetError extends TypeBoxError {
|
||||
constructor(message: string) {
|
||||
super(`${message}`)
|
||||
}
|
||||
|
||||
39
examples/formats/date-time.ts
Normal file
39
examples/formats/date-time.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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 { IsDate } from './date'
|
||||
import { IsTime } from './time'
|
||||
|
||||
const DATE_TIME_SEPARATOR = /t|\s/i
|
||||
|
||||
/**
|
||||
* `[ajv-formats]` ISO8601 DateTime
|
||||
* @example `2020-12-12T20:20:40+00:00`
|
||||
*/
|
||||
export 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)
|
||||
}
|
||||
44
examples/formats/date.ts
Normal file
44
examples/formats/date.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||
const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
|
||||
|
||||
function IsLeapYear(year: number): boolean {
|
||||
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
||||
}
|
||||
/**
|
||||
* `[ajv-formats]` ISO8601 Date component
|
||||
* @example `2020-12-12`
|
||||
*/
|
||||
export function IsDate(value: string): boolean {
|
||||
const matches: string[] | null = DATE.exec(value)
|
||||
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])
|
||||
}
|
||||
35
examples/formats/email.ts
Normal file
35
examples/formats/email.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* `[ajv-formats]` Internet Email Address [RFC 5321, section 4.1.2.](http://tools.ietf.org/html/rfc5321#section-4.1.2)
|
||||
* @example `user@domain.com`
|
||||
*/
|
||||
export function IsEmail(value: string): boolean {
|
||||
return Email.test(value)
|
||||
}
|
||||
@@ -1 +1,36 @@
|
||||
export * from './standard'
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
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 './date-time'
|
||||
export * from './date'
|
||||
export * from './email'
|
||||
export * from './ipv4'
|
||||
export * from './ipv6'
|
||||
export * from './time'
|
||||
export * from './url'
|
||||
export * from './uuid'
|
||||
|
||||
35
examples/formats/ipv4.ts
Normal file
35
examples/formats/ipv4.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
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)$/
|
||||
|
||||
/**
|
||||
* `[ajv-formats]` 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)
|
||||
* @example `192.168.0.1`
|
||||
*/
|
||||
export function IsIPv4(value: string): boolean {
|
||||
return IPv4.test(value)
|
||||
}
|
||||
36
examples/formats/ipv6.ts
Normal file
36
examples/formats/ipv6.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* `[ajv-formats]` IPv6 address as defined in [RFC 2373, section 2.2](http://tools.ietf.org/html/rfc2373#section-2.2).
|
||||
* @example `2001:0db8:85a3:0000:0000:8a2e:0370:7334`
|
||||
*/
|
||||
export function IsIPv6(value: string): boolean {
|
||||
return IPv6.test(value)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# Formats
|
||||
|
||||
This example provides TypeCompiler supported versions of the `ajv-formats` package.
|
||||
|
||||
## Standard
|
||||
|
||||
The `standard.ts` file provided with this example implements several standard string formats.
|
||||
|
||||
| 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). |
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
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)
|
||||
}
|
||||
|
||||
48
examples/formats/time.ts
Normal file
48
examples/formats/time.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i
|
||||
|
||||
/**
|
||||
* `[ajv-formats]` ISO8601 Time component
|
||||
* @example `20:20:39+00:00`
|
||||
*/
|
||||
export function IsTime(value: string, strictTimeZone?: boolean): boolean {
|
||||
const matches: string[] | null = TIME.exec(value)
|
||||
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
|
||||
}
|
||||
36
examples/formats/url.ts
Normal file
36
examples/formats/url.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* `[ajv-formats:deprecated]` A uniform resource locator as defined in [RFC 1738](https://www.rfc-editor.org/rfc/rfc1738)
|
||||
* @example `http://domain.com`
|
||||
*/
|
||||
export function IsUrl(value: string) {
|
||||
return Url.test(value)
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/transform
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
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
|
||||
@@ -26,4 +24,12 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './transform'
|
||||
const Uuid = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i
|
||||
|
||||
/**
|
||||
* `[ajv-formats]` A Universally Unique Identifier as defined by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122).
|
||||
* @example `9aa8a673-8590-4db2-9830-01755844f7c1`
|
||||
*/
|
||||
export function IsUuid(value: string): boolean {
|
||||
return Uuid.test(value)
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
# Transform Types
|
||||
|
||||
Transform Types are an experimental codec system that supports deferred encode and decode of typed data structures received over IO interfaces. This system is undergoing consideration for inclusion in the TypeBox library.
|
||||
|
||||
### Design
|
||||
|
||||
The following is the high level design for Transform types. It consists of three functions, `Transform`, `Decode` and `Encode`. The following is the general usage.
|
||||
|
||||
```typescript
|
||||
import { Transform, Encode, Decode } from './transform'
|
||||
|
||||
const Timestamp = Transform(Type.Number(), { // The Transform function wraps a TypeBox type with two codec
|
||||
decode: (value) => new Date(value), // functions which implement logic to decode a received value
|
||||
encode: (value) => value.getTime(), // (i.e. number) into a application type (Date). The encode
|
||||
}) // function handles the reverse mapping.
|
||||
|
||||
type N = Static<typeof N> // type N = { timestamp: number }
|
||||
//
|
||||
const N = Type.Object({ // Transform types are to be used like any other type and will
|
||||
timestamp: Timestamp // infer as the original TypeBox type. For example, the type `N`
|
||||
}) // above will infer as { timestamp: number } (as derived from
|
||||
// the TB type)
|
||||
|
||||
|
||||
|
||||
const D = Decode(N, { timestamp: 123 }) // const D = { timestamp: Date(123) }
|
||||
//
|
||||
// The Decode function accepts any type plus a value. The Decode
|
||||
// function return type will be that of the transforms decode()
|
||||
// return type (which is Date), with the second argument statically
|
||||
// typed as N. This function acts as a kind of parse() that returns
|
||||
// the decoded type or throws on validation error.
|
||||
|
||||
|
||||
const E = Encode(N, { timestamp: new Date(123) }) // const E = { timestamp: 123 }
|
||||
//
|
||||
// The encode function performs the inverse, accepting the
|
||||
// decoded type { timestamp: Date } and re-encoding to the
|
||||
// target type { timestamp: number }. This function will
|
||||
// also throw on validation error.
|
||||
```
|
||||
### Integration
|
||||
|
||||
A practical use case for Transform types is the automatic encode and decode of values received over the network. The following is an example implementation using the Fastify web framework with the TypeBox Type Provider (used for auto type inference). Note that this usage wouldn't be exclusive to Fastify. The implementation below uses explicit Decode and Encode within a route handler.
|
||||
|
||||
```typescript
|
||||
import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
||||
import Fastify from 'fastify'
|
||||
|
||||
const Data = Type.Object({
|
||||
timestamp: Transform(Type.Number(), {
|
||||
decode: (value) => new Date(value),
|
||||
encode: (value) => value.getTime(),
|
||||
})
|
||||
})
|
||||
|
||||
Fastify().withTypeProvider<TypeBoxTypeProvider>().post('/', {
|
||||
schema: {
|
||||
body: Data
|
||||
response: { 200: Data }
|
||||
}
|
||||
}, (req, res) => {
|
||||
const decoded = Decode(Data, req.body) // decode { timestamp: number }
|
||||
// as { timestamp: Date }
|
||||
|
||||
// ... some operations on decoded value
|
||||
//
|
||||
// i.e. decoded.timestamp.getTime()
|
||||
|
||||
const encoded = Encode(Data, decoded) // encode { timetamp: Date }
|
||||
// as { timestamp: number }
|
||||
|
||||
res.status(200).send(encoded) // send!
|
||||
})
|
||||
```
|
||||
|
||||
### Current Status
|
||||
|
||||
From the example above, the current design of Transform types would require explicit Encode and Decode within a route handler (or other function in receipt of data); and it would be very challenging for general purpose frameworks to integrate this functionality in a more concise fashion (currently).
|
||||
|
||||
Possible options to make this more cohesive may be to provide route wrapping functions to handle codec execution and type inference. More integrated solutions would involve framework maintainers offering codec supportive type definitions to act as hooks for auto static inference (on both decode and encode phases). The latter option is the preference but would require a fair amount of engineering in downstream frameworks.
|
||||
|
||||
Given the many unknowns and general complexity surrounding this feature, TypeBox offers the Transform implementation to the community (as a single `transform.ts` module) for experimentation and general feedback. TypeBox welcomes third party packages, example user integrations, code enhancements or general insight into end user usage patterns.
|
||||
@@ -1,350 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/transform
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
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 * as ValueGuard from '@sinclair/typebox/value/guard'
|
||||
import * as ValueCheck from '@sinclair/typebox/value/check'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Symbols
|
||||
// --------------------------------------------------------------------------
|
||||
export const TransformSymbol = Symbol.for('TypeBox.Transform')
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Errors
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
export class TransformDecodeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown) {
|
||||
super('ValueTransform: The value passed to decode was invalid')
|
||||
}
|
||||
}
|
||||
export class TransformEncodeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown) {
|
||||
super('ValueTransform: The encode function returned an invalid value')
|
||||
}
|
||||
}
|
||||
export class ValueTransformUnknownTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('Unknown type')
|
||||
}
|
||||
}
|
||||
export class ValueTransformDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TThis) {
|
||||
super(`Unable to dereference type with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
export class ValueTransformFallthroughError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown, public mode: ValueTransformMode) {
|
||||
super('Unexpected transform error')
|
||||
}
|
||||
}
|
||||
export class ValueTransformCodecError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown, public mode: ValueTransformMode, error: any) {
|
||||
super(`${error instanceof Error ? error.message : 'Unknown'}`)
|
||||
}
|
||||
}
|
||||
export type ValueTransformMode = 'encode' | 'decode'
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Apply
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
function Apply(schema: Types.TSchema, value: any, mode: ValueTransformMode) {
|
||||
try {
|
||||
if (!HasTransform(schema)) return value
|
||||
const transform = schema[TransformSymbol] as unknown as TransformCodec
|
||||
if (mode === 'decode') return transform.decode(value)
|
||||
if (mode === 'encode') return transform.encode(value)
|
||||
} catch (error) {
|
||||
throw new ValueTransformCodecError(schema, value, mode, error)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Guards
|
||||
// --------------------------------------------------------------------------
|
||||
function HasTransform(schema: Types.TSchema): schema is Types.TSchema & { [TransformSymbol]: TransformCodec } {
|
||||
return ValueGuard.HasPropertyKey(schema, TransformSymbol)
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Transform
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
function TAny(schema: Types.TAny, references: Types.TSchema[], value: any, mode: ValueTransformMode): any {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TArray(schema: Types.TArray, references: Types.TSchema[], value: any, mode: ValueTransformMode): any {
|
||||
const transformed = Apply(schema, value, mode)
|
||||
return transformed.map((value: any) => Visit(schema.items, references, value, mode))
|
||||
}
|
||||
function TBigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TBoolean(schema: Types.TBoolean, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TConstructor(schema: Types.TConstructor, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TDate(schema: Types.TDate, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TFunction(schema: Types.TFunction, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TInteger(schema: Types.TInteger, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TLiteral(schema: Types.TLiteral, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TNever(schema: Types.TNever, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TNull(schema: Types.TNull, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TNumber(schema: Types.TNumber, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TObject(schema: Types.TObject, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
const transformed = Apply(schema, value, mode)
|
||||
const properties = Object.keys(transformed).reduce((acc, key) => {
|
||||
return key in schema.properties
|
||||
? { ...acc, [key]: Visit(schema.properties[key], references, transformed[key], mode) }
|
||||
: { ...acc, [key]: transformed[key] }
|
||||
}, {})
|
||||
return { ...properties }
|
||||
}
|
||||
function TPromise(schema: Types.TSchema, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
const transformed = Apply(schema, value, mode)
|
||||
const propertyKey = Object.getOwnPropertyNames(schema.patternProperties)[0]
|
||||
const property = schema.patternProperties[propertyKey]
|
||||
const result = {} as Record<string, unknown>
|
||||
for (const [propKey, propValue] of Object.entries(transformed)) {
|
||||
result[propKey] = Visit(property, references, propValue, mode)
|
||||
}
|
||||
return result
|
||||
}
|
||||
function TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueTransformDereferenceError(schema)
|
||||
const target = references[index]
|
||||
const resolved = Visit(target, references, value, mode)
|
||||
return Apply(schema, resolved, mode)
|
||||
}
|
||||
function TString(schema: Types.TString, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TSymbol(schema: Types.TSymbol, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TTemplateLiteral(schema: Types.TTemplateLiteral, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TThis(schema: Types.TThis, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueTransformDereferenceError(schema)
|
||||
const target = references[index]
|
||||
const resolved = Visit(target, references, value, mode)
|
||||
return Apply(schema, resolved, mode)
|
||||
}
|
||||
function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
const transformed = Apply(schema, value, mode)
|
||||
// prettier-ignore
|
||||
return Apply(schema, transformed.map((value: any, index: any) => {
|
||||
return index < schema.items!.length ? Visit(schema.items![index], references, value, mode) : value
|
||||
}), mode)
|
||||
}
|
||||
function TUndefined(schema: Types.TUndefined, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TUnion(schema: Types.TUnion, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
const transformed = Apply(schema, value, mode)
|
||||
for (const subschema of schema.anyOf) {
|
||||
const inner = Visit(subschema, references, transformed, mode)
|
||||
if (ValueCheck.Check(subschema, references, inner)) {
|
||||
return Apply(schema, inner, mode)
|
||||
}
|
||||
}
|
||||
return transformed
|
||||
}
|
||||
function TUint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TUnknown(schema: Types.TUnknown, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TVoid(schema: Types.TVoid, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function TKind(schema: Types.TSchema, references: Types.TSchema[], value: any, mode: ValueTransformMode) {
|
||||
return Apply(schema, value, mode)
|
||||
}
|
||||
function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any, mode: ValueTransformMode): any {
|
||||
const references_ = ValueGuard.IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema[Types.Kind]) {
|
||||
case 'Any':
|
||||
return TAny(schema_, references_, value, mode)
|
||||
case 'Array':
|
||||
return TArray(schema_, references_, value, mode)
|
||||
case 'BigInt':
|
||||
return TBigInt(schema_, references_, value, mode)
|
||||
case 'Boolean':
|
||||
return TBoolean(schema_, references_, value, mode)
|
||||
case 'Constructor':
|
||||
return TConstructor(schema_, references_, value, mode)
|
||||
case 'Date':
|
||||
return TDate(schema_, references_, value, mode)
|
||||
case 'Function':
|
||||
return TFunction(schema_, references_, value, mode)
|
||||
case 'Integer':
|
||||
return TInteger(schema_, references_, value, mode)
|
||||
case 'Intersect':
|
||||
return TIntersect(schema_, references_, value, mode)
|
||||
case 'Literal':
|
||||
return TLiteral(schema_, references_, value, mode)
|
||||
case 'Never':
|
||||
return TNever(schema_, references_, value, mode)
|
||||
case 'Null':
|
||||
return TNull(schema_, references_, value, mode)
|
||||
case 'Number':
|
||||
return TNumber(schema_, references_, value, mode)
|
||||
case 'Object':
|
||||
return TObject(schema_, references_, value, mode)
|
||||
case 'Promise':
|
||||
return TPromise(schema_, references_, value, mode)
|
||||
case 'Record':
|
||||
return TRecord(schema_, references_, value, mode)
|
||||
case 'Ref':
|
||||
return TRef(schema_, references_, value, mode)
|
||||
case 'String':
|
||||
return TString(schema_, references_, value, mode)
|
||||
case 'Symbol':
|
||||
return TSymbol(schema_, references_, value, mode)
|
||||
case 'TemplateLiteral':
|
||||
return TTemplateLiteral(schema_, references_, value, mode)
|
||||
case 'This':
|
||||
return TThis(schema_, references_, value, mode)
|
||||
case 'Tuple':
|
||||
return TTuple(schema_, references_, value, mode)
|
||||
case 'Undefined':
|
||||
return TUndefined(schema_, references_, value, mode)
|
||||
case 'Union':
|
||||
return TUnion(schema_, references_, value, mode)
|
||||
case 'Uint8Array':
|
||||
return TUint8Array(schema_, references_, value, mode)
|
||||
case 'Unknown':
|
||||
return TUnknown(schema_, references_, value, mode)
|
||||
case 'Void':
|
||||
return TVoid(schema_, references_, value, mode)
|
||||
default:
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueTransformUnknownTypeError(schema_)
|
||||
return TKind(schema_, references_, value, mode)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Transform Unwrap
|
||||
// --------------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
export type TransformUnwrapProperties<T extends Types.TProperties> = {
|
||||
[K in keyof T]: TransformUnwrap<T[K]>
|
||||
}
|
||||
// prettier-ignore
|
||||
export type TransformUnwrapRest<T extends Types.TSchema[]> = T extends [infer L, ...infer R]
|
||||
? [TransformUnwrap<Types.AssertType<L>>, ...TransformUnwrapRest<Types.AssertRest<R>>]
|
||||
: []
|
||||
// prettier-ignore
|
||||
export type TransformUnwrap<T extends Types.TSchema> =
|
||||
T extends TTransform<infer _, infer R> ? Types.TUnsafe<R> :
|
||||
T extends Types.TConstructor<infer P, infer R> ? Types.TConstructor<Types.AssertRest<TransformUnwrapRest<P>>, TransformUnwrap<R>> :
|
||||
T extends Types.TFunction<infer P, infer R> ? Types.TFunction<Types.AssertRest<TransformUnwrapRest<P>>, TransformUnwrap<R>> :
|
||||
T extends Types.TIntersect<infer S> ? Types.TIntersect<Types.AssertRest<TransformUnwrapRest<S>>> :
|
||||
T extends Types.TUnion<infer S> ? Types.TUnion<Types.AssertRest<TransformUnwrapRest<S>>> :
|
||||
T extends Types.TNot<infer S> ? Types.TNot<TransformUnwrap<S>> :
|
||||
T extends Types.TTuple<infer S> ? Types.TTuple<Types.AssertRest<TransformUnwrapRest<S>>> :
|
||||
T extends Types.TAsyncIterator<infer S> ? Types.TAsyncIterator<TransformUnwrap<S>> :
|
||||
T extends Types.TIterator<infer S> ? Types.TIterator<TransformUnwrap<S>> :
|
||||
T extends Types.TObject<infer S> ? Types.TObject<Types.Evaluate<TransformUnwrapProperties<S>>> :
|
||||
T extends Types.TRecord<infer K, infer S> ? Types.TRecord<K, TransformUnwrap<S>> :
|
||||
T extends Types.TArray<infer S> ? Types.TArray<TransformUnwrap<S>> :
|
||||
T extends Types.TPromise<infer S> ? Types.TPromise<TransformUnwrap<S>> :
|
||||
T
|
||||
// --------------------------------------------------------------------------
|
||||
// Transform Types
|
||||
// --------------------------------------------------------------------------
|
||||
export type TransformFunction<T = any, U = any> = (value: T) => U
|
||||
|
||||
export interface TransformCodec<Input extends Types.TSchema = Types.TSchema, Output extends unknown = unknown> {
|
||||
decode: TransformFunction<Types.Static<Input>, Output>
|
||||
encode: TransformFunction<Output, Types.Static<Input>>
|
||||
}
|
||||
export interface TTransform<Input extends Types.TSchema = Types.TSchema, Output extends unknown = unknown> extends Types.TSchema {
|
||||
static: Types.Static<Input>
|
||||
[TransformSymbol]: TransformCodec<Input, Output>
|
||||
[key: string]: any
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Transform Functions
|
||||
// --------------------------------------------------------------------------
|
||||
/** Creates a transform type by applying transform codec */
|
||||
export function Transform<Input extends Types.TSchema, Output extends unknown>(schema: Input, codec: TransformCodec<Input, Output>): TTransform<Input, Output> {
|
||||
if (!HasTransform(schema)) return { ...schema, [TransformSymbol]: codec } as TTransform<Input, Output>
|
||||
const mapped_encode = (value: unknown) => schema[TransformSymbol].encode(codec.encode(value as any))
|
||||
const mapped_decode = (value: unknown) => codec.decode(schema[TransformSymbol].decode(value))
|
||||
const mapped_codec = { encode: mapped_encode, decode: mapped_decode }
|
||||
return { ...schema, [TransformSymbol]: mapped_codec } as TTransform<Input, Output>
|
||||
}
|
||||
/** Decodes a value using the given type. Will apply an transform codecs found for any sub type */
|
||||
export function Decode<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: Types.Static<T>): Types.Static<TransformUnwrap<T>>
|
||||
/** Decodes a value using the given type. Will apply an transform codecs found for any sub type */
|
||||
export function Decode<T extends Types.TSchema>(schema: T, value: Types.Static<T>): Types.Static<TransformUnwrap<T>>
|
||||
/** Decodes a value using the given type. Will apply an transform codecs found for any sub type */
|
||||
export function Decode(...args: any[]) {
|
||||
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
|
||||
const check = ValueCheck.Check(schema, references, value)
|
||||
if (!check) throw new TransformDecodeError(schema, value)
|
||||
const decoded = Visit(schema, references, value, 'decode')
|
||||
return decoded
|
||||
}
|
||||
/** Encodes a value using the given type. Will apply an transforms found for any sub type */
|
||||
export function Encode<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: Types.Static<TransformUnwrap<T>>): Types.Static<T>
|
||||
/** Encodes a value using the given type. Will apply an transforms found for any sub type */
|
||||
export function Encode<T extends Types.TSchema>(schema: T, value: Types.Static<TransformUnwrap<T>>): Types.Static<T>
|
||||
/** Encodes a value using the given type. Will apply an transforms found for any sub type */
|
||||
export function Encode(...args: any[]) {
|
||||
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
|
||||
const encoded = Visit(schema, references, value, 'encode')
|
||||
const check = ValueCheck.Check(schema, references, encoded)
|
||||
if (!check) throw new TransformEncodeError(schema, value)
|
||||
return encoded
|
||||
}
|
||||
@@ -26,6 +26,7 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { TypeSystemErrorFunction, DefaultErrorFunction } from '@sinclair/typebox/system'
|
||||
import * as Types from '@sinclair/typebox'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -239,9 +240,9 @@ export namespace TimestampFormat {
|
||||
// --------------------------------------------------------------------------
|
||||
// ValueCheck
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueCheckError extends Error {
|
||||
export class ValueCheckError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCheck: Unknown type')
|
||||
super('Unknown type')
|
||||
}
|
||||
}
|
||||
export namespace ValueCheck {
|
||||
@@ -484,6 +485,27 @@ Types.TypeRegistry.Set<TString>('TypeDef:String', (schema, value) => ValueCheck.
|
||||
Types.TypeRegistry.Set<TStruct>('TypeDef:Struct', (schema, value) => ValueCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TTimestamp>('TypeDef:Timestamp', (schema, value) => ValueCheck.Check(schema, value))
|
||||
// --------------------------------------------------------------------------
|
||||
// TypeSystemErrorFunction
|
||||
// --------------------------------------------------------------------------
|
||||
TypeSystemErrorFunction.Set((schema, type) => {
|
||||
switch(schema[Types.Kind]) {
|
||||
case 'TypeDef:Array': return 'Expected Array'
|
||||
case 'TypeDef:Boolean': return 'Expected Boolean'
|
||||
case 'TypeDef:Union': return 'Expected Union'
|
||||
case 'TypeDef:Int8': return 'Expected Int8'
|
||||
case 'TypeDef:Int16': return 'Expected Int16'
|
||||
case 'TypeDef:Int32': return 'Expected Int32'
|
||||
case 'TypeDef:Uint8': return 'Expected Uint8'
|
||||
case 'TypeDef:Uint16': return 'Expected Uint16'
|
||||
case 'TypeDef:Uint32': return 'Expected Uint32'
|
||||
case 'TypeDef:Record': return 'Expected Record'
|
||||
case 'TypeDef:String': return 'Expected String'
|
||||
case 'TypeDef:Struct': return 'Expected Struct'
|
||||
case 'TypeDef:Timestamp': return 'Expected Timestamp'
|
||||
}
|
||||
return DefaultErrorFunction(schema, type)
|
||||
})
|
||||
// --------------------------------------------------------------------------
|
||||
// TypeDefBuilder
|
||||
// --------------------------------------------------------------------------
|
||||
export class TypeDefBuilder {
|
||||
|
||||
@@ -22,10 +22,16 @@ export async function start() {
|
||||
// -------------------------------------------------------------------------------
|
||||
// Benchmark
|
||||
// -------------------------------------------------------------------------------
|
||||
export async function benchmark() {
|
||||
export async function benchmark_compression() {
|
||||
await compression()
|
||||
}
|
||||
export async function benchmark_measurement() {
|
||||
await measurement()
|
||||
}
|
||||
export async function benchmark() {
|
||||
await benchmark_compression()
|
||||
await benchmark_measurement()
|
||||
}
|
||||
// -------------------------------------------------------------------------------
|
||||
// Test
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
604
package-lock.json
generated
604
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sinclair/typebox",
|
||||
"version": "0.30.4",
|
||||
"version": "0.31.0",
|
||||
"description": "JSONSchema Type Builder with Static Type Resolution for TypeScript",
|
||||
"keywords": [
|
||||
"typescript",
|
||||
@@ -22,11 +22,13 @@
|
||||
"./value/convert": "./value/convert.js",
|
||||
"./value/create": "./value/create.js",
|
||||
"./value/delta": "./value/delta.js",
|
||||
"./value/deref": "./value/deref.js",
|
||||
"./value/equal": "./value/equal.js",
|
||||
"./value/guard": "./value/guard.js",
|
||||
"./value/hash": "./value/hash.js",
|
||||
"./value/mutate": "./value/mutate.js",
|
||||
"./value/pointer": "./value/pointer.js",
|
||||
"./value/transform": "./value/transform.js",
|
||||
"./value": "./value/index.js",
|
||||
".": "./typebox.js"
|
||||
},
|
||||
@@ -42,6 +44,8 @@
|
||||
"clean": "hammer task clean",
|
||||
"format": "hammer task format",
|
||||
"start": "hammer task start",
|
||||
"benchmark:compression": "hammer task benchmark_compression",
|
||||
"benchmark:measurement": "hammer task benchmark_measurement",
|
||||
"benchmark": "hammer task benchmark",
|
||||
"build": "hammer task build",
|
||||
"publish": "hammer task publish",
|
||||
@@ -49,12 +53,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sinclair/hammer": "^0.17.1",
|
||||
"@types/chai": "^4.3.3",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/node": "^18.11.9",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^5.1.6"
|
||||
|
||||
@@ -26,11 +26,13 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { TypeSystem } from '../system/index'
|
||||
import { EncodeTransform, DecodeTransform, HasTransform, TransformDecodeCheckError, TransformEncodeCheckError } from '../value/transform'
|
||||
import { IsArray, IsString, IsNumber, IsBigInt } from '../value/guard'
|
||||
import { Errors, ValueErrorIterator } from '../errors/errors'
|
||||
import { TypeSystemPolicy } from '../system/index'
|
||||
import { Deref } from '../value/deref'
|
||||
import { Hash } from '../value/hash'
|
||||
import * as Types from '../typebox'
|
||||
import * as ValueErrors from '../errors/index'
|
||||
import * as ValueHash from '../value/hash'
|
||||
import * as ValueGuard from '../value/guard'
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CheckFunction
|
||||
@@ -40,19 +42,33 @@ export type CheckFunction = (value: unknown) => boolean
|
||||
// TypeCheck
|
||||
// -------------------------------------------------------------------
|
||||
export class TypeCheck<T extends Types.TSchema> {
|
||||
constructor(private readonly schema: T, private readonly references: Types.TSchema[], private readonly checkFunc: CheckFunction, private readonly code: string) {}
|
||||
private readonly hasTransform: boolean
|
||||
constructor(private readonly schema: T, private readonly references: Types.TSchema[], private readonly checkFunc: CheckFunction, private readonly code: string) {
|
||||
this.hasTransform = HasTransform.Has(schema, references)
|
||||
}
|
||||
/** 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): ValueErrors.ValueErrorIterator {
|
||||
return ValueErrors.Errors(this.schema, this.references, value)
|
||||
public Errors(value: unknown): ValueErrorIterator {
|
||||
return Errors(this.schema, this.references, value)
|
||||
}
|
||||
/** Returns true if the value matches the compiled type. */
|
||||
public Check(value: unknown): value is Types.Static<T> {
|
||||
return this.checkFunc(value)
|
||||
}
|
||||
/** Decodes a value or throws if error */
|
||||
public Decode(value: unknown): Types.StaticDecode<T> {
|
||||
if (!this.checkFunc(value)) throw new TransformDecodeCheckError(this.schema, value, this.Errors(value).First()!)
|
||||
return this.hasTransform ? DecodeTransform.Decode(this.schema, this.references, value, (_, __, value) => this.Check(value)) : value
|
||||
}
|
||||
/** Encodes a value or throws if error */
|
||||
public Encode(value: unknown): Types.StaticEncode<T> {
|
||||
const encoded = this.hasTransform ? EncodeTransform.Encode(this.schema, this.references, value, (_, __, value) => this.Check(value)) : value
|
||||
if (!this.checkFunc(encoded)) throw new TransformEncodeCheckError(this.schema, value, this.Errors(value).First()!)
|
||||
return encoded
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// Character
|
||||
@@ -115,26 +131,43 @@ namespace Identifier {
|
||||
// -------------------------------------------------------------------
|
||||
// Errors
|
||||
// -------------------------------------------------------------------
|
||||
export class TypeCompilerUnknownTypeError extends Error {
|
||||
export class TypeCompilerUnknownTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('TypeCompiler: Unknown type')
|
||||
super('Unknown type')
|
||||
}
|
||||
}
|
||||
export class TypeCompilerDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef) {
|
||||
super(`TypeCompiler: Unable to dereference type with $id '${schema.$ref}'`)
|
||||
export class TypeCompilerTypeGuardError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('Preflight validation check failed to guard for the given schema')
|
||||
}
|
||||
}
|
||||
export class TypeCompilerTypeGuardError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('TypeCompiler: Preflight validation check failed to guard for the given schema')
|
||||
// -------------------------------------------------------------------
|
||||
// Policy
|
||||
// -------------------------------------------------------------------
|
||||
export namespace Policy {
|
||||
export function IsExactOptionalProperty(value: string, key: string, expression: string) {
|
||||
return TypeSystemPolicy.ExactOptionalPropertyTypes ? `('${key}' in ${value} ? ${expression} : true)` : `(${MemberExpression.Encode(value, key)} !== undefined ? ${expression} : true)`
|
||||
}
|
||||
export function IsObjectLike(value: string): string {
|
||||
return !TypeSystemPolicy.AllowArrayObject ? `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}))` : `(typeof ${value} === 'object' && ${value} !== null)`
|
||||
}
|
||||
export function IsRecordLike(value: string): string {
|
||||
return !TypeSystemPolicy.AllowArrayObject
|
||||
? `(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))`
|
||||
}
|
||||
export function IsNumberLike(value: string): string {
|
||||
return !TypeSystemPolicy.AllowNaN ? `(typeof ${value} === 'number' && Number.isFinite(${value}))` : `typeof ${value} === 'number'`
|
||||
}
|
||||
export function IsVoidLike(value: string): string {
|
||||
return TypeSystemPolicy.AllowNullVoid ? `(${value} === undefined || ${value} === null)` : `${value} === undefined`
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// TypeCompiler
|
||||
// -------------------------------------------------------------------
|
||||
export type TypeCompilerLanguageOption = 'typescript' | 'javascript'
|
||||
export interface TypeCompilerOptions {
|
||||
export interface TypeCompilerCodegenOptions {
|
||||
language?: TypeCompilerLanguageOption
|
||||
}
|
||||
/** Compiles Types for Runtime Type Checking */
|
||||
@@ -146,26 +179,6 @@ export namespace TypeCompiler {
|
||||
return schema[Types.Kind] === 'Any' || schema[Types.Kind] === 'Unknown'
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// Polices
|
||||
// -------------------------------------------------------------------
|
||||
function IsExactOptionalProperty(value: string, key: string, expression: string) {
|
||||
return TypeSystem.ExactOptionalPropertyTypes ? `('${key}' in ${value} ? ${expression} : true)` : `(${MemberExpression.Encode(value, key)} !== undefined ? ${expression} : true)`
|
||||
}
|
||||
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 IsNumberCheck(value: string): string {
|
||||
return !TypeSystem.AllowNaN ? `(typeof ${value} === 'number' && Number.isFinite(${value}))` : `typeof ${value} === 'number'`
|
||||
}
|
||||
function IsVoidCheck(value: string): string {
|
||||
return TypeSystem.AllowVoidNull ? `(${value} === undefined || ${value} === null)` : `${value} === undefined`
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// Types
|
||||
// -------------------------------------------------------------------
|
||||
function* TAny(schema: Types.TAny, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
@@ -174,15 +187,15 @@ export namespace TypeCompiler {
|
||||
function* TArray(schema: Types.TArray, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `Array.isArray(${value})`
|
||||
const [parameter, accumulator] = [CreateParameter('value', 'any'), CreateParameter('acc', 'number')]
|
||||
if (ValueGuard.IsNumber(schema.minItems)) yield `${value}.length >= ${schema.minItems}`
|
||||
if (ValueGuard.IsNumber(schema.maxItems)) yield `${value}.length <= ${schema.maxItems}`
|
||||
if (IsNumber(schema.maxItems)) yield `${value}.length <= ${schema.maxItems}`
|
||||
if (IsNumber(schema.minItems)) yield `${value}.length >= ${schema.minItems}`
|
||||
const elementExpression = CreateExpression(schema.items, references, 'value')
|
||||
yield `${value}.every((${parameter}) => ${elementExpression})`
|
||||
if (Types.TypeGuard.TSchema(schema.contains) || ValueGuard.IsNumber(schema.minContains) || ValueGuard.IsNumber(schema.maxContains)) {
|
||||
if (Types.TypeGuard.TSchema(schema.contains) || IsNumber(schema.minContains) || IsNumber(schema.maxContains)) {
|
||||
const containsSchema = Types.TypeGuard.TSchema(schema.contains) ? schema.contains : Types.Type.Never()
|
||||
const checkExpression = CreateExpression(containsSchema, references, 'value')
|
||||
const checkMinContains = ValueGuard.IsNumber(schema.minContains) ? [`(count >= ${schema.minContains})`] : []
|
||||
const checkMaxContains = ValueGuard.IsNumber(schema.maxContains) ? [`(count <= ${schema.maxContains})`] : []
|
||||
const checkMinContains = IsNumber(schema.minContains) ? [`(count >= ${schema.minContains})`] : []
|
||||
const checkMaxContains = IsNumber(schema.maxContains) ? [`(count <= ${schema.maxContains})`] : []
|
||||
const checkCount = `const count = ${value}.reduce((${accumulator}, ${parameter}) => ${checkExpression} ? acc + 1 : acc, 0)`
|
||||
const check = [`(count > 0)`, ...checkMinContains, ...checkMaxContains].join(' && ')
|
||||
yield `((${parameter}) => { ${checkCount}; return ${check}})(${value})`
|
||||
@@ -198,11 +211,11 @@ export namespace TypeCompiler {
|
||||
}
|
||||
function* TBigInt(schema: Types.TBigInt, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'bigint')`
|
||||
if (ValueGuard.IsBigInt(schema.multipleOf)) yield `(${value} % BigInt(${schema.multipleOf})) === 0`
|
||||
if (ValueGuard.IsBigInt(schema.exclusiveMinimum)) yield `${value} > BigInt(${schema.exclusiveMinimum})`
|
||||
if (ValueGuard.IsBigInt(schema.exclusiveMaximum)) yield `${value} < BigInt(${schema.exclusiveMaximum})`
|
||||
if (ValueGuard.IsBigInt(schema.minimum)) yield `${value} >= BigInt(${schema.minimum})`
|
||||
if (ValueGuard.IsBigInt(schema.maximum)) yield `${value} <= BigInt(${schema.maximum})`
|
||||
if (IsBigInt(schema.exclusiveMaximum)) yield `${value} < BigInt(${schema.exclusiveMaximum})`
|
||||
if (IsBigInt(schema.exclusiveMinimum)) yield `${value} > BigInt(${schema.exclusiveMinimum})`
|
||||
if (IsBigInt(schema.maximum)) yield `${value} <= BigInt(${schema.maximum})`
|
||||
if (IsBigInt(schema.minimum)) yield `${value} >= BigInt(${schema.minimum})`
|
||||
if (IsBigInt(schema.multipleOf)) yield `(${value} % BigInt(${schema.multipleOf})) === 0`
|
||||
}
|
||||
function* TBoolean(schema: Types.TBoolean, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'boolean')`
|
||||
@@ -212,21 +225,22 @@ export namespace TypeCompiler {
|
||||
}
|
||||
function* TDate(schema: Types.TDate, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(${value} instanceof Date) && Number.isFinite(${value}.getTime())`
|
||||
if (ValueGuard.IsNumber(schema.exclusiveMinimumTimestamp)) yield `${value}.getTime() > ${schema.exclusiveMinimumTimestamp}`
|
||||
if (ValueGuard.IsNumber(schema.exclusiveMaximumTimestamp)) yield `${value}.getTime() < ${schema.exclusiveMaximumTimestamp}`
|
||||
if (ValueGuard.IsNumber(schema.minimumTimestamp)) yield `${value}.getTime() >= ${schema.minimumTimestamp}`
|
||||
if (ValueGuard.IsNumber(schema.maximumTimestamp)) yield `${value}.getTime() <= ${schema.maximumTimestamp}`
|
||||
if (IsNumber(schema.exclusiveMaximumTimestamp)) yield `${value}.getTime() < ${schema.exclusiveMaximumTimestamp}`
|
||||
if (IsNumber(schema.exclusiveMinimumTimestamp)) yield `${value}.getTime() > ${schema.exclusiveMinimumTimestamp}`
|
||||
if (IsNumber(schema.maximumTimestamp)) yield `${value}.getTime() <= ${schema.maximumTimestamp}`
|
||||
if (IsNumber(schema.minimumTimestamp)) yield `${value}.getTime() >= ${schema.minimumTimestamp}`
|
||||
if (IsNumber(schema.multipleOfTimestamp)) yield `(${value}.getTime() % ${schema.multipleOfTimestamp}) === 0`
|
||||
}
|
||||
function* TFunction(schema: Types.TFunction, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'function')`
|
||||
}
|
||||
function* TInteger(schema: Types.TInteger, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'number' && Number.isInteger(${value}))`
|
||||
if (ValueGuard.IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0`
|
||||
if (ValueGuard.IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}`
|
||||
if (ValueGuard.IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
|
||||
if (ValueGuard.IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}`
|
||||
if (ValueGuard.IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}`
|
||||
if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
|
||||
if (IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}`
|
||||
if (IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}`
|
||||
if (IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}`
|
||||
if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0`
|
||||
}
|
||||
function* TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const check1 = schema.allOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value)).join(' && ')
|
||||
@@ -263,17 +277,17 @@ export namespace TypeCompiler {
|
||||
yield `(${value} === null)`
|
||||
}
|
||||
function* TNumber(schema: Types.TNumber, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield IsNumberCheck(value)
|
||||
if (ValueGuard.IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0`
|
||||
if (ValueGuard.IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}`
|
||||
if (ValueGuard.IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
|
||||
if (ValueGuard.IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}`
|
||||
if (ValueGuard.IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}`
|
||||
yield Policy.IsNumberLike(value)
|
||||
if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
|
||||
if (IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}`
|
||||
if (IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}`
|
||||
if (IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}`
|
||||
if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0`
|
||||
}
|
||||
function* TObject(schema: Types.TObject, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield IsObjectCheck(value)
|
||||
if (ValueGuard.IsNumber(schema.minProperties)) yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`
|
||||
if (ValueGuard.IsNumber(schema.maxProperties)) yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`
|
||||
yield Policy.IsObjectLike(value)
|
||||
if (IsNumber(schema.minProperties)) yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`
|
||||
if (IsNumber(schema.maxProperties)) yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`
|
||||
const knownKeys = Object.getOwnPropertyNames(schema.properties)
|
||||
for (const knownKey of knownKeys) {
|
||||
const memberExpression = MemberExpression.Encode(value, knownKey)
|
||||
@@ -283,7 +297,7 @@ export namespace TypeCompiler {
|
||||
if (Types.ExtendsUndefined.Check(property) || IsAnyOrUnknown(property)) yield `('${knownKey}' in ${value})`
|
||||
} else {
|
||||
const expression = CreateExpression(property, references, memberExpression)
|
||||
yield IsExactOptionalProperty(value, knownKey, expression)
|
||||
yield Policy.IsExactOptionalProperty(value, knownKey, expression)
|
||||
}
|
||||
}
|
||||
if (schema.additionalProperties === false) {
|
||||
@@ -304,9 +318,9 @@ export namespace TypeCompiler {
|
||||
yield `(typeof value === 'object' && typeof ${value}.then === 'function')`
|
||||
}
|
||||
function* TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield IsRecordCheck(value)
|
||||
if (ValueGuard.IsNumber(schema.minProperties)) yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`
|
||||
if (ValueGuard.IsNumber(schema.maxProperties)) yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`
|
||||
yield Policy.IsRecordLike(value)
|
||||
if (IsNumber(schema.minProperties)) yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`
|
||||
if (IsNumber(schema.maxProperties)) yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`
|
||||
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
|
||||
const variable = CreateVariable(`new RegExp(/${patternKey}/)`)
|
||||
const check1 = CreateExpression(patternSchema, references, 'value')
|
||||
@@ -315,9 +329,7 @@ export namespace TypeCompiler {
|
||||
yield `(Object.entries(${value}).every(([key, value]) => ${expression}))`
|
||||
}
|
||||
function* TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new TypeCompilerDereferenceError(schema)
|
||||
const target = references[index]
|
||||
const target = Deref(schema, references)
|
||||
// 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.
|
||||
if (state.functions.has(schema.$ref)) return yield `${CreateFunctionName(schema.$ref)}(${value})`
|
||||
@@ -325,8 +337,8 @@ export namespace TypeCompiler {
|
||||
}
|
||||
function* TString(schema: Types.TString, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'string')`
|
||||
if (ValueGuard.IsNumber(schema.minLength)) yield `${value}.length >= ${schema.minLength}`
|
||||
if (ValueGuard.IsNumber(schema.maxLength)) yield `${value}.length <= ${schema.maxLength}`
|
||||
if (IsNumber(schema.maxLength)) yield `${value}.length <= ${schema.maxLength}`
|
||||
if (IsNumber(schema.minLength)) yield `${value}.length >= ${schema.minLength}`
|
||||
if (schema.pattern !== undefined) {
|
||||
const variable = CreateVariable(`${new RegExp(schema.pattern)};`)
|
||||
yield `${variable}.test(${value})`
|
||||
@@ -344,8 +356,8 @@ export namespace TypeCompiler {
|
||||
yield `${variable}.test(${value})`
|
||||
}
|
||||
function* TThis(schema: Types.TThis, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const func = CreateFunctionName(schema.$ref)
|
||||
yield `${func}(${value})`
|
||||
// Note: This types are assured to be hoisted prior to this call. Just yield the function.
|
||||
yield `${CreateFunctionName(schema.$ref)}(${value})`
|
||||
}
|
||||
function* TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `Array.isArray(${value})`
|
||||
@@ -365,14 +377,14 @@ export namespace TypeCompiler {
|
||||
}
|
||||
function* TUint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `${value} instanceof Uint8Array`
|
||||
if (ValueGuard.IsNumber(schema.maxByteLength)) yield `(${value}.length <= ${schema.maxByteLength})`
|
||||
if (ValueGuard.IsNumber(schema.minByteLength)) yield `(${value}.length >= ${schema.minByteLength})`
|
||||
if (IsNumber(schema.maxByteLength)) yield `(${value}.length <= ${schema.maxByteLength})`
|
||||
if (IsNumber(schema.minByteLength)) yield `(${value}.length >= ${schema.minByteLength})`
|
||||
}
|
||||
function* TUnknown(schema: Types.TUnknown, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield 'true'
|
||||
}
|
||||
function* TVoid(schema: Types.TVoid, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield IsVoidCheck(value)
|
||||
yield Policy.IsVoidLike(value)
|
||||
}
|
||||
function* TKind(schema: Types.TSchema, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const instance = state.instances.size
|
||||
@@ -380,12 +392,12 @@ export namespace TypeCompiler {
|
||||
yield `kind('${schema[Types.Kind]}', ${instance}, ${value})`
|
||||
}
|
||||
function* Visit<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: string, useHoisting: boolean = true): IterableIterator<string> {
|
||||
const references_ = ValueGuard.IsString(schema.$id) ? [...references, schema] : references
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
// ----------------------------------------------------------------------------------
|
||||
// Hoisting
|
||||
// ----------------------------------------------------------------------------------
|
||||
if (useHoisting && ValueGuard.IsString(schema.$id)) {
|
||||
if (useHoisting && IsString(schema.$id)) {
|
||||
const functionName = CreateFunctionName(schema.$id)
|
||||
if (state.functions.has(functionName)) {
|
||||
return yield `${functionName}(${value})`
|
||||
@@ -395,9 +407,6 @@ export namespace TypeCompiler {
|
||||
return yield `${functionName}(${value})`
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------
|
||||
// Types
|
||||
// ----------------------------------------------------------------------------------
|
||||
switch (schema_[Types.Kind]) {
|
||||
case 'Any':
|
||||
return yield* TAny(schema_, references_, value)
|
||||
@@ -505,29 +514,29 @@ export namespace TypeCompiler {
|
||||
// -------------------------------------------------------------------
|
||||
// Compile
|
||||
// -------------------------------------------------------------------
|
||||
function Build<T extends Types.TSchema>(schema: T, references: Types.TSchema[], options: TypeCompilerOptions): string {
|
||||
function Build<T extends Types.TSchema>(schema: T, references: Types.TSchema[], options: TypeCompilerCodegenOptions): string {
|
||||
const functionCode = CreateFunction('check', schema, references, 'value') // will populate functions and variables
|
||||
const parameter = CreateParameter('value', 'any')
|
||||
const returns = CreateReturns('boolean')
|
||||
const functions = [...state.functions.values()]
|
||||
const variables = [...state.variables.values()]
|
||||
// prettier-ignore
|
||||
const checkFunction = ValueGuard.IsString(schema.$id) // ensure top level schemas with $id's are hoisted
|
||||
const checkFunction = IsString(schema.$id) // ensure top level schemas with $id's are hoisted
|
||||
? `return function check(${parameter})${returns} {\n return ${CreateFunctionName(schema.$id)}(value)\n}`
|
||||
: `return ${functionCode}`
|
||||
return [...variables, ...functions, checkFunction].join('\n')
|
||||
}
|
||||
/** Returns the generated assertion code used to validate this type. */
|
||||
export function Code<T extends Types.TSchema>(schema: T, references: Types.TSchema[], options?: TypeCompilerOptions): string
|
||||
/** Returns the generated assertion code used to validate this type. */
|
||||
export function Code<T extends Types.TSchema>(schema: T, options?: TypeCompilerOptions): string
|
||||
/** Returns the generated assertion code used to validate this type. */
|
||||
/** Generates the code used to assert this type and returns it as a string */
|
||||
export function Code<T extends Types.TSchema>(schema: T, references: Types.TSchema[], options?: TypeCompilerCodegenOptions): string
|
||||
/** Generates the code used to assert this type and returns it as a string */
|
||||
export function Code<T extends Types.TSchema>(schema: T, options?: TypeCompilerCodegenOptions): string
|
||||
/** Generates the code used to assert this type and returns it as a string */
|
||||
export function Code(...args: any[]) {
|
||||
const defaults = { language: 'javascript' }
|
||||
// prettier-ignore
|
||||
const [schema, references, options] = (
|
||||
args.length === 2 && ValueGuard.IsArray(args[1]) ? [args[0], args[1], defaults] :
|
||||
args.length === 2 && !ValueGuard.IsArray(args[1]) ? [args[0], [], args[1]] :
|
||||
args.length === 2 && IsArray(args[1]) ? [args[0], args[1], defaults] :
|
||||
args.length === 2 && !IsArray(args[1]) ? [args[0], [], args[1]] :
|
||||
args.length === 3 ? [args[0], args[1], args[2]] :
|
||||
args.length === 1 ? [args[0], [], defaults] :
|
||||
[null, [], defaults]
|
||||
@@ -541,15 +550,15 @@ export namespace TypeCompiler {
|
||||
for (const schema of references) if (!Types.TypeGuard.TSchema(schema)) throw new TypeCompilerTypeGuardError(schema)
|
||||
return Build(schema, references, options)
|
||||
}
|
||||
/** Compiles the given type for runtime type checking. This compiler only accepts known TypeBox types non-inclusive of unsafe types. */
|
||||
/** Compiles a TypeBox type for optimal runtime type checking. Types must be valid TypeBox types of TSchema */
|
||||
export function Compile<T extends Types.TSchema>(schema: T, references: Types.TSchema[] = []): TypeCheck<T> {
|
||||
const generatedCode = Code(schema, references, { language: 'javascript' })
|
||||
const compiledFunction = globalThis.Function('kind', 'format', 'hash', generatedCode)
|
||||
const instances = new Map(state.instances)
|
||||
function typeRegistryFunction(kind: string, instance: number, value: unknown) {
|
||||
if (!Types.TypeRegistry.Has(kind) || !instances.has(instance)) return false
|
||||
const schema = instances.get(instance)
|
||||
const checkFunc = Types.TypeRegistry.Get(kind)!
|
||||
const schema = instances.get(instance)!
|
||||
return checkFunc(schema, value)
|
||||
}
|
||||
function formatRegistryFunction(format: string, value: string) {
|
||||
@@ -557,10 +566,10 @@ export namespace TypeCompiler {
|
||||
const checkFunc = Types.FormatRegistry.Get(format)!
|
||||
return checkFunc(value)
|
||||
}
|
||||
function valueHashFunction(value: unknown) {
|
||||
return ValueHash.Hash(value)
|
||||
function hashFunction(value: unknown) {
|
||||
return Hash(value)
|
||||
}
|
||||
const checkFunction = compiledFunction(typeRegistryFunction, formatRegistryFunction, valueHashFunction)
|
||||
const checkFunction = compiledFunction(typeRegistryFunction, formatRegistryFunction, hashFunction)
|
||||
return new TypeCheck(schema, references, checkFunction, generatedCode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export { ValueError, ValueErrorType } from '../errors/index'
|
||||
export { ValueError, ValueErrorType, ValueErrorIterator } from '../errors/index'
|
||||
export * from './compiler'
|
||||
|
||||
@@ -26,79 +26,79 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { TypeSystem } from '../system/index'
|
||||
import { IsArray, IsUint8Array, IsDate, IsPromise, IsFunction, IsAsyncIterator, IsIterator, IsBoolean, IsNumber, IsBigInt, IsString, IsSymbol, IsInteger, IsNull, IsUndefined } from '../value/guard'
|
||||
import { TypeSystemPolicy, TypeSystemErrorFunction } from '../system/system'
|
||||
import { Deref } from '../value/deref'
|
||||
import { Hash } from '../value/hash'
|
||||
import * as Types from '../typebox'
|
||||
import * as ValueHash from '../value/hash'
|
||||
import * as ValueGuard from '../value/guard'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// ValueErrorType
|
||||
// --------------------------------------------------------------------------
|
||||
export enum ValueErrorType {
|
||||
Array,
|
||||
ArrayMinItems,
|
||||
ArrayMaxItems,
|
||||
ArrayContains,
|
||||
ArrayMinContains,
|
||||
ArrayMaxContains,
|
||||
ArrayMaxItems,
|
||||
ArrayMinContains,
|
||||
ArrayMinItems,
|
||||
ArrayUniqueItems,
|
||||
Array,
|
||||
AsyncIterator,
|
||||
BigInt,
|
||||
BigIntMultipleOf,
|
||||
BigIntExclusiveMinimum,
|
||||
BigIntExclusiveMaximum,
|
||||
BigIntMinimum,
|
||||
BigIntExclusiveMinimum,
|
||||
BigIntMaximum,
|
||||
BigIntMinimum,
|
||||
BigIntMultipleOf,
|
||||
BigInt,
|
||||
Boolean,
|
||||
Date,
|
||||
DateExclusiveMinimumTimestamp,
|
||||
DateExclusiveMaximumTimestamp,
|
||||
DateMinimumTimestamp,
|
||||
DateExclusiveMinimumTimestamp,
|
||||
DateMaximumTimestamp,
|
||||
DateMinimumTimestamp,
|
||||
DateMultipleOfTimestamp,
|
||||
Date,
|
||||
Function,
|
||||
Integer,
|
||||
IntegerMultipleOf,
|
||||
IntegerExclusiveMinimum,
|
||||
IntegerExclusiveMaximum,
|
||||
IntegerMinimum,
|
||||
IntegerExclusiveMinimum,
|
||||
IntegerMaximum,
|
||||
Intersect,
|
||||
IntegerMinimum,
|
||||
IntegerMultipleOf,
|
||||
Integer,
|
||||
IntersectUnevaluatedProperties,
|
||||
Intersect,
|
||||
Iterator,
|
||||
Kind,
|
||||
Literal,
|
||||
Never,
|
||||
Not,
|
||||
Null,
|
||||
Number,
|
||||
NumberMultipleOf,
|
||||
NumberExclusiveMinimum,
|
||||
NumberExclusiveMaximum,
|
||||
NumberMinimum,
|
||||
NumberExclusiveMinimum,
|
||||
NumberMaximum,
|
||||
Object,
|
||||
ObjectMinProperties,
|
||||
ObjectMaxProperties,
|
||||
NumberMinimum,
|
||||
NumberMultipleOf,
|
||||
Number,
|
||||
ObjectAdditionalProperties,
|
||||
ObjectRequiredProperties,
|
||||
ObjectMaxProperties,
|
||||
ObjectMinProperties,
|
||||
ObjectRequiredProperty,
|
||||
Object,
|
||||
Promise,
|
||||
RecordKeyNumeric,
|
||||
RecordKeyString,
|
||||
String,
|
||||
StringMinLength,
|
||||
StringMaxLength,
|
||||
StringPattern,
|
||||
StringFormatUnknown,
|
||||
StringFormat,
|
||||
StringMaxLength,
|
||||
StringMinLength,
|
||||
StringPattern,
|
||||
String,
|
||||
Symbol,
|
||||
TupleZeroLength,
|
||||
TupleLength,
|
||||
Tuple,
|
||||
Uint8ArrayMaxByteLength,
|
||||
Uint8ArrayMinByteLength,
|
||||
Uint8Array,
|
||||
Undefined,
|
||||
Union,
|
||||
Uint8Array,
|
||||
Uint8ArrayMinByteLength,
|
||||
Uint8ArrayMaxByteLength,
|
||||
Void,
|
||||
Kind,
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// ValueError
|
||||
@@ -111,6 +111,20 @@ export interface ValueError {
|
||||
message: string
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// ValueErrors
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueErrorsUnknownTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('Unknown type')
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Guards
|
||||
// --------------------------------------------------------------------------
|
||||
function IsDefined<T>(value: unknown): value is T {
|
||||
return value !== undefined
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// ValueErrorIterator
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueErrorIterator {
|
||||
@@ -125,175 +139,127 @@ export class ValueErrorIterator {
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// ValueErrors
|
||||
// Create
|
||||
// --------------------------------------------------------------------------
|
||||
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.TThis) {
|
||||
super(`ValueErrors: Unable to dereference type with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Guards
|
||||
// --------------------------------------------------------------------------
|
||||
function IsDefined<T>(value: unknown): value is T {
|
||||
return value !== undefined
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Policies
|
||||
// --------------------------------------------------------------------------
|
||||
function IsExactOptionalProperty(value: Record<keyof any, unknown>, key: string) {
|
||||
return TypeSystem.ExactOptionalPropertyTypes ? key in value : value[key] !== undefined
|
||||
}
|
||||
function IsObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
const isObject = ValueGuard.IsObject(value)
|
||||
return TypeSystem.AllowArrayObjects ? isObject : isObject && !ValueGuard.IsArray(value)
|
||||
}
|
||||
function IsRecordObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
return IsObject(value) && !(value instanceof Date) && !(value instanceof Uint8Array)
|
||||
}
|
||||
function IsNumber(value: unknown): value is number {
|
||||
const isNumber = ValueGuard.IsNumber(value)
|
||||
return TypeSystem.AllowNaN ? isNumber : isNumber && Number.isFinite(value)
|
||||
}
|
||||
function IsVoid(value: unknown): value is void {
|
||||
const isUndefined = ValueGuard.IsUndefined(value)
|
||||
return TypeSystem.AllowVoidNull ? isUndefined || value === null : isUndefined
|
||||
function Create(type: ValueErrorType, schema: Types.TSchema, path: string, value: unknown): ValueError {
|
||||
return { type, schema, path, value, message: TypeSystemErrorFunction.Get()(schema, type) }
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Types
|
||||
// --------------------------------------------------------------------------
|
||||
function* TAny(schema: Types.TAny, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {}
|
||||
function* TArray(schema: Types.TArray, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsArray(value)) {
|
||||
return yield { type: ValueErrorType.Array, schema, path, value, message: `Expected array` }
|
||||
if (!IsArray(value)) {
|
||||
return yield Create(ValueErrorType.Array, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(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}` }
|
||||
yield Create(ValueErrorType.ArrayMinItems, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(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}` }
|
||||
yield Create(ValueErrorType.ArrayMaxItems, schema, path, value)
|
||||
}
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
yield* Visit(schema.items, references, `${path}/${i}`, value[i])
|
||||
}
|
||||
// prettier-ignore
|
||||
if (schema.uniqueItems === true && !((function () { const set = new Set(); for (const element of value) { const hashed = ValueHash.Hash(element); if (set.has(hashed)) { return false } else { set.add(hashed) } } return true })())) {
|
||||
yield { type: ValueErrorType.ArrayUniqueItems, schema, path, value, message: `Expected array elements to be unique` }
|
||||
if (schema.uniqueItems === true && !((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 })())) {
|
||||
yield Create(ValueErrorType.ArrayUniqueItems, schema, path, value)
|
||||
}
|
||||
// contains
|
||||
if (!(IsDefined(schema.contains) || IsNumber(schema.minContains) || IsNumber(schema.maxContains))) {
|
||||
if (!(IsDefined(schema.contains) || IsDefined(schema.minContains) || IsDefined(schema.maxContains))) {
|
||||
return
|
||||
}
|
||||
const containsSchema = IsDefined<Types.TSchema>(schema.contains) ? schema.contains : Types.Type.Never()
|
||||
const containsCount = value.reduce((acc: number, value, index) => (Visit(containsSchema, references, `${path}${index}`, value).next().done === true ? acc + 1 : acc), 0)
|
||||
if (containsCount === 0) {
|
||||
yield { type: ValueErrorType.ArrayContains, schema, path, value, message: `Expected array to contain at least one matching type` }
|
||||
yield Create(ValueErrorType.ArrayContains, schema, path, value)
|
||||
}
|
||||
if (ValueGuard.IsNumber(schema.minContains) && containsCount < schema.minContains) {
|
||||
yield { type: ValueErrorType.ArrayMinContains, schema, path, value, message: `Expected array to contain at least ${schema.minContains} matching types` }
|
||||
if (IsNumber(schema.minContains) && containsCount < schema.minContains) {
|
||||
yield Create(ValueErrorType.ArrayMinContains, schema, path, value)
|
||||
}
|
||||
if (ValueGuard.IsNumber(schema.maxContains) && containsCount > schema.maxContains) {
|
||||
yield { type: ValueErrorType.ArrayMaxContains, schema, path, value, message: `Expected array to contain no more than ${schema.maxContains} matching types` }
|
||||
if (IsNumber(schema.maxContains) && containsCount > schema.maxContains) {
|
||||
yield Create(ValueErrorType.ArrayMaxContains, schema, path, value)
|
||||
}
|
||||
}
|
||||
function* TAsyncIterator(schema: Types.TAsyncIterator, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsAsyncIterator(value)) {
|
||||
yield { type: ValueErrorType.AsyncIterator, schema, path, value, message: `Expected value to be an async iterator` }
|
||||
}
|
||||
if (!IsAsyncIterator(value)) yield Create(ValueErrorType.AsyncIterator, schema, path, value)
|
||||
}
|
||||
function* TBigInt(schema: Types.TBigInt, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsBigInt(value)) {
|
||||
return yield { type: ValueErrorType.BigInt, schema, path, value, message: `Expected bigint` }
|
||||
}
|
||||
if (IsDefined<bigint>(schema.multipleOf) && !(value % schema.multipleOf === BigInt(0))) {
|
||||
yield { type: ValueErrorType.BigIntMultipleOf, schema, path, value, message: `Expected bigint to be a multiple of ${schema.multipleOf}` }
|
||||
if (!IsBigInt(value)) return yield Create(ValueErrorType.BigInt, schema, path, value)
|
||||
if (IsDefined<bigint>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield Create(ValueErrorType.BigIntExclusiveMaximum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<bigint>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
yield { type: ValueErrorType.BigIntExclusiveMinimum, schema, path, value, message: `Expected bigint to be greater than ${schema.exclusiveMinimum}` }
|
||||
}
|
||||
if (IsDefined<bigint>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield { type: ValueErrorType.BigIntExclusiveMaximum, schema, path, value, message: `Expected bigint to be less than ${schema.exclusiveMaximum}` }
|
||||
}
|
||||
if (IsDefined<bigint>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield { type: ValueErrorType.BigIntMinimum, schema, path, value, message: `Expected bigint to be greater or equal to ${schema.minimum}` }
|
||||
yield Create(ValueErrorType.BigIntExclusiveMinimum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<bigint>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
yield { type: ValueErrorType.BigIntMaximum, schema, path, value, message: `Expected bigint to be less or equal to ${schema.maximum}` }
|
||||
yield Create(ValueErrorType.BigIntMaximum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<bigint>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield Create(ValueErrorType.BigIntMinimum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<bigint>(schema.multipleOf) && !(value % schema.multipleOf === BigInt(0))) {
|
||||
yield Create(ValueErrorType.BigIntMultipleOf, schema, path, value)
|
||||
}
|
||||
}
|
||||
function* TBoolean(schema: Types.TBoolean, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsBoolean(value)) {
|
||||
return yield { type: ValueErrorType.Boolean, schema, path, value, message: `Expected boolean` }
|
||||
}
|
||||
if (!IsBoolean(value)) yield Create(ValueErrorType.Boolean, schema, path, value)
|
||||
}
|
||||
function* TConstructor(schema: Types.TConstructor, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
yield* Visit(schema.returns, references, path, value.prototype)
|
||||
}
|
||||
function* TDate(schema: Types.TDate, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsDate(value)) {
|
||||
return yield { type: ValueErrorType.Date, schema, path, value, message: `Expected Date object` }
|
||||
}
|
||||
if (!isFinite(value.getTime())) {
|
||||
return yield { type: ValueErrorType.Date, schema, path, value, message: `Invalid Date` }
|
||||
if (!IsDate(value)) return yield Create(ValueErrorType.Date, schema, path, value)
|
||||
if (IsDefined<number>(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
|
||||
yield Create(ValueErrorType.DateExclusiveMaximumTimestamp, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) {
|
||||
yield { type: ValueErrorType.DateExclusiveMinimumTimestamp, schema, path, value, message: `Expected Date timestamp to be greater than ${schema.exclusiveMinimum}` }
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
|
||||
yield { type: ValueErrorType.DateExclusiveMaximumTimestamp, schema, path, value, message: `Expected Date timestamp to be less than ${schema.exclusiveMaximum}` }
|
||||
}
|
||||
if (IsDefined<number>(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}` }
|
||||
yield Create(ValueErrorType.DateExclusiveMinimumTimestamp, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(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}` }
|
||||
yield Create(ValueErrorType.DateMaximumTimestamp, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) {
|
||||
yield Create(ValueErrorType.DateMinimumTimestamp, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOfTimestamp) && !(value.getTime() % schema.multipleOfTimestamp === 0)) {
|
||||
yield Create(ValueErrorType.DateMultipleOfTimestamp, schema, path, value)
|
||||
}
|
||||
}
|
||||
function* TFunction(schema: Types.TFunction, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsFunction(value)) {
|
||||
return yield { type: ValueErrorType.Function, schema, path, value, message: `Expected function` }
|
||||
}
|
||||
if (!IsFunction(value)) yield Create(ValueErrorType.Function, schema, path, value)
|
||||
}
|
||||
function* TInteger(schema: Types.TInteger, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsInteger(value)) {
|
||||
return yield { type: ValueErrorType.Integer, schema, path, value, message: `Expected integer` }
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
yield { type: ValueErrorType.IntegerMultipleOf, schema, path, value, message: `Expected integer to be a multiple of ${schema.multipleOf}` }
|
||||
if (!IsInteger(value)) return yield Create(ValueErrorType.Integer, schema, path, value)
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield Create(ValueErrorType.IntegerExclusiveMaximum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
yield { type: ValueErrorType.IntegerExclusiveMinimum, schema, path, value, message: `Expected integer to be greater than ${schema.exclusiveMinimum}` }
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield { type: ValueErrorType.IntegerExclusiveMaximum, schema, path, value, message: `Expected integer to be less than ${schema.exclusiveMaximum}` }
|
||||
}
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield { type: ValueErrorType.IntegerMinimum, schema, path, value, message: `Expected integer to be greater or equal to ${schema.minimum}` }
|
||||
yield Create(ValueErrorType.IntegerExclusiveMinimum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
yield { type: ValueErrorType.IntegerMaximum, schema, path, value, message: `Expected integer to be less or equal to ${schema.maximum}` }
|
||||
yield Create(ValueErrorType.IntegerMaximum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield Create(ValueErrorType.IntegerMinimum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
yield Create(ValueErrorType.IntegerMultipleOf, schema, path, value)
|
||||
}
|
||||
}
|
||||
function* TIntersect(schema: Types.TIntersect, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
for (const inner of schema.allOf) {
|
||||
const next = Visit(inner, references, path, value).next()
|
||||
if (!next.done) {
|
||||
yield Create(ValueErrorType.Intersect, schema, path, value)
|
||||
yield next.value
|
||||
yield { type: ValueErrorType.Intersect, schema, path, value, message: `Expected all sub schemas to be valid` }
|
||||
return
|
||||
}
|
||||
}
|
||||
if (schema.unevaluatedProperties === false) {
|
||||
const keyCheck = new RegExp(Types.KeyResolver.ResolvePattern(schema))
|
||||
for (const valueKey of Object.getOwnPropertyNames(value)) {
|
||||
if (!keyCheck.test(valueKey)) {
|
||||
yield { type: ValueErrorType.IntersectUnevaluatedProperties, schema, path: `${path}/${valueKey}`, value, message: `Unexpected property` }
|
||||
yield Create(ValueErrorType.IntersectUnevaluatedProperties, schema, `${path}/${valueKey}`, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,93 +268,63 @@ function* TIntersect(schema: Types.TIntersect, references: Types.TSchema[], path
|
||||
for (const valueKey of Object.getOwnPropertyNames(value)) {
|
||||
if (!keyCheck.test(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
|
||||
}
|
||||
if (!next.done) yield next.value // yield interior
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function* TIterator(schema: Types.TIterator, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(IsObject(value) && Symbol.iterator in value)) {
|
||||
yield { type: ValueErrorType.Iterator, schema, path, value, message: `Expected value to be an iterator` }
|
||||
}
|
||||
if (!IsIterator(value)) yield Create(ValueErrorType.Iterator, schema, path, value)
|
||||
}
|
||||
function* TLiteral(schema: Types.TLiteral, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
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}` }
|
||||
}
|
||||
if (!(value === schema.const)) yield Create(ValueErrorType.Literal, schema, path, value)
|
||||
}
|
||||
function* TNever(schema: Types.TNever, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
yield { type: ValueErrorType.Never, schema, path, value, message: `Value cannot be validated` }
|
||||
yield Create(ValueErrorType.Never, schema, path, value)
|
||||
}
|
||||
function* TNot(schema: Types.TNot, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (Visit(schema.not, references, path, value).next().done === true) {
|
||||
yield { type: ValueErrorType.Not, schema, path, value, message: `Value should not validate` }
|
||||
}
|
||||
if (Visit(schema.not, references, path, value).next().done === true) yield Create(ValueErrorType.Not, schema, path, value)
|
||||
}
|
||||
function* TNull(schema: Types.TNull, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsNull(value)) {
|
||||
return yield { type: ValueErrorType.Null, schema, path, value, message: `Expected null` }
|
||||
}
|
||||
if (!IsNull(value)) yield Create(ValueErrorType.Null, schema, path, value)
|
||||
}
|
||||
function* TNumber(schema: Types.TNumber, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!IsNumber(value)) {
|
||||
return yield { type: ValueErrorType.Number, schema, path, value, message: `Expected number` }
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
yield { type: ValueErrorType.NumberMultipleOf, schema, path, value, message: `Expected number to be a multiple of ${schema.multipleOf}` }
|
||||
if (!TypeSystemPolicy.IsNumberLike(value)) return yield Create(ValueErrorType.Number, schema, path, value)
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield Create(ValueErrorType.NumberExclusiveMaximum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
yield { type: ValueErrorType.NumberExclusiveMinimum, schema, path, value, message: `Expected number to be greater than ${schema.exclusiveMinimum}` }
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield { type: ValueErrorType.NumberExclusiveMaximum, schema, path, value, message: `Expected number to be less than ${schema.exclusiveMaximum}` }
|
||||
}
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield { type: ValueErrorType.NumberMinimum, schema, path, value, message: `Expected number to be greater or equal to ${schema.minimum}` }
|
||||
yield Create(ValueErrorType.NumberExclusiveMinimum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
yield { type: ValueErrorType.NumberMaximum, schema, path, value, message: `Expected number to be less or equal to ${schema.maximum}` }
|
||||
yield Create(ValueErrorType.NumberMaximum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield Create(ValueErrorType.NumberMinimum, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
yield Create(ValueErrorType.NumberMultipleOf, schema, path, value)
|
||||
}
|
||||
}
|
||||
function* TObject(schema: Types.TObject, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!IsObject(value)) {
|
||||
return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` }
|
||||
}
|
||||
if (!TypeSystemPolicy.IsObjectLike(value)) return yield Create(ValueErrorType.Object, schema, path, value)
|
||||
if (IsDefined<number>(schema.minProperties) && !(Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
|
||||
yield { type: ValueErrorType.ObjectMinProperties, schema, path, value, message: `Expected object to have at least ${schema.minProperties} properties` }
|
||||
yield Create(ValueErrorType.ObjectMinProperties, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.maxProperties) && !(Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
|
||||
yield { type: ValueErrorType.ObjectMaxProperties, schema, path, value, message: `Expected object to have no more than ${schema.maxProperties} properties` }
|
||||
yield Create(ValueErrorType.ObjectMaxProperties, schema, path, value)
|
||||
}
|
||||
const requiredKeys = Array.isArray(schema.required) ? schema.required : ([] as string[])
|
||||
const knownKeys = Object.getOwnPropertyNames(schema.properties)
|
||||
const unknownKeys = 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 (IsExactOptionalProperty(value, knownKey)) {
|
||||
yield* Visit(property, references, `${path}/${knownKey}`, value[knownKey])
|
||||
}
|
||||
}
|
||||
}
|
||||
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` }
|
||||
yield Create(ValueErrorType.ObjectRequiredProperty, schema.properties[requiredKey], `${path}/${requiredKey}`, undefined)
|
||||
}
|
||||
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` }
|
||||
yield Create(ValueErrorType.ObjectAdditionalProperties, schema, `${path}/${valueKey}`, value[valueKey])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,101 +334,96 @@ function* TObject(schema: Types.TObject, references: Types.TSchema[], path: stri
|
||||
yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${valueKey}`, value[valueKey])
|
||||
}
|
||||
}
|
||||
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 Create(ValueErrorType.ObjectRequiredProperty, property, `${path}/${knownKey}`, undefined)
|
||||
}
|
||||
} else {
|
||||
if (TypeSystemPolicy.IsExactOptionalProperty(value, knownKey)) {
|
||||
yield* Visit(property, references, `${path}/${knownKey}`, value[knownKey])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function* TPromise(schema: Types.TPromise, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsPromise(value)) {
|
||||
yield { type: ValueErrorType.Promise, schema, path, value, message: `Expected Promise` }
|
||||
}
|
||||
if (!IsPromise(value)) yield Create(ValueErrorType.Promise, schema, path, value)
|
||||
}
|
||||
function* TRecord(schema: Types.TRecord, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!IsRecordObject(value)) {
|
||||
return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected record object` }
|
||||
}
|
||||
if (!TypeSystemPolicy.IsRecordLike(value)) return yield Create(ValueErrorType.Object, schema, path, value)
|
||||
if (IsDefined<number>(schema.minProperties) && !(Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
|
||||
yield { type: ValueErrorType.ObjectMinProperties, schema, path, value, message: `Expected object to have at least ${schema.minProperties} properties` }
|
||||
yield Create(ValueErrorType.ObjectMinProperties, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.maxProperties) && !(Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
|
||||
yield { type: ValueErrorType.ObjectMaxProperties, schema, path, value, message: `Expected object to have no more than ${schema.maxProperties} properties` }
|
||||
yield Create(ValueErrorType.ObjectMaxProperties, schema, path, value)
|
||||
}
|
||||
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
|
||||
const regex = new RegExp(patternKey)
|
||||
for (const [propertyKey, propertyValue] of Object.entries(value)) {
|
||||
if (regex.test(propertyKey)) {
|
||||
yield* Visit(patternSchema, references, `${path}/${propertyKey}`, propertyValue)
|
||||
continue
|
||||
if (regex.test(propertyKey)) yield* Visit(patternSchema, references, `${path}/${propertyKey}`, propertyValue)
|
||||
}
|
||||
if (typeof schema.additionalProperties === 'object') {
|
||||
for (const [propertyKey, propertyValue] of Object.entries(value)) {
|
||||
if (!regex.test(propertyKey)) yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${propertyKey}`, propertyValue)
|
||||
}
|
||||
if (typeof schema.additionalProperties === 'object') {
|
||||
yield* Visit(schema.additionalProperties, references, `${path}/${propertyKey}`, propertyValue)
|
||||
}
|
||||
if (schema.additionalProperties === false) {
|
||||
const propertyPath = `${path}/${propertyKey}`
|
||||
const message = `Unexpected property '${propertyPath}'`
|
||||
return yield { type: ValueErrorType.ObjectAdditionalProperties, schema, path: propertyPath, value: propertyValue, message }
|
||||
}
|
||||
if (schema.additionalProperties === false) {
|
||||
for (const [propertyKey, propertyValue] of Object.entries(value)) {
|
||||
if (regex.test(propertyKey)) continue
|
||||
return yield Create(ValueErrorType.ObjectAdditionalProperties, schema, `${path}/${propertyKey}`, propertyValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
function* TRef(schema: Types.TRef<any>, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
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)
|
||||
yield* Visit(Deref(schema, references), references, path, value)
|
||||
}
|
||||
function* TString(schema: Types.TString, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsString(value)) {
|
||||
return yield { type: ValueErrorType.String, schema, path, value, message: 'Expected string' }
|
||||
}
|
||||
if (!IsString(value)) return yield Create(ValueErrorType.String, schema, path, value)
|
||||
if (IsDefined<number>(schema.minLength) && !(value.length >= schema.minLength)) {
|
||||
yield { type: ValueErrorType.StringMinLength, schema, path, value, message: `Expected string length greater or equal to ${schema.minLength}` }
|
||||
yield Create(ValueErrorType.StringMinLength, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(schema.maxLength) && !(value.length <= schema.maxLength)) {
|
||||
yield { type: ValueErrorType.StringMaxLength, schema, path, value, message: `Expected string length less or equal to ${schema.maxLength}` }
|
||||
yield Create(ValueErrorType.StringMaxLength, schema, path, value)
|
||||
}
|
||||
if (ValueGuard.IsString(schema.pattern)) {
|
||||
if (IsString(schema.pattern)) {
|
||||
const regex = new RegExp(schema.pattern)
|
||||
if (!regex.test(value)) {
|
||||
yield { type: ValueErrorType.StringPattern, schema, path, value, message: `Expected string to match pattern ${schema.pattern}` }
|
||||
yield Create(ValueErrorType.StringPattern, schema, path, value)
|
||||
}
|
||||
}
|
||||
if (ValueGuard.IsString(schema.format)) {
|
||||
if (IsString(schema.format)) {
|
||||
if (!Types.FormatRegistry.Has(schema.format)) {
|
||||
yield { type: ValueErrorType.StringFormatUnknown, schema, path, value, message: `Unknown string format '${schema.format}'` }
|
||||
yield Create(ValueErrorType.StringFormatUnknown, schema, path, value)
|
||||
} else {
|
||||
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}'` }
|
||||
yield Create(ValueErrorType.StringFormat, schema, path, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function* TSymbol(schema: Types.TSymbol, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsSymbol(value)) {
|
||||
return yield { type: ValueErrorType.Symbol, schema, path, value, message: 'Expected symbol' }
|
||||
}
|
||||
if (!IsSymbol(value)) yield Create(ValueErrorType.Symbol, schema, path, value)
|
||||
}
|
||||
function* TTemplateLiteral(schema: Types.TTemplateLiteral, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsString(value)) {
|
||||
return yield { type: ValueErrorType.String, schema, path, value, message: 'Expected string' }
|
||||
}
|
||||
if (!IsString(value)) return yield Create(ValueErrorType.String, schema, path, value)
|
||||
const regex = new RegExp(schema.pattern)
|
||||
if (!regex.test(value)) {
|
||||
yield { type: ValueErrorType.StringPattern, schema, path, value, message: `Expected string to match pattern ${schema.pattern}` }
|
||||
yield Create(ValueErrorType.StringPattern, schema, path, value)
|
||||
}
|
||||
}
|
||||
function* TThis(schema: Types.TThis, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
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)
|
||||
yield* Visit(Deref(schema, references), references, path, value)
|
||||
}
|
||||
function* TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsArray(value)) {
|
||||
return yield { type: ValueErrorType.Array, schema, path, value, message: 'Expected Array' }
|
||||
}
|
||||
if (!IsArray(value)) return yield Create(ValueErrorType.Tuple, schema, path, value)
|
||||
if (schema.items === undefined && !(value.length === 0)) {
|
||||
return yield { type: ValueErrorType.TupleZeroLength, schema, path, value, message: 'Expected tuple to have 0 elements' }
|
||||
return yield Create(ValueErrorType.TupleLength, schema, path, value)
|
||||
}
|
||||
if (!(value.length === schema.maxItems)) {
|
||||
yield { type: ValueErrorType.TupleLength, schema, path, value, message: `Expected tuple to have ${schema.maxItems} elements` }
|
||||
return yield Create(ValueErrorType.TupleLength, schema, path, value)
|
||||
}
|
||||
if (!schema.items) {
|
||||
return
|
||||
@@ -502,46 +433,35 @@ function* TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], path:
|
||||
}
|
||||
}
|
||||
function* TUndefined(schema: Types.TUndefined, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(value === undefined)) {
|
||||
yield { type: ValueErrorType.Undefined, schema, path, value, message: `Expected undefined` }
|
||||
}
|
||||
if (!IsUndefined(value)) yield Create(ValueErrorType.Undefined, schema, path, value)
|
||||
}
|
||||
function* TUnion(schema: Types.TUnion, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
const errors: ValueError[] = []
|
||||
for (const inner of schema.anyOf) {
|
||||
const variantErrors = [...Visit(inner, references, path, value)]
|
||||
if (variantErrors.length === 0) return
|
||||
errors.push(...variantErrors)
|
||||
let count = 0
|
||||
for (const subschema of schema.anyOf) {
|
||||
const errors = [...Visit(subschema, references, path, value)]
|
||||
if (errors.length === 0) return // matched
|
||||
count += errors.length
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
yield { type: ValueErrorType.Union, schema, path, value, message: 'Expected value of union' }
|
||||
}
|
||||
for (const error of errors) {
|
||||
yield error
|
||||
if (count > 0) {
|
||||
yield Create(ValueErrorType.Union, schema, path, value)
|
||||
}
|
||||
}
|
||||
function* TUint8Array(schema: Types.TUint8Array, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!ValueGuard.IsUint8Array(value)) {
|
||||
return yield { type: ValueErrorType.Uint8Array, schema, path, value, message: `Expected Uint8Array` }
|
||||
}
|
||||
if (!IsUint8Array(value)) return yield Create(ValueErrorType.Uint8Array, schema, path, value)
|
||||
if (IsDefined<number>(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}` }
|
||||
yield Create(ValueErrorType.Uint8ArrayMaxByteLength, schema, path, value)
|
||||
}
|
||||
if (IsDefined<number>(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}` }
|
||||
yield Create(ValueErrorType.Uint8ArrayMinByteLength, schema, path, value)
|
||||
}
|
||||
}
|
||||
function* TUnknown(schema: Types.TUnknown, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {}
|
||||
function* TVoid(schema: Types.TVoid, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!IsVoid(value)) {
|
||||
return yield { type: ValueErrorType.Void, schema, path, value, message: `Expected void` }
|
||||
}
|
||||
if (!TypeSystemPolicy.IsVoidLike(value)) yield Create(ValueErrorType.Void, schema, path, value)
|
||||
}
|
||||
function* TKind(schema: Types.TSchema, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
const check = Types.TypeRegistry.Get(schema[Types.Kind])!
|
||||
if (!check(schema, value)) {
|
||||
return yield { type: ValueErrorType.Kind, schema, path, value, message: `Expected kind ${schema[Types.Kind]}` }
|
||||
}
|
||||
if (!check(schema, value)) yield Create(ValueErrorType.Kind, schema, path, value)
|
||||
}
|
||||
function* Visit<T extends Types.TSchema>(schema: T, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
const references_ = IsDefined<string>(schema.$id) ? [...references, schema] : references
|
||||
|
||||
@@ -26,31 +26,30 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { IsObject, IsArray, IsNumber, IsUndefined } from '../value/guard'
|
||||
import { ValueErrorType } from '../errors/errors'
|
||||
import * as Types from '../typebox'
|
||||
|
||||
export class TypeSystemDuplicateTypeKind extends Error {
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class TypeSystemDuplicateTypeKind extends Types.TypeBoxError {
|
||||
constructor(kind: string) {
|
||||
super(`Duplicate type kind '${kind}' detected`)
|
||||
}
|
||||
}
|
||||
export class TypeSystemDuplicateFormat extends Error {
|
||||
export class TypeSystemDuplicateFormat extends Types.TypeBoxError {
|
||||
constructor(kind: string) {
|
||||
super(`Duplicate string format '${kind}' detected`)
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------------------------------
|
||||
// TypeSystem
|
||||
// -------------------------------------------------------------------------------------------
|
||||
/** Creates user defined types and formats and provides overrides for value checking behaviours */
|
||||
export namespace TypeSystem {
|
||||
/** Sets whether TypeBox should assert optional properties using the TypeScript `exactOptionalPropertyTypes` assertion policy. The default is `false` */
|
||||
export let ExactOptionalPropertyTypes: boolean = false
|
||||
/** Sets whether arrays should be treated as a kind of objects. The default is `false` */
|
||||
export let AllowArrayObjects: boolean = false
|
||||
/** Sets whether `NaN` or `Infinity` should be treated as valid numeric values. The default is `false` */
|
||||
export let AllowNaN: boolean = false
|
||||
/** Sets whether `null` should validate for void types. The default is `false` */
|
||||
export let AllowVoidNull: boolean = false
|
||||
|
||||
/** Creates a new type */
|
||||
export function Type<Type, Options = object>(kind: string, check: (options: Options, value: unknown) => boolean) {
|
||||
export function Type<Type, Options = Record<PropertyKey, unknown>>(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<Options> = {}) => Types.Type.Unsafe<Type>({ ...options, [Types.Kind]: kind })
|
||||
@@ -62,3 +61,199 @@ export namespace TypeSystem {
|
||||
return format
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// TypeSystemErrorFunction
|
||||
// --------------------------------------------------------------------------
|
||||
/** Manages error message providers */
|
||||
export namespace TypeSystemErrorFunction {
|
||||
let errorMessageFunction: ErrorFunction = DefaultErrorFunction
|
||||
/** Resets the error message function to en-us */
|
||||
export function Reset() {
|
||||
errorMessageFunction = DefaultErrorFunction
|
||||
}
|
||||
/** Sets the error message function used to generate error messages */
|
||||
export function Set(callback: ErrorFunction) {
|
||||
errorMessageFunction = callback
|
||||
}
|
||||
/** Gets the error message function */
|
||||
export function Get(): ErrorFunction {
|
||||
return errorMessageFunction
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// TypeSystemPolicy
|
||||
// --------------------------------------------------------------------------
|
||||
/** Shared assertion routines used by the value and errors modules */
|
||||
export namespace TypeSystemPolicy {
|
||||
/** Sets whether TypeBox should assert optional properties using the TypeScript `exactOptionalPropertyTypes` assertion policy. The default is `false` */
|
||||
export let ExactOptionalPropertyTypes: boolean = false
|
||||
/** Sets whether arrays should be treated as a kind of objects. The default is `false` */
|
||||
export let AllowArrayObject: boolean = false
|
||||
/** Sets whether `NaN` or `Infinity` should be treated as valid numeric values. The default is `false` */
|
||||
export let AllowNaN: boolean = false
|
||||
/** Sets whether `null` should validate for void types. The default is `false` */
|
||||
export let AllowNullVoid: boolean = false
|
||||
/** Asserts this value using the ExactOptionalPropertyTypes policy */
|
||||
export function IsExactOptionalProperty(value: Record<keyof any, unknown>, key: string) {
|
||||
return ExactOptionalPropertyTypes ? key in value : value[key] !== undefined
|
||||
}
|
||||
/** Asserts this value using the AllowArrayObjects policy */
|
||||
export function IsObjectLike(value: unknown): value is Record<keyof any, unknown> {
|
||||
const isObject = IsObject(value)
|
||||
return AllowArrayObject ? isObject : isObject && !IsArray(value)
|
||||
}
|
||||
/** Asserts this value as a record using the AllowArrayObjects policy */
|
||||
export function IsRecordLike(value: unknown): value is Record<keyof any, unknown> {
|
||||
return IsObjectLike(value) && !(value instanceof Date) && !(value instanceof Uint8Array)
|
||||
}
|
||||
/** Asserts this value using the AllowNaN policy */
|
||||
export function IsNumberLike(value: unknown): value is number {
|
||||
const isNumber = IsNumber(value)
|
||||
return AllowNaN ? isNumber : isNumber && Number.isFinite(value)
|
||||
}
|
||||
/** Asserts this value using the AllowVoidNull policy */
|
||||
export function IsVoidLike(value: unknown): value is void {
|
||||
const isUndefined = IsUndefined(value)
|
||||
return AllowNullVoid ? isUndefined || value === null : isUndefined
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// ErrorFunction
|
||||
// --------------------------------------------------------------------------
|
||||
export type ErrorFunction = (schema: Types.TSchema, type: ValueErrorType) => string
|
||||
// --------------------------------------------------------------------------
|
||||
// DefaultErrorFunction
|
||||
// --------------------------------------------------------------------------
|
||||
/** Creates an error message using en-US as the default locale */
|
||||
export function DefaultErrorFunction(schema: Types.TSchema, errorType: ValueErrorType) {
|
||||
switch (errorType) {
|
||||
case ValueErrorType.ArrayContains:
|
||||
return 'Expected array to contain at least one matching value'
|
||||
case ValueErrorType.ArrayMaxContains:
|
||||
return `Expected array to contain no more than ${schema.maxContains} matching values`
|
||||
case ValueErrorType.ArrayMinContains:
|
||||
return `Expected array to contain at least ${schema.minContains} matching values`
|
||||
case ValueErrorType.ArrayMaxItems:
|
||||
return `Expected array length to be less or equal to ${schema.maxItems}`
|
||||
case ValueErrorType.ArrayMinItems:
|
||||
return `Expected array length to be greater or equal to ${schema.minItems}`
|
||||
case ValueErrorType.ArrayUniqueItems:
|
||||
return 'Expected array elements to be unique'
|
||||
case ValueErrorType.Array:
|
||||
return 'Expected array'
|
||||
case ValueErrorType.AsyncIterator:
|
||||
return 'Expected AsyncIterator'
|
||||
case ValueErrorType.BigIntExclusiveMaximum:
|
||||
return `Expected bigint to be less than ${schema.exclusiveMaximum}`
|
||||
case ValueErrorType.BigIntExclusiveMinimum:
|
||||
return `Expected bigint to be greater than ${schema.exclusiveMinimum}`
|
||||
case ValueErrorType.BigIntMaximum:
|
||||
return `Expected bigint to be less or equal to ${schema.maximum}`
|
||||
case ValueErrorType.BigIntMinimum:
|
||||
return `Expected bigint to be greater or equal to ${schema.minimum}`
|
||||
case ValueErrorType.BigIntMultipleOf:
|
||||
return `Expected bigint to be a multiple of ${schema.multipleOf}`
|
||||
case ValueErrorType.BigInt:
|
||||
return 'Expected bigint'
|
||||
case ValueErrorType.Boolean:
|
||||
return 'Expected boolean'
|
||||
case ValueErrorType.DateExclusiveMinimumTimestamp:
|
||||
return `Expected Date timestamp to be greater than ${schema.exclusiveMinimumTimestamp}`
|
||||
case ValueErrorType.DateExclusiveMaximumTimestamp:
|
||||
return `Expected Date timestamp to be less than ${schema.exclusiveMaximumTimestamp}`
|
||||
case ValueErrorType.DateMinimumTimestamp:
|
||||
return `Expected Date timestamp to be greater or equal to ${schema.minimumTimestamp}`
|
||||
case ValueErrorType.DateMaximumTimestamp:
|
||||
return `Expected Date timestamp to be less or equal to ${schema.maximumTimestamp}`
|
||||
case ValueErrorType.DateMultipleOfTimestamp:
|
||||
return `Expected Date timestamp to be a multiple of ${schema.multipleOfTimestamp}`
|
||||
case ValueErrorType.Date:
|
||||
return 'Expected Date'
|
||||
case ValueErrorType.Function:
|
||||
return 'Expected function'
|
||||
case ValueErrorType.IntegerExclusiveMaximum:
|
||||
return `Expected integer to be less than ${schema.exclusiveMaximum}`
|
||||
case ValueErrorType.IntegerExclusiveMinimum:
|
||||
return `Expected integer to be greater than ${schema.exclusiveMinimum}`
|
||||
case ValueErrorType.IntegerMaximum:
|
||||
return `Expected integer to be less or equal to ${schema.maximum}`
|
||||
case ValueErrorType.IntegerMinimum:
|
||||
return `Expected integer to be greater or equal to ${schema.minimum}`
|
||||
case ValueErrorType.IntegerMultipleOf:
|
||||
return `Expected integer to be a multiple of ${schema.multipleOf}`
|
||||
case ValueErrorType.Integer:
|
||||
return 'Expected integer'
|
||||
case ValueErrorType.IntersectUnevaluatedProperties:
|
||||
return 'Unexpected property'
|
||||
case ValueErrorType.Intersect:
|
||||
return 'Expected all values to match'
|
||||
case ValueErrorType.Iterator:
|
||||
return 'Expected Iterator'
|
||||
case ValueErrorType.Literal:
|
||||
return `Expected ${typeof schema.const === 'string' ? `'${schema.const}'` : schema.const}`
|
||||
case ValueErrorType.Never:
|
||||
return 'Never'
|
||||
case ValueErrorType.Not:
|
||||
return 'Value should not match'
|
||||
case ValueErrorType.Null:
|
||||
return 'Expected null'
|
||||
case ValueErrorType.NumberExclusiveMaximum:
|
||||
return `Expected number to be less than ${schema.exclusiveMaximum}`
|
||||
case ValueErrorType.NumberExclusiveMinimum:
|
||||
return `Expected number to be greater than ${schema.exclusiveMinimum}`
|
||||
case ValueErrorType.NumberMaximum:
|
||||
return `Expected number to be less or equal to ${schema.maximum}`
|
||||
case ValueErrorType.NumberMinimum:
|
||||
return `Expected number to be greater or equal to ${schema.minimum}`
|
||||
case ValueErrorType.NumberMultipleOf:
|
||||
return `Expected number to be a multiple of ${schema.multipleOf}`
|
||||
case ValueErrorType.Number:
|
||||
return 'Expected number'
|
||||
case ValueErrorType.Object:
|
||||
return 'Expected object'
|
||||
case ValueErrorType.ObjectAdditionalProperties:
|
||||
return 'Unexpected property'
|
||||
case ValueErrorType.ObjectMaxProperties:
|
||||
return `Expected object to have no more than ${schema.maxProperties} properties`
|
||||
case ValueErrorType.ObjectMinProperties:
|
||||
return `Expected object to have at least ${schema.minProperties} properties`
|
||||
case ValueErrorType.ObjectRequiredProperty:
|
||||
return 'Required property'
|
||||
case ValueErrorType.Promise:
|
||||
return 'Expected Promise'
|
||||
case ValueErrorType.StringFormatUnknown:
|
||||
return `Unknown format '${schema.format}'`
|
||||
case ValueErrorType.StringFormat:
|
||||
return `Expected string to match '${schema.format}' format`
|
||||
case ValueErrorType.StringMaxLength:
|
||||
return `Expected string length less or equal to ${schema.maxLength}`
|
||||
case ValueErrorType.StringMinLength:
|
||||
return `Expected string length greater or equal to ${schema.minLength}`
|
||||
case ValueErrorType.StringPattern:
|
||||
return `Expected string to match '${schema.pattern}'`
|
||||
case ValueErrorType.String:
|
||||
return 'Expected string'
|
||||
case ValueErrorType.Symbol:
|
||||
return 'Expected symbol'
|
||||
case ValueErrorType.TupleLength:
|
||||
return `Expected tuple to have ${schema.maxItems || 0} elements`
|
||||
case ValueErrorType.Tuple:
|
||||
return 'Expected tuple'
|
||||
case ValueErrorType.Uint8ArrayMaxByteLength:
|
||||
return `Expected byte length less or equal to ${schema.maxByteLength}`
|
||||
case ValueErrorType.Uint8ArrayMinByteLength:
|
||||
return `Expected byte length greater or equal to ${schema.minByteLength}`
|
||||
case ValueErrorType.Uint8Array:
|
||||
return 'Expected Uint8Array'
|
||||
case ValueErrorType.Undefined:
|
||||
return 'Expected undefined'
|
||||
case ValueErrorType.Union:
|
||||
return 'Expected union value'
|
||||
case ValueErrorType.Void:
|
||||
return 'Expected void'
|
||||
case ValueErrorType.Kind:
|
||||
return `Expected kind '${schema[Types.Kind]}'`
|
||||
default:
|
||||
return 'Unknown error type'
|
||||
}
|
||||
}
|
||||
|
||||
1794
src/typebox.ts
1794
src/typebox.ts
File diff suppressed because it is too large
Load Diff
@@ -26,43 +26,34 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { IsPlainObject, IsArray, IsString, IsNumber, IsNull } from './guard'
|
||||
import { Create } from './create'
|
||||
import { Check } from './check'
|
||||
import { Clone } from './clone'
|
||||
import { Deref } from './deref'
|
||||
import * as Types from '../typebox'
|
||||
import * as ValueCreate from './create'
|
||||
import * as ValueCheck from './check'
|
||||
import * as ValueClone from './clone'
|
||||
import * as ValueGuard from './guard'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueCastReferenceTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TThis) {
|
||||
super(`ValueCast: Cannot locate referenced schema with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
export class ValueCastArrayUniqueItemsTypeError extends Error {
|
||||
export class ValueCastArrayUniqueItemsTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown) {
|
||||
super('ValueCast: Array cast produced invalid data due to uniqueItems constraint')
|
||||
super('Array cast produced invalid data due to uniqueItems constraint')
|
||||
}
|
||||
}
|
||||
export class ValueCastNeverTypeError extends Error {
|
||||
export class ValueCastNeverTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCast: Never types cannot be cast')
|
||||
super('Never types cannot be cast')
|
||||
}
|
||||
}
|
||||
export class ValueCastRecursiveTypeError extends Error {
|
||||
export class ValueCastRecursiveTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCast.Recursive: Cannot cast recursive schemas')
|
||||
super('Cannot cast recursive schemas')
|
||||
}
|
||||
}
|
||||
export class ValueCastUnknownTypeError extends Error {
|
||||
export class ValueCastUnknownTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCast: Unknown type')
|
||||
}
|
||||
}
|
||||
export class ValueCastDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TThis) {
|
||||
super(`ValueCast: Unable to dereference type with $id '${schema.$ref}'`)
|
||||
super('Unknown type')
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -74,19 +65,19 @@ export class ValueCastDereferenceError extends Error {
|
||||
// --------------------------------------------------------------------------
|
||||
namespace UnionCastCreate {
|
||||
function Score(schema: Types.TSchema, references: Types.TSchema[], value: any): number {
|
||||
if (schema[Types.Kind] === 'Object' && typeof value === 'object' && !ValueGuard.IsNull(value)) {
|
||||
if (schema[Types.Kind] === 'Object' && typeof value === 'object' && !IsNull(value)) {
|
||||
const object = schema as Types.TObject
|
||||
const keys = Object.getOwnPropertyNames(value)
|
||||
const entries = Object.entries(object.properties)
|
||||
const [point, max] = [1 / entries.length, entries.length]
|
||||
return entries.reduce((acc, [key, schema]) => {
|
||||
const literal = schema[Types.Kind] === 'Literal' && schema.const === value[key] ? max : 0
|
||||
const checks = ValueCheck.Check(schema, references, value[key]) ? point : 0
|
||||
const checks = Check(schema, references, value[key]) ? point : 0
|
||||
const exists = keys.includes(key) ? point : 0
|
||||
return acc + (literal + checks + exists)
|
||||
}, 0)
|
||||
} else {
|
||||
return ValueCheck.Check(schema, references, value) ? 1 : 0
|
||||
return Check(schema, references, value) ? 1 : 0
|
||||
}
|
||||
}
|
||||
function Select(union: Types.TUnion, references: Types.TSchema[], value: any): Types.TSchema {
|
||||
@@ -110,33 +101,30 @@ namespace UnionCastCreate {
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Default
|
||||
// --------------------------------------------------------------------------
|
||||
export function DefaultClone(schema: Types.TSchema, references: Types.TSchema[], value: any): any {
|
||||
return Check(schema, references, value) ? Clone(value) : Create(schema, references)
|
||||
}
|
||||
export function Default(schema: Types.TSchema, references: Types.TSchema[], value: any): any {
|
||||
return Check(schema, references, value) ? value : Create(schema, references)
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Cast
|
||||
// --------------------------------------------------------------------------
|
||||
function TAny(schema: Types.TAny, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TArray(schema: Types.TArray, references: Types.TSchema[], value: any): any {
|
||||
if (ValueCheck.Check(schema, references, value)) return ValueClone.Clone(value)
|
||||
const created = ValueGuard.IsArray(value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
const minimum = ValueGuard.IsNumber(schema.minItems) && created.length < schema.minItems ? [...created, ...Array.from({ length: schema.minItems - created.length }, () => null)] : created
|
||||
const maximum = ValueGuard.IsNumber(schema.maxItems) && minimum.length > schema.maxItems ? minimum.slice(0, schema.maxItems) : minimum
|
||||
if (Check(schema, references, value)) return Clone(value)
|
||||
const created = IsArray(value) ? Clone(value) : Create(schema, references)
|
||||
const minimum = IsNumber(schema.minItems) && created.length < schema.minItems ? [...created, ...Array.from({ length: schema.minItems - created.length }, () => null)] : created
|
||||
const maximum = IsNumber(schema.maxItems) && minimum.length > schema.maxItems ? minimum.slice(0, schema.maxItems) : minimum
|
||||
const casted = maximum.map((value: unknown) => Visit(schema.items, references, value))
|
||||
if (schema.uniqueItems !== true) return casted
|
||||
const unique = [...new Set(casted)]
|
||||
if (!ValueCheck.Check(schema, references, unique)) throw new ValueCastArrayUniqueItemsTypeError(schema, unique)
|
||||
if (!Check(schema, references, unique)) throw new ValueCastArrayUniqueItemsTypeError(schema, unique)
|
||||
return unique
|
||||
}
|
||||
function TAsyncIterator(schema: Types.TAsyncIterator, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TBigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TBoolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TConstructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): any {
|
||||
if (ValueCheck.Check(schema, references, value)) return ValueCreate.Create(schema, references)
|
||||
if (Check(schema, references, value)) return Create(schema, references)
|
||||
const required = new Set(schema.returns.required || [])
|
||||
const result = function () {}
|
||||
for (const [key, property] of Object.entries(schema.returns.properties)) {
|
||||
@@ -145,41 +133,17 @@ function TConstructor(schema: Types.TConstructor, references: Types.TSchema[], v
|
||||
}
|
||||
return result
|
||||
}
|
||||
function TDate(schema: Types.TDate, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TFunction(schema: Types.TFunction, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TInteger(schema: Types.TInteger, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): any {
|
||||
const created = ValueCreate.Create(schema, references)
|
||||
const mapped = ValueGuard.IsPlainObject(created) && ValueGuard.IsPlainObject(value) ? { ...(created as any), ...value } : value
|
||||
return ValueCheck.Check(schema, references, mapped) ? mapped : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TIterator(schema: Types.TIterator, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TLiteral(schema: Types.TLiteral, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
const created = Create(schema, references)
|
||||
const mapped = IsPlainObject(created) && IsPlainObject(value) ? { ...(created as any), ...value } : value
|
||||
return Check(schema, references, mapped) ? mapped : Create(schema, references)
|
||||
}
|
||||
function TNever(schema: Types.TNever, references: Types.TSchema[], value: any): any {
|
||||
throw new ValueCastNeverTypeError(schema)
|
||||
}
|
||||
function TNot(schema: Types.TNot, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TNull(schema: Types.TNull, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TNumber(schema: Types.TNumber, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TObject(schema: Types.TObject, references: Types.TSchema[], value: any): any {
|
||||
if (ValueCheck.Check(schema, references, value)) return value
|
||||
if (value === null || typeof value !== 'object') return ValueCreate.Create(schema, references)
|
||||
if (Check(schema, references, value)) return value
|
||||
if (value === null || typeof value !== 'object') return Create(schema, references)
|
||||
const required = new Set(schema.required || [])
|
||||
const result = {} as Record<string, any>
|
||||
for (const [key, property] of Object.entries(schema.properties)) {
|
||||
@@ -196,12 +160,9 @@ function TObject(schema: Types.TObject, references: Types.TSchema[], value: any)
|
||||
}
|
||||
return result
|
||||
}
|
||||
function TPromise(schema: Types.TSchema, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any): any {
|
||||
if (ValueCheck.Check(schema, references, value)) return ValueClone.Clone(value)
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value) || value instanceof Date) return ValueCreate.Create(schema, references)
|
||||
if (Check(schema, references, value)) return Clone(value)
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value) || value instanceof Date) return Create(schema, references)
|
||||
const subschemaPropertyName = Object.getOwnPropertyNames(schema.patternProperties)[0]
|
||||
const subschema = schema.patternProperties[subschemaPropertyName]
|
||||
const result = {} as Record<string, any>
|
||||
@@ -211,117 +172,78 @@ function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], v
|
||||
return result
|
||||
}
|
||||
function TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: any): any {
|
||||
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 TString(schema: Types.TString, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TSymbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TTemplateLiteral(schema: Types.TSymbol, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
return Visit(Deref(schema, references), references, value)
|
||||
}
|
||||
function TThis(schema: Types.TThis, references: Types.TSchema[], value: any): any {
|
||||
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)
|
||||
return Visit(Deref(schema, references), references, value)
|
||||
}
|
||||
function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: any): any {
|
||||
if (ValueCheck.Check(schema, references, value)) return ValueClone.Clone(value)
|
||||
if (!ValueGuard.IsArray(value)) return ValueCreate.Create(schema, references)
|
||||
if (Check(schema, references, value)) return Clone(value)
|
||||
if (!IsArray(value)) return Create(schema, references)
|
||||
if (schema.items === undefined) return []
|
||||
return schema.items.map((schema, index) => Visit(schema, references, value[index]))
|
||||
}
|
||||
function TUndefined(schema: Types.TUndefined, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TUnion(schema: Types.TUnion, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : UnionCastCreate.Create(schema, references, value)
|
||||
}
|
||||
function TUint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TUnknown(schema: Types.TUnknown, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TVoid(schema: Types.TVoid, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function TKind(schema: Types.TSchema, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
return Check(schema, references, value) ? Clone(value) : UnionCastCreate.Create(schema, references, value)
|
||||
}
|
||||
function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): any {
|
||||
const references_ = ValueGuard.IsString(schema.$id) ? [...references, schema] : references
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema[Types.Kind]) {
|
||||
case 'Any':
|
||||
return TAny(schema_, references_, value)
|
||||
// ------------------------------------------------------
|
||||
// Structural
|
||||
// ------------------------------------------------------
|
||||
case 'Array':
|
||||
return TArray(schema_, references_, value)
|
||||
case 'AsyncIterator':
|
||||
return TAsyncIterator(schema_, references_, value)
|
||||
case 'BigInt':
|
||||
return TBigInt(schema_, references_, value)
|
||||
case 'Boolean':
|
||||
return TBoolean(schema_, references_, value)
|
||||
case 'Constructor':
|
||||
return TConstructor(schema_, references_, value)
|
||||
case 'Date':
|
||||
return TDate(schema_, references_, value)
|
||||
case 'Function':
|
||||
return TFunction(schema_, references_, value)
|
||||
case 'Integer':
|
||||
return TInteger(schema_, references_, value)
|
||||
case 'Intersect':
|
||||
return TIntersect(schema_, references_, value)
|
||||
case 'Iterator':
|
||||
return TIterator(schema_, references_, value)
|
||||
case 'Literal':
|
||||
return TLiteral(schema_, references_, value)
|
||||
case 'Never':
|
||||
return TNever(schema_, references_, value)
|
||||
case 'Not':
|
||||
return TNot(schema_, references_, value)
|
||||
case 'Null':
|
||||
return TNull(schema_, references_, value)
|
||||
case 'Number':
|
||||
return TNumber(schema_, references_, value)
|
||||
case 'Object':
|
||||
return TObject(schema_, references_, value)
|
||||
case 'Promise':
|
||||
return TPromise(schema_, references_, value)
|
||||
case 'Record':
|
||||
return TRecord(schema_, references_, value)
|
||||
case 'Ref':
|
||||
return TRef(schema_, references_, value)
|
||||
case 'String':
|
||||
return TString(schema_, references_, value)
|
||||
case 'Symbol':
|
||||
return TSymbol(schema_, references_, value)
|
||||
case 'TemplateLiteral':
|
||||
return TTemplateLiteral(schema_, references_, value)
|
||||
case 'This':
|
||||
return TThis(schema_, references_, value)
|
||||
case 'Tuple':
|
||||
return TTuple(schema_, references_, value)
|
||||
case 'Undefined':
|
||||
return TUndefined(schema_, references_, value)
|
||||
case 'Union':
|
||||
return TUnion(schema_, references_, value)
|
||||
// ------------------------------------------------------
|
||||
// DefaultClone
|
||||
// ------------------------------------------------------
|
||||
case 'Date':
|
||||
case 'Symbol':
|
||||
case 'Uint8Array':
|
||||
return TUint8Array(schema_, references_, value)
|
||||
return DefaultClone(schema, references, value)
|
||||
// ------------------------------------------------------
|
||||
// Default
|
||||
// ------------------------------------------------------
|
||||
case 'Any':
|
||||
case 'AsyncIterator':
|
||||
case 'BigInt':
|
||||
case 'Boolean':
|
||||
case 'Function':
|
||||
case 'Integer':
|
||||
case 'Iterator':
|
||||
case 'Literal':
|
||||
case 'Not':
|
||||
case 'Null':
|
||||
case 'Number':
|
||||
case 'Promise':
|
||||
case 'String':
|
||||
case 'TemplateLiteral':
|
||||
case 'Undefined':
|
||||
case 'Unknown':
|
||||
return TUnknown(schema_, references_, value)
|
||||
case 'Void':
|
||||
return TVoid(schema_, references_, value)
|
||||
return Default(schema_, references_, value)
|
||||
default:
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueCastUnknownTypeError(schema_)
|
||||
return TKind(schema_, references_, value)
|
||||
return Default(schema_, references_, value)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
@@ -26,22 +26,18 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { TypeSystem } from '../system/index'
|
||||
import { IsArray, IsUint8Array, IsDate, IsPromise, IsFunction, IsAsyncIterator, IsIterator, IsBoolean, IsNumber, IsBigInt, IsString, IsSymbol, IsInteger, IsNull, IsUndefined } from './guard'
|
||||
import { TypeSystemPolicy } from '../system/index'
|
||||
import { Deref } from './deref'
|
||||
import { Hash } from './hash'
|
||||
import * as Types from '../typebox'
|
||||
import * as ValueGuard from './guard'
|
||||
import * as ValueHash from './hash'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueCheckUnknownTypeError extends Error {
|
||||
export class ValueCheckUnknownTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super(`ValueCheck: ${schema[Types.Kind] ? `Unknown type '${schema[Types.Kind]}'` : 'Unknown type'}`)
|
||||
}
|
||||
}
|
||||
export class ValueCheckDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TThis) {
|
||||
super(`ValueCheck: Unable to dereference type with $id '${schema.$ref}'`)
|
||||
super(`Unknown type`)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -57,36 +53,13 @@ function IsDefined<T>(value: unknown): value is T {
|
||||
return value !== undefined
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Policies
|
||||
// --------------------------------------------------------------------------
|
||||
function IsExactOptionalProperty(value: Record<keyof any, unknown>, key: string) {
|
||||
return TypeSystem.ExactOptionalPropertyTypes ? key in value : value[key] !== undefined
|
||||
}
|
||||
function IsObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
const isObject = ValueGuard.IsObject(value)
|
||||
return TypeSystem.AllowArrayObjects ? isObject : isObject && !ValueGuard.IsArray(value)
|
||||
}
|
||||
function IsRecordObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
return IsObject(value) && !(value instanceof Date) && !(value instanceof Uint8Array)
|
||||
}
|
||||
function IsNumber(value: unknown): value is number {
|
||||
const isNumber = ValueGuard.IsNumber(value)
|
||||
return TypeSystem.AllowNaN ? isNumber : isNumber && Number.isFinite(value)
|
||||
}
|
||||
function IsVoid(value: unknown): value is void {
|
||||
const isUndefined = ValueGuard.IsUndefined(value)
|
||||
return TypeSystem.AllowVoidNull ? isUndefined || value === null : isUndefined
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Types
|
||||
// --------------------------------------------------------------------------
|
||||
function TAny(schema: Types.TAny, references: Types.TSchema[], value: any): boolean {
|
||||
return true
|
||||
}
|
||||
function TArray(schema: Types.TArray, references: Types.TSchema[], value: any): boolean {
|
||||
if (!Array.isArray(value)) {
|
||||
return false
|
||||
}
|
||||
if (!IsArray(value)) return false
|
||||
if (IsDefined<number>(schema.minItems) && !(value.length >= schema.minItems)) {
|
||||
return false
|
||||
}
|
||||
@@ -97,7 +70,7 @@ function TArray(schema: Types.TArray, references: Types.TSchema[], value: any):
|
||||
return false
|
||||
}
|
||||
// prettier-ignore
|
||||
if (schema.uniqueItems === true && !((function() { const set = new Set(); for(const element of value) { const hashed = ValueHash.Hash(element); if(set.has(hashed)) { return false } else { set.add(hashed) } } return true })())) {
|
||||
if (schema.uniqueItems === true && !((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 })())) {
|
||||
return false
|
||||
}
|
||||
// contains
|
||||
@@ -105,7 +78,7 @@ function TArray(schema: Types.TArray, references: Types.TSchema[], value: any):
|
||||
return true // exit
|
||||
}
|
||||
const containsSchema = IsDefined<Types.TSchema>(schema.contains) ? schema.contains : Types.Type.Never()
|
||||
const containsCount = value.reduce((acc, value) => (Visit(containsSchema, references, value) ? acc + 1 : acc), 0)
|
||||
const containsCount = value.reduce((acc: number, value) => (Visit(containsSchema, references, value) ? acc + 1 : acc), 0)
|
||||
if (containsCount === 0) {
|
||||
return false
|
||||
}
|
||||
@@ -118,78 +91,74 @@ function TArray(schema: Types.TArray, references: Types.TSchema[], value: any):
|
||||
return true
|
||||
}
|
||||
function TAsyncIterator(schema: Types.TAsyncIterator, references: Types.TSchema[], value: any): boolean {
|
||||
return IsObject(value) && Symbol.asyncIterator in value
|
||||
return IsAsyncIterator(value)
|
||||
}
|
||||
function TBigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): boolean {
|
||||
if (!ValueGuard.IsBigInt(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.multipleOf) && !(value % schema.multipleOf === BigInt(0))) {
|
||||
if (!IsBigInt(value)) return false
|
||||
if (IsDefined<bigint>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
if (IsDefined<bigint>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
if (IsDefined<bigint>(schema.multipleOf) && !(value % schema.multipleOf === BigInt(0))) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
function TBoolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): boolean {
|
||||
return typeof value === 'boolean'
|
||||
return IsBoolean(value)
|
||||
}
|
||||
function TConstructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): boolean {
|
||||
return Visit(schema.returns, references, value.prototype)
|
||||
}
|
||||
function TDate(schema: Types.TDate, references: Types.TSchema[], value: any): boolean {
|
||||
if (!(value instanceof Date)) {
|
||||
return false
|
||||
}
|
||||
if (!IsNumber(value.getTime())) {
|
||||
if (!IsDate(value)) return false
|
||||
if (IsDefined<number>(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
|
||||
if (IsDefined<number>(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) {
|
||||
if (IsDefined<number>(schema.multipleOfTimestamp) && !(value.getTime() % schema.multipleOfTimestamp === 0)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
function TFunction(schema: Types.TFunction, references: Types.TSchema[], value: any): boolean {
|
||||
return typeof value === 'function'
|
||||
return IsFunction(value)
|
||||
}
|
||||
function TInteger(schema: Types.TInteger, references: Types.TSchema[], value: any): boolean {
|
||||
if (!ValueGuard.IsInteger(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
if (!IsInteger(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): boolean {
|
||||
@@ -207,7 +176,7 @@ function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value
|
||||
}
|
||||
}
|
||||
function TIterator(schema: Types.TIterator, references: Types.TSchema[], value: any): boolean {
|
||||
return IsObject(value) && Symbol.iterator in value
|
||||
return IsIterator(value)
|
||||
}
|
||||
function TLiteral(schema: Types.TLiteral, references: Types.TSchema[], value: any): boolean {
|
||||
return value === schema.const
|
||||
@@ -219,33 +188,29 @@ function TNot(schema: Types.TNot, references: Types.TSchema[], value: any): bool
|
||||
return !Visit(schema.not, references, value)
|
||||
}
|
||||
function TNull(schema: Types.TNull, references: Types.TSchema[], value: any): boolean {
|
||||
return value === null
|
||||
return IsNull(value)
|
||||
}
|
||||
function TNumber(schema: Types.TNumber, references: Types.TSchema[], value: any): boolean {
|
||||
if (!IsNumber(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
if (!TypeSystemPolicy.IsNumberLike(value)) return false
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
function TObject(schema: Types.TObject, references: Types.TSchema[], value: any): boolean {
|
||||
if (!IsObject(value)) {
|
||||
return false
|
||||
}
|
||||
if (!TypeSystemPolicy.IsObjectLike(value)) return false
|
||||
if (IsDefined<number>(schema.minProperties) && !(Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
|
||||
return false
|
||||
}
|
||||
@@ -263,7 +228,7 @@ function TObject(schema: Types.TObject, references: Types.TSchema[], value: any)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (IsExactOptionalProperty(value, knownKey) && !Visit(property, references, value[knownKey])) {
|
||||
if (TypeSystemPolicy.IsExactOptionalProperty(value, knownKey) && !Visit(property, references, value[knownKey])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -284,10 +249,10 @@ function TObject(schema: Types.TObject, references: Types.TSchema[], value: any)
|
||||
}
|
||||
}
|
||||
function TPromise(schema: Types.TPromise<any>, references: Types.TSchema[], value: any): boolean {
|
||||
return typeof value === 'object' && typeof value.then === 'function'
|
||||
return IsPromise(value)
|
||||
}
|
||||
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any): boolean {
|
||||
if (!IsRecordObject(value)) {
|
||||
if (!TypeSystemPolicy.IsRecordLike(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.minProperties) && !(Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
|
||||
@@ -298,27 +263,27 @@ function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], v
|
||||
}
|
||||
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
|
||||
const regex = new RegExp(patternKey)
|
||||
return Object.entries(value).every(([key, value]) => {
|
||||
if (regex.test(key)) {
|
||||
return Visit(patternSchema, references, value)
|
||||
}
|
||||
if (typeof schema.additionalProperties === 'object') {
|
||||
return Visit(schema.additionalProperties, references, value)
|
||||
}
|
||||
if (schema.additionalProperties === false) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
// prettier-ignore
|
||||
const check1 = Object.entries(value).every(([key, value]) => {
|
||||
return (regex.test(key)) ? Visit(patternSchema, references, value) : true
|
||||
})
|
||||
// prettier-ignore
|
||||
const check2 = typeof schema.additionalProperties === 'object' ? Object.entries(value).every(([key, value]) => {
|
||||
return (!regex.test(key)) ? Visit(schema.additionalProperties as Types.TSchema, references, value) : true
|
||||
}) : true
|
||||
const check3 =
|
||||
schema.additionalProperties === false
|
||||
? Object.getOwnPropertyNames(value).every((key) => {
|
||||
return regex.test(key)
|
||||
})
|
||||
: true
|
||||
return check1 && check2 && check3
|
||||
}
|
||||
function TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: any): boolean {
|
||||
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)
|
||||
return Visit(Deref(schema, references), references, value)
|
||||
}
|
||||
function TString(schema: Types.TString, references: Types.TSchema[], value: any): boolean {
|
||||
if (!ValueGuard.IsString(value)) {
|
||||
if (!IsString(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.minLength)) {
|
||||
@@ -339,25 +304,16 @@ function TString(schema: Types.TString, references: Types.TSchema[], value: any)
|
||||
return true
|
||||
}
|
||||
function TSymbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): boolean {
|
||||
if (!(typeof value === 'symbol')) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return IsSymbol(value)
|
||||
}
|
||||
function TTemplateLiteral(schema: Types.TTemplateLiteralKind, references: Types.TSchema[], value: any): boolean {
|
||||
if (!ValueGuard.IsString(value)) {
|
||||
return false
|
||||
}
|
||||
return new RegExp(schema.pattern).test(value)
|
||||
return IsString(value) && new RegExp(schema.pattern).test(value)
|
||||
}
|
||||
function TThis(schema: Types.TThis, references: Types.TSchema[], value: any): boolean {
|
||||
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)
|
||||
return Visit(Deref(schema, references), references, value)
|
||||
}
|
||||
function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: any): boolean {
|
||||
if (!ValueGuard.IsArray(value)) {
|
||||
if (!IsArray(value)) {
|
||||
return false
|
||||
}
|
||||
if (schema.items === undefined && !(value.length === 0)) {
|
||||
@@ -375,13 +331,13 @@ function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value:
|
||||
return true
|
||||
}
|
||||
function TUndefined(schema: Types.TUndefined, references: Types.TSchema[], value: any): boolean {
|
||||
return value === undefined
|
||||
return IsUndefined(value)
|
||||
}
|
||||
function TUnion(schema: Types.TUnion<any[]>, references: Types.TSchema[], value: any): boolean {
|
||||
return schema.anyOf.some((inner) => Visit(inner, references, value))
|
||||
}
|
||||
function TUint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: any): boolean {
|
||||
if (!(value instanceof Uint8Array)) {
|
||||
if (!IsUint8Array(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) {
|
||||
@@ -396,7 +352,7 @@ function TUnknown(schema: Types.TUnknown, references: Types.TSchema[], value: an
|
||||
return true
|
||||
}
|
||||
function TVoid(schema: Types.TVoid, references: Types.TSchema[], value: any): boolean {
|
||||
return IsVoid(value)
|
||||
return TypeSystemPolicy.IsVoidLike(value)
|
||||
}
|
||||
function TKind(schema: Types.TSchema, references: Types.TSchema[], value: unknown): boolean {
|
||||
if (!Types.TypeRegistry.Has(schema[Types.Kind])) return false
|
||||
|
||||
@@ -26,40 +26,26 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as ValueGuard from './guard'
|
||||
import { IsArray, IsDate, IsPlainObject, IsTypedArray, IsValueType } from './guard'
|
||||
import type { ObjectType, ArrayType, TypedArrayType, ValueType } from './guard'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Clonable
|
||||
// --------------------------------------------------------------------------
|
||||
function ObjectType(value: ValueGuard.ObjectType): any {
|
||||
function ObjectType(value: ObjectType): any {
|
||||
const keys = [...Object.getOwnPropertyNames(value), ...Object.getOwnPropertySymbols(value)]
|
||||
return keys.reduce((acc, key) => ({ ...acc, [key]: Clone(value[key]) }), {})
|
||||
}
|
||||
function ArrayType(value: ValueGuard.ArrayType): any {
|
||||
function ArrayType(value: ArrayType): any {
|
||||
return value.map((element: any) => Clone(element))
|
||||
}
|
||||
function TypedArrayType(value: ValueGuard.TypedArrayType): any {
|
||||
function TypedArrayType(value: TypedArrayType): any {
|
||||
return value.slice()
|
||||
}
|
||||
function DateType(value: Date): any {
|
||||
return new Date(value.toISOString())
|
||||
}
|
||||
function ValueType(value: ValueGuard.ValueType): any {
|
||||
return value
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Non-Clonable
|
||||
// --------------------------------------------------------------------------
|
||||
function AsyncIteratorType(value: AsyncIterableIterator<unknown>): any {
|
||||
return value
|
||||
}
|
||||
function IteratorType(value: IterableIterator<unknown>): any {
|
||||
return value
|
||||
}
|
||||
function FunctionType(value: Function): any {
|
||||
return value
|
||||
}
|
||||
function PromiseType(value: Promise<unknown>): any {
|
||||
function ValueType(value: ValueType): any {
|
||||
return value
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -67,14 +53,10 @@ function PromiseType(value: Promise<unknown>): any {
|
||||
// --------------------------------------------------------------------------
|
||||
/** Returns a clone of the given value */
|
||||
export function Clone<T extends unknown>(value: T): T {
|
||||
if (ValueGuard.IsArray(value)) return ArrayType(value)
|
||||
if (ValueGuard.IsAsyncIterator(value)) return AsyncIteratorType(value)
|
||||
if (ValueGuard.IsFunction(value)) return FunctionType(value)
|
||||
if (ValueGuard.IsIterator(value)) return IteratorType(value)
|
||||
if (ValueGuard.IsPromise(value)) return PromiseType(value)
|
||||
if (ValueGuard.IsDate(value)) return DateType(value)
|
||||
if (ValueGuard.IsPlainObject(value)) return ObjectType(value)
|
||||
if (ValueGuard.IsTypedArray(value)) return TypedArrayType(value)
|
||||
if (ValueGuard.IsValueType(value)) return ValueType(value)
|
||||
if (IsArray(value)) return ArrayType(value)
|
||||
if (IsDate(value)) return DateType(value)
|
||||
if (IsPlainObject(value)) return ObjectType(value)
|
||||
if (IsTypedArray(value)) return TypedArrayType(value)
|
||||
if (IsValueType(value)) return ValueType(value)
|
||||
throw new Error('ValueClone: Unable to clone value')
|
||||
}
|
||||
|
||||
@@ -26,53 +26,49 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { IsArray, IsObject, IsDate, IsUndefined, IsString, IsNumber, IsBoolean, IsBigInt, IsSymbol } from './guard'
|
||||
import { Clone } from './clone'
|
||||
import { Check } from './check'
|
||||
import { Deref } from './deref'
|
||||
import * as Types from '../typebox'
|
||||
import * as ValueClone from './clone'
|
||||
import * as ValueCheck from './check'
|
||||
import * as ValueGuard from './guard'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueConvertUnknownTypeError extends Error {
|
||||
export class ValueConvertUnknownTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueConvert: Unknown type')
|
||||
}
|
||||
}
|
||||
export class ValueConvertDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TThis) {
|
||||
super(`ValueConvert: Unable to dereference type with $id '${schema.$ref}'`)
|
||||
super('Unknown type')
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Conversions
|
||||
// --------------------------------------------------------------------------
|
||||
function IsStringNumeric(value: unknown): value is string {
|
||||
return ValueGuard.IsString(value) && !isNaN(value as any) && !isNaN(parseFloat(value))
|
||||
return IsString(value) && !isNaN(value as any) && !isNaN(parseFloat(value))
|
||||
}
|
||||
function IsValueToString(value: unknown): value is { toString: () => string } {
|
||||
return ValueGuard.IsBigInt(value) || ValueGuard.IsBoolean(value) || ValueGuard.IsNumber(value)
|
||||
return IsBigInt(value) || IsBoolean(value) || IsNumber(value)
|
||||
}
|
||||
function IsValueTrue(value: unknown): value is true {
|
||||
return value === true || (ValueGuard.IsNumber(value) && value === 1) || (ValueGuard.IsBigInt(value) && value === BigInt('1')) || (ValueGuard.IsString(value) && (value.toLowerCase() === 'true' || value === '1'))
|
||||
return value === true || (IsNumber(value) && value === 1) || (IsBigInt(value) && value === BigInt('1')) || (IsString(value) && (value.toLowerCase() === 'true' || value === '1'))
|
||||
}
|
||||
function IsValueFalse(value: unknown): value is false {
|
||||
return value === false || (ValueGuard.IsNumber(value) && value === 0) || (ValueGuard.IsBigInt(value) && value === BigInt('0')) || (ValueGuard.IsString(value) && (value.toLowerCase() === 'false' || value === '0'))
|
||||
return value === false || (IsNumber(value) && (value === 0 || Object.is(value, -0))) || (IsBigInt(value) && value === BigInt('0')) || (IsString(value) && (value.toLowerCase() === 'false' || value === '0' || value === '-0'))
|
||||
}
|
||||
function IsTimeStringWithTimeZone(value: unknown): value is string {
|
||||
return ValueGuard.IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value)
|
||||
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 ValueGuard.IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value)
|
||||
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 ValueGuard.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)
|
||||
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 ValueGuard.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)
|
||||
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 ValueGuard.IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\d$/i.test(value)
|
||||
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\d$/i.test(value)
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Convert
|
||||
@@ -97,29 +93,29 @@ function TryConvertLiteral(schema: Types.TLiteral, value: unknown) {
|
||||
} else if (typeof schema.const === 'boolean') {
|
||||
return TryConvertLiteralBoolean(value, schema.const)
|
||||
} else {
|
||||
return ValueClone.Clone(value)
|
||||
return Clone(value)
|
||||
}
|
||||
}
|
||||
function TryConvertBoolean(value: unknown) {
|
||||
return IsValueTrue(value) ? true : IsValueFalse(value) ? false : value
|
||||
}
|
||||
function TryConvertBigInt(value: unknown) {
|
||||
return IsStringNumeric(value) ? BigInt(parseInt(value)) : ValueGuard.IsNumber(value) ? BigInt(value | 0) : IsValueFalse(value) ? 0 : IsValueTrue(value) ? 1 : value
|
||||
return IsStringNumeric(value) ? BigInt(parseInt(value)) : IsNumber(value) ? BigInt(value | 0) : IsValueFalse(value) ? BigInt(0) : IsValueTrue(value) ? BigInt(1) : value
|
||||
}
|
||||
function TryConvertString(value: unknown) {
|
||||
return IsValueToString(value) ? value.toString() : ValueGuard.IsSymbol(value) && value.description !== undefined ? value.description.toString() : value
|
||||
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) : ValueGuard.IsNumber(value) ? value | 0 : IsValueTrue(value) ? 1 : IsValueFalse(value) ? 0 : value
|
||||
return IsStringNumeric(value) ? parseInt(value) : IsNumber(value) ? value | 0 : IsValueTrue(value) ? 1 : IsValueFalse(value) ? 0 : value
|
||||
}
|
||||
function TryConvertNull(value: unknown) {
|
||||
return ValueGuard.IsString(value) && value.toLowerCase() === 'null' ? null : value
|
||||
return IsString(value) && value.toLowerCase() === 'null' ? null : value
|
||||
}
|
||||
function TryConvertUndefined(value: unknown) {
|
||||
return ValueGuard.IsString(value) && value === 'undefined' ? undefined : value
|
||||
return IsString(value) && value === 'undefined' ? undefined : value
|
||||
}
|
||||
function TryConvertDate(value: unknown) {
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -128,9 +124,9 @@ function TryConvertDate(value: unknown) {
|
||||
// and will return a epoch date if invalid. Consider better string parsing
|
||||
// for the iso dates in future revisions.
|
||||
// --------------------------------------------------------------------------
|
||||
return ValueGuard.IsDate(value)
|
||||
return IsDate(value)
|
||||
? value
|
||||
: ValueGuard.IsNumber(value)
|
||||
: IsNumber(value)
|
||||
? new Date(value)
|
||||
: IsValueTrue(value)
|
||||
? new Date(1)
|
||||
@@ -151,50 +147,35 @@ function TryConvertDate(value: unknown) {
|
||||
: value
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Cast
|
||||
// Default
|
||||
// --------------------------------------------------------------------------
|
||||
function TAny(schema: Types.TAny, references: Types.TSchema[], value: any): any {
|
||||
export function Default(value: any) {
|
||||
return value
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Convert
|
||||
// --------------------------------------------------------------------------
|
||||
function TArray(schema: Types.TArray, references: Types.TSchema[], value: any): any {
|
||||
if (ValueGuard.IsArray(value)) {
|
||||
if (IsArray(value)) {
|
||||
return value.map((value) => Visit(schema.items, references, value))
|
||||
}
|
||||
return value
|
||||
}
|
||||
function TAsyncIterator(schema: Types.TAsyncIterator, references: Types.TSchema[], value: any): any {
|
||||
return value
|
||||
}
|
||||
function TBigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertBigInt(value)
|
||||
}
|
||||
function TBoolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertBoolean(value)
|
||||
}
|
||||
function TConstructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): unknown {
|
||||
return ValueClone.Clone(value)
|
||||
}
|
||||
function TDate(schema: Types.TDate, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertDate(value)
|
||||
}
|
||||
function TFunction(schema: Types.TFunction, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TInteger(schema: Types.TInteger, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertInteger(value)
|
||||
}
|
||||
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TIterator(schema: Types.TIterator, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TLiteral(schema: Types.TLiteral, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertLiteral(schema, value)
|
||||
}
|
||||
function TNever(schema: Types.TNever, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TNull(schema: Types.TNull, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertNull(value)
|
||||
}
|
||||
@@ -202,15 +183,12 @@ function TNumber(schema: Types.TNumber, references: Types.TSchema[], value: any)
|
||||
return TryConvertNumber(value)
|
||||
}
|
||||
function TObject(schema: Types.TObject, references: Types.TSchema[], value: any): unknown {
|
||||
if (ValueGuard.IsObject(value))
|
||||
if (IsObject(value))
|
||||
return Object.getOwnPropertyNames(schema.properties).reduce((acc, key) => {
|
||||
return value[key] !== undefined ? { ...acc, [key]: Visit(schema.properties[key], references, value[key]) } : { ...acc }
|
||||
}, value)
|
||||
return value
|
||||
}
|
||||
function TPromise(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any): unknown {
|
||||
const propertyKey = Object.getOwnPropertyNames(schema.patternProperties)[0]
|
||||
const property = schema.patternProperties[propertyKey]
|
||||
@@ -221,28 +199,19 @@ function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], v
|
||||
return result
|
||||
}
|
||||
function TRef(schema: Types.TRef<any>, 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)
|
||||
return Visit(Deref(schema, references), references, value)
|
||||
}
|
||||
function TString(schema: Types.TString, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertString(value)
|
||||
}
|
||||
function TSymbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TTemplateLiteral(schema: Types.TTemplateLiteral, references: Types.TSchema[], value: any) {
|
||||
return value
|
||||
return IsString(value) || IsNumber(value) ? Symbol(value) : value
|
||||
}
|
||||
function TThis(schema: Types.TThis, 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)
|
||||
return Visit(Deref(schema, references), references, value)
|
||||
}
|
||||
function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: any): unknown {
|
||||
if (ValueGuard.IsArray(value) && !ValueGuard.IsUndefined(schema.items)) {
|
||||
if (IsArray(value) && !IsUndefined(schema.items)) {
|
||||
return value.map((value, index) => {
|
||||
return index < schema.items!.length ? Visit(schema.items![index], references, value) : value
|
||||
})
|
||||
@@ -255,62 +224,37 @@ function TUndefined(schema: Types.TUndefined, references: Types.TSchema[], value
|
||||
function TUnion(schema: Types.TUnion, references: Types.TSchema[], value: any): unknown {
|
||||
for (const subschema of schema.anyOf) {
|
||||
const converted = Visit(subschema, references, value)
|
||||
if (ValueCheck.Check(subschema, references, converted)) {
|
||||
if (Check(subschema, references, converted)) {
|
||||
return converted
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
function TUint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TUnknown(schema: Types.TUnknown, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TVoid(schema: Types.TVoid, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function TKind(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown {
|
||||
const references_ = ValueGuard.IsString(schema.$id) ? [...references, schema] : references
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema[Types.Kind]) {
|
||||
case 'Any':
|
||||
return TAny(schema_, references_, value)
|
||||
// ------------------------------------------------------
|
||||
// Structural
|
||||
// ------------------------------------------------------
|
||||
case 'Array':
|
||||
return TArray(schema_, references_, value)
|
||||
case 'AsyncIterator':
|
||||
return TAsyncIterator(schema_, references_, value)
|
||||
case 'BigInt':
|
||||
return TBigInt(schema_, references_, value)
|
||||
case 'Boolean':
|
||||
return TBoolean(schema_, references_, value)
|
||||
case 'Constructor':
|
||||
return TConstructor(schema_, references_, value)
|
||||
case 'Date':
|
||||
return TDate(schema_, references_, value)
|
||||
case 'Function':
|
||||
return TFunction(schema_, references_, value)
|
||||
case 'Integer':
|
||||
return TInteger(schema_, references_, value)
|
||||
case 'Intersect':
|
||||
return TIntersect(schema_, references_, value)
|
||||
case 'Iterator':
|
||||
return TIterator(schema_, references_, value)
|
||||
case 'Literal':
|
||||
return TLiteral(schema_, references_, value)
|
||||
case 'Never':
|
||||
return TNever(schema_, references_, value)
|
||||
case 'Null':
|
||||
return TNull(schema_, references_, value)
|
||||
case 'Number':
|
||||
return TNumber(schema_, references_, value)
|
||||
case 'Object':
|
||||
return TObject(schema_, references_, value)
|
||||
case 'Promise':
|
||||
return TPromise(schema_, references_, value)
|
||||
case 'Record':
|
||||
return TRecord(schema_, references_, value)
|
||||
case 'Ref':
|
||||
@@ -319,8 +263,6 @@ function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any):
|
||||
return TString(schema_, references_, value)
|
||||
case 'Symbol':
|
||||
return TSymbol(schema_, references_, value)
|
||||
case 'TemplateLiteral':
|
||||
return TTemplateLiteral(schema_, references_, value)
|
||||
case 'This':
|
||||
return TThis(schema_, references_, value)
|
||||
case 'Tuple':
|
||||
@@ -329,15 +271,25 @@ function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any):
|
||||
return TUndefined(schema_, references_, value)
|
||||
case 'Union':
|
||||
return TUnion(schema_, references_, value)
|
||||
// ------------------------------------------------------
|
||||
// Default
|
||||
// ------------------------------------------------------
|
||||
case 'Any':
|
||||
case 'AsyncIterator':
|
||||
case 'Constructor':
|
||||
case 'Function':
|
||||
case 'Intersect':
|
||||
case 'Iterator':
|
||||
case 'Never':
|
||||
case 'Promise':
|
||||
case 'TemplateLiteral':
|
||||
case 'Uint8Array':
|
||||
return TUint8Array(schema_, references_, value)
|
||||
case 'Unknown':
|
||||
return TUnknown(schema_, references_, value)
|
||||
case 'Void':
|
||||
return TVoid(schema_, references_, value)
|
||||
return Default(value)
|
||||
default:
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueConvertUnknownTypeError(schema_)
|
||||
return TKind(schema_, references_, value)
|
||||
return Default(value)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
@@ -26,62 +26,58 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { HasPropertyKey, IsString } from './guard'
|
||||
import { Check } from './check'
|
||||
import { Deref } from './deref'
|
||||
import * as Types from '../typebox'
|
||||
import * as ValueCheck from './check'
|
||||
import * as ValueGuard from './guard'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueCreateUnknownTypeError extends Error {
|
||||
export class ValueCreateUnknownTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Unknown type')
|
||||
super('Unknown type')
|
||||
}
|
||||
}
|
||||
export class ValueCreateNeverTypeError extends Error {
|
||||
export class ValueCreateNeverTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Never types cannot be created')
|
||||
super('Never types cannot be created')
|
||||
}
|
||||
}
|
||||
export class ValueCreateNotTypeError extends Error {
|
||||
export class ValueCreateNotTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Not types must have a default value')
|
||||
super('Not types must have a default value')
|
||||
}
|
||||
}
|
||||
export class ValueCreateIntersectTypeError extends Error {
|
||||
export class ValueCreateIntersectTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Intersect produced invalid value. Consider using a default value.')
|
||||
super('Intersect produced invalid value. Consider using a default value.')
|
||||
}
|
||||
}
|
||||
export class ValueCreateTempateLiteralTypeError extends Error {
|
||||
export class ValueCreateTempateLiteralTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Can only create template literal values from patterns that produce finite sequences. Consider using a default value.')
|
||||
super('Can only create template literal values from patterns that produce finite sequences. Consider using a default value.')
|
||||
}
|
||||
}
|
||||
export class ValueCreateDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TThis) {
|
||||
super(`ValueCreate: Unable to dereference type with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
export class ValueCreateRecursiveInstantiationError extends Error {
|
||||
export class ValueCreateRecursiveInstantiationError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly recursiveMaxDepth: number) {
|
||||
super('ValueCreate: Value cannot be created as recursive type may produce value of infinite size. Consider using a default.')
|
||||
super('Value cannot be created as recursive type may produce value of infinite size. Consider using a default.')
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Types
|
||||
// --------------------------------------------------------------------------
|
||||
function TAny(schema: Types.TAny, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
function TArray(schema: Types.TArray, references: Types.TSchema[]): any {
|
||||
if (schema.uniqueItems === true && !ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (schema.uniqueItems === true && !HasPropertyKey(schema, 'default')) {
|
||||
throw new Error('ValueCreate.Array: Array with the uniqueItems constraint requires a default value')
|
||||
} else if ('contains' in schema && !ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
} else if ('contains' in schema && !HasPropertyKey(schema, 'default')) {
|
||||
throw new Error('ValueCreate.Array: Array with the contains constraint requires a default value')
|
||||
} else if ('default' in schema) {
|
||||
return schema.default
|
||||
@@ -94,28 +90,28 @@ function TArray(schema: Types.TArray, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TAsyncIterator(schema: Types.TAsyncIterator, references: Types.TSchema[]) {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return (async function* () {})()
|
||||
}
|
||||
}
|
||||
function TBigInt(schema: Types.TBigInt, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return BigInt(0)
|
||||
}
|
||||
}
|
||||
function TBoolean(schema: Types.TBoolean, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
function TConstructor(schema: Types.TConstructor, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
const value = Visit(schema.returns, references) as any
|
||||
@@ -134,7 +130,7 @@ function TConstructor(schema: Types.TConstructor, references: Types.TSchema[]):
|
||||
}
|
||||
}
|
||||
function TDate(schema: Types.TDate, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else if (schema.minimumTimestamp !== undefined) {
|
||||
return new Date(schema.minimumTimestamp)
|
||||
@@ -143,14 +139,14 @@ function TDate(schema: Types.TDate, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TFunction(schema: Types.TFunction, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return () => Visit(schema.returns, references)
|
||||
}
|
||||
}
|
||||
function TInteger(schema: Types.TInteger, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else if (schema.minimum !== undefined) {
|
||||
return schema.minimum
|
||||
@@ -159,7 +155,7 @@ function TInteger(schema: Types.TInteger, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
// Note: The best we can do here is attempt to instance each sub type and apply through object assign. For non-object
|
||||
@@ -169,19 +165,19 @@ function TIntersect(schema: Types.TIntersect, references: Types.TSchema[]): any
|
||||
const next = Visit(schema, references) as any
|
||||
return typeof next === 'object' ? { ...acc, ...next } : next
|
||||
}, {})
|
||||
if (!ValueCheck.Check(schema, references, value)) throw new ValueCreateIntersectTypeError(schema)
|
||||
if (!Check(schema, references, value)) throw new ValueCreateIntersectTypeError(schema)
|
||||
return value
|
||||
}
|
||||
}
|
||||
function TIterator(schema: Types.TIterator, references: Types.TSchema[]) {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return (function* () {})()
|
||||
}
|
||||
}
|
||||
function TLiteral(schema: Types.TLiteral, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return schema.const
|
||||
@@ -191,21 +187,21 @@ function TNever(schema: Types.TNever, references: Types.TSchema[]): any {
|
||||
throw new ValueCreateNeverTypeError(schema)
|
||||
}
|
||||
function TNot(schema: Types.TNot, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
throw new ValueCreateNotTypeError(schema)
|
||||
}
|
||||
}
|
||||
function TNull(schema: Types.TNull, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
function TNumber(schema: Types.TNumber, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else if (schema.minimum !== undefined) {
|
||||
return schema.minimum
|
||||
@@ -214,7 +210,7 @@ function TNumber(schema: Types.TNumber, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TObject(schema: Types.TObject, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
const required = new Set(schema.required)
|
||||
@@ -227,7 +223,7 @@ function TObject(schema: Types.TObject, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TPromise(schema: Types.TPromise<any>, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return Promise.resolve(Visit(schema.item, references))
|
||||
@@ -235,7 +231,7 @@ function TPromise(schema: Types.TPromise<any>, references: Types.TSchema[]): any
|
||||
}
|
||||
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[]): any {
|
||||
const [keyPattern, valueSchema] = Object.entries(schema.patternProperties)[0]
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else if (!(keyPattern === Types.PatternStringExact || keyPattern === Types.PatternNumberExact)) {
|
||||
const propertyKeys = keyPattern.slice(1, keyPattern.length - 1).split('|')
|
||||
@@ -247,30 +243,27 @@ function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[]):
|
||||
}
|
||||
}
|
||||
function TRef(schema: Types.TRef<any>, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueCreateDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references)
|
||||
return Visit(Deref(schema, references), references)
|
||||
}
|
||||
}
|
||||
function TString(schema: Types.TString, references: Types.TSchema[]): any {
|
||||
if (schema.pattern !== undefined) {
|
||||
if (!ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (!HasPropertyKey(schema, 'default')) {
|
||||
throw new Error('ValueCreate.String: String types with patterns must specify a default value')
|
||||
} else {
|
||||
return schema.default
|
||||
}
|
||||
} else if (schema.format !== undefined) {
|
||||
if (!ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (!HasPropertyKey(schema, 'default')) {
|
||||
throw new Error('ValueCreate.String: String types with formats must specify a default value')
|
||||
} else {
|
||||
return schema.default
|
||||
}
|
||||
} else {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else if (schema.minLength !== undefined) {
|
||||
return Array.from({ length: schema.minLength })
|
||||
@@ -282,7 +275,7 @@ function TString(schema: Types.TString, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TSymbol(schema: Types.TString, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else if ('value' in schema) {
|
||||
return Symbol.for(schema.value)
|
||||
@@ -291,7 +284,7 @@ function TSymbol(schema: Types.TString, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TTemplateLiteral(schema: Types.TTemplateLiteral, references: Types.TSchema[]) {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
}
|
||||
const expression = Types.TemplateLiteralParser.ParseExact(schema.pattern)
|
||||
@@ -301,17 +294,14 @@ function TTemplateLiteral(schema: Types.TTemplateLiteral, references: Types.TSch
|
||||
}
|
||||
function TThis(schema: Types.TThis, references: Types.TSchema[]): any {
|
||||
if (recursiveDepth++ > recursiveMaxDepth) throw new ValueCreateRecursiveInstantiationError(schema, recursiveMaxDepth)
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueCreateDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references)
|
||||
return Visit(Deref(schema, references), references)
|
||||
}
|
||||
}
|
||||
function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
}
|
||||
if (schema.items === undefined) {
|
||||
@@ -321,14 +311,14 @@ function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TUndefined(schema: Types.TUndefined, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
function TUnion(schema: Types.TUnion<any[]>, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else if (schema.anyOf.length === 0) {
|
||||
throw new Error('ValueCreate.Union: Cannot create Union with zero variants')
|
||||
@@ -337,7 +327,7 @@ function TUnion(schema: Types.TUnion<any[]>, references: Types.TSchema[]): any {
|
||||
}
|
||||
}
|
||||
function TUint8Array(schema: Types.TUint8Array, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else if (schema.minByteLength !== undefined) {
|
||||
return new Uint8Array(schema.minByteLength)
|
||||
@@ -346,28 +336,28 @@ function TUint8Array(schema: Types.TUint8Array, references: Types.TSchema[]): an
|
||||
}
|
||||
}
|
||||
function TUnknown(schema: Types.TUnknown, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
function TVoid(schema: Types.TVoid, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
return void 0
|
||||
}
|
||||
}
|
||||
function TKind(schema: Types.TSchema, references: Types.TSchema[]): any {
|
||||
if (ValueGuard.HasPropertyKey(schema, 'default')) {
|
||||
if (HasPropertyKey(schema, 'default')) {
|
||||
return schema.default
|
||||
} else {
|
||||
throw new Error('ValueCreate: User defined types must specify a default value')
|
||||
throw new Error('User defined types must specify a default value')
|
||||
}
|
||||
}
|
||||
function Visit(schema: Types.TSchema, references: Types.TSchema[]): unknown {
|
||||
const references_ = ValueGuard.IsString(schema.$id) ? [...references, schema] : references
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema_[Types.Kind]) {
|
||||
case 'Any':
|
||||
|
||||
@@ -26,10 +26,11 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { IsPlainObject, IsArray, IsTypedArray, IsValueType, IsSymbol, IsUndefined } from './guard'
|
||||
import type { ObjectType, ArrayType, TypedArrayType, ValueType } from './guard'
|
||||
import { Type, Static } from '../typebox'
|
||||
import { ValuePointer } from './pointer'
|
||||
import * as ValueGuard from './guard'
|
||||
import * as ValueClone from './clone'
|
||||
import { Clone } from './clone'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Commands
|
||||
@@ -58,12 +59,12 @@ export const Edit = Type.Union([Insert, Update, Delete])
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueDeltaObjectWithSymbolKeyError extends Error {
|
||||
constructor(public readonly key: unknown) {
|
||||
super('ValueDelta: Cannot diff objects with symbol keys')
|
||||
super('Cannot diff objects with symbol keys')
|
||||
}
|
||||
}
|
||||
export class ValueDeltaUnableToDiffUnknownValue extends Error {
|
||||
constructor(public readonly value: unknown) {
|
||||
super('ValueDelta: Unable to create diff edits for unknown value')
|
||||
super('Unable to create diff edits for unknown value')
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -81,30 +82,30 @@ function CreateDelete(path: string): Edit {
|
||||
// --------------------------------------------------------------------------
|
||||
// Diffing Generators
|
||||
// --------------------------------------------------------------------------
|
||||
function* ObjectType(path: string, current: ValueGuard.ObjectType, next: unknown): IterableIterator<Edit> {
|
||||
if (!ValueGuard.IsPlainObject(next)) return yield CreateUpdate(path, next)
|
||||
function* ObjectType(path: string, current: ObjectType, next: unknown): IterableIterator<Edit> {
|
||||
if (!IsPlainObject(next)) return yield CreateUpdate(path, next)
|
||||
const currentKeys = [...Object.keys(current), ...Object.getOwnPropertySymbols(current)]
|
||||
const nextKeys = [...Object.keys(next), ...Object.getOwnPropertySymbols(next)]
|
||||
for (const key of currentKeys) {
|
||||
if (ValueGuard.IsSymbol(key)) throw new ValueDeltaObjectWithSymbolKeyError(key)
|
||||
if (ValueGuard.IsUndefined(next[key]) && nextKeys.includes(key)) yield CreateUpdate(`${path}/${String(key)}`, undefined)
|
||||
if (IsSymbol(key)) throw new ValueDeltaObjectWithSymbolKeyError(key)
|
||||
if (IsUndefined(next[key]) && nextKeys.includes(key)) yield CreateUpdate(`${path}/${String(key)}`, undefined)
|
||||
}
|
||||
for (const key of nextKeys) {
|
||||
if (ValueGuard.IsUndefined(current[key]) || ValueGuard.IsUndefined(next[key])) continue
|
||||
if (ValueGuard.IsSymbol(key)) throw new ValueDeltaObjectWithSymbolKeyError(key)
|
||||
if (IsUndefined(current[key]) || IsUndefined(next[key])) continue
|
||||
if (IsSymbol(key)) throw new ValueDeltaObjectWithSymbolKeyError(key)
|
||||
yield* Visit(`${path}/${String(key)}`, current[key], next[key])
|
||||
}
|
||||
for (const key of nextKeys) {
|
||||
if (ValueGuard.IsSymbol(key)) throw new ValueDeltaObjectWithSymbolKeyError(key)
|
||||
if (ValueGuard.IsUndefined(current[key])) yield CreateInsert(`${path}/${String(key)}`, next[key])
|
||||
if (IsSymbol(key)) throw new ValueDeltaObjectWithSymbolKeyError(key)
|
||||
if (IsUndefined(current[key])) yield CreateInsert(`${path}/${String(key)}`, next[key])
|
||||
}
|
||||
for (const key of currentKeys.reverse()) {
|
||||
if (ValueGuard.IsSymbol(key)) throw new ValueDeltaObjectWithSymbolKeyError(key)
|
||||
if (ValueGuard.IsUndefined(next[key]) && !nextKeys.includes(key)) yield CreateDelete(`${path}/${String(key)}`)
|
||||
if (IsSymbol(key)) throw new ValueDeltaObjectWithSymbolKeyError(key)
|
||||
if (IsUndefined(next[key]) && !nextKeys.includes(key)) yield CreateDelete(`${path}/${String(key)}`)
|
||||
}
|
||||
}
|
||||
function* ArrayType(path: string, current: ValueGuard.ArrayType, next: unknown): IterableIterator<Edit> {
|
||||
if (!ValueGuard.IsArray(next)) return yield CreateUpdate(path, next)
|
||||
function* ArrayType(path: string, current: ArrayType, next: unknown): IterableIterator<Edit> {
|
||||
if (!IsArray(next)) return yield CreateUpdate(path, next)
|
||||
for (let i = 0; i < Math.min(current.length, next.length); i++) {
|
||||
yield* Visit(`${path}/${i}`, current[i], next[i])
|
||||
}
|
||||
@@ -117,21 +118,21 @@ function* ArrayType(path: string, current: ValueGuard.ArrayType, next: unknown):
|
||||
yield CreateDelete(`${path}/${i}`)
|
||||
}
|
||||
}
|
||||
function* TypedArrayType(path: string, current: ValueGuard.TypedArrayType, next: unknown): IterableIterator<Edit> {
|
||||
if (!ValueGuard.IsTypedArray(next) || current.length !== next.length || Object.getPrototypeOf(current).constructor.name !== Object.getPrototypeOf(next).constructor.name) return yield CreateUpdate(path, next)
|
||||
function* TypedArrayType(path: string, current: TypedArrayType, next: unknown): IterableIterator<Edit> {
|
||||
if (!IsTypedArray(next) || current.length !== next.length || Object.getPrototypeOf(current).constructor.name !== Object.getPrototypeOf(next).constructor.name) return yield CreateUpdate(path, next)
|
||||
for (let i = 0; i < Math.min(current.length, next.length); i++) {
|
||||
yield* Visit(`${path}/${i}`, current[i], next[i])
|
||||
}
|
||||
}
|
||||
function* ValueType(path: string, current: ValueGuard.ValueType, next: unknown): IterableIterator<Edit> {
|
||||
function* ValueType(path: string, current: ValueType, next: unknown): IterableIterator<Edit> {
|
||||
if (current === next) return
|
||||
yield CreateUpdate(path, next)
|
||||
}
|
||||
function* Visit(path: string, current: unknown, next: unknown): IterableIterator<Edit> {
|
||||
if (ValueGuard.IsPlainObject(current)) return yield* ObjectType(path, current, next)
|
||||
if (ValueGuard.IsArray(current)) return yield* ArrayType(path, current, next)
|
||||
if (ValueGuard.IsTypedArray(current)) return yield* TypedArrayType(path, current, next)
|
||||
if (ValueGuard.IsValueType(current)) return yield* ValueType(path, current, next)
|
||||
if (IsPlainObject(current)) return yield* ObjectType(path, current, next)
|
||||
if (IsArray(current)) return yield* ArrayType(path, current, next)
|
||||
if (IsTypedArray(current)) return yield* TypedArrayType(path, current, next)
|
||||
if (IsValueType(current)) return yield* ValueType(path, current, next)
|
||||
throw new ValueDeltaUnableToDiffUnknownValue(current)
|
||||
}
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -151,12 +152,12 @@ function IsIdentity(edits: Edit[]) {
|
||||
}
|
||||
export function Patch<T = any>(current: unknown, edits: Edit[]): T {
|
||||
if (IsRootUpdate(edits)) {
|
||||
return ValueClone.Clone(edits[0].value) as T
|
||||
return Clone(edits[0].value) as T
|
||||
}
|
||||
if (IsIdentity(edits)) {
|
||||
return ValueClone.Clone(current) as T
|
||||
return Clone(current) as T
|
||||
}
|
||||
const clone = ValueClone.Clone(current)
|
||||
const clone = Clone(current)
|
||||
for (const edit of edits) {
|
||||
switch (edit.type) {
|
||||
case 'insert': {
|
||||
|
||||
41
src/value/deref.ts
Normal file
41
src/value/deref.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/value
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
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 { TypeBoxError, TSchema, TRef, TThis } from '../typebox'
|
||||
|
||||
export class TypeDereferenceError extends TypeBoxError {
|
||||
constructor(public readonly schema: TRef | TThis) {
|
||||
super(`Unable to dereference schema with $id '${schema.$id}'`)
|
||||
}
|
||||
}
|
||||
/** Dereferences a schema from the references array or throws if not found */
|
||||
export function Deref(schema: TRef | TThis, references: TSchema[]): TSchema {
|
||||
const index = references.findIndex((target) => target.$id === schema.$ref)
|
||||
if (index === -1) throw new TypeDereferenceError(schema)
|
||||
return references[index]
|
||||
}
|
||||
@@ -26,30 +26,31 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as ValueGuard from './guard'
|
||||
import { IsPlainObject, IsDate, IsArray, IsTypedArray, IsValueType } from './guard'
|
||||
import type { ObjectType, ArrayType, TypedArrayType, ValueType } from './guard'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Equality Checks
|
||||
// --------------------------------------------------------------------------
|
||||
function ObjectType(left: ValueGuard.ObjectType, right: unknown): boolean {
|
||||
if (!ValueGuard.IsPlainObject(right)) return false
|
||||
function ObjectType(left: ObjectType, right: unknown): boolean {
|
||||
if (!IsPlainObject(right)) return false
|
||||
const leftKeys = [...Object.keys(left), ...Object.getOwnPropertySymbols(left)]
|
||||
const rightKeys = [...Object.keys(right), ...Object.getOwnPropertySymbols(right)]
|
||||
if (leftKeys.length !== rightKeys.length) return false
|
||||
return leftKeys.every((key) => Equal(left[key], right[key]))
|
||||
}
|
||||
function DateType(left: Date, right: unknown): any {
|
||||
return ValueGuard.IsDate(right) && left.getTime() === right.getTime()
|
||||
return IsDate(right) && left.getTime() === right.getTime()
|
||||
}
|
||||
function ArrayType(left: ValueGuard.ArrayType, right: unknown): any {
|
||||
if (!ValueGuard.IsArray(right) || left.length !== right.length) return false
|
||||
function ArrayType(left: ArrayType, right: unknown): any {
|
||||
if (!IsArray(right) || left.length !== right.length) return false
|
||||
return left.every((value, index) => Equal(value, right[index]))
|
||||
}
|
||||
function TypedArrayType(left: ValueGuard.TypedArrayType, right: unknown): any {
|
||||
if (!ValueGuard.IsTypedArray(right) || left.length !== right.length || Object.getPrototypeOf(left).constructor.name !== Object.getPrototypeOf(right).constructor.name) return false
|
||||
function TypedArrayType(left: TypedArrayType, right: unknown): any {
|
||||
if (!IsTypedArray(right) || left.length !== right.length || Object.getPrototypeOf(left).constructor.name !== Object.getPrototypeOf(right).constructor.name) return false
|
||||
return left.every((value, index) => Equal(value, right[index]))
|
||||
}
|
||||
function ValueType(left: ValueGuard.ValueType, right: unknown): any {
|
||||
function ValueType(left: ValueType, right: unknown): any {
|
||||
return left === right
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -57,10 +58,10 @@ function ValueType(left: ValueGuard.ValueType, right: unknown): any {
|
||||
// --------------------------------------------------------------------------
|
||||
/** Returns true if the left value deep-equals the right */
|
||||
export function Equal<T>(left: T, right: unknown): right is T {
|
||||
if (ValueGuard.IsPlainObject(left)) return ObjectType(left, right)
|
||||
if (ValueGuard.IsDate(left)) return DateType(left, right)
|
||||
if (ValueGuard.IsTypedArray(left)) return TypedArrayType(left, right)
|
||||
if (ValueGuard.IsArray(left)) return ArrayType(left, right)
|
||||
if (ValueGuard.IsValueType(left)) return ValueType(left, right)
|
||||
if (IsPlainObject(left)) return ObjectType(left, right)
|
||||
if (IsDate(left)) return DateType(left, right)
|
||||
if (IsTypedArray(left)) return TypedArrayType(left, right)
|
||||
if (IsArray(left)) return ArrayType(left, right)
|
||||
if (IsValueType(left)) return ValueType(left, right)
|
||||
throw new Error('ValueEquals: Unable to compare value')
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export function IsUint8Array(value: unknown): value is Uint8Array {
|
||||
}
|
||||
/** Returns true if this value is a Date */
|
||||
export function IsDate(value: unknown): value is Date {
|
||||
return value instanceof Date
|
||||
return value instanceof Date && Number.isFinite(value.getTime())
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Standard
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/hash
|
||||
@sinclair/typebox/value
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -26,14 +26,14 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as ValueGuard from './guard'
|
||||
import { IsArray, IsBoolean, IsBigInt, IsDate, IsNull, IsNumber, IsPlainObject, IsString, IsSymbol, IsUint8Array, IsUndefined } from './guard'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueHashError extends Error {
|
||||
constructor(public readonly value: unknown) {
|
||||
super(`Hash: Unable to hash value`)
|
||||
super(`Unable to hash value`)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -122,17 +122,17 @@ function UndefinedType(value: undefined) {
|
||||
return FNV1A64(ByteMarker.Undefined)
|
||||
}
|
||||
function Visit(value: any) {
|
||||
if (ValueGuard.IsArray(value)) return ArrayType(value)
|
||||
if (ValueGuard.IsBoolean(value)) return BooleanType(value)
|
||||
if (ValueGuard.IsBigInt(value)) return BigIntType(value)
|
||||
if (ValueGuard.IsDate(value)) return DateType(value)
|
||||
if (ValueGuard.IsNull(value)) return NullType(value)
|
||||
if (ValueGuard.IsNumber(value)) return NumberType(value)
|
||||
if (ValueGuard.IsPlainObject(value)) return ObjectType(value)
|
||||
if (ValueGuard.IsString(value)) return StringType(value)
|
||||
if (ValueGuard.IsSymbol(value)) return SymbolType(value)
|
||||
if (ValueGuard.IsUint8Array(value)) return Uint8ArrayType(value)
|
||||
if (ValueGuard.IsUndefined(value)) return UndefinedType(value)
|
||||
if (IsArray(value)) return ArrayType(value)
|
||||
if (IsBoolean(value)) return BooleanType(value)
|
||||
if (IsBigInt(value)) return BigIntType(value)
|
||||
if (IsDate(value)) return DateType(value)
|
||||
if (IsNull(value)) return NullType(value)
|
||||
if (IsNumber(value)) return NumberType(value)
|
||||
if (IsPlainObject(value)) return ObjectType(value)
|
||||
if (IsString(value)) return StringType(value)
|
||||
if (IsSymbol(value)) return SymbolType(value)
|
||||
if (IsUint8Array(value)) return Uint8ArrayType(value)
|
||||
if (IsUndefined(value)) return UndefinedType(value)
|
||||
throw new ValueHashError(value)
|
||||
}
|
||||
function FNV1A64(byte: number) {
|
||||
|
||||
@@ -26,21 +26,21 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { IsPlainObject, IsArray, IsTypedArray, IsValueType, type TypedArrayType } from './guard'
|
||||
import { ValuePointer } from './pointer'
|
||||
import * as ValueClone from './clone'
|
||||
import * as ValueGuard from './guard'
|
||||
import { Clone } from './clone'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueMutateTypeMismatchError extends Error {
|
||||
constructor() {
|
||||
super('ValueMutate: Cannot assign due type mismatch of assignable values')
|
||||
super('Cannot assign due type mismatch of assignable values')
|
||||
}
|
||||
}
|
||||
export class ValueMutateInvalidRootMutationError extends Error {
|
||||
constructor() {
|
||||
super('ValueMutate: Only object and array types can be mutated at the root level')
|
||||
super('Only object and array types can be mutated at the root level')
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -48,8 +48,8 @@ export class ValueMutateInvalidRootMutationError extends Error {
|
||||
// --------------------------------------------------------------------------
|
||||
export type Mutable = { [key: string]: unknown } | unknown[]
|
||||
function ObjectType(root: Mutable, path: string, current: unknown, next: Record<string, unknown>) {
|
||||
if (!ValueGuard.IsPlainObject(current)) {
|
||||
ValuePointer.Set(root, path, ValueClone.Clone(next))
|
||||
if (!IsPlainObject(current)) {
|
||||
ValuePointer.Set(root, path, Clone(next))
|
||||
} else {
|
||||
const currentKeys = Object.keys(current)
|
||||
const nextKeys = Object.keys(next)
|
||||
@@ -69,8 +69,8 @@ function ObjectType(root: Mutable, path: string, current: unknown, next: Record<
|
||||
}
|
||||
}
|
||||
function ArrayType(root: Mutable, path: string, current: unknown, next: unknown[]) {
|
||||
if (!ValueGuard.IsArray(current)) {
|
||||
ValuePointer.Set(root, path, ValueClone.Clone(next))
|
||||
if (!IsArray(current)) {
|
||||
ValuePointer.Set(root, path, Clone(next))
|
||||
} else {
|
||||
for (let index = 0; index < next.length; index++) {
|
||||
Visit(root, `${path}/${index}`, current[index], next[index])
|
||||
@@ -78,13 +78,13 @@ function ArrayType(root: Mutable, path: string, current: unknown, next: unknown[
|
||||
current.splice(next.length)
|
||||
}
|
||||
}
|
||||
function TypedArrayType(root: Mutable, path: string, current: unknown, next: ValueGuard.TypedArrayType) {
|
||||
if (ValueGuard.IsTypedArray(current) && current.length === next.length) {
|
||||
function TypedArrayType(root: Mutable, path: string, current: unknown, next: TypedArrayType) {
|
||||
if (IsTypedArray(current) && current.length === next.length) {
|
||||
for (let i = 0; i < current.length; i++) {
|
||||
current[i] = next[i]
|
||||
}
|
||||
} else {
|
||||
ValuePointer.Set(root, path, ValueClone.Clone(next))
|
||||
ValuePointer.Set(root, path, Clone(next))
|
||||
}
|
||||
}
|
||||
function ValueType(root: Mutable, path: string, current: unknown, next: unknown) {
|
||||
@@ -92,22 +92,22 @@ function ValueType(root: Mutable, path: string, current: unknown, next: unknown)
|
||||
ValuePointer.Set(root, path, next)
|
||||
}
|
||||
function Visit(root: Mutable, path: string, current: unknown, next: unknown) {
|
||||
if (ValueGuard.IsArray(next)) return ArrayType(root, path, current, next)
|
||||
if (ValueGuard.IsTypedArray(next)) return TypedArrayType(root, path, current, next)
|
||||
if (ValueGuard.IsPlainObject(next)) return ObjectType(root, path, current, next)
|
||||
if (ValueGuard.IsValueType(next)) return ValueType(root, path, current, next)
|
||||
if (IsArray(next)) return ArrayType(root, path, current, next)
|
||||
if (IsTypedArray(next)) return TypedArrayType(root, path, current, next)
|
||||
if (IsPlainObject(next)) return ObjectType(root, path, current, next)
|
||||
if (IsValueType(next)) return ValueType(root, path, current, next)
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Mutate
|
||||
// --------------------------------------------------------------------------
|
||||
function IsNonMutableValue(value: unknown): value is Mutable {
|
||||
return ValueGuard.IsTypedArray(value) || ValueGuard.IsValueType(value)
|
||||
return IsTypedArray(value) || IsValueType(value)
|
||||
}
|
||||
function IsMismatchedValue(current: unknown, next: unknown) {
|
||||
// prettier-ignore
|
||||
return (
|
||||
(ValueGuard.IsPlainObject(current) && ValueGuard.IsArray(next)) ||
|
||||
(ValueGuard.IsArray(current) && ValueGuard.IsPlainObject(next))
|
||||
(IsPlainObject(current) && IsArray(next)) ||
|
||||
(IsArray(current) && IsPlainObject(next))
|
||||
)
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
@@ -31,12 +31,12 @@ THE SOFTWARE.
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValuePointerRootSetError extends Error {
|
||||
constructor(public readonly value: unknown, public readonly path: string, public readonly update: unknown) {
|
||||
super('ValuePointer: Cannot set root value')
|
||||
super('Cannot set root value')
|
||||
}
|
||||
}
|
||||
export class ValuePointerRootDeleteError extends Error {
|
||||
constructor(public readonly value: unknown, public readonly path: string) {
|
||||
super('ValuePointer: Cannot delete root value')
|
||||
super('Cannot delete root value')
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
473
src/value/transform.ts
Normal file
473
src/value/transform.ts
Normal file
@@ -0,0 +1,473 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/value
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
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 { IsString, IsPlainObject, IsArray, IsValueType } from './guard'
|
||||
import { ValueError } from '../errors/errors'
|
||||
import { Deref } from './deref'
|
||||
import * as Types from '../typebox'
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// CheckFunction
|
||||
// -------------------------------------------------------------------------
|
||||
export type CheckFunction = (schema: Types.TSchema, references: Types.TSchema[], value: unknown) => boolean
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Errors
|
||||
// -------------------------------------------------------------------------
|
||||
export class TransformUnknownTypeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TRef | Types.TThis) {
|
||||
super(`Unknown type`)
|
||||
}
|
||||
}
|
||||
export class TransformDecodeCheckError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown, public readonly error: ValueError) {
|
||||
super(`Unable to decode due to invalid value`)
|
||||
}
|
||||
}
|
||||
export class TransformEncodeCheckError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown, public readonly error: ValueError) {
|
||||
super(`Unable to encode due to invalid value`)
|
||||
}
|
||||
}
|
||||
export class TransformDecodeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown, error: any) {
|
||||
super(`${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
}
|
||||
}
|
||||
export class TransformEncodeError extends Types.TypeBoxError {
|
||||
constructor(public readonly schema: Types.TSchema, public readonly value: unknown, error: any) {
|
||||
super(`${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
// HasTransform
|
||||
// -------------------------------------------------------------------------
|
||||
/** Recursively checks a schema for transform codecs */
|
||||
export namespace HasTransform {
|
||||
function TArray(schema: Types.TArray, references: Types.TSchema[]): boolean {
|
||||
return Types.TypeGuard.TTransform(schema) || Visit(schema.items, references)
|
||||
}
|
||||
function TAsyncIterator(schema: Types.TAsyncIterator, references: Types.TSchema[]): boolean {
|
||||
return Types.TypeGuard.TTransform(schema) || Visit(schema.items, references)
|
||||
}
|
||||
function TConstructor(schema: Types.TConstructor, references: Types.TSchema[]) {
|
||||
return Types.TypeGuard.TTransform(schema) || Visit(schema.returns, references) || schema.parameters.some((schema) => Visit(schema.returns, references))
|
||||
}
|
||||
function TFunction(schema: Types.TFunction, references: Types.TSchema[]) {
|
||||
return Types.TypeGuard.TTransform(schema) || Visit(schema.returns, references) || schema.parameters.some((schema) => Visit(schema.returns, references))
|
||||
}
|
||||
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[]) {
|
||||
return Types.TypeGuard.TTransform(schema) || Types.TypeGuard.TTransform(schema.unevaluatedProperties) || schema.allOf.some((schema) => Visit(schema, references))
|
||||
}
|
||||
function TIterator(schema: Types.TIterator, references: Types.TSchema[]) {
|
||||
return Types.TypeGuard.TTransform(schema) || Visit(schema.items, references)
|
||||
}
|
||||
function TNot(schema: Types.TNot, references: Types.TSchema[]) {
|
||||
return Types.TypeGuard.TTransform(schema) || Visit(schema.not, references)
|
||||
}
|
||||
function TObject(schema: Types.TObject, references: Types.TSchema[]) {
|
||||
// prettier-ignore
|
||||
return (Types.TypeGuard.TTransform(schema) || Object.values(schema.properties).some((schema) => Visit(schema, references)) || Types.TypeGuard.TSchema(schema.additionalProperties) && Visit(schema.additionalProperties, references)
|
||||
)
|
||||
}
|
||||
function TPromise(schema: Types.TPromise, references: Types.TSchema[]) {
|
||||
return Types.TypeGuard.TTransform(schema) || Visit(schema.item, references)
|
||||
}
|
||||
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[]) {
|
||||
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
|
||||
const property = schema.patternProperties[pattern]
|
||||
return Types.TypeGuard.TTransform(schema) || Visit(property, references) || (Types.TypeGuard.TSchema(schema.additionalProperties) && Types.TypeGuard.TTransform(schema.additionalProperties))
|
||||
}
|
||||
function TRef(schema: Types.TRef<any>, references: Types.TSchema[]) {
|
||||
if (Types.TypeGuard.TTransform(schema)) return true
|
||||
return Visit(Deref(schema, references), references)
|
||||
}
|
||||
function TThis(schema: Types.TThis, references: Types.TSchema[]) {
|
||||
if (Types.TypeGuard.TTransform(schema)) return true
|
||||
return Visit(Deref(schema, references), references)
|
||||
}
|
||||
function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[]) {
|
||||
return Types.TypeGuard.TTransform(schema) || (Types.TypeGuard.TSchema(schema.items) && schema.items.some((schema) => Visit(schema, references)))
|
||||
}
|
||||
function TUnion(schema: Types.TUnion, references: Types.TSchema[]) {
|
||||
return Types.TypeGuard.TTransform(schema) || schema.anyOf.some((schema) => Visit(schema, references))
|
||||
}
|
||||
function Visit(schema: Types.TSchema, references: Types.TSchema[]): boolean {
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
if (schema.$id && visited.has(schema.$id)) return false
|
||||
if (schema.$id) visited.add(schema.$id)
|
||||
switch (schema[Types.Kind]) {
|
||||
// ------------------------------------------------------
|
||||
// Structural
|
||||
// ------------------------------------------------------
|
||||
case 'Array':
|
||||
return TArray(schema_, references_)
|
||||
case 'AsyncIterator':
|
||||
return TAsyncIterator(schema_, references_)
|
||||
case 'Constructor':
|
||||
return TConstructor(schema_, references_)
|
||||
case 'Function':
|
||||
return TFunction(schema_, references_)
|
||||
case 'Intersect':
|
||||
return TIntersect(schema_, references_)
|
||||
case 'Iterator':
|
||||
return TIterator(schema_, references_)
|
||||
case 'Not':
|
||||
return TNot(schema_, references_)
|
||||
case 'Object':
|
||||
return TObject(schema_, references_)
|
||||
case 'Promise':
|
||||
return TPromise(schema_, references_)
|
||||
case 'Record':
|
||||
return TRecord(schema_, references_)
|
||||
case 'Ref':
|
||||
return TRef(schema_, references_)
|
||||
case 'This':
|
||||
return TThis(schema_, references_)
|
||||
case 'Tuple':
|
||||
return TTuple(schema_, references_)
|
||||
case 'Union':
|
||||
return TUnion(schema_, references_)
|
||||
// ------------------------------------------------------
|
||||
// Default
|
||||
// ------------------------------------------------------
|
||||
case 'Any':
|
||||
case 'BigInt':
|
||||
case 'Boolean':
|
||||
case 'Date':
|
||||
case 'Integer':
|
||||
case 'Literal':
|
||||
case 'Never':
|
||||
case 'Null':
|
||||
case 'Number':
|
||||
case 'String':
|
||||
case 'Symbol':
|
||||
case 'TemplateLiteral':
|
||||
case 'Undefined':
|
||||
case 'Uint8Array':
|
||||
case 'Unknown':
|
||||
case 'Void':
|
||||
return Types.TypeGuard.TTransform(schema)
|
||||
default:
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new TransformUnknownTypeError(schema_)
|
||||
return Types.TypeGuard.TTransform(schema)
|
||||
}
|
||||
}
|
||||
const visited = new Set<string>()
|
||||
/** Returns true if this schema contains a transform codec */
|
||||
export function Has(schema: Types.TSchema, references: Types.TSchema[]): boolean {
|
||||
visited.clear()
|
||||
return Visit(schema, references)
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
// DecodeTransform
|
||||
// -------------------------------------------------------------------------
|
||||
/** Decodes a value using transform decoders if available. Does not ensure correct results. */
|
||||
export namespace DecodeTransform {
|
||||
function Default(schema: Types.TSchema, value: any) {
|
||||
try {
|
||||
return Types.TypeGuard.TTransform(schema) ? schema[Types.Transform].Decode(value) : value
|
||||
} catch (error) {
|
||||
throw new TransformDecodeError(schema, value, error)
|
||||
}
|
||||
}
|
||||
function TArray(schema: Types.TArray, references: Types.TSchema[], value: any): any {
|
||||
const elements1 = value.map((value: any) => Visit(schema.items, references, value)) as unknown[]
|
||||
return Default(schema, elements1)
|
||||
}
|
||||
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any) {
|
||||
if (!IsPlainObject(value) || IsValueType(value)) return Default(schema, value)
|
||||
const keys = Types.KeyResolver.ResolveKeys(schema, { includePatterns: false })
|
||||
const properties1 = Object.entries(value).reduce((acc, [key, value]) => {
|
||||
return !keys.includes(key) ? { ...acc, [key]: value } : { ...acc, [key]: Default(Types.IndexedAccessor.Resolve(schema, [key]), value) }
|
||||
}, {} as Record<any, any>)
|
||||
if (!Types.TypeGuard.TTransform(schema.unevaluatedProperties)) return Default(schema, properties1)
|
||||
const properties2 = Object.entries(properties1).reduce((acc, [key, value]) => {
|
||||
return keys.includes(key) ? { ...acc, [key]: value } : { ...acc, [key]: Default(schema.unevaluatedProperties as Types.TSchema, value) }
|
||||
}, {} as Record<any, any>)
|
||||
return Default(schema, properties2)
|
||||
}
|
||||
function TNot(schema: Types.TNot, references: Types.TSchema[], value: any) {
|
||||
const value1 = Visit(schema.not, references, value)
|
||||
return Default(schema, value1)
|
||||
}
|
||||
function TObject(schema: Types.TObject, references: Types.TSchema[], value: any) {
|
||||
if (!IsPlainObject(value)) return Default(schema, value)
|
||||
const properties1 = Object.entries(value).reduce((acc, [key, value]) => {
|
||||
return !(key in schema.properties) ? { ...acc, [key]: value } : { ...acc, [key]: Visit(schema.properties[key], references, value) }
|
||||
}, {} as Record<any, any>)
|
||||
if (!Types.TypeGuard.TSchema(schema.additionalProperties)) return Default(schema, properties1)
|
||||
const additionalProperties = schema.additionalProperties as Types.TSchema
|
||||
const properties2 = Object.entries(properties1).reduce((acc, [key, value]) => {
|
||||
return key in schema.properties ? { ...acc, [key]: value } : { ...acc, [key]: Visit(additionalProperties, references, value) }
|
||||
}, {} as Record<any, any>)
|
||||
return Default(schema, properties2)
|
||||
}
|
||||
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any) {
|
||||
if (!IsPlainObject(value)) return Default(schema, value)
|
||||
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
|
||||
const property = schema.patternProperties[pattern]
|
||||
const regex = new RegExp(pattern)
|
||||
const properties1 = Object.entries(value).reduce((acc, [key, value]) => {
|
||||
return !regex.test(key) ? { ...acc, [key]: value } : { ...acc, [key]: Visit(property, references, value) }
|
||||
}, {} as Record<any, any>)
|
||||
if (!Types.TypeGuard.TSchema(schema.additionalProperties)) return Default(schema, properties1)
|
||||
const additionalProperties = schema.additionalProperties as Types.TSchema
|
||||
const properties2 = Object.entries(properties1).reduce((acc, [key, value]) => {
|
||||
return regex.test(key) ? { ...acc, [key]: value } : { ...acc, [key]: Visit(additionalProperties, references, value) }
|
||||
}, {} as Record<any, any>)
|
||||
return Default(schema, properties2)
|
||||
}
|
||||
function TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: any) {
|
||||
const target = Deref(schema, references)
|
||||
const resolved = Visit(target, references, value)
|
||||
return Default(schema, resolved)
|
||||
}
|
||||
function TThis(schema: Types.TThis, references: Types.TSchema[], value: any) {
|
||||
const target = Deref(schema, references)
|
||||
const resolved = Visit(target, references, value)
|
||||
return Default(schema, resolved)
|
||||
}
|
||||
function TTuple(schema: Types.TTuple, references: Types.TSchema[], value: any) {
|
||||
const value1 = IsArray(schema.items) ? schema.items.map((schema, index) => Visit(schema, references, value[index])) : []
|
||||
return Default(schema, value1)
|
||||
}
|
||||
function TUnion(schema: Types.TUnion, references: Types.TSchema[], value: any) {
|
||||
const value1 = Default(schema, value)
|
||||
for (const subschema of schema.anyOf) {
|
||||
if (!checkFunction(subschema, references, value1)) continue
|
||||
return Visit(subschema, references, value1)
|
||||
}
|
||||
return value1
|
||||
}
|
||||
function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): any {
|
||||
const references_ = typeof schema.$id === 'string' ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema[Types.Kind]) {
|
||||
// ------------------------------------------------------
|
||||
// Structural
|
||||
// ------------------------------------------------------
|
||||
case 'Array':
|
||||
return TArray(schema_, references_, value)
|
||||
case 'Intersect':
|
||||
return TIntersect(schema_, references_, value)
|
||||
case 'Not':
|
||||
return TNot(schema_, references_, value)
|
||||
case 'Object':
|
||||
return TObject(schema_, references_, value)
|
||||
case 'Record':
|
||||
return TRecord(schema_, references_, value)
|
||||
case 'Ref':
|
||||
return TRef(schema_, references_, value)
|
||||
case 'Symbol':
|
||||
return Default(schema_, value)
|
||||
case 'This':
|
||||
return TThis(schema_, references_, value)
|
||||
case 'Tuple':
|
||||
return TTuple(schema_, references_, value)
|
||||
case 'Union':
|
||||
return TUnion(schema_, references_, value)
|
||||
// ------------------------------------------------------
|
||||
// Default
|
||||
// ------------------------------------------------------
|
||||
case 'Any':
|
||||
case 'AsyncIterator':
|
||||
case 'BigInt':
|
||||
case 'Boolean':
|
||||
case 'Constructor':
|
||||
case 'Date':
|
||||
case 'Function':
|
||||
case 'Integer':
|
||||
case 'Iterator':
|
||||
case 'Literal':
|
||||
case 'Never':
|
||||
case 'Null':
|
||||
case 'Number':
|
||||
case 'Promise':
|
||||
case 'String':
|
||||
case 'TemplateLiteral':
|
||||
case 'Undefined':
|
||||
case 'Uint8Array':
|
||||
case 'Unknown':
|
||||
case 'Void':
|
||||
return Default(schema_, value)
|
||||
default:
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new TransformUnknownTypeError(schema_)
|
||||
return Default(schema_, value)
|
||||
}
|
||||
}
|
||||
let checkFunction: CheckFunction = () => false
|
||||
export function Decode(schema: Types.TSchema, references: Types.TSchema[], value: unknown, check: CheckFunction): unknown {
|
||||
checkFunction = check
|
||||
return Visit(schema, references, value)
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
// DecodeTransform
|
||||
// -------------------------------------------------------------------------
|
||||
/** Encodes a value using transform encoders if available. Does not ensure correct results. */
|
||||
export namespace EncodeTransform {
|
||||
function Default(schema: Types.TSchema, value: any) {
|
||||
try {
|
||||
return Types.TypeGuard.TTransform(schema) ? schema[Types.Transform].Encode(value) : value
|
||||
} catch (error) {
|
||||
throw new TransformEncodeError(schema, value, error)
|
||||
}
|
||||
}
|
||||
function TArray(schema: Types.TArray, references: Types.TSchema[], value: any): any {
|
||||
const elements1 = Default(schema, value)
|
||||
return elements1.map((value: any) => Visit(schema.items, references, value)) as unknown[]
|
||||
}
|
||||
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any) {
|
||||
const properties1 = Default(schema, value)
|
||||
if (!IsPlainObject(value) || IsValueType(value)) return properties1
|
||||
const keys = Types.KeyResolver.ResolveKeys(schema, { includePatterns: false })
|
||||
const properties2 = Object.entries(properties1).reduce((acc, [key, value]) => {
|
||||
return !keys.includes(key) ? { ...acc, [key]: value } : { ...acc, [key]: Default(Types.IndexedAccessor.Resolve(schema, [key]), value) }
|
||||
}, {} as Record<any, any>)
|
||||
if (!Types.TypeGuard.TTransform(schema.unevaluatedProperties)) return Default(schema, properties2)
|
||||
return Object.entries(properties2).reduce((acc, [key, value]) => {
|
||||
return keys.includes(key) ? { ...acc, [key]: value } : { ...acc, [key]: Default(schema.unevaluatedProperties as Types.TSchema, value) }
|
||||
}, {} as Record<any, any>)
|
||||
}
|
||||
function TNot(schema: Types.TNot, references: Types.TSchema[], value: any) {
|
||||
const value1 = Default(schema, value)
|
||||
return Default(schema.not, value1)
|
||||
}
|
||||
function TObject(schema: Types.TObject, references: Types.TSchema[], value: any) {
|
||||
const properties1 = Default(schema, value) as Record<any, any>
|
||||
if (!IsPlainObject(value)) return properties1
|
||||
const properties2 = Object.entries(properties1).reduce((acc, [key, value]) => {
|
||||
return !(key in schema.properties) ? { ...acc, [key]: value } : { ...acc, [key]: Visit(schema.properties[key], references, value) }
|
||||
}, {} as Record<any, any>)
|
||||
if (!Types.TypeGuard.TSchema(schema.additionalProperties)) return properties2
|
||||
const additionalProperties = schema.additionalProperties as Types.TSchema
|
||||
return Object.entries(properties2).reduce((acc, [key, value]) => {
|
||||
return key in schema.properties ? { ...acc, [key]: value } : { ...acc, [key]: Visit(additionalProperties, references, value) }
|
||||
}, {} as Record<any, any>)
|
||||
}
|
||||
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any) {
|
||||
const properties1 = Default(schema, value) as Record<any, any>
|
||||
if (!IsPlainObject(value)) return properties1
|
||||
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
|
||||
const property = schema.patternProperties[pattern]
|
||||
const regex = new RegExp(pattern)
|
||||
const properties2 = Object.entries(properties1).reduce((acc, [key, value]) => {
|
||||
return !regex.test(key) ? { ...acc, [key]: value } : { ...acc, [key]: Visit(property, references, value) }
|
||||
}, {} as Record<any, any>)
|
||||
if (!Types.TypeGuard.TSchema(schema.additionalProperties)) return Default(schema, properties2)
|
||||
const additionalProperties = schema.additionalProperties as Types.TSchema
|
||||
return Object.entries(properties2).reduce((acc, [key, value]) => {
|
||||
return regex.test(key) ? { ...acc, [key]: value } : { ...acc, [key]: Visit(additionalProperties, references, value) }
|
||||
}, {} as Record<any, any>)
|
||||
}
|
||||
function TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: any) {
|
||||
const target = Deref(schema, references)
|
||||
const resolved = Visit(target, references, value)
|
||||
return Default(schema, resolved)
|
||||
}
|
||||
function TThis(schema: Types.TThis, references: Types.TSchema[], value: any) {
|
||||
const target = Deref(schema, references)
|
||||
const resolved = Visit(target, references, value)
|
||||
return Default(schema, resolved)
|
||||
}
|
||||
function TTuple(schema: Types.TTuple, references: Types.TSchema[], value: any) {
|
||||
const value1 = Default(schema, value)
|
||||
return IsArray(schema.items) ? schema.items.map((schema, index) => Visit(schema, references, value1[index])) : []
|
||||
}
|
||||
function TUnion(schema: Types.TUnion, references: Types.TSchema[], value: any) {
|
||||
for (const subschema of schema.anyOf) {
|
||||
if (!checkFunction(subschema, references, value)) continue
|
||||
const value1 = Visit(subschema, references, value)
|
||||
return Default(schema, value1)
|
||||
}
|
||||
return Default(schema, value)
|
||||
}
|
||||
function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): any {
|
||||
const references_ = typeof schema.$id === 'string' ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema[Types.Kind]) {
|
||||
// ------------------------------------------------------
|
||||
// Structural
|
||||
// ------------------------------------------------------
|
||||
case 'Array':
|
||||
return TArray(schema_, references_, value)
|
||||
case 'Intersect':
|
||||
return TIntersect(schema_, references_, value)
|
||||
case 'Not':
|
||||
return TNot(schema_, references_, value)
|
||||
case 'Object':
|
||||
return TObject(schema_, references_, value)
|
||||
case 'Record':
|
||||
return TRecord(schema_, references_, value)
|
||||
case 'Ref':
|
||||
return TRef(schema_, references_, value)
|
||||
case 'This':
|
||||
return TThis(schema_, references_, value)
|
||||
case 'Tuple':
|
||||
return TTuple(schema_, references_, value)
|
||||
case 'Union':
|
||||
return TUnion(schema_, references_, value)
|
||||
// ------------------------------------------------------
|
||||
// Apply
|
||||
// ------------------------------------------------------
|
||||
case 'Any':
|
||||
case 'AsyncIterator':
|
||||
case 'BigInt':
|
||||
case 'Boolean':
|
||||
case 'Constructor':
|
||||
case 'Date':
|
||||
case 'Function':
|
||||
case 'Integer':
|
||||
case 'Iterator':
|
||||
case 'Literal':
|
||||
case 'Never':
|
||||
case 'Null':
|
||||
case 'Number':
|
||||
case 'Promise':
|
||||
case 'String':
|
||||
case 'Symbol':
|
||||
case 'TemplateLiteral':
|
||||
case 'Undefined':
|
||||
case 'Uint8Array':
|
||||
case 'Unknown':
|
||||
case 'Void':
|
||||
return Default(schema_, value)
|
||||
default:
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new TransformUnknownTypeError(schema_)
|
||||
return Default(schema_, value)
|
||||
}
|
||||
}
|
||||
let checkFunction: CheckFunction = () => false
|
||||
export function Encode(schema: Types.TSchema, references: Types.TSchema[], value: unknown, check: CheckFunction): unknown {
|
||||
checkFunction = check
|
||||
return Visit(schema, references, value)
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import * as ValueConvert from './convert'
|
||||
import * as ValueCreate from './create'
|
||||
import * as ValueCheck from './check'
|
||||
import * as ValueDelta from './delta'
|
||||
import * as ValueTransform from './transform'
|
||||
import * as Types from '../typebox'
|
||||
|
||||
/** Functions to perform structural operations on JavaScript values */
|
||||
@@ -76,6 +77,27 @@ export namespace Value {
|
||||
export function Clone<T>(value: T): T {
|
||||
return ValueClone.Clone(value)
|
||||
}
|
||||
/** Decodes a value or throws if error */
|
||||
export function Decode<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: unknown): Types.StaticDecode<T>
|
||||
/** Decodes a value or throws if error */
|
||||
export function Decode<T extends Types.TSchema>(schema: T, value: unknown): Types.StaticDecode<T>
|
||||
/** Decodes a value or throws if error */
|
||||
export function Decode(...args: any[]) {
|
||||
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
|
||||
if (!Check(schema, references, value)) throw new ValueTransform.TransformDecodeCheckError(schema, value, Errors(schema, references, value).First()!)
|
||||
return ValueTransform.DecodeTransform.Decode(schema, references, value, ValueCheck.Check)
|
||||
}
|
||||
/** Encodes a value or throws if error */
|
||||
export function Encode<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: unknown): Types.StaticEncode<T>
|
||||
/** Encodes a value or throws if error */
|
||||
export function Encode<T extends Types.TSchema>(schema: T, value: unknown): Types.StaticEncode<T>
|
||||
/** Encodes a value or throws if error */
|
||||
export function Encode(...args: any[]) {
|
||||
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
|
||||
const encoded = ValueTransform.EncodeTransform.Encode(schema, references, value, ValueCheck.Check)
|
||||
if (!Check(schema, references, encoded)) throw new ValueTransform.TransformEncodeCheckError(schema, value, Errors(schema, references, value).First()!)
|
||||
return encoded
|
||||
}
|
||||
/** Returns an iterator for each error in this value. */
|
||||
export function Errors<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: unknown): ValueErrors.ValueErrorIterator
|
||||
/** Returns an iterator for each error in this value. */
|
||||
|
||||
@@ -12,7 +12,7 @@ export namespace Assert {
|
||||
assert.equal(actual.length, expect.length)
|
||||
for (let i = 0; i < actual.length; i++) assert.equal(actual[i], expect[i])
|
||||
}
|
||||
return assert.deepEqual(actual, expect)
|
||||
return assert.deepStrictEqual(actual, expect)
|
||||
}
|
||||
export function NotEqual(actual: unknown, expect: unknown) {
|
||||
return assert.notEqual(actual, expect)
|
||||
@@ -37,7 +37,7 @@ export namespace Assert {
|
||||
}
|
||||
throw Error('Expected throw')
|
||||
}
|
||||
export function IsInstanceOf(value: any, constructor: any) {
|
||||
export function IsInstanceOf<T extends new (...args: any[]) => any>(value: any, constructor: T): asserts value is InstanceType<T> {
|
||||
if (value instanceof constructor) return
|
||||
throw Error(`Value is not instance of ${constructor}`)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok } from './validate'
|
||||
|
||||
describe('type/compiler/Any', () => {
|
||||
describe('compiler/Any', () => {
|
||||
it('Should validate number', () => {
|
||||
const T = Type.Any()
|
||||
Ok(T, 1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Array', () => {
|
||||
describe('compiler/Array', () => {
|
||||
it('Should validate an array of any', () => {
|
||||
const T = Type.Array(Type.Any())
|
||||
Ok(T, [0, true, 'hello', {}])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/AsyncIterator', () => {
|
||||
describe('compiler/AsyncIterator', () => {
|
||||
it('Should validate a async iterator 1', () => {
|
||||
async function* f() {}
|
||||
const T = Type.AsyncIterator(Type.Any())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/BigInt', () => {
|
||||
describe('compiler/BigInt', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, 3.14)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Boolean', () => {
|
||||
describe('compiler/Boolean', () => {
|
||||
it('Should validate a boolean', () => {
|
||||
const T = Type.Boolean()
|
||||
Ok(T, true)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Composite', () => {
|
||||
describe('compiler/Composite', () => {
|
||||
it('Should compose two objects', () => {
|
||||
const A = Type.Object({ a: Type.String() })
|
||||
const B = Type.Object({ b: Type.Number() })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Date', () => {
|
||||
describe('compiler/Date', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.Date()
|
||||
Fail(T, 1)
|
||||
@@ -53,8 +53,8 @@ describe('type/compiler/Date', () => {
|
||||
})
|
||||
it('Should validate Date maximumTimestamp', () => {
|
||||
const T = Type.Date({ maximumTimestamp: 10 })
|
||||
Ok(T, new Date(10))
|
||||
Fail(T, new Date(11))
|
||||
Ok(T, new Date(10))
|
||||
})
|
||||
it('Should validate Date exclusiveMinimumTimestamp', () => {
|
||||
const T = Type.Date({ exclusiveMinimumTimestamp: 10 })
|
||||
@@ -63,7 +63,12 @@ describe('type/compiler/Date', () => {
|
||||
})
|
||||
it('Should validate Date exclusiveMaximumTimestamp', () => {
|
||||
const T = Type.Date({ exclusiveMaximumTimestamp: 10 })
|
||||
Ok(T, new Date(9))
|
||||
Fail(T, new Date(10))
|
||||
Ok(T, new Date(9))
|
||||
})
|
||||
it('Should validate Date multipleOfTimestamp', () => {
|
||||
const T = Type.Date({ multipleOfTimestamp: 2 })
|
||||
Fail(T, new Date(1))
|
||||
Ok(T, new Date(2))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Enum', () => {
|
||||
describe('compiler/Enum', () => {
|
||||
it('Should validate when emum uses default numeric values', () => {
|
||||
enum Kind {
|
||||
Foo, // = 0
|
||||
|
||||
@@ -13,7 +13,6 @@ import './iterator'
|
||||
import './keyof'
|
||||
import './kind'
|
||||
import './literal'
|
||||
import './modifier'
|
||||
import './never'
|
||||
import './not'
|
||||
import './null'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Integer', () => {
|
||||
describe('compiler/Integer', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.Integer()
|
||||
Fail(T, 3.14)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type, Static } from '@sinclair/typebox'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Intersect', () => {
|
||||
describe('compiler/Intersect', () => {
|
||||
it('Should intersect number and number', () => {
|
||||
const A = Type.Number()
|
||||
const B = Type.Number()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Iterator', () => {
|
||||
describe('compiler/Iterator', () => {
|
||||
it('Should validate a iterator 1', () => {
|
||||
function* f() {}
|
||||
const T = Type.Iterator(Type.Any())
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { strictEqual } from 'assert'
|
||||
|
||||
describe('type/compiler/KeyOf', () => {
|
||||
describe('compiler/KeyOf', () => {
|
||||
it('Should validate with all object keys as a kind of union', () => {
|
||||
const T = Type.KeyOf(
|
||||
Type.Object({
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TypeCompiler } from '@sinclair/typebox/compiler'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { Assert } from '../assert'
|
||||
|
||||
describe('type/compiler/Kind', () => {
|
||||
describe('compiler/Kind', () => {
|
||||
// ------------------------------------------------------------
|
||||
// Fixtures
|
||||
// ------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Literal', () => {
|
||||
describe('compiler/Literal', () => {
|
||||
it('Should validate literal number', () => {
|
||||
const T = Type.Literal(42)
|
||||
Ok(T, 42)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
describe('type/compiler/Modifier', () => {})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Never', () => {
|
||||
describe('compiler/Never', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.Never()
|
||||
Fail(T, 1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Not', () => {
|
||||
describe('compiler/Not', () => {
|
||||
it('Should validate not number', () => {
|
||||
const T = Type.Not(Type.Number())
|
||||
Fail(T, 1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Null', () => {
|
||||
describe('compiler/Null', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.Null()
|
||||
Fail(T, 1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Number', () => {
|
||||
describe('compiler/Number', () => {
|
||||
it('Should validate number', () => {
|
||||
const T = Type.Number()
|
||||
Ok(T, 3.14)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Object', () => {
|
||||
describe('compiler/Object', () => {
|
||||
// -----------------------------------------------------
|
||||
// TypeCompiler Only
|
||||
// -----------------------------------------------------
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Type, Kind } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { deepEqual, strictEqual } from 'assert'
|
||||
|
||||
describe('type/compiler/Omit', () => {
|
||||
describe('compiler/Omit', () => {
|
||||
it('Should omit properties on the source schema', () => {
|
||||
const A = Type.Object(
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { strictEqual } from 'assert'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok } from './validate'
|
||||
|
||||
describe('type/compiler/Optional', () => {
|
||||
describe('compiler/Optional', () => {
|
||||
it('Should validate object with optional', () => {
|
||||
const T = Type.Object(
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Type, Kind, Optional, Readonly } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { strictEqual } from 'assert'
|
||||
|
||||
describe('type/compiler/Partial', () => {
|
||||
describe('compiler/Partial', () => {
|
||||
it('Should convert a required object into a partial', () => {
|
||||
const A = Type.Object(
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { strictEqual } from 'assert'
|
||||
|
||||
describe('type/compiler/Pick', () => {
|
||||
describe('compiler/Pick', () => {
|
||||
it('Should pick properties from the source schema', () => {
|
||||
const Vector3 = Type.Object(
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { strictEqual } from 'assert'
|
||||
|
||||
describe('type/compiler/ReadonlyOptional', () => {
|
||||
describe('compiler/ReadonlyOptional', () => {
|
||||
it('Should validate object with optional', () => {
|
||||
const T = Type.Object(
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { deepStrictEqual, strictEqual } from 'assert'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Readonly', () => {
|
||||
describe('compiler/Readonly', () => {
|
||||
it('Should validate object with readonly', () => {
|
||||
const T = Type.Object(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Record', () => {
|
||||
describe('compiler/Record', () => {
|
||||
// -------------------------------------------------------------
|
||||
// Issues
|
||||
// -------------------------------------------------------------
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox'
|
||||
import { Assert } from '../assert/index'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Recursive', () => {
|
||||
describe('compiler/Recursive', () => {
|
||||
it('Should generate default ordinal $id if not specified', () => {
|
||||
const Node = Type.Recursive((Node) =>
|
||||
Type.Object({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { Assert } from '../assert/index'
|
||||
|
||||
describe('type/compiler/Ref', () => {
|
||||
describe('compiler/Ref', () => {
|
||||
it('Should should validate when referencing a type', () => {
|
||||
const T = Type.Object(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/RegExp', () => {
|
||||
describe('compiler/RegExp', () => {
|
||||
//-----------------------------------------------------
|
||||
// Regular Expression
|
||||
//-----------------------------------------------------
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Type, Readonly, Optional } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { strictEqual } from 'assert'
|
||||
|
||||
describe('type/compiler/compiler/Required', () => {
|
||||
describe('compiler/Required', () => {
|
||||
it('Should convert a partial object into a required object', () => {
|
||||
const A = Type.Object(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/String', () => {
|
||||
describe('compiler/String', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.String()
|
||||
Fail(T, 1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Symbol', () => {
|
||||
describe('compiler/Symbol', () => {
|
||||
it('Should not validate a boolean', () => {
|
||||
const T = Type.Symbol()
|
||||
Fail(T, true)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/TemplateLiteral', () => {
|
||||
describe('compiler/TemplateLiteral', () => {
|
||||
// --------------------------------------------------------
|
||||
// Finite
|
||||
// --------------------------------------------------------
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { Assert } from '../assert'
|
||||
|
||||
describe('type/compiler/Tuple', () => {
|
||||
describe('compiler/Tuple', () => {
|
||||
it('Should validate tuple of [string, number]', () => {
|
||||
const A = Type.String()
|
||||
const B = Type.Number()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Uint8Array', () => {
|
||||
describe('compiler/Uint8Array', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.Uint8Array()
|
||||
Fail(T, 1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok } from './validate'
|
||||
|
||||
describe('type/compiler/Unicode', () => {
|
||||
describe('compiler/Unicode', () => {
|
||||
// ---------------------------------------------------------
|
||||
// Identifiers
|
||||
// ---------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Union', () => {
|
||||
describe('compiler/Union', () => {
|
||||
it('Should validate union of string, number and boolean', () => {
|
||||
const A = Type.String()
|
||||
const B = Type.Number()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
import { Ok } from './validate'
|
||||
|
||||
describe('type/compiler/Unknown', () => {
|
||||
describe('compiler/Unknown', () => {
|
||||
it('Should validate number', () => {
|
||||
const T = Type.Any()
|
||||
Ok(T, 1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Void', () => {
|
||||
describe('compiler/Void', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.Void()
|
||||
Fail(T, 1)
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
import './iterator'
|
||||
import './iterator/index'
|
||||
import './types/index'
|
||||
|
||||
1
test/runtime/errors/iterator/index.ts
Normal file
1
test/runtime/errors/iterator/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './iterator'
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Errors } from '@sinclair/typebox/errors'
|
||||
import { Assert } from '../assert'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/ValueErrorIterator', () => {
|
||||
it('Should return undefined for non error', () => {
|
||||
17
test/runtime/errors/types/array-contains.ts
Normal file
17
test/runtime/errors/types/array-contains.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/ArrayMaxContainsItems', () => {
|
||||
const T = Type.Array(Type.Any(), { contains: Type.Literal(1) })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, [1])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, [2])
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayContains)
|
||||
})
|
||||
})
|
||||
36
test/runtime/errors/types/array-max-contains.ts
Normal file
36
test/runtime/errors/types/array-max-contains.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/ArrayMaxContainsItems', () => {
|
||||
const T = Type.Array(Type.Any(), { contains: Type.Literal(1), maxContains: 4 })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, [1, 1, 1, 1])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, null)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.Array)
|
||||
})
|
||||
it('Should pass 2', () => {
|
||||
const R = Resolve(T, [1, 1, 1, 1, 1])
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayMaxContains)
|
||||
})
|
||||
it('Should pass 3', () => {
|
||||
const R = Resolve(T, [])
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayContains)
|
||||
})
|
||||
it('Should pass 4', () => {
|
||||
const R = Resolve(T, [1, 2, 3, 4])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 5', () => {
|
||||
const R = Resolve(T, [2, 3, 4])
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayContains)
|
||||
})
|
||||
})
|
||||
22
test/runtime/errors/types/array-max-items.ts
Normal file
22
test/runtime/errors/types/array-max-items.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/ArrayMaxItems', () => {
|
||||
const T = Type.Array(Type.Any(), { maxItems: 4 })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, [1, 2, 3, 4])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, null)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.Array)
|
||||
})
|
||||
it('Should pass 2', () => {
|
||||
const R = Resolve(T, [1, 2, 3, 4, 5])
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayMaxItems)
|
||||
})
|
||||
})
|
||||
28
test/runtime/errors/types/array-min-contains.ts
Normal file
28
test/runtime/errors/types/array-min-contains.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/ArrayMinContainsItems', () => {
|
||||
const T = Type.Array(Type.Any(), { contains: Type.Literal(1), minContains: 4 })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, [1, 1, 1, 1])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, null)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.Array)
|
||||
})
|
||||
it('Should pass 2', () => {
|
||||
const R = Resolve(T, [1])
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayMinContains)
|
||||
})
|
||||
it('Should pass 3', () => {
|
||||
const R = Resolve(T, [])
|
||||
Assert.IsEqual(R.length, 2)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayContains)
|
||||
Assert.IsEqual(R[1].type, ValueErrorType.ArrayMinContains)
|
||||
})
|
||||
})
|
||||
22
test/runtime/errors/types/array-min-items.ts
Normal file
22
test/runtime/errors/types/array-min-items.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/ArrayMinItems', () => {
|
||||
const T = Type.Array(Type.Any(), { minItems: 4 })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, [1, 2, 3, 4])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, null)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.Array)
|
||||
})
|
||||
it('Should pass 2', () => {
|
||||
const R = Resolve(T, [])
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayMinItems)
|
||||
})
|
||||
})
|
||||
21
test/runtime/errors/types/array-unique-items.ts
Normal file
21
test/runtime/errors/types/array-unique-items.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/ArrayUniqueItems', () => {
|
||||
const T = Type.Array(Type.Any(), { uniqueItems: true })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, [1, 2, 3, 4])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 2', () => {
|
||||
const R = Resolve(T, [])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 3', () => {
|
||||
const R = Resolve(T, [1, 1, 3, 4])
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.ArrayUniqueItems)
|
||||
})
|
||||
})
|
||||
27
test/runtime/errors/types/array.ts
Normal file
27
test/runtime/errors/types/array.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/Array', () => {
|
||||
const T = Type.Array(Type.Any())
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, [1, 2, 3])
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, 1)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.Array)
|
||||
})
|
||||
it('Should pass 2', () => {
|
||||
const R = Resolve(T, {})
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.Array)
|
||||
})
|
||||
it('Should pass 3', () => {
|
||||
const R = Resolve(T, null)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.Array)
|
||||
})
|
||||
})
|
||||
17
test/runtime/errors/types/async-iterator.ts
Normal file
17
test/runtime/errors/types/async-iterator.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/AsyncIterator', () => {
|
||||
const T = Type.AsyncIterator(Type.Any())
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, (async function* (): any {})())
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, 1)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.AsyncIterator)
|
||||
})
|
||||
})
|
||||
17
test/runtime/errors/types/bigint-exclusive-maximum.ts
Normal file
17
test/runtime/errors/types/bigint-exclusive-maximum.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/BigIntExclusiveMaximum', () => {
|
||||
const T = Type.BigInt({ exclusiveMaximum: 4n })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, 0n)
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, 4n)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.BigIntExclusiveMaximum)
|
||||
})
|
||||
})
|
||||
17
test/runtime/errors/types/bigint-exclusive-minimum.ts
Normal file
17
test/runtime/errors/types/bigint-exclusive-minimum.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/BigIntExclusiveMinimum', () => {
|
||||
const T = Type.BigInt({ exclusiveMinimum: 4n })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, 5n)
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, 4n)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.BigIntExclusiveMinimum)
|
||||
})
|
||||
})
|
||||
17
test/runtime/errors/types/bigint-maximum.ts
Normal file
17
test/runtime/errors/types/bigint-maximum.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/BigIntMaximum', () => {
|
||||
const T = Type.BigInt({ maximum: 4n })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, 0n)
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, 5n)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.BigIntMaximum)
|
||||
})
|
||||
})
|
||||
17
test/runtime/errors/types/bigint-minimum.ts
Normal file
17
test/runtime/errors/types/bigint-minimum.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/BigIntMinimum', () => {
|
||||
const T = Type.BigInt({ minimum: 4n })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, 4n)
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, 3n)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.BigIntMinimum)
|
||||
})
|
||||
})
|
||||
17
test/runtime/errors/types/bigint-multiple-of.ts
Normal file
17
test/runtime/errors/types/bigint-multiple-of.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
import { Resolve } from './resolve'
|
||||
import { Assert } from '../../assert'
|
||||
|
||||
describe('errors/type/BigIntMultipleOf', () => {
|
||||
const T = Type.BigInt({ multipleOf: 2n })
|
||||
it('Should pass 0', () => {
|
||||
const R = Resolve(T, 0n)
|
||||
Assert.IsEqual(R.length, 0)
|
||||
})
|
||||
it('Should pass 1', () => {
|
||||
const R = Resolve(T, 1n)
|
||||
Assert.IsEqual(R.length, 1)
|
||||
Assert.IsEqual(R[0].type, ValueErrorType.BigIntMultipleOf)
|
||||
})
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user