@sinclair/typebox 0.24.2 → 0.24.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compiler/compiler.d.ts +10 -15
- package/compiler/compiler.js +71 -82
- package/compiler/errors.d.ts +10 -0
- package/compiler/errors.js +311 -0
- package/compiler/index.d.ts +1 -0
- package/compiler/index.js +1 -0
- package/package.json +1 -1
- package/readme.md +33 -2
- package/typebox.js +0 -1
package/compiler/compiler.d.ts
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
|
+
import { TypeError } from './errors';
|
|
1
2
|
import * as Types from '../typebox';
|
|
2
3
|
export declare type CheckFunction = (value: unknown) => boolean;
|
|
3
|
-
export declare class TypeCheckAssertError extends Error {
|
|
4
|
-
readonly schema: Types.TSchema;
|
|
5
|
-
readonly value: unknown;
|
|
6
|
-
constructor(schema: Types.TSchema, value: unknown);
|
|
7
|
-
}
|
|
8
4
|
export declare class TypeCheck<T extends Types.TSchema> {
|
|
9
5
|
private readonly schema;
|
|
6
|
+
private readonly additional;
|
|
10
7
|
private readonly checkFunc;
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
private readonly code;
|
|
9
|
+
constructor(schema: T, additional: Types.TSchema[], checkFunc: CheckFunction, code: string);
|
|
10
|
+
/** Returns the generated validation code used to validate this type */
|
|
11
|
+
Code(): string;
|
|
12
|
+
/** Returns an iterator for each type error found in this value */
|
|
13
|
+
Errors(value: unknown): Generator<TypeError>;
|
|
14
|
+
/** Returns true if the value matches the given type. */
|
|
13
15
|
Check(value: unknown): value is Types.Static<T>;
|
|
14
|
-
/** Asserts the given value and throws a TypeCheckAssertError if invalid. */
|
|
15
|
-
Assert(value: unknown): void;
|
|
16
16
|
}
|
|
17
17
|
export declare namespace TypeCompiler {
|
|
18
|
-
|
|
19
|
-
schema: Types.TSchema;
|
|
20
|
-
expr: string;
|
|
21
|
-
path: string;
|
|
22
|
-
}
|
|
23
|
-
/** Compiles a type into validation function */
|
|
18
|
+
/** Compiles the given type for runtime type checking. This compiler only accepts known TypeBox types non-inclusive of unsafe types. */
|
|
24
19
|
function Compile<T extends Types.TSchema>(schema: T, additional?: Types.TSchema[]): TypeCheck<T>;
|
|
25
20
|
}
|
package/compiler/compiler.js
CHANGED
|
@@ -27,40 +27,35 @@ THE SOFTWARE.
|
|
|
27
27
|
|
|
28
28
|
---------------------------------------------------------------------------*/
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.TypeCompiler = exports.TypeCheck =
|
|
30
|
+
exports.TypeCompiler = exports.TypeCheck = void 0;
|
|
31
|
+
const errors_1 = require("./errors");
|
|
31
32
|
const Types = require("../typebox");
|
|
32
33
|
// -------------------------------------------------------------------
|
|
33
34
|
// TypeCheck
|
|
34
35
|
// -------------------------------------------------------------------
|
|
35
|
-
class TypeCheckAssertError extends Error {
|
|
36
|
-
schema;
|
|
37
|
-
value;
|
|
38
|
-
constructor(schema, value) {
|
|
39
|
-
super(`TypeCheckAssertError`);
|
|
40
|
-
this.schema = Types.Type.Strict(schema);
|
|
41
|
-
this.value = value;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
exports.TypeCheckAssertError = TypeCheckAssertError;
|
|
45
36
|
class TypeCheck {
|
|
46
37
|
schema;
|
|
38
|
+
additional;
|
|
47
39
|
checkFunc;
|
|
48
|
-
|
|
40
|
+
code;
|
|
41
|
+
constructor(schema, additional, checkFunc, code) {
|
|
49
42
|
this.schema = schema;
|
|
43
|
+
this.additional = additional;
|
|
50
44
|
this.checkFunc = checkFunc;
|
|
45
|
+
this.code = code;
|
|
46
|
+
}
|
|
47
|
+
/** Returns the generated validation code used to validate this type */
|
|
48
|
+
Code() {
|
|
49
|
+
return this.code;
|
|
50
|
+
}
|
|
51
|
+
/** Returns an iterator for each type error found in this value */
|
|
52
|
+
Errors(value) {
|
|
53
|
+
return errors_1.TypeErrors.Errors(this.schema, this.additional, value);
|
|
51
54
|
}
|
|
52
|
-
/** Returns true if the value
|
|
55
|
+
/** Returns true if the value matches the given type. */
|
|
53
56
|
Check(value) {
|
|
54
57
|
return this.checkFunc(value);
|
|
55
58
|
}
|
|
56
|
-
/** Asserts the given value and throws a TypeCheckAssertError if invalid. */
|
|
57
|
-
Assert(value) {
|
|
58
|
-
// The return type for this function should be 'asserts value is Static<T>' but due
|
|
59
|
-
// to a limitation in TypeScript, this currently isn't possible. See issue below.
|
|
60
|
-
// https://github.com/microsoft/TypeScript/issues/36931
|
|
61
|
-
if (!this.checkFunc(value))
|
|
62
|
-
throw new TypeCheckAssertError(this.schema, value);
|
|
63
|
-
}
|
|
64
59
|
}
|
|
65
60
|
exports.TypeCheck = TypeCheck;
|
|
66
61
|
// -------------------------------------------------------------------
|
|
@@ -68,74 +63,68 @@ exports.TypeCheck = TypeCheck;
|
|
|
68
63
|
// -------------------------------------------------------------------
|
|
69
64
|
var TypeCompiler;
|
|
70
65
|
(function (TypeCompiler) {
|
|
71
|
-
// -------------------------------------------------------------------
|
|
72
|
-
// Condition
|
|
73
|
-
// -------------------------------------------------------------------
|
|
74
|
-
function CreateCondition(schema, path, expr) {
|
|
75
|
-
return { schema, path, expr };
|
|
76
|
-
}
|
|
77
66
|
// -------------------------------------------------------------------
|
|
78
67
|
// Schemas
|
|
79
68
|
// -------------------------------------------------------------------
|
|
80
69
|
function* Any(schema, path) {
|
|
81
|
-
yield
|
|
70
|
+
yield '(true)';
|
|
82
71
|
}
|
|
83
72
|
function* Array(schema, path) {
|
|
84
|
-
const expr = [...Visit(schema.items, `value`)].map((condition) => condition
|
|
85
|
-
yield
|
|
73
|
+
const expr = [...Visit(schema.items, `value`)].map((condition) => condition).join(' && ');
|
|
74
|
+
yield `(Array.isArray(${path}) && ${path}.every(value => ${expr}))`;
|
|
86
75
|
}
|
|
87
76
|
function* Boolean(schema, path) {
|
|
88
|
-
yield
|
|
77
|
+
yield `(typeof ${path} === 'boolean')`;
|
|
89
78
|
}
|
|
90
79
|
function* Constructor(schema, path) {
|
|
91
80
|
yield* Visit(schema.yields, path);
|
|
92
81
|
}
|
|
93
82
|
function* Function(schema, path) {
|
|
94
|
-
yield
|
|
83
|
+
yield `(typeof ${path} === 'function')`;
|
|
95
84
|
}
|
|
96
85
|
function* Integer(schema, path) {
|
|
97
|
-
yield
|
|
86
|
+
yield `(typeof ${path} === 'number' && Number.isInteger(${path}))`;
|
|
98
87
|
if (schema.multipleOf)
|
|
99
|
-
yield
|
|
88
|
+
yield `(${path} % ${schema.multipleOf} === 0)`;
|
|
100
89
|
if (schema.exclusiveMinimum)
|
|
101
|
-
yield
|
|
90
|
+
yield `(${path} > ${schema.exclusiveMinimum})`;
|
|
102
91
|
if (schema.exclusiveMaximum)
|
|
103
|
-
yield
|
|
92
|
+
yield `(${path} < ${schema.exclusiveMaximum})`;
|
|
104
93
|
if (schema.minimum)
|
|
105
|
-
yield
|
|
94
|
+
yield `(${path} >= ${schema.minimum})`;
|
|
106
95
|
if (schema.maximum)
|
|
107
|
-
yield
|
|
96
|
+
yield `(${path} <= ${schema.maximum})`;
|
|
108
97
|
}
|
|
109
98
|
function* Literal(schema, path) {
|
|
110
99
|
if (typeof schema.const === 'string') {
|
|
111
|
-
yield
|
|
100
|
+
yield `(${path} === '${schema.const}')`;
|
|
112
101
|
}
|
|
113
102
|
else {
|
|
114
|
-
yield
|
|
103
|
+
yield `(${path} === ${schema.const})`;
|
|
115
104
|
}
|
|
116
105
|
}
|
|
117
106
|
function* Null(schema, path) {
|
|
118
|
-
yield
|
|
107
|
+
yield `(${path} === null)`;
|
|
119
108
|
}
|
|
120
109
|
function* Number(schema, path) {
|
|
121
|
-
yield
|
|
110
|
+
yield `(typeof ${path} === 'number')`;
|
|
122
111
|
if (schema.multipleOf)
|
|
123
|
-
yield
|
|
112
|
+
yield `(${path} % ${schema.multipleOf} === 0)`;
|
|
124
113
|
if (schema.exclusiveMinimum)
|
|
125
|
-
yield
|
|
114
|
+
yield `(${path} > ${schema.exclusiveMinimum})`;
|
|
126
115
|
if (schema.exclusiveMaximum)
|
|
127
|
-
yield
|
|
116
|
+
yield `(${path} < ${schema.exclusiveMaximum})`;
|
|
128
117
|
if (schema.minimum)
|
|
129
|
-
yield
|
|
118
|
+
yield `(${path} >= ${schema.minimum})`;
|
|
130
119
|
if (schema.maximum)
|
|
131
|
-
yield
|
|
120
|
+
yield `(${path} <= ${schema.maximum})`;
|
|
132
121
|
}
|
|
133
122
|
function* Object(schema, path) {
|
|
134
|
-
yield
|
|
123
|
+
yield `(typeof ${path} === 'object' && ${path} !== null && !Array.isArray(${path}))`;
|
|
135
124
|
if (schema.minProperties !== undefined)
|
|
136
|
-
yield
|
|
125
|
+
yield `(Object.keys(${path}).length >= ${schema.minProperties})`;
|
|
137
126
|
if (schema.maxProperties !== undefined)
|
|
138
|
-
yield
|
|
127
|
+
yield `(Object.keys(${path}).length <= ${schema.maxProperties})`;
|
|
139
128
|
const propertyKeys = globalThis.Object.keys(schema.properties);
|
|
140
129
|
if (schema.additionalProperties === false) {
|
|
141
130
|
// optimization: If the property key length matches the required keys length
|
|
@@ -143,11 +132,11 @@ var TypeCompiler;
|
|
|
143
132
|
// of the property key length. This is because exhaustive testing for values
|
|
144
133
|
// will occur in subsequent property tests.
|
|
145
134
|
if (schema.required && schema.required.length === propertyKeys.length) {
|
|
146
|
-
yield
|
|
135
|
+
yield `(Object.keys(${path}).length === ${propertyKeys.length})`;
|
|
147
136
|
}
|
|
148
137
|
else {
|
|
149
138
|
const keys = `[${propertyKeys.map((key) => `'${key}'`).join(', ')}]`;
|
|
150
|
-
yield
|
|
139
|
+
yield `(Object.keys(${path}).every(key => ${keys}.includes(key)))`;
|
|
151
140
|
}
|
|
152
141
|
}
|
|
153
142
|
for (const propertyKey of propertyKeys) {
|
|
@@ -156,21 +145,21 @@ var TypeCompiler;
|
|
|
156
145
|
yield* Visit(propertySchema, `${path}.${propertyKey}`);
|
|
157
146
|
}
|
|
158
147
|
else {
|
|
159
|
-
const expr = [...Visit(propertySchema, `${path}.${propertyKey}`)].map((condition) => condition
|
|
160
|
-
yield
|
|
148
|
+
const expr = [...Visit(propertySchema, `${path}.${propertyKey}`)].map((condition) => condition).join(' && ');
|
|
149
|
+
yield `(${path}.${propertyKey} === undefined ? true : (${expr}))`;
|
|
161
150
|
}
|
|
162
151
|
}
|
|
163
152
|
}
|
|
164
153
|
function* Promise(schema, path) {
|
|
165
|
-
yield
|
|
154
|
+
yield `(typeof value === 'object' && typeof ${path}.then === 'function')`;
|
|
166
155
|
}
|
|
167
156
|
function* Record(schema, path) {
|
|
168
|
-
yield
|
|
157
|
+
yield `(typeof ${path} === 'object' && ${path} !== null && !Array.isArray(${path}))`;
|
|
169
158
|
const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0];
|
|
170
159
|
const local = PushLocal(`const local = new RegExp(/${keyPattern}/)`);
|
|
171
|
-
yield
|
|
172
|
-
const expr = [...Visit(valueSchema, 'value')].map((
|
|
173
|
-
yield
|
|
160
|
+
yield `(Object.keys(${path}).every(key => ${local}.test(key)))`;
|
|
161
|
+
const expr = [...Visit(valueSchema, 'value')].map((condition) => condition).join(' && ');
|
|
162
|
+
yield `(Object.values(${path}).every(value => ${expr}))`;
|
|
174
163
|
}
|
|
175
164
|
function* Ref(schema, path) {
|
|
176
165
|
// reference: referenced schemas can originate from either additional
|
|
@@ -185,48 +174,48 @@ var TypeCompiler;
|
|
|
185
174
|
PushLocal(body);
|
|
186
175
|
}
|
|
187
176
|
const func = CreateFunctionName(schema.$ref);
|
|
188
|
-
yield
|
|
177
|
+
yield `(${func}(${path}))`;
|
|
189
178
|
}
|
|
190
179
|
function* Self(schema, path) {
|
|
191
180
|
const func = CreateFunctionName(schema.$ref);
|
|
192
|
-
yield
|
|
181
|
+
yield `(${func}(${path}))`;
|
|
193
182
|
}
|
|
194
183
|
function* String(schema, path) {
|
|
195
|
-
yield
|
|
184
|
+
yield `(typeof ${path} === 'string')`;
|
|
196
185
|
if (schema.pattern !== undefined) {
|
|
197
186
|
const local = PushLocal(`const local = new RegExp('${schema.pattern}');`);
|
|
198
|
-
yield
|
|
187
|
+
yield `(${local}.test(${path}))`;
|
|
199
188
|
}
|
|
200
189
|
}
|
|
201
190
|
function* Tuple(schema, path) {
|
|
202
|
-
yield
|
|
191
|
+
yield `(Array.isArray(${path}))`;
|
|
203
192
|
if (schema.items === undefined)
|
|
204
|
-
return yield
|
|
205
|
-
yield
|
|
193
|
+
return yield `(${path}.length === 0)`;
|
|
194
|
+
yield `(${path}.length === ${schema.maxItems})`;
|
|
206
195
|
for (let i = 0; i < schema.items.length; i++) {
|
|
207
|
-
const expr = [...Visit(schema.items[i], `${path}[${i}]`)].map((condition) => condition
|
|
208
|
-
yield
|
|
196
|
+
const expr = [...Visit(schema.items[i], `${path}[${i}]`)].map((condition) => condition).join(' && ');
|
|
197
|
+
yield `(${expr})`;
|
|
209
198
|
}
|
|
210
199
|
}
|
|
211
200
|
function* Undefined(schema, path) {
|
|
212
|
-
yield
|
|
201
|
+
yield `${path} === undefined`;
|
|
213
202
|
}
|
|
214
203
|
function* Union(schema, path) {
|
|
215
|
-
const exprs = schema.anyOf.map((schema) => [...Visit(schema, path)].map((
|
|
216
|
-
yield
|
|
204
|
+
const exprs = schema.anyOf.map((schema) => [...Visit(schema, path)].map((condition) => condition).join(' && '));
|
|
205
|
+
yield `(${exprs.join(' || ')})`;
|
|
217
206
|
}
|
|
218
207
|
function* Uint8Array(schema, path) {
|
|
219
|
-
yield
|
|
208
|
+
yield `(${path} instanceof Uint8Array)`;
|
|
220
209
|
if (schema.maxByteLength)
|
|
221
|
-
yield
|
|
210
|
+
yield `(${path}.length <= ${schema.maxByteLength})`;
|
|
222
211
|
if (schema.minByteLength)
|
|
223
|
-
yield
|
|
212
|
+
yield `(${path}.length >= ${schema.minByteLength})`;
|
|
224
213
|
}
|
|
225
214
|
function* Unknown(schema, path) {
|
|
226
|
-
yield
|
|
215
|
+
yield '(true)';
|
|
227
216
|
}
|
|
228
217
|
function* Void(schema, path) {
|
|
229
|
-
yield
|
|
218
|
+
yield `(${path} === null)`;
|
|
230
219
|
}
|
|
231
220
|
function* Visit(schema, path) {
|
|
232
221
|
// reference: referenced schemas can originate from either additional
|
|
@@ -238,7 +227,7 @@ var TypeCompiler;
|
|
|
238
227
|
const name = CreateFunctionName(schema.$id);
|
|
239
228
|
const body = CreateFunction(name, conditions);
|
|
240
229
|
PushLocal(body);
|
|
241
|
-
yield
|
|
230
|
+
yield `(${name}(${path}))`;
|
|
242
231
|
return;
|
|
243
232
|
}
|
|
244
233
|
const anySchema = schema;
|
|
@@ -324,11 +313,11 @@ var TypeCompiler;
|
|
|
324
313
|
return `check_${$id.replace(/-/g, '_')}`;
|
|
325
314
|
}
|
|
326
315
|
function CreateFunction(name, conditions) {
|
|
327
|
-
const
|
|
328
|
-
return `function ${name}(value) {\n
|
|
316
|
+
const expression = conditions.map((condition) => ` ${condition}`).join(' &&\n');
|
|
317
|
+
return `function ${name}(value) {\n return (\n${expression}\n )\n}`;
|
|
329
318
|
}
|
|
330
319
|
// -------------------------------------------------------------------
|
|
331
|
-
//
|
|
320
|
+
// Compile
|
|
332
321
|
// -------------------------------------------------------------------
|
|
333
322
|
function Build(schema, additional = []) {
|
|
334
323
|
ClearLocals();
|
|
@@ -337,11 +326,11 @@ var TypeCompiler;
|
|
|
337
326
|
const locals = GetLocals();
|
|
338
327
|
return `${locals.join('\n')}\nreturn ${CreateFunction('check', conditions)}`;
|
|
339
328
|
}
|
|
340
|
-
/** Compiles
|
|
329
|
+
/** Compiles the given type for runtime type checking. This compiler only accepts known TypeBox types non-inclusive of unsafe types. */
|
|
341
330
|
function Compile(schema, additional = []) {
|
|
342
331
|
const code = Build(schema, additional);
|
|
343
332
|
const func = globalThis.Function(code);
|
|
344
|
-
return new TypeCheck(schema, func());
|
|
333
|
+
return new TypeCheck(schema, additional, func(), code);
|
|
345
334
|
}
|
|
346
335
|
TypeCompiler.Compile = Compile;
|
|
347
336
|
})(TypeCompiler = exports.TypeCompiler || (exports.TypeCompiler = {}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as Types from '../typebox';
|
|
2
|
+
export interface TypeError {
|
|
3
|
+
schema: Types.TSchema;
|
|
4
|
+
path: string;
|
|
5
|
+
value: unknown;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export declare namespace TypeErrors {
|
|
9
|
+
function Errors<T extends Types.TSchema>(schema: T, additional: Types.TSchema[], value: any): Generator<TypeError>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*--------------------------------------------------------------------------
|
|
3
|
+
|
|
4
|
+
@sinclair/typebox/compiler
|
|
5
|
+
|
|
6
|
+
The MIT License (MIT)
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2022 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in
|
|
18
|
+
all copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
26
|
+
THE SOFTWARE.
|
|
27
|
+
|
|
28
|
+
---------------------------------------------------------------------------*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.TypeErrors = void 0;
|
|
31
|
+
const Types = require("../typebox");
|
|
32
|
+
var TypeErrors;
|
|
33
|
+
(function (TypeErrors) {
|
|
34
|
+
function* Any(schema, path, value) { }
|
|
35
|
+
function* Array(schema, path, value) {
|
|
36
|
+
if (!globalThis.Array.isArray(value)) {
|
|
37
|
+
return yield { schema, path, value, message: `Expected array` };
|
|
38
|
+
}
|
|
39
|
+
for (let i = 0; i < value.length; i++) {
|
|
40
|
+
yield* Visit(schema.items, `${path}/${i}`, value[i]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function* Boolean(schema, path, value) {
|
|
44
|
+
if (!(typeof value === 'boolean')) {
|
|
45
|
+
return yield { schema, path, value, message: `Expected boolean` };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function* Constructor(schema, path, value) {
|
|
49
|
+
yield* Visit(schema.yields, path, value);
|
|
50
|
+
}
|
|
51
|
+
function* Function(schema, path, value) {
|
|
52
|
+
if (!(typeof value === 'function')) {
|
|
53
|
+
return yield { schema, path, value, message: `Expected function` };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function* Integer(schema, path, value) {
|
|
57
|
+
if (!(typeof value === 'number')) {
|
|
58
|
+
return yield { schema, path, value, message: `Expected number` };
|
|
59
|
+
}
|
|
60
|
+
if (!globalThis.Number.isInteger(value)) {
|
|
61
|
+
yield { schema, path, value, message: `Expected integer` };
|
|
62
|
+
}
|
|
63
|
+
if (schema.multipleOf && !(value % schema.multipleOf === 0)) {
|
|
64
|
+
yield { schema, path, value, message: `Expected number to be a multiple of ${schema.multipleOf}` };
|
|
65
|
+
}
|
|
66
|
+
if (schema.exclusiveMinimum && !(value > schema.exclusiveMinimum)) {
|
|
67
|
+
yield { schema, path, value, message: `Expected number to be greater than ${schema.exclusiveMinimum}` };
|
|
68
|
+
}
|
|
69
|
+
if (schema.exclusiveMaximum && !(value < schema.exclusiveMaximum)) {
|
|
70
|
+
yield { schema, path, value, message: `Expected number to be less than ${schema.exclusiveMaximum}` };
|
|
71
|
+
}
|
|
72
|
+
if (schema.minimum && !(value >= schema.minimum)) {
|
|
73
|
+
yield { schema, path, value, message: `Expected number to be greater or equal to ${schema.minimum}` };
|
|
74
|
+
}
|
|
75
|
+
if (schema.maximum && !(value <= schema.maximum)) {
|
|
76
|
+
yield { schema, path, value, message: `Expected number to be less or equal to ${schema.maximum}` };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function* Literal(schema, path, value) {
|
|
80
|
+
if (!(value === schema.const)) {
|
|
81
|
+
const error = typeof schema.const === 'string' ? `'${schema.const}'` : schema.const;
|
|
82
|
+
return yield { schema, path, value, message: `Expected ${error}` };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function* Null(schema, path, value) {
|
|
86
|
+
if (!(value === null)) {
|
|
87
|
+
return yield { schema, path, value, message: `Expected null` };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function* Number(schema, path, value) {
|
|
91
|
+
if (!(typeof value === 'number')) {
|
|
92
|
+
return yield { schema, path, value, message: `Expected number` };
|
|
93
|
+
}
|
|
94
|
+
if (schema.multipleOf && !(value % schema.multipleOf === 0)) {
|
|
95
|
+
yield { schema, path, value, message: `Expected number to be a multiple of ${schema.multipleOf}` };
|
|
96
|
+
}
|
|
97
|
+
if (schema.exclusiveMinimum && !(value > schema.exclusiveMinimum)) {
|
|
98
|
+
yield { schema, path, value, message: `Expected number to be greater than ${schema.exclusiveMinimum}` };
|
|
99
|
+
}
|
|
100
|
+
if (schema.exclusiveMaximum && !(value < schema.exclusiveMaximum)) {
|
|
101
|
+
yield { schema, path, value, message: `Expected number to be less than ${schema.exclusiveMaximum}` };
|
|
102
|
+
}
|
|
103
|
+
if (schema.minimum && !(value >= schema.minimum)) {
|
|
104
|
+
yield { schema, path, value, message: `Expected number to be greater or equal to ${schema.minimum}` };
|
|
105
|
+
}
|
|
106
|
+
if (schema.maximum && !(value <= schema.maximum)) {
|
|
107
|
+
yield { schema, path, value, message: `Expected number to be less or equal to ${schema.maximum}` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function* Object(schema, path, value) {
|
|
111
|
+
if (!(typeof value === 'object' && value !== null && !globalThis.Array.isArray(value))) {
|
|
112
|
+
return yield { schema, path, value, message: `Expected object` };
|
|
113
|
+
}
|
|
114
|
+
if (schema.minProperties !== undefined && !(globalThis.Object.keys(value).length >= schema.minProperties)) {
|
|
115
|
+
yield { schema, path, value, message: `Expected object to have at least ${schema.minProperties} properties` };
|
|
116
|
+
}
|
|
117
|
+
if (schema.maxProperties !== undefined && !(globalThis.Object.keys(value).length <= schema.maxProperties)) {
|
|
118
|
+
yield { schema, path, value, message: `Expected object to have less than ${schema.minProperties} properties` };
|
|
119
|
+
}
|
|
120
|
+
const propertyKeys = globalThis.Object.keys(schema.properties);
|
|
121
|
+
if (schema.additionalProperties === false) {
|
|
122
|
+
// optimization: If the property key length matches the required keys length
|
|
123
|
+
// then we only need check that the values property key length matches that
|
|
124
|
+
// of the property key length. This is because exhaustive testing for values
|
|
125
|
+
// will occur in subsequent property tests.
|
|
126
|
+
if (schema.required && schema.required.length === propertyKeys.length && !(globalThis.Object.keys(value).length === propertyKeys.length)) {
|
|
127
|
+
yield { schema, path, value, message: 'Expected object must not have additional properties' };
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
if (!globalThis.Object.keys(value).every((key) => propertyKeys.includes(key))) {
|
|
131
|
+
yield { schema, path, value, message: 'Expected object must not have additional properties' };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const propertyKey of propertyKeys) {
|
|
136
|
+
const propertySchema = schema.properties[propertyKey];
|
|
137
|
+
if (schema.required && schema.required.includes(propertyKey)) {
|
|
138
|
+
yield* Visit(propertySchema, `${path}/${propertyKey}`, value[propertyKey]);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
if (value[propertyKey] !== undefined) {
|
|
142
|
+
yield* Visit(propertySchema, `${path}/${propertyKey}`, value[propertyKey]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function* Promise(schema, path, value) {
|
|
148
|
+
if (!(typeof value === 'object' && typeof value.then === 'function')) {
|
|
149
|
+
yield { schema, path, value, message: `Expected Promise` };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function* Record(schema, path, value) {
|
|
153
|
+
if (!(typeof value === 'object' && value !== null && !globalThis.Array.isArray(value))) {
|
|
154
|
+
return yield { schema, path, value, message: `Expected object` };
|
|
155
|
+
}
|
|
156
|
+
const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0];
|
|
157
|
+
const regex = new RegExp(keyPattern);
|
|
158
|
+
if (!globalThis.Object.keys(value).every((key) => regex.test(key))) {
|
|
159
|
+
const message = keyPattern === '^(0|[1-9][0-9]*)$' ? 'Expected all object property keys to be numeric' : 'Expected all object property keys to be strings';
|
|
160
|
+
return yield { schema, path, value, message };
|
|
161
|
+
}
|
|
162
|
+
for (const [propKey, propValue] of globalThis.Object.entries(value)) {
|
|
163
|
+
yield* Visit(valueSchema, `${path}/${propKey}`, propValue);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function* Ref(schema, path, value) {
|
|
167
|
+
if (!referenceMap.has(schema.$ref)) {
|
|
168
|
+
throw Error(`TypeErrors: Cannot locate referenced schema for $id '${schema.$id}'`);
|
|
169
|
+
}
|
|
170
|
+
const referencedSchema = referenceMap.get(schema.$ref);
|
|
171
|
+
yield* Visit(referencedSchema, path, value);
|
|
172
|
+
}
|
|
173
|
+
function* Self(schema, path, value) {
|
|
174
|
+
if (!referenceMap.has(schema.$ref)) {
|
|
175
|
+
throw Error(`TypeErrors: Cannot locate referenced schema for $id '${schema.$id}'`);
|
|
176
|
+
}
|
|
177
|
+
const referencedSchema = referenceMap.get(schema.$ref);
|
|
178
|
+
yield* Visit(referencedSchema, path, value);
|
|
179
|
+
}
|
|
180
|
+
function* String(schema, path, value) {
|
|
181
|
+
if (!(typeof value === 'string')) {
|
|
182
|
+
return yield { schema, path, value, message: 'Expected string' };
|
|
183
|
+
}
|
|
184
|
+
if (schema.pattern !== undefined) {
|
|
185
|
+
const regex = new RegExp(schema.pattern);
|
|
186
|
+
if (!regex.test(value)) {
|
|
187
|
+
yield { schema, path, value, message: `Expected string to match pattern ${schema.pattern}` };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function* Tuple(schema, path, value) {
|
|
192
|
+
if (!global.Array.isArray(value)) {
|
|
193
|
+
return yield { schema, path, value, message: 'Expected Array' };
|
|
194
|
+
}
|
|
195
|
+
if (schema.items === undefined && !(value.length === 0)) {
|
|
196
|
+
return yield { schema, path, value, message: 'Expected tuple to have 0 elements' };
|
|
197
|
+
}
|
|
198
|
+
if (!(value.length === schema.maxItems)) {
|
|
199
|
+
yield { schema, path, value, message: `Expected tuple to have ${schema.maxItems} elements` };
|
|
200
|
+
}
|
|
201
|
+
if (!schema.items) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
for (let i = 0; i < schema.items.length; i++) {
|
|
205
|
+
yield* Visit(schema.items[i], `${path}/${i}`, value[i]);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function* Undefined(schema, path, value) {
|
|
209
|
+
if (!(value === undefined)) {
|
|
210
|
+
yield { schema, path, value, message: `Expected undefined` };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function* Union(schema, path, value) {
|
|
214
|
+
const errors = [];
|
|
215
|
+
for (const inner of schema.anyOf) {
|
|
216
|
+
const variantErrors = [...Visit(inner, path, value)];
|
|
217
|
+
if (variantErrors.length === 0)
|
|
218
|
+
return;
|
|
219
|
+
errors.push(...variantErrors);
|
|
220
|
+
}
|
|
221
|
+
for (const error of errors) {
|
|
222
|
+
yield error;
|
|
223
|
+
}
|
|
224
|
+
if (errors.length > 0) {
|
|
225
|
+
yield { schema, path, value, message: 'Expected value of union' };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function* Uint8Array(schema, path, value) {
|
|
229
|
+
if (!(value instanceof globalThis.Uint8Array)) {
|
|
230
|
+
return yield { schema, path, value, message: `Expected Uint8Array` };
|
|
231
|
+
}
|
|
232
|
+
if (schema.maxByteLength && !(value.length <= schema.maxByteLength)) {
|
|
233
|
+
yield { schema, path, value, message: `Expected Uint8Array to have a byte length less or equal to ${schema.maxByteLength}` };
|
|
234
|
+
}
|
|
235
|
+
if (schema.minByteLength && !(value.length >= schema.minByteLength)) {
|
|
236
|
+
yield { schema, path, value, message: `Expected Uint8Array to have a byte length greater or equal to ${schema.maxByteLength}` };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function* Unknown(schema, path, value) { }
|
|
240
|
+
function* Void(schema, path, value) {
|
|
241
|
+
if (!(value === null)) {
|
|
242
|
+
return yield { schema, path, value, message: `Expected null` };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function* Visit(schema, path, value) {
|
|
246
|
+
if (schema.$id !== undefined) {
|
|
247
|
+
referenceMap.set(schema.$id, schema);
|
|
248
|
+
}
|
|
249
|
+
const anySchema = schema;
|
|
250
|
+
switch (anySchema[Types.Kind]) {
|
|
251
|
+
case 'Any':
|
|
252
|
+
return yield* Any(anySchema, path, value);
|
|
253
|
+
case 'Array':
|
|
254
|
+
return yield* Array(anySchema, path, value);
|
|
255
|
+
case 'Boolean':
|
|
256
|
+
return yield* Boolean(anySchema, path, value);
|
|
257
|
+
case 'Constructor':
|
|
258
|
+
return yield* Constructor(anySchema, path, value);
|
|
259
|
+
case 'Function':
|
|
260
|
+
return yield* Function(anySchema, path, value);
|
|
261
|
+
case 'Integer':
|
|
262
|
+
return yield* Integer(anySchema, path, value);
|
|
263
|
+
case 'Literal':
|
|
264
|
+
return yield* Literal(anySchema, path, value);
|
|
265
|
+
case 'Null':
|
|
266
|
+
return yield* Null(anySchema, path, value);
|
|
267
|
+
case 'Number':
|
|
268
|
+
return yield* Number(anySchema, path, value);
|
|
269
|
+
case 'Object':
|
|
270
|
+
return yield* Object(anySchema, path, value);
|
|
271
|
+
case 'Promise':
|
|
272
|
+
return yield* Promise(anySchema, path, value);
|
|
273
|
+
case 'Record':
|
|
274
|
+
return yield* Record(anySchema, path, value);
|
|
275
|
+
case 'Ref':
|
|
276
|
+
return yield* Ref(anySchema, path, value);
|
|
277
|
+
case 'Self':
|
|
278
|
+
return yield* Self(anySchema, path, value);
|
|
279
|
+
case 'String':
|
|
280
|
+
return yield* String(anySchema, path, value);
|
|
281
|
+
case 'Tuple':
|
|
282
|
+
return yield* Tuple(anySchema, path, value);
|
|
283
|
+
case 'Undefined':
|
|
284
|
+
return yield* Undefined(anySchema, path, value);
|
|
285
|
+
case 'Union':
|
|
286
|
+
return yield* Union(anySchema, path, value);
|
|
287
|
+
case 'Uint8Array':
|
|
288
|
+
return yield* Uint8Array(anySchema, path, value);
|
|
289
|
+
case 'Unknown':
|
|
290
|
+
return yield* Unknown(anySchema, path, value);
|
|
291
|
+
case 'Void':
|
|
292
|
+
return yield* Void(anySchema, path, value);
|
|
293
|
+
default:
|
|
294
|
+
throw Error(`Unknown schema kind '${schema[Types.Kind]}'`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const referenceMap = new Map();
|
|
298
|
+
function SetAdditional(additional = []) {
|
|
299
|
+
referenceMap.clear();
|
|
300
|
+
for (const schema of additional) {
|
|
301
|
+
if (!schema.$id)
|
|
302
|
+
throw Error('TypeErrors: Referenced additional schemas must have an $id');
|
|
303
|
+
referenceMap.set(schema.$id, schema);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function* Errors(schema, additional, value) {
|
|
307
|
+
SetAdditional(additional);
|
|
308
|
+
yield* Visit(schema, '', value);
|
|
309
|
+
}
|
|
310
|
+
TypeErrors.Errors = Errors;
|
|
311
|
+
})(TypeErrors = exports.TypeErrors || (exports.TypeErrors = {}));
|
package/compiler/index.d.ts
CHANGED
package/compiler/index.js
CHANGED
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -727,7 +727,7 @@ Please refer to the official AJV [documentation](https://ajv.js.org/guide/gettin
|
|
|
727
727
|
|
|
728
728
|
### Compiler
|
|
729
729
|
|
|
730
|
-
TypeBox
|
|
730
|
+
TypeBox includes a specialized type compiler that can be used as a runtime type checker in absense of a JSON Schema validator. This compiler is optimized for high throughput validation scenarios and generally performs better than AJV for most structural checks. Please note that this compiler is not fully JSON Schema compliant and is limited to TypeBox types only. The `TypeCompiler` contains a `Compile(T)` function that returns a `TypeCheck<T>` object that can be used to test the validity of a value as well as obtain errors.
|
|
731
731
|
|
|
732
732
|
```typescript
|
|
733
733
|
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
|
@@ -747,9 +747,40 @@ const OK = C.Check({
|
|
|
747
747
|
z: 3
|
|
748
748
|
}) // -> true
|
|
749
749
|
```
|
|
750
|
+
Errors can be obtained by calling the `Errors(...)` function. This function returns an iterator that may contain zero or more errors for the given value. For performance, you should only call `Errors(V)` if the `Check(V)` function returns false.
|
|
751
|
+
```typescript
|
|
752
|
+
const C = TypeCompiler.Compile(Type.Object({
|
|
753
|
+
x: Type.Number(),
|
|
754
|
+
y: Type.Number(),
|
|
755
|
+
z: Type.Number()
|
|
756
|
+
}))
|
|
757
|
+
|
|
758
|
+
const V = { } // invalid
|
|
759
|
+
|
|
760
|
+
if(!C.Check(V)) {
|
|
761
|
+
for(const error of C.Errors(V)) {
|
|
762
|
+
console.log(error)
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
```
|
|
766
|
+
To inspect the generated validation code created by the compiler. You can call the `Code()` function on the `TypeCheck<T>` object.
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
const C = TypeCompiler.Compile(Type.String())
|
|
770
|
+
|
|
771
|
+
console.log(C.Code())
|
|
772
|
+
//
|
|
773
|
+
// outputs:
|
|
774
|
+
//
|
|
775
|
+
// return function check(value) {
|
|
776
|
+
// return (
|
|
777
|
+
// (typeof value === 'string')
|
|
778
|
+
// )
|
|
779
|
+
// }
|
|
780
|
+
```
|
|
750
781
|
|
|
751
782
|
<a name="Contribute"></a>
|
|
752
783
|
|
|
753
784
|
### Contribute
|
|
754
785
|
|
|
755
|
-
TypeBox is open to community contribution, however please ensure you submit an open issue before submitting your pull request. The TypeBox project does preference open community discussion prior to accepting new features.
|
|
786
|
+
TypeBox is open to community contribution, however please ensure you submit an open issue before submitting your pull request. The TypeBox project does preference open community discussion prior to accepting new features.
|
package/typebox.js
CHANGED
|
@@ -107,7 +107,6 @@ class TypeBuilder {
|
|
|
107
107
|
const properties = {};
|
|
108
108
|
for (const object of objects) {
|
|
109
109
|
for (const [key, schema] of Object.entries(object.properties)) {
|
|
110
|
-
delete schema[exports.Modifier];
|
|
111
110
|
properties[key] = properties[key] === undefined ? schema : { [exports.Kind]: 'Union', anyOf: [properties[key], { ...schema }] };
|
|
112
111
|
}
|
|
113
112
|
}
|