schema-shield 1.0.5 → 1.1.0
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/README.md +400 -1050
- package/dist/formats.d.ts.map +1 -1
- package/dist/index.d.ts +33 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1247 -392
- package/dist/index.min.js +2 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +1247 -392
- package/dist/keywords/array-keywords.d.ts.map +1 -1
- package/dist/keywords/number-keywords.d.ts.map +1 -1
- package/dist/keywords/object-keywords.d.ts +7 -1
- package/dist/keywords/object-keywords.d.ts.map +1 -1
- package/dist/keywords/other-keywords.d.ts +17 -1
- package/dist/keywords/other-keywords.d.ts.map +1 -1
- package/dist/keywords/string-keywords.d.ts.map +1 -1
- package/dist/utils/deep-freeze.d.ts.map +1 -1
- package/dist/utils/main-utils.d.ts +7 -4
- package/dist/utils/main-utils.d.ts.map +1 -1
- package/lib/formats.ts +36 -10
- package/lib/index.ts +1157 -155
- package/lib/keywords/array-keywords.ts +18 -3
- package/lib/keywords/number-keywords.ts +19 -5
- package/lib/keywords/object-keywords.ts +59 -61
- package/lib/keywords/other-keywords.ts +205 -192
- package/lib/keywords/string-keywords.ts +51 -8
- package/lib/types.ts +2 -2
- package/lib/utils/deep-freeze.ts +6 -4
- package/lib/utils/main-utils.ts +91 -47
- package/package.json +8 -10
package/lib/index.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DefineErrorFunction,
|
|
4
4
|
ValidationError,
|
|
5
|
+
definePropertyOrThrow,
|
|
5
6
|
getDefinedErrorFunctionForKey,
|
|
6
7
|
getNamedFunction,
|
|
8
|
+
hasOwn,
|
|
7
9
|
resolvePath
|
|
8
10
|
} from "./utils/main-utils";
|
|
9
11
|
|
|
@@ -11,18 +13,31 @@ import { Formats } from "./formats";
|
|
|
11
13
|
import { Types } from "./types";
|
|
12
14
|
import { keywords } from "./keywords";
|
|
13
15
|
import { deepCloneUnfreeze } from "./utils/deep-freeze";
|
|
16
|
+
import {
|
|
17
|
+
createCombinatorValidator,
|
|
18
|
+
prepareCombinatorEntries
|
|
19
|
+
} from "./keywords/other-keywords";
|
|
20
|
+
import {
|
|
21
|
+
applyPropertyDefaults,
|
|
22
|
+
applyEmptyPropertyDefaults
|
|
23
|
+
} from "./keywords/object-keywords";
|
|
14
24
|
|
|
15
25
|
export { ValidationError } from "./utils/main-utils";
|
|
16
26
|
export { deepCloneUnfreeze as deepClone } from "./utils/deep-freeze";
|
|
17
27
|
|
|
18
28
|
export type Result = void | ValidationError | true;
|
|
19
29
|
|
|
30
|
+
export interface ValidateSubschemaFunction {
|
|
31
|
+
(schema: CompiledSchema, data: any): Result;
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
export interface KeywordFunction {
|
|
21
35
|
(
|
|
22
36
|
schema: CompiledSchema,
|
|
23
37
|
data: any,
|
|
24
38
|
defineError: DefineErrorFunction,
|
|
25
|
-
instance: SchemaShield
|
|
39
|
+
instance: SchemaShield,
|
|
40
|
+
validateSubschema?: ValidateSubschemaFunction
|
|
26
41
|
): Result;
|
|
27
42
|
}
|
|
28
43
|
|
|
@@ -57,24 +72,134 @@ interface ValidatorItem {
|
|
|
57
72
|
validate: ValidateFunction;
|
|
58
73
|
}
|
|
59
74
|
|
|
75
|
+
interface PendingCombinator {
|
|
76
|
+
item: ValidatorItem;
|
|
77
|
+
key: "allOf" | "anyOf" | "oneOf";
|
|
78
|
+
defineError: DefineErrorFunction;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ValidationContext {
|
|
82
|
+
active: boolean;
|
|
83
|
+
depth: number;
|
|
84
|
+
depthExceeded: boolean;
|
|
85
|
+
depthError?: ValidationError | true;
|
|
86
|
+
defaults: DefaultJournalEntry[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface SchemaAnalysis {
|
|
90
|
+
requiresDepthGuard: boolean;
|
|
91
|
+
requiresMutationJournal: boolean;
|
|
92
|
+
mutableSchemas: WeakSet<object>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface SchemaPosition {
|
|
96
|
+
source: Record<string, any>;
|
|
97
|
+
baseUri: string;
|
|
98
|
+
resourceRoot: Record<string, any>;
|
|
99
|
+
pointer: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface ReferenceRegistry {
|
|
103
|
+
aliases: ReadonlyMap<string, Record<string, any>>;
|
|
104
|
+
positions: ReadonlyArray<SchemaPosition>;
|
|
105
|
+
positionsByNode: WeakMap<object, SchemaPosition>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface DefaultMutation {
|
|
109
|
+
target: Record<string, any>;
|
|
110
|
+
key: string;
|
|
111
|
+
value: any;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface DefaultJournalEntry {
|
|
115
|
+
target: Record<string, any>;
|
|
116
|
+
key: string;
|
|
117
|
+
descriptor?: PropertyDescriptor;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface DepthGuardState {
|
|
121
|
+
context: ValidationContext | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const MAX_COMPILE_DEPTH = 128;
|
|
125
|
+
const LOCAL_SCHEMA_BASE = "schema-shield://local/root";
|
|
126
|
+
|
|
127
|
+
const FAIL_FAST_TYPE_VALIDATORS: Record<string, ValidateFunction> = {
|
|
128
|
+
object: (data) =>
|
|
129
|
+
data !== null && typeof data === "object" && !Array.isArray(data)
|
|
130
|
+
? undefined
|
|
131
|
+
: true,
|
|
132
|
+
array: (data) => (Array.isArray(data) ? undefined : true),
|
|
133
|
+
string: (data) => (typeof data === "string" ? undefined : true),
|
|
134
|
+
number: (data) =>
|
|
135
|
+
typeof data === "number" && Number.isFinite(data) ? undefined : true,
|
|
136
|
+
integer: (data) =>
|
|
137
|
+
typeof data === "number" && Number.isFinite(data) && Number.isInteger(data)
|
|
138
|
+
? undefined
|
|
139
|
+
: true,
|
|
140
|
+
boolean: (data) => (typeof data === "boolean" ? undefined : true),
|
|
141
|
+
null: (data) => (data === null ? undefined : true)
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
function createBuiltinTypeValidator(
|
|
145
|
+
_type: string,
|
|
146
|
+
defineError: DefineErrorFunction,
|
|
147
|
+
fallback: TypeFunction
|
|
148
|
+
): ValidateFunction {
|
|
149
|
+
return (data) => {
|
|
150
|
+
if (!fallback(data)) {
|
|
151
|
+
return defineError("Invalid type", { data });
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
60
156
|
export class SchemaShield {
|
|
61
157
|
private types: Record<string, TypeFunction | false> = {};
|
|
62
158
|
private formats: Record<string, FormatFunction | false> = {};
|
|
63
159
|
private keywords: Record<string, KeywordFunction | false> = {};
|
|
64
160
|
private immutable = false;
|
|
161
|
+
private useDefaults: boolean | "empty" = false;
|
|
65
162
|
private rootSchema: CompiledSchema | null = null;
|
|
66
|
-
private idRegistry: Map<string, CompiledSchema> = new Map();
|
|
67
163
|
private failFast: boolean = true;
|
|
164
|
+
private maxDepth: number;
|
|
165
|
+
private validationContexts: ValidationContext[] = [];
|
|
166
|
+
private compileCache: WeakMap<object, CompiledSchema> = new WeakMap();
|
|
167
|
+
private compilingRequiresContext = false;
|
|
168
|
+
private compilingMutableSchemas: WeakSet<object> = new WeakSet();
|
|
68
169
|
|
|
69
170
|
constructor({
|
|
70
171
|
immutable = false,
|
|
71
|
-
failFast = true
|
|
172
|
+
failFast = true,
|
|
173
|
+
maxDepth = 128,
|
|
174
|
+
useDefaults = false
|
|
72
175
|
}: {
|
|
73
176
|
immutable?: boolean;
|
|
74
177
|
failFast?: boolean;
|
|
178
|
+
maxDepth?: number;
|
|
179
|
+
useDefaults?: boolean | "empty";
|
|
75
180
|
} = {}) {
|
|
181
|
+
if (!Number.isInteger(maxDepth) || maxDepth < 1 || maxDepth > 256) {
|
|
182
|
+
const error = new ValidationError("maxDepth must be an integer from 1 to 256");
|
|
183
|
+
error.code = "INVALID_MAX_DEPTH";
|
|
184
|
+
error.keyword = "maxDepth";
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
if (
|
|
188
|
+
useDefaults !== false &&
|
|
189
|
+
useDefaults !== true &&
|
|
190
|
+
useDefaults !== "empty"
|
|
191
|
+
) {
|
|
192
|
+
const error = new ValidationError(
|
|
193
|
+
'useDefaults must be false, true, or "empty"'
|
|
194
|
+
);
|
|
195
|
+
error.code = "INVALID_USE_DEFAULTS";
|
|
196
|
+
error.keyword = "useDefaults";
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
76
199
|
this.immutable = immutable;
|
|
77
200
|
this.failFast = failFast;
|
|
201
|
+
this.maxDepth = maxDepth;
|
|
202
|
+
this.useDefaults = useDefaults;
|
|
78
203
|
|
|
79
204
|
for (const [type, validator] of Object.entries(Types)) {
|
|
80
205
|
if (validator) {
|
|
@@ -93,6 +218,56 @@ export class SchemaShield {
|
|
|
93
218
|
}
|
|
94
219
|
}
|
|
95
220
|
|
|
221
|
+
setDefault(target: Record<string, any>, key: string, value: any) {
|
|
222
|
+
const context = this.validationContexts[this.validationContexts.length - 1];
|
|
223
|
+
if (context) {
|
|
224
|
+
context.defaults.push({
|
|
225
|
+
target,
|
|
226
|
+
key,
|
|
227
|
+
descriptor: Reflect.getOwnPropertyDescriptor(target, key)
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
definePropertyOrThrow(target, key, {
|
|
231
|
+
value,
|
|
232
|
+
enumerable: true,
|
|
233
|
+
configurable: true,
|
|
234
|
+
writable: true
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#defaultSavepoint() {
|
|
239
|
+
const context = this.validationContexts[this.validationContexts.length - 1];
|
|
240
|
+
return context ? context.defaults.length : 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#rollbackDefaultSavepoint(savepoint: number) {
|
|
244
|
+
const context = this.validationContexts[this.validationContexts.length - 1];
|
|
245
|
+
if (context) {
|
|
246
|
+
this.rollbackDefaults(context, savepoint);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#captureDefaultSavepoint(savepoint: number): DefaultMutation[] {
|
|
251
|
+
const context = this.validationContexts[this.validationContexts.length - 1];
|
|
252
|
+
if (!context || context.defaults.length === savepoint) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const mutations = context.defaults.slice(savepoint).map((entry) => ({
|
|
257
|
+
...entry,
|
|
258
|
+
value: entry.target[entry.key]
|
|
259
|
+
}));
|
|
260
|
+
this.rollbackDefaults(context, savepoint);
|
|
261
|
+
return mutations;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#restoreDefaults(mutations: DefaultMutation[]) {
|
|
265
|
+
for (let index = 0; index < mutations.length; index++) {
|
|
266
|
+
const mutation = mutations[index];
|
|
267
|
+
this.setDefault(mutation.target, mutation.key, mutation.value);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
96
271
|
addType(name: string, validator: TypeFunction, overwrite = false) {
|
|
97
272
|
if (this.types[name] && !overwrite) {
|
|
98
273
|
throw new ValidationError(`Type "${name}" already exists`);
|
|
@@ -138,15 +313,570 @@ export class SchemaShield {
|
|
|
138
313
|
}
|
|
139
314
|
|
|
140
315
|
getSchemaById(id: string): CompiledSchema | undefined {
|
|
141
|
-
|
|
316
|
+
if (!this.rootSchema) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const stack: CompiledSchema[] = [this.rootSchema];
|
|
321
|
+
const seen = new WeakSet<object>();
|
|
322
|
+
while (stack.length > 0) {
|
|
323
|
+
const node = stack.pop()!;
|
|
324
|
+
if (seen.has(node)) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
seen.add(node);
|
|
328
|
+
if (node.$id === id) {
|
|
329
|
+
return node;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const children = this.schemaChildren(node);
|
|
333
|
+
for (let index = 0; index < children.length; index++) {
|
|
334
|
+
stack.push(children[index] as CompiledSchema);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private depthError(message = "Maximum schema depth exceeded") {
|
|
342
|
+
if (this.failFast) {
|
|
343
|
+
return true as const;
|
|
344
|
+
}
|
|
345
|
+
const error = new ValidationError(message);
|
|
346
|
+
error.code = "MAX_DEPTH_EXCEEDED";
|
|
347
|
+
error.keyword = "maxDepth";
|
|
348
|
+
return error;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private schemaChildEntries(
|
|
352
|
+
schema: Record<string, any>
|
|
353
|
+
): Array<{ value: object; pointer: string }> {
|
|
354
|
+
const children: Array<{ value: object; pointer: string }> = [];
|
|
355
|
+
const mapKeys = [
|
|
356
|
+
"properties",
|
|
357
|
+
"patternProperties",
|
|
358
|
+
"definitions",
|
|
359
|
+
"$defs",
|
|
360
|
+
"dependencies"
|
|
361
|
+
];
|
|
362
|
+
const arrayKeys = ["allOf", "anyOf", "oneOf", "items"];
|
|
363
|
+
const singleKeys = [
|
|
364
|
+
"items",
|
|
365
|
+
"additionalItems",
|
|
366
|
+
"additionalProperties",
|
|
367
|
+
"contains",
|
|
368
|
+
"propertyNames",
|
|
369
|
+
"values",
|
|
370
|
+
"elements",
|
|
371
|
+
"not",
|
|
372
|
+
"if",
|
|
373
|
+
"then",
|
|
374
|
+
"else"
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
for (const key of mapKeys) {
|
|
378
|
+
const value = schema[key];
|
|
379
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
for (const childKey of Object.keys(value)) {
|
|
383
|
+
const child = value[childKey];
|
|
384
|
+
if (child && typeof child === "object") {
|
|
385
|
+
children.push({
|
|
386
|
+
value: child,
|
|
387
|
+
pointer: `/${this.escapePointerToken(key)}/${this.escapePointerToken(
|
|
388
|
+
childKey
|
|
389
|
+
)}`
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (const key of arrayKeys) {
|
|
396
|
+
const value = schema[key];
|
|
397
|
+
if (!Array.isArray(value)) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
for (let index = 0; index < value.length; index++) {
|
|
401
|
+
const child = value[index];
|
|
402
|
+
if (child && typeof child === "object") {
|
|
403
|
+
children.push({
|
|
404
|
+
value: child,
|
|
405
|
+
pointer: `/${this.escapePointerToken(key)}/${index}`
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for (const key of singleKeys) {
|
|
412
|
+
const value = schema[key];
|
|
413
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
414
|
+
children.push({
|
|
415
|
+
value,
|
|
416
|
+
pointer: `/${this.escapePointerToken(key)}`
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
for (const key of Object.keys(schema)) {
|
|
422
|
+
if (
|
|
423
|
+
key === "enum" ||
|
|
424
|
+
key === "const" ||
|
|
425
|
+
key === "default" ||
|
|
426
|
+
key === "examples" ||
|
|
427
|
+
mapKeys.includes(key) ||
|
|
428
|
+
arrayKeys.includes(key) ||
|
|
429
|
+
singleKeys.includes(key)
|
|
430
|
+
) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
const keyword = this.getKeyword(key);
|
|
434
|
+
const value = schema[key];
|
|
435
|
+
if (
|
|
436
|
+
keyword &&
|
|
437
|
+
keyword !== keywords[key] &&
|
|
438
|
+
value &&
|
|
439
|
+
typeof value === "object"
|
|
440
|
+
) {
|
|
441
|
+
children.push({
|
|
442
|
+
value,
|
|
443
|
+
pointer: `/${this.escapePointerToken(key)}`
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return children;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private schemaChildren(schema: Record<string, any>): object[] {
|
|
452
|
+
return this.schemaChildEntries(schema).map((entry) => entry.value);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private registrySubschemaEntries(
|
|
456
|
+
schema: Record<string, any>
|
|
457
|
+
): Array<{ value: object; pointer: string }> {
|
|
458
|
+
const children: Array<{ value: object; pointer: string }> = [];
|
|
459
|
+
const mapKeys = ["properties", "patternProperties", "definitions"];
|
|
460
|
+
const arrayKeys = ["allOf", "anyOf", "oneOf", "items"];
|
|
461
|
+
const singleKeys = [
|
|
462
|
+
"items",
|
|
463
|
+
"additionalItems",
|
|
464
|
+
"additionalProperties",
|
|
465
|
+
"contains",
|
|
466
|
+
"propertyNames",
|
|
467
|
+
"not",
|
|
468
|
+
"if",
|
|
469
|
+
"then",
|
|
470
|
+
"else"
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
for (const key of mapKeys) {
|
|
474
|
+
const value = schema[key];
|
|
475
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
for (const childKey of Object.keys(value)) {
|
|
479
|
+
const child = value[childKey];
|
|
480
|
+
if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
481
|
+
children.push({
|
|
482
|
+
value: child,
|
|
483
|
+
pointer: `/${this.escapePointerToken(key)}/${this.escapePointerToken(
|
|
484
|
+
childKey
|
|
485
|
+
)}`
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const dependencies = schema.dependencies;
|
|
492
|
+
if (
|
|
493
|
+
dependencies &&
|
|
494
|
+
typeof dependencies === "object" &&
|
|
495
|
+
!Array.isArray(dependencies)
|
|
496
|
+
) {
|
|
497
|
+
for (const dependencyKey of Object.keys(dependencies)) {
|
|
498
|
+
const child = dependencies[dependencyKey];
|
|
499
|
+
if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
500
|
+
children.push({
|
|
501
|
+
value: child,
|
|
502
|
+
pointer: `/dependencies/${this.escapePointerToken(dependencyKey)}`
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
for (const key of arrayKeys) {
|
|
509
|
+
const value = schema[key];
|
|
510
|
+
if (!Array.isArray(value)) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
for (let index = 0; index < value.length; index++) {
|
|
514
|
+
const child = value[index];
|
|
515
|
+
if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
516
|
+
children.push({
|
|
517
|
+
value: child,
|
|
518
|
+
pointer: `/${this.escapePointerToken(key)}/${index}`
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
for (const key of singleKeys) {
|
|
525
|
+
const value = schema[key];
|
|
526
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
527
|
+
children.push({
|
|
528
|
+
value,
|
|
529
|
+
pointer: `/${this.escapePointerToken(key)}`
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return children;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private escapePointerToken(value: string): string {
|
|
538
|
+
return value.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private resolveUri(reference: string, baseUri: string, keyword: "$id" | "$ref") {
|
|
542
|
+
try {
|
|
543
|
+
return new URL(reference, baseUri).href;
|
|
544
|
+
} catch {
|
|
545
|
+
const error = new ValidationError(`Invalid ${keyword} URI: ${reference}`);
|
|
546
|
+
error.code = keyword === "$id" ? "INVALID_SCHEMA_ID" : "REFERENCE_NOT_FOUND";
|
|
547
|
+
error.keyword = keyword;
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private resourceUri(uri: string): string {
|
|
553
|
+
const hashIndex = uri.indexOf("#");
|
|
554
|
+
return hashIndex === -1 ? uri : uri.slice(0, hashIndex);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private buildReferenceRegistry(schema: any): ReferenceRegistry {
|
|
558
|
+
const aliases = new Map<string, Record<string, any>>();
|
|
559
|
+
const positions: SchemaPosition[] = [];
|
|
560
|
+
const positionsByNode = new WeakMap<object, SchemaPosition>();
|
|
561
|
+
|
|
562
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
563
|
+
return Object.freeze({
|
|
564
|
+
aliases: aliases as ReadonlyMap<string, Record<string, any>>,
|
|
565
|
+
positions: Object.freeze(positions),
|
|
566
|
+
positionsByNode
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const register = (uri: string, node: Record<string, any>) => {
|
|
571
|
+
const existing = aliases.get(uri);
|
|
572
|
+
if (existing && existing !== node) {
|
|
573
|
+
const error = new ValidationError(`Duplicate schema identity: ${uri}`);
|
|
574
|
+
error.code = "DUPLICATE_SCHEMA_ID";
|
|
575
|
+
error.keyword = "$id";
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
578
|
+
aliases.set(uri, node);
|
|
579
|
+
};
|
|
580
|
+
register(LOCAL_SCHEMA_BASE, schema);
|
|
581
|
+
|
|
582
|
+
const visited = new WeakSet<object>();
|
|
583
|
+
const stack: Array<{
|
|
584
|
+
node: Record<string, any>;
|
|
585
|
+
inheritedBase: string;
|
|
586
|
+
resourceRoot: Record<string, any>;
|
|
587
|
+
pointer: string;
|
|
588
|
+
}> = [
|
|
589
|
+
{
|
|
590
|
+
node: schema,
|
|
591
|
+
inheritedBase: LOCAL_SCHEMA_BASE,
|
|
592
|
+
resourceRoot: schema,
|
|
593
|
+
pointer: "#"
|
|
594
|
+
}
|
|
595
|
+
];
|
|
596
|
+
|
|
597
|
+
while (stack.length > 0) {
|
|
598
|
+
const entry = stack.pop()!;
|
|
599
|
+
if (visited.has(entry.node)) {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
visited.add(entry.node);
|
|
603
|
+
|
|
604
|
+
let baseUri = entry.inheritedBase;
|
|
605
|
+
let resourceRoot = entry.resourceRoot;
|
|
606
|
+
if (typeof entry.node.$id === "string" && !("$ref" in entry.node)) {
|
|
607
|
+
baseUri = this.resolveUri(entry.node.$id, entry.inheritedBase, "$id");
|
|
608
|
+
register(baseUri, entry.node);
|
|
609
|
+
if (baseUri.indexOf("#") === -1 || baseUri.endsWith("#")) {
|
|
610
|
+
resourceRoot = entry.node;
|
|
611
|
+
register(this.resourceUri(baseUri), entry.node);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const position = {
|
|
616
|
+
source: entry.node,
|
|
617
|
+
baseUri,
|
|
618
|
+
resourceRoot,
|
|
619
|
+
pointer: entry.pointer
|
|
620
|
+
};
|
|
621
|
+
positions.push(position);
|
|
622
|
+
positionsByNode.set(entry.node, position);
|
|
623
|
+
|
|
624
|
+
const children = this.registrySubschemaEntries(entry.node);
|
|
625
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
626
|
+
const child = children[index];
|
|
627
|
+
if (Array.isArray(child.value)) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
stack.push({
|
|
631
|
+
node: child.value as Record<string, any>,
|
|
632
|
+
inheritedBase: baseUri,
|
|
633
|
+
resourceRoot,
|
|
634
|
+
pointer: `${entry.pointer}${child.pointer}`
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return Object.freeze({
|
|
640
|
+
aliases: aliases as ReadonlyMap<string, Record<string, any>>,
|
|
641
|
+
positions: Object.freeze(positions),
|
|
642
|
+
positionsByNode
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private resolveReferenceSource(
|
|
647
|
+
ref: string,
|
|
648
|
+
position: SchemaPosition,
|
|
649
|
+
registry: ReferenceRegistry
|
|
650
|
+
): any {
|
|
651
|
+
const resolvedUri = this.resolveUri(ref, position.baseUri, "$ref");
|
|
652
|
+
const exactTarget = registry.aliases.get(resolvedUri);
|
|
653
|
+
if (exactTarget) {
|
|
654
|
+
return exactTarget;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const resourceRoot = registry.aliases.get(this.resourceUri(resolvedUri));
|
|
658
|
+
if (!resourceRoot) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const hashIndex = resolvedUri.indexOf("#");
|
|
663
|
+
const fragment = hashIndex === -1 ? "" : resolvedUri.slice(hashIndex + 1);
|
|
664
|
+
if (fragment.length === 0) {
|
|
665
|
+
return resourceRoot;
|
|
666
|
+
}
|
|
667
|
+
if (fragment.startsWith("/")) {
|
|
668
|
+
return resolvePath(resourceRoot, `#${fragment}`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private analyzeSchema(schema: any, registry: ReferenceRegistry): SchemaAnalysis {
|
|
675
|
+
if (!schema || typeof schema !== "object") {
|
|
676
|
+
return {
|
|
677
|
+
requiresDepthGuard: false,
|
|
678
|
+
requiresMutationJournal: false,
|
|
679
|
+
mutableSchemas: new WeakSet()
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const visiting = new WeakSet<object>();
|
|
684
|
+
const visited = new WeakSet<object>();
|
|
685
|
+
const stack: Array<{
|
|
686
|
+
value: Record<string, any>;
|
|
687
|
+
depth: number;
|
|
688
|
+
exit: boolean;
|
|
689
|
+
}> = [{ value: schema, depth: 0, exit: false }];
|
|
690
|
+
let requiresDepthGuard = false;
|
|
691
|
+
let requiresMutationJournal = false;
|
|
692
|
+
|
|
693
|
+
while (stack.length > 0) {
|
|
694
|
+
const entry = stack.pop()!;
|
|
695
|
+
if (entry.exit) {
|
|
696
|
+
visiting.delete(entry.value);
|
|
697
|
+
visited.add(entry.value);
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
if (visited.has(entry.value)) {
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (visiting.has(entry.value)) {
|
|
704
|
+
const error = new ValidationError("Cyclic schema graph is not supported");
|
|
705
|
+
error.code = "CYCLIC_SCHEMA_GRAPH";
|
|
706
|
+
error.keyword = "compile";
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
if (entry.depth > MAX_COMPILE_DEPTH) {
|
|
710
|
+
const error = new ValidationError("Maximum compile depth exceeded");
|
|
711
|
+
error.code = "MAX_COMPILE_DEPTH_EXCEEDED";
|
|
712
|
+
error.keyword = "compile";
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
if (entry.depth > this.maxDepth) {
|
|
716
|
+
requiresDepthGuard = true;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
visiting.add(entry.value);
|
|
720
|
+
stack.push({ ...entry, exit: true });
|
|
721
|
+
|
|
722
|
+
if (this.useDefaults !== false && this.hasPropertyDefaults(entry.value)) {
|
|
723
|
+
requiresMutationJournal = true;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
for (const key of Object.keys(entry.value)) {
|
|
727
|
+
const keyword = this.getKeyword(key);
|
|
728
|
+
if (keyword && keyword !== keywords[key]) {
|
|
729
|
+
requiresDepthGuard = true;
|
|
730
|
+
requiresMutationJournal = true;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const children = this.schemaChildren(entry.value);
|
|
735
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
736
|
+
stack.push({
|
|
737
|
+
value: children[index] as Record<string, any>,
|
|
738
|
+
depth: entry.depth + 1,
|
|
739
|
+
exit: false
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const semanticState = new WeakMap<object, 1 | 2>();
|
|
745
|
+
const semanticStack: Array<{
|
|
746
|
+
value: Record<string, any>;
|
|
747
|
+
exit: boolean;
|
|
748
|
+
}> = [{ value: schema, exit: false }];
|
|
749
|
+
while (semanticStack.length > 0 && !requiresDepthGuard) {
|
|
750
|
+
const entry = semanticStack.pop()!;
|
|
751
|
+
if (entry.exit) {
|
|
752
|
+
semanticState.set(entry.value, 2);
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
const state = semanticState.get(entry.value);
|
|
756
|
+
if (state === 1) {
|
|
757
|
+
requiresDepthGuard = true;
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
if (state === 2) {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
semanticState.set(entry.value, 1);
|
|
764
|
+
semanticStack.push({ value: entry.value, exit: true });
|
|
765
|
+
const children = this.schemaChildren(entry.value);
|
|
766
|
+
if (
|
|
767
|
+
typeof entry.value.$ref === "string" &&
|
|
768
|
+
this.getKeyword("$ref") === keywords.$ref
|
|
769
|
+
) {
|
|
770
|
+
const position = registry.positionsByNode.get(entry.value);
|
|
771
|
+
const target = position
|
|
772
|
+
? this.resolveReferenceSource(entry.value.$ref, position, registry)
|
|
773
|
+
: undefined;
|
|
774
|
+
if (target && typeof target === "object") {
|
|
775
|
+
children.push(target);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
779
|
+
semanticStack.push({
|
|
780
|
+
value: children[index] as Record<string, any>,
|
|
781
|
+
exit: false
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const mutableSchemas = new WeakSet<object>();
|
|
787
|
+
if (requiresMutationJournal) {
|
|
788
|
+
const mutationStack: Array<{
|
|
789
|
+
value: Record<string, any>;
|
|
790
|
+
exit: boolean;
|
|
791
|
+
}> = [{ value: schema, exit: false }];
|
|
792
|
+
const mutationVisited = new WeakSet<object>();
|
|
793
|
+
while (mutationStack.length > 0) {
|
|
794
|
+
const entry = mutationStack.pop()!;
|
|
795
|
+
if (entry.exit) {
|
|
796
|
+
const children = this.schemaChildren(entry.value);
|
|
797
|
+
const hasCustomKeyword = Object.keys(entry.value).some((key) => {
|
|
798
|
+
const keyword = this.getKeyword(key);
|
|
799
|
+
return !!keyword && keyword !== keywords[key];
|
|
800
|
+
});
|
|
801
|
+
if (
|
|
802
|
+
(this.useDefaults !== false &&
|
|
803
|
+
this.hasPropertyDefaults(entry.value)) ||
|
|
804
|
+
hasCustomKeyword ||
|
|
805
|
+
typeof entry.value.$ref === "string" ||
|
|
806
|
+
children.some((child) => mutableSchemas.has(child))
|
|
807
|
+
) {
|
|
808
|
+
mutableSchemas.add(entry.value);
|
|
809
|
+
}
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (mutationVisited.has(entry.value)) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
mutationVisited.add(entry.value);
|
|
816
|
+
mutationStack.push({ value: entry.value, exit: true });
|
|
817
|
+
const children = this.schemaChildren(entry.value);
|
|
818
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
819
|
+
mutationStack.push({
|
|
820
|
+
value: children[index] as Record<string, any>,
|
|
821
|
+
exit: false
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return { requiresDepthGuard, requiresMutationJournal, mutableSchemas };
|
|
142
828
|
}
|
|
143
829
|
|
|
144
830
|
compile(schema: any): Validator {
|
|
145
|
-
this.
|
|
831
|
+
const prepared = this.prepareSchema(schema);
|
|
832
|
+
const compiledSchema = prepared.compiledSchema;
|
|
833
|
+
if (!prepared.requiresDepthGuard && !prepared.requiresMutationJournal) {
|
|
834
|
+
const directValidate = compiledSchema.$validate!;
|
|
835
|
+
const validate = (this.immutable
|
|
836
|
+
? ((data: any) => {
|
|
837
|
+
const clonedData = deepCloneUnfreeze(data);
|
|
838
|
+
const error = directValidate(clonedData);
|
|
839
|
+
return error
|
|
840
|
+
? { data: clonedData, error, valid: false }
|
|
841
|
+
: { data: clonedData, error: null, valid: true };
|
|
842
|
+
})
|
|
843
|
+
: ((data: any) => {
|
|
844
|
+
const error = directValidate(data);
|
|
845
|
+
return error
|
|
846
|
+
? { data, error, valid: false }
|
|
847
|
+
: { data, error: null, valid: true };
|
|
848
|
+
})) as Validator;
|
|
849
|
+
validate.compiledSchema = compiledSchema;
|
|
850
|
+
return validate;
|
|
851
|
+
}
|
|
852
|
+
return this.createGuardedValidator(compiledSchema, prepared.depthGuardState!);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
private prepareSchema(schema: any) {
|
|
856
|
+
const referenceRegistry = this.buildReferenceRegistry(schema);
|
|
857
|
+
const analysis = this.analyzeSchema(schema, referenceRegistry);
|
|
858
|
+
this.compileCache = new WeakMap();
|
|
859
|
+
this.compilingRequiresContext =
|
|
860
|
+
analysis.requiresDepthGuard || analysis.requiresMutationJournal;
|
|
861
|
+
this.compilingMutableSchemas = analysis.mutableSchemas;
|
|
146
862
|
const compiledSchema = this.compileSchema(schema);
|
|
147
863
|
this.rootSchema = compiledSchema;
|
|
864
|
+
|
|
865
|
+
let depthGuardState: DepthGuardState | null = null;
|
|
866
|
+
if (analysis.requiresDepthGuard) {
|
|
867
|
+
depthGuardState = this.installDepthGuards(compiledSchema);
|
|
868
|
+
definePropertyOrThrow(compiledSchema, "_requiresDepthGuard", {
|
|
869
|
+
value: true,
|
|
870
|
+
enumerable: false,
|
|
871
|
+
configurable: false,
|
|
872
|
+
writable: false
|
|
873
|
+
});
|
|
874
|
+
} else if (analysis.requiresMutationJournal) {
|
|
875
|
+
depthGuardState = { context: null };
|
|
876
|
+
}
|
|
877
|
+
|
|
148
878
|
if ((compiledSchema as any)._hasRef === true) {
|
|
149
|
-
this.linkReferences(
|
|
879
|
+
this.linkReferences(referenceRegistry);
|
|
150
880
|
}
|
|
151
881
|
|
|
152
882
|
if (!compiledSchema.$validate) {
|
|
@@ -176,19 +906,62 @@ export class SchemaShield {
|
|
|
176
906
|
}
|
|
177
907
|
}
|
|
178
908
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (res) {
|
|
186
|
-
return { data: clonedData, error: res, valid: false };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return { data: clonedData, error: null, valid: true };
|
|
909
|
+
return {
|
|
910
|
+
compiledSchema,
|
|
911
|
+
requiresDepthGuard: analysis.requiresDepthGuard,
|
|
912
|
+
requiresMutationJournal: analysis.requiresMutationJournal,
|
|
913
|
+
depthGuardState
|
|
190
914
|
};
|
|
915
|
+
}
|
|
191
916
|
|
|
917
|
+
private createGuardedValidator(
|
|
918
|
+
compiledSchema: CompiledSchema,
|
|
919
|
+
depthGuardState: DepthGuardState
|
|
920
|
+
): Validator {
|
|
921
|
+
const reusableContext: ValidationContext = {
|
|
922
|
+
active: false,
|
|
923
|
+
depth: -1,
|
|
924
|
+
depthExceeded: false,
|
|
925
|
+
defaults: []
|
|
926
|
+
};
|
|
927
|
+
const validate = ((data: any) => {
|
|
928
|
+
this.rootSchema = compiledSchema;
|
|
929
|
+
const context = reusableContext.active
|
|
930
|
+
? {
|
|
931
|
+
active: false,
|
|
932
|
+
depth: -1,
|
|
933
|
+
depthExceeded: false,
|
|
934
|
+
defaults: []
|
|
935
|
+
}
|
|
936
|
+
: reusableContext;
|
|
937
|
+
context.active = true;
|
|
938
|
+
context.depth = -1;
|
|
939
|
+
context.depthExceeded = false;
|
|
940
|
+
delete context.depthError;
|
|
941
|
+
context.defaults.length = 0;
|
|
942
|
+
this.validationContexts.push(context);
|
|
943
|
+
const priorContext = depthGuardState.context;
|
|
944
|
+
depthGuardState.context = context;
|
|
945
|
+
let clonedData = data;
|
|
946
|
+
try {
|
|
947
|
+
clonedData = this.immutable ? deepCloneUnfreeze(data) : data;
|
|
948
|
+
let error = compiledSchema.$validate!(clonedData);
|
|
949
|
+
if (this.isDepthError(error)) {
|
|
950
|
+
this.rollbackDefaults(context, 0);
|
|
951
|
+
error = context.depthError || this.depthError();
|
|
952
|
+
}
|
|
953
|
+
return error
|
|
954
|
+
? { data: clonedData, error, valid: false }
|
|
955
|
+
: { data: clonedData, error: null, valid: true };
|
|
956
|
+
} catch (error) {
|
|
957
|
+
this.rollbackDefaults(context, 0);
|
|
958
|
+
throw error;
|
|
959
|
+
} finally {
|
|
960
|
+
depthGuardState.context = priorContext;
|
|
961
|
+
this.validationContexts.pop();
|
|
962
|
+
context.active = false;
|
|
963
|
+
}
|
|
964
|
+
}) as Validator;
|
|
192
965
|
validate.compiledSchema = compiledSchema;
|
|
193
966
|
return validate;
|
|
194
967
|
}
|
|
@@ -346,7 +1119,7 @@ export class SchemaShield {
|
|
|
346
1119
|
return;
|
|
347
1120
|
}
|
|
348
1121
|
|
|
349
|
-
|
|
1122
|
+
definePropertyOrThrow(schema, "_hasRef", {
|
|
350
1123
|
value: true,
|
|
351
1124
|
enumerable: false,
|
|
352
1125
|
configurable: false,
|
|
@@ -421,16 +1194,19 @@ export class SchemaShield {
|
|
|
421
1194
|
}
|
|
422
1195
|
}
|
|
423
1196
|
|
|
424
|
-
private
|
|
1197
|
+
private hasPropertyDefaults(schema: Record<string, any>): boolean {
|
|
425
1198
|
const properties = schema.properties;
|
|
426
1199
|
if (!this.isPlainObject(properties)) {
|
|
427
1200
|
return false;
|
|
428
1201
|
}
|
|
429
1202
|
|
|
430
|
-
const
|
|
431
|
-
for (let i = 0; i <
|
|
432
|
-
const subSchema = properties[
|
|
433
|
-
if (
|
|
1203
|
+
const propertyKeys = Object.keys(properties);
|
|
1204
|
+
for (let i = 0; i < propertyKeys.length; i++) {
|
|
1205
|
+
const subSchema = properties[propertyKeys[i]];
|
|
1206
|
+
if (
|
|
1207
|
+
this.isPlainObject(subSchema) &&
|
|
1208
|
+
hasOwn(subSchema, "default")
|
|
1209
|
+
) {
|
|
434
1210
|
return true;
|
|
435
1211
|
}
|
|
436
1212
|
}
|
|
@@ -442,28 +1218,150 @@ export class SchemaShield {
|
|
|
442
1218
|
return (Types as Record<string, TypeFunction | false>)[type] === validator;
|
|
443
1219
|
}
|
|
444
1220
|
|
|
445
|
-
private
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
schema = { oneOf: [] }; // Always invalid
|
|
1221
|
+
private rollbackDefaults(context: ValidationContext, start: number) {
|
|
1222
|
+
for (let index = context.defaults.length - 1; index >= start; index--) {
|
|
1223
|
+
const entry = context.defaults[index];
|
|
1224
|
+
if (entry.descriptor) {
|
|
1225
|
+
definePropertyOrThrow(entry.target, entry.key, entry.descriptor);
|
|
451
1226
|
} else {
|
|
452
|
-
|
|
1227
|
+
delete entry.target[entry.key];
|
|
453
1228
|
}
|
|
454
1229
|
}
|
|
1230
|
+
context.defaults.length = start;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
private isDepthError(error: Result): boolean {
|
|
1234
|
+
const context = this.validationContexts[this.validationContexts.length - 1];
|
|
1235
|
+
if (context?.depthExceeded) {
|
|
1236
|
+
return true;
|
|
1237
|
+
}
|
|
1238
|
+
if (!(error instanceof ValidationError)) {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
return error.getCause().code === "MAX_DEPTH_EXCEEDED";
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
private validateSubschema(schema: CompiledSchema, data: any): Result {
|
|
1245
|
+
if (!schema || typeof schema.$validate !== "function") {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const context = this.validationContexts[this.validationContexts.length - 1];
|
|
1249
|
+
const savepoint = context?.defaults.length || 0;
|
|
1250
|
+
try {
|
|
1251
|
+
const error = schema.$validate(data);
|
|
1252
|
+
if (error && context) {
|
|
1253
|
+
this.rollbackDefaults(context, savepoint);
|
|
1254
|
+
}
|
|
1255
|
+
return error;
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
if (context) {
|
|
1258
|
+
this.rollbackDefaults(context, savepoint);
|
|
1259
|
+
}
|
|
1260
|
+
throw error;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
private installDepthGuards(root: CompiledSchema): DepthGuardState {
|
|
1265
|
+
const state: DepthGuardState = { context: null };
|
|
1266
|
+
const stack: CompiledSchema[] = [root];
|
|
1267
|
+
const seen = new WeakSet<object>();
|
|
1268
|
+
|
|
1269
|
+
while (stack.length > 0) {
|
|
1270
|
+
const schema = stack.pop()!;
|
|
1271
|
+
if (!schema || typeof schema !== "object" || seen.has(schema)) {
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
seen.add(schema);
|
|
1275
|
+
|
|
1276
|
+
if (typeof schema.$validate === "function") {
|
|
1277
|
+
const directValidate = schema.$validate;
|
|
1278
|
+
schema.$validate = getNamedFunction(directValidate.name, (data: any) => {
|
|
1279
|
+
const context = state.context;
|
|
1280
|
+
if (!context) {
|
|
1281
|
+
return directValidate(data);
|
|
1282
|
+
}
|
|
1283
|
+
const nextDepth = context.depth + 1;
|
|
1284
|
+
if (nextDepth > this.maxDepth) {
|
|
1285
|
+
context.depthExceeded = true;
|
|
1286
|
+
if (!context.depthError) {
|
|
1287
|
+
context.depthError = this.depthError();
|
|
1288
|
+
}
|
|
1289
|
+
return context.depthError;
|
|
1290
|
+
}
|
|
1291
|
+
context.depth = nextDepth;
|
|
1292
|
+
try {
|
|
1293
|
+
return directValidate(data);
|
|
1294
|
+
} finally {
|
|
1295
|
+
context.depth--;
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const children = this.schemaChildren(schema);
|
|
1301
|
+
for (const child of children) {
|
|
1302
|
+
stack.push(child as CompiledSchema);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return state;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
private compileSchema(schema: Partial<CompiledSchema> | any): CompiledSchema {
|
|
1309
|
+
if (schema === true) {
|
|
1310
|
+
return {
|
|
1311
|
+
$validate: getNamedFunction<ValidateFunction>("Validate_True", () => {})
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (schema === false) {
|
|
1315
|
+
const compiledFalse: CompiledSchema = {};
|
|
1316
|
+
const defineError = getDefinedErrorFunctionForKey(
|
|
1317
|
+
"oneOf",
|
|
1318
|
+
compiledFalse,
|
|
1319
|
+
this.failFast
|
|
1320
|
+
);
|
|
1321
|
+
compiledFalse.$validate = getNamedFunction<ValidateFunction>(
|
|
1322
|
+
"Validate_False",
|
|
1323
|
+
(data) => defineError("Value is not valid", { data })
|
|
1324
|
+
);
|
|
1325
|
+
return compiledFalse;
|
|
1326
|
+
}
|
|
1327
|
+
const sourceSchema =
|
|
1328
|
+
schema && typeof schema === "object" && !Array.isArray(schema)
|
|
1329
|
+
? schema
|
|
1330
|
+
: null;
|
|
1331
|
+
const schemaCanApplyDefaults =
|
|
1332
|
+
sourceSchema !== null && this.compilingMutableSchemas.has(sourceSchema);
|
|
1333
|
+
if (sourceSchema) {
|
|
1334
|
+
const cached = this.compileCache.get(sourceSchema);
|
|
1335
|
+
if (cached) {
|
|
1336
|
+
return cached;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
1340
|
+
schema = { oneOf: [schema] };
|
|
1341
|
+
}
|
|
455
1342
|
|
|
456
1343
|
schema = this.normalizeSchemaForCompile(schema);
|
|
457
1344
|
|
|
458
1345
|
const compiledSchema: CompiledSchema = deepCloneUnfreeze(
|
|
459
1346
|
schema
|
|
460
1347
|
) as CompiledSchema;
|
|
1348
|
+
if (sourceSchema) {
|
|
1349
|
+
this.compileCache.set(sourceSchema, compiledSchema);
|
|
1350
|
+
}
|
|
1351
|
+
if (schemaCanApplyDefaults) {
|
|
1352
|
+
definePropertyOrThrow(compiledSchema, "_canApplyDefaults", {
|
|
1353
|
+
value: true,
|
|
1354
|
+
enumerable: false,
|
|
1355
|
+
configurable: false,
|
|
1356
|
+
writable: false
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
461
1359
|
|
|
462
|
-
|
|
1360
|
+
const validateSubschema = this.compilingRequiresContext
|
|
1361
|
+
? this.validateSubschema.bind(this)
|
|
1362
|
+
: undefined;
|
|
463
1363
|
|
|
464
|
-
|
|
465
|
-
this.idRegistry.set(schema.$id, compiledSchema);
|
|
466
|
-
}
|
|
1364
|
+
let schemaHasRef = false;
|
|
467
1365
|
|
|
468
1366
|
if ("$ref" in schema) {
|
|
469
1367
|
schemaHasRef = true;
|
|
@@ -475,24 +1373,64 @@ export class SchemaShield {
|
|
|
475
1373
|
this.failFast
|
|
476
1374
|
);
|
|
477
1375
|
|
|
1376
|
+
const isBuiltinRef = refValidator === keywords.$ref;
|
|
478
1377
|
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
479
|
-
"Validate_Reference",
|
|
1378
|
+
isBuiltinRef ? "Validate_Reference" : refValidator.name || "$ref",
|
|
480
1379
|
(data) =>
|
|
481
1380
|
(refValidator as KeywordFunction)(
|
|
482
1381
|
compiledSchema,
|
|
483
1382
|
data,
|
|
484
1383
|
defineError,
|
|
485
|
-
this
|
|
1384
|
+
this,
|
|
1385
|
+
validateSubschema
|
|
486
1386
|
)
|
|
487
1387
|
);
|
|
1388
|
+
if (!isBuiltinRef) {
|
|
1389
|
+
schemaHasRef = false;
|
|
1390
|
+
}
|
|
488
1391
|
}
|
|
489
1392
|
|
|
490
|
-
|
|
1393
|
+
for (const key of ["definitions", "$defs"]) {
|
|
1394
|
+
const definitions = schema[key];
|
|
1395
|
+
if (!definitions || typeof definitions !== "object") {
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
const compiledDefinitions: Record<string, any> = {};
|
|
1399
|
+
for (const definitionKey of Object.keys(definitions)) {
|
|
1400
|
+
compiledDefinitions[definitionKey] = this.compileSchema(
|
|
1401
|
+
definitions[definitionKey]
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
compiledSchema[key] = compiledDefinitions;
|
|
1405
|
+
}
|
|
1406
|
+
if (schemaHasRef) {
|
|
1407
|
+
this.markSchemaHasRef(compiledSchema);
|
|
1408
|
+
}
|
|
491
1409
|
return compiledSchema;
|
|
492
1410
|
}
|
|
493
1411
|
|
|
494
1412
|
const validators: ValidatorItem[] = [];
|
|
495
1413
|
const activeNames: string[] = [];
|
|
1414
|
+
const pendingCombinators: PendingCombinator[] = [];
|
|
1415
|
+
|
|
1416
|
+
if (
|
|
1417
|
+
this.useDefaults !== false &&
|
|
1418
|
+
this.getKeyword("properties") === keywords.properties &&
|
|
1419
|
+
this.hasPropertyDefaults(schema)
|
|
1420
|
+
) {
|
|
1421
|
+
const applyDefaults =
|
|
1422
|
+
this.useDefaults === "empty"
|
|
1423
|
+
? applyEmptyPropertyDefaults
|
|
1424
|
+
: applyPropertyDefaults;
|
|
1425
|
+
validators.push({
|
|
1426
|
+
name: applyDefaults.name,
|
|
1427
|
+
validate: getNamedFunction<ValidateFunction>(
|
|
1428
|
+
applyDefaults.name,
|
|
1429
|
+
(data) => applyDefaults(compiledSchema, data, this)
|
|
1430
|
+
)
|
|
1431
|
+
});
|
|
1432
|
+
activeNames.push(applyDefaults.name);
|
|
1433
|
+
}
|
|
496
1434
|
|
|
497
1435
|
if ("type" in schema) {
|
|
498
1436
|
const defineTypeError = getDefinedErrorFunctionForKey(
|
|
@@ -536,66 +1474,14 @@ export class SchemaShield {
|
|
|
536
1474
|
if (typeFunctions.length === 1 && allTypesDefault) {
|
|
537
1475
|
const singleTypeName = defaultTypeNames[0];
|
|
538
1476
|
typeMethodName = singleTypeName;
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
break;
|
|
548
|
-
case "array":
|
|
549
|
-
combinedTypeValidator = (data) => {
|
|
550
|
-
if (!Array.isArray(data)) {
|
|
551
|
-
return defineTypeError("Invalid type", { data });
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
break;
|
|
555
|
-
case "string":
|
|
556
|
-
combinedTypeValidator = (data) => {
|
|
557
|
-
if (typeof data !== "string") {
|
|
558
|
-
return defineTypeError("Invalid type", { data });
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
break;
|
|
562
|
-
case "number":
|
|
563
|
-
combinedTypeValidator = (data) => {
|
|
564
|
-
if (typeof data !== "number") {
|
|
565
|
-
return defineTypeError("Invalid type", { data });
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
break;
|
|
569
|
-
case "integer":
|
|
570
|
-
combinedTypeValidator = (data) => {
|
|
571
|
-
if (typeof data !== "number" || !Number.isInteger(data)) {
|
|
572
|
-
return defineTypeError("Invalid type", { data });
|
|
573
|
-
}
|
|
574
|
-
};
|
|
575
|
-
break;
|
|
576
|
-
case "boolean":
|
|
577
|
-
combinedTypeValidator = (data) => {
|
|
578
|
-
if (typeof data !== "boolean") {
|
|
579
|
-
return defineTypeError("Invalid type", { data });
|
|
580
|
-
}
|
|
581
|
-
};
|
|
582
|
-
break;
|
|
583
|
-
case "null":
|
|
584
|
-
combinedTypeValidator = (data) => {
|
|
585
|
-
if (data !== null) {
|
|
586
|
-
return defineTypeError("Invalid type", { data });
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
break;
|
|
590
|
-
default: {
|
|
591
|
-
const singleTypeFn = typeFunctions[0];
|
|
592
|
-
combinedTypeValidator = (data) => {
|
|
593
|
-
if (!singleTypeFn(data)) {
|
|
594
|
-
return defineTypeError("Invalid type", { data });
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
}
|
|
1477
|
+
combinedTypeValidator =
|
|
1478
|
+
this.failFast && FAIL_FAST_TYPE_VALIDATORS[singleTypeName]
|
|
1479
|
+
? FAIL_FAST_TYPE_VALIDATORS[singleTypeName]
|
|
1480
|
+
: createBuiltinTypeValidator(
|
|
1481
|
+
singleTypeName,
|
|
1482
|
+
defineTypeError,
|
|
1483
|
+
typeFunctions[0]
|
|
1484
|
+
);
|
|
599
1485
|
} else if (typeFunctions.length > 1 && allTypesDefault) {
|
|
600
1486
|
typeMethodName = defaultTypeNames.join("_OR_");
|
|
601
1487
|
|
|
@@ -611,6 +1497,9 @@ export class SchemaShield {
|
|
|
611
1497
|
const dataType = typeof data;
|
|
612
1498
|
|
|
613
1499
|
if (dataType === "number") {
|
|
1500
|
+
if (!Number.isFinite(data)) {
|
|
1501
|
+
return defineTypeError("Invalid type", { data });
|
|
1502
|
+
}
|
|
614
1503
|
if (allowsNumber || (allowsInteger && Number.isInteger(data))) {
|
|
615
1504
|
return;
|
|
616
1505
|
}
|
|
@@ -689,13 +1578,8 @@ export class SchemaShield {
|
|
|
689
1578
|
|
|
690
1579
|
const { type, $id, $ref, $validate, required, ...otherKeys } = schema; // Exclude handled keys
|
|
691
1580
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const keyOrder = required
|
|
695
|
-
? this.hasRequiredDefaults(schema)
|
|
696
|
-
? [...Object.keys(otherKeys), "required"]
|
|
697
|
-
: ["required", ...Object.keys(otherKeys)]
|
|
698
|
-
: Object.keys(otherKeys);
|
|
1581
|
+
const otherKeyNames = Object.keys(otherKeys);
|
|
1582
|
+
const keyOrder = required ? ["required", ...otherKeyNames] : otherKeyNames;
|
|
699
1583
|
|
|
700
1584
|
for (const key of keyOrder) {
|
|
701
1585
|
const keywordFn = this.getKeyword(key);
|
|
@@ -714,12 +1598,41 @@ export class SchemaShield {
|
|
|
714
1598
|
this.failFast
|
|
715
1599
|
);
|
|
716
1600
|
const fnName = keywordFn.name || key;
|
|
1601
|
+
if (
|
|
1602
|
+
(key === "allOf" || key === "anyOf" || key === "oneOf") &&
|
|
1603
|
+
keywordFn === keywords[key]
|
|
1604
|
+
) {
|
|
1605
|
+
const item: ValidatorItem = {
|
|
1606
|
+
name: fnName,
|
|
1607
|
+
validate: () => {
|
|
1608
|
+
throw new ValidationError("Combinator validator was not prepared");
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
validators.push(item);
|
|
1612
|
+
pendingCombinators.push({ item, key, defineError });
|
|
1613
|
+
activeNames.push(fnName);
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
717
1616
|
|
|
1617
|
+
const keywordValidate = validateSubschema
|
|
1618
|
+
? (data: any) =>
|
|
1619
|
+
(keywordFn as KeywordFunction)(
|
|
1620
|
+
compiledSchema,
|
|
1621
|
+
data,
|
|
1622
|
+
defineError,
|
|
1623
|
+
this,
|
|
1624
|
+
validateSubschema
|
|
1625
|
+
)
|
|
1626
|
+
: (data: any) =>
|
|
1627
|
+
(keywordFn as KeywordFunction)(
|
|
1628
|
+
compiledSchema,
|
|
1629
|
+
data,
|
|
1630
|
+
defineError,
|
|
1631
|
+
this
|
|
1632
|
+
);
|
|
718
1633
|
validators.push({
|
|
719
1634
|
name: fnName,
|
|
720
|
-
validate: getNamedFunction<ValidateFunction>(fnName,
|
|
721
|
-
(keywordFn as KeywordFunction)(compiledSchema, data, defineError, this)
|
|
722
|
-
)
|
|
1635
|
+
validate: getNamedFunction<ValidateFunction>(fnName, keywordValidate)
|
|
723
1636
|
});
|
|
724
1637
|
|
|
725
1638
|
activeNames.push(fnName);
|
|
@@ -774,6 +1687,69 @@ export class SchemaShield {
|
|
|
774
1687
|
}
|
|
775
1688
|
}
|
|
776
1689
|
|
|
1690
|
+
if (this.isPlainObject(schema.properties)) {
|
|
1691
|
+
definePropertyOrThrow(compiledSchema, "_propKeys", {
|
|
1692
|
+
value: Object.keys(schema.properties),
|
|
1693
|
+
enumerable: false,
|
|
1694
|
+
configurable: false,
|
|
1695
|
+
writable: false
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
if (
|
|
1700
|
+
this.useDefaults !== false &&
|
|
1701
|
+
this.isPlainObject(schema.properties) &&
|
|
1702
|
+
this.hasPropertyDefaults(schema)
|
|
1703
|
+
) {
|
|
1704
|
+
const defaultKeys = Object.keys(schema.properties).filter(
|
|
1705
|
+
(key: string) => {
|
|
1706
|
+
const property = schema.properties[key];
|
|
1707
|
+
return (
|
|
1708
|
+
property &&
|
|
1709
|
+
typeof property === "object" &&
|
|
1710
|
+
!Array.isArray(property) &&
|
|
1711
|
+
hasOwn(property, "default")
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
);
|
|
1715
|
+
if (defaultKeys.length > 0) {
|
|
1716
|
+
definePropertyOrThrow(compiledSchema, "_defaultKeys", {
|
|
1717
|
+
value: defaultKeys,
|
|
1718
|
+
enumerable: false,
|
|
1719
|
+
configurable: false,
|
|
1720
|
+
writable: false
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
prepareCombinatorEntries(compiledSchema);
|
|
1726
|
+
|
|
1727
|
+
for (let index = 0; index < pendingCombinators.length; index++) {
|
|
1728
|
+
const pending = pendingCombinators[index];
|
|
1729
|
+
const transactions =
|
|
1730
|
+
(compiledSchema as any)._canApplyDefaults === true
|
|
1731
|
+
? {
|
|
1732
|
+
savepoint: () => this.#defaultSavepoint(),
|
|
1733
|
+
rollback: (savepoint: number) =>
|
|
1734
|
+
this.#rollbackDefaultSavepoint(savepoint),
|
|
1735
|
+
capture: (savepoint: number) =>
|
|
1736
|
+
this.#captureDefaultSavepoint(savepoint),
|
|
1737
|
+
restore: (mutations: DefaultMutation[]) =>
|
|
1738
|
+
this.#restoreDefaults(mutations)
|
|
1739
|
+
}
|
|
1740
|
+
: undefined;
|
|
1741
|
+
pending.item.validate = getNamedFunction(
|
|
1742
|
+
pending.item.name,
|
|
1743
|
+
createCombinatorValidator(
|
|
1744
|
+
pending.key,
|
|
1745
|
+
compiledSchema,
|
|
1746
|
+
pending.defineError,
|
|
1747
|
+
validateSubschema,
|
|
1748
|
+
transactions
|
|
1749
|
+
)
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
777
1753
|
if (schemaHasRef) {
|
|
778
1754
|
this.markSchemaHasRef(compiledSchema);
|
|
779
1755
|
}
|
|
@@ -827,64 +1803,90 @@ export class SchemaShield {
|
|
|
827
1803
|
return false;
|
|
828
1804
|
}
|
|
829
1805
|
|
|
830
|
-
private
|
|
831
|
-
|
|
1806
|
+
private getCompiledReferenceTarget(
|
|
1807
|
+
ref: string,
|
|
1808
|
+
position: SchemaPosition,
|
|
1809
|
+
registry: ReferenceRegistry
|
|
1810
|
+
): CompiledSchema | boolean | undefined {
|
|
1811
|
+
const resolvedUri = this.resolveUri(ref, position.baseUri, "$ref");
|
|
1812
|
+
const exactTarget = registry.aliases.get(resolvedUri);
|
|
1813
|
+
if (exactTarget) {
|
|
1814
|
+
return this.compileCache.get(exactTarget);
|
|
1815
|
+
}
|
|
832
1816
|
|
|
833
|
-
|
|
834
|
-
|
|
1817
|
+
const resourceRoot = registry.aliases.get(this.resourceUri(resolvedUri));
|
|
1818
|
+
if (!resourceRoot) {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
const compiledResource = this.compileCache.get(resourceRoot);
|
|
1822
|
+
if (!compiledResource) {
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
835
1825
|
|
|
836
|
-
|
|
1826
|
+
const hashIndex = resolvedUri.indexOf("#");
|
|
1827
|
+
const fragment = hashIndex === -1 ? "" : resolvedUri.slice(hashIndex + 1);
|
|
1828
|
+
if (fragment.length === 0) {
|
|
1829
|
+
return compiledResource;
|
|
1830
|
+
}
|
|
1831
|
+
if (fragment.startsWith("/")) {
|
|
1832
|
+
return resolvePath(compiledResource, `#${fragment}`);
|
|
1833
|
+
}
|
|
837
1834
|
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
private linkReferences(registry: ReferenceRegistry) {
|
|
1839
|
+
for (let index = 0; index < registry.positions.length; index++) {
|
|
1840
|
+
const position = registry.positions[index];
|
|
838
1841
|
if (
|
|
839
|
-
typeof
|
|
840
|
-
|
|
841
|
-
node.$validate.name === "Validate_Reference"
|
|
1842
|
+
typeof position.source.$ref !== "string" ||
|
|
1843
|
+
this.getKeyword("$ref") !== keywords.$ref
|
|
842
1844
|
) {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
let target: any = this.getSchemaRef(refPath);
|
|
846
|
-
if (typeof target === "undefined") {
|
|
847
|
-
target = this.getSchemaById(refPath);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
if (typeof target === "boolean") {
|
|
851
|
-
if (target === true) {
|
|
852
|
-
node.$validate = getNamedFunction("Validate_Ref_True", () => {});
|
|
853
|
-
} else {
|
|
854
|
-
const defineError = getDefinedErrorFunctionForKey(
|
|
855
|
-
"$ref",
|
|
856
|
-
node as any,
|
|
857
|
-
this.failFast
|
|
858
|
-
);
|
|
859
|
-
|
|
860
|
-
node.$validate = getNamedFunction(
|
|
861
|
-
"Validate_Ref_False",
|
|
862
|
-
(_data: any) => defineError("Value is not valid")
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
continue;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
if (target && typeof target.$validate === "function") {
|
|
869
|
-
node.$validate = target.$validate;
|
|
870
|
-
}
|
|
1845
|
+
continue;
|
|
871
1846
|
}
|
|
872
1847
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1848
|
+
const node = this.compileCache.get(position.source);
|
|
1849
|
+
const target = this.getCompiledReferenceTarget(
|
|
1850
|
+
position.source.$ref,
|
|
1851
|
+
position,
|
|
1852
|
+
registry
|
|
1853
|
+
);
|
|
1854
|
+
if (!node || typeof target === "undefined") {
|
|
1855
|
+
const error = new ValidationError(
|
|
1856
|
+
`Reference not found: ${position.source.$ref}`
|
|
1857
|
+
);
|
|
1858
|
+
error.code = "REFERENCE_NOT_FOUND";
|
|
1859
|
+
error.keyword = "$ref";
|
|
1860
|
+
throw error;
|
|
1861
|
+
}
|
|
876
1862
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1863
|
+
let targetValidate: ValidateFunction;
|
|
1864
|
+
if (target === true) {
|
|
1865
|
+
targetValidate = getNamedFunction("Validate_Ref_True", () => {});
|
|
1866
|
+
} else if (target === false) {
|
|
1867
|
+
const defineError = getDefinedErrorFunctionForKey(
|
|
1868
|
+
"$ref",
|
|
1869
|
+
node,
|
|
1870
|
+
this.failFast
|
|
1871
|
+
);
|
|
1872
|
+
targetValidate = getNamedFunction("Validate_Ref_False", (data: any) =>
|
|
1873
|
+
defineError("Value is not valid", { data })
|
|
1874
|
+
);
|
|
1875
|
+
} else {
|
|
1876
|
+
if (typeof target.$validate !== "function") {
|
|
1877
|
+
target.$validate = getNamedFunction<ValidateFunction>(
|
|
1878
|
+
"Validate_Ref_Any",
|
|
1879
|
+
() => {}
|
|
1880
|
+
);
|
|
886
1881
|
}
|
|
1882
|
+
targetValidate = target.$validate;
|
|
887
1883
|
}
|
|
1884
|
+
definePropertyOrThrow(node, "_resolvedRef", {
|
|
1885
|
+
value: targetValidate,
|
|
1886
|
+
enumerable: false,
|
|
1887
|
+
configurable: false,
|
|
1888
|
+
writable: false
|
|
1889
|
+
});
|
|
888
1890
|
}
|
|
889
1891
|
}
|
|
890
1892
|
}
|