schema-shield 1.0.0 → 1.0.1
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 +38 -12
- package/dist/formats.d.ts.map +1 -1
- package/dist/index.d.ts +14 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1445 -447
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +1445 -447
- package/dist/keywords/array-keywords.d.ts.map +1 -1
- package/dist/keywords/object-keywords.d.ts.map +1 -1
- package/dist/keywords/other-keywords.d.ts.map +1 -1
- package/dist/keywords/string-keywords.d.ts.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/deep-freeze.d.ts +5 -0
- package/dist/utils/deep-freeze.d.ts.map +1 -0
- package/dist/utils/has-changed.d.ts +2 -0
- package/dist/utils/has-changed.d.ts.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/{utils.d.ts → utils/main-utils.d.ts} +3 -6
- package/dist/utils/main-utils.d.ts.map +1 -0
- package/dist/utils/pattern-matcher.d.ts +3 -0
- package/dist/utils/pattern-matcher.d.ts.map +1 -0
- package/lib/formats.ts +402 -84
- package/lib/index.ts +494 -46
- package/lib/keywords/array-keywords.ts +215 -21
- package/lib/keywords/number-keywords.ts +1 -1
- package/lib/keywords/object-keywords.ts +218 -113
- package/lib/keywords/other-keywords.ts +229 -76
- package/lib/keywords/string-keywords.ts +97 -7
- package/lib/types.ts +4 -5
- package/lib/utils/deep-freeze.ts +208 -0
- package/lib/utils/has-changed.ts +51 -0
- package/lib/utils/index.ts +4 -0
- package/lib/utils/main-utils.ts +190 -0
- package/lib/utils/pattern-matcher.ts +66 -0
- package/package.json +1 -1
- package/dist/utils.d.ts.map +0 -1
- package/lib/utils.ts +0 -362
package/lib/index.ts
CHANGED
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DefineErrorFunction,
|
|
4
4
|
ValidationError,
|
|
5
|
-
deepClone,
|
|
6
5
|
getDefinedErrorFunctionForKey,
|
|
7
6
|
getNamedFunction,
|
|
8
|
-
isObject,
|
|
9
7
|
resolvePath
|
|
10
|
-
} from "./utils";
|
|
8
|
+
} from "./utils/main-utils";
|
|
11
9
|
|
|
12
10
|
import { Formats } from "./formats";
|
|
13
11
|
import { Types } from "./types";
|
|
14
12
|
import { keywords } from "./keywords";
|
|
13
|
+
import { deepCloneUnfreeze } from "./utils/deep-freeze";
|
|
15
14
|
|
|
16
|
-
export { ValidationError } from "./utils";
|
|
17
|
-
export { deepClone } from "./utils";
|
|
15
|
+
export { ValidationError } from "./utils/main-utils";
|
|
16
|
+
export { deepCloneUnfreeze as deepClone } from "./utils/deep-freeze";
|
|
18
17
|
|
|
19
18
|
export type Result = void | ValidationError | true;
|
|
20
19
|
|
|
@@ -54,8 +53,8 @@ export interface Validator {
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
interface ValidatorItem {
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
name: string;
|
|
57
|
+
validate: ValidateFunction;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
export class SchemaShield {
|
|
@@ -116,6 +115,10 @@ export class SchemaShield {
|
|
|
116
115
|
return this.formats[format];
|
|
117
116
|
}
|
|
118
117
|
|
|
118
|
+
isDefaultFormatValidator(format: string, validator: FormatFunction): boolean {
|
|
119
|
+
return (Formats as Record<string, FormatFunction | false>)[format] === validator;
|
|
120
|
+
}
|
|
121
|
+
|
|
119
122
|
addKeyword(name: string, validator: KeywordFunction, overwrite = false) {
|
|
120
123
|
if (this.keywords[name] && !overwrite) {
|
|
121
124
|
throw new ValidationError(`Keyword "${name}" already exists`);
|
|
@@ -142,23 +145,41 @@ export class SchemaShield {
|
|
|
142
145
|
this.idRegistry.clear();
|
|
143
146
|
const compiledSchema = this.compileSchema(schema);
|
|
144
147
|
this.rootSchema = compiledSchema;
|
|
145
|
-
|
|
148
|
+
if ((compiledSchema as any)._hasRef === true) {
|
|
149
|
+
this.linkReferences(compiledSchema);
|
|
150
|
+
}
|
|
146
151
|
|
|
147
152
|
if (!compiledSchema.$validate) {
|
|
148
|
-
if (
|
|
153
|
+
if (schema === false) {
|
|
154
|
+
const defineError = getDefinedErrorFunctionForKey(
|
|
155
|
+
"oneOf",
|
|
156
|
+
compiledSchema,
|
|
157
|
+
this.failFast
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
161
|
+
"Validate_False",
|
|
162
|
+
(data) => defineError("Value is not valid", { data })
|
|
163
|
+
);
|
|
164
|
+
} else if (schema === true) {
|
|
165
|
+
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
166
|
+
"Validate_Any",
|
|
167
|
+
() => {}
|
|
168
|
+
);
|
|
169
|
+
} else if (this.isSchemaLike(schema) === false) {
|
|
149
170
|
throw new ValidationError("Invalid schema");
|
|
171
|
+
} else {
|
|
172
|
+
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
173
|
+
"Validate_Any",
|
|
174
|
+
() => {}
|
|
175
|
+
);
|
|
150
176
|
}
|
|
151
|
-
|
|
152
|
-
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
153
|
-
"Validate_Any",
|
|
154
|
-
() => {}
|
|
155
|
-
);
|
|
156
177
|
}
|
|
157
178
|
|
|
158
179
|
const validate: Validator = (data: any) => {
|
|
159
180
|
this.rootSchema = compiledSchema;
|
|
160
181
|
|
|
161
|
-
const clonedData = this.immutable ?
|
|
182
|
+
const clonedData = this.immutable ? deepCloneUnfreeze(data) : data;
|
|
162
183
|
const res = compiledSchema.$validate!(clonedData);
|
|
163
184
|
|
|
164
185
|
if (res) {
|
|
@@ -172,8 +193,257 @@ export class SchemaShield {
|
|
|
172
193
|
return validate;
|
|
173
194
|
}
|
|
174
195
|
|
|
196
|
+
private isPlainObject(value: any): value is Record<string, any> {
|
|
197
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private isTrivialAlwaysValidSubschema(value: any): boolean {
|
|
201
|
+
return (
|
|
202
|
+
value === true ||
|
|
203
|
+
(this.isPlainObject(value) && Object.keys(value).length === 0)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private shallowArrayEquals(a: any[], b: any[]): boolean {
|
|
208
|
+
if (a === b) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (a.length !== b.length) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (let i = 0; i < a.length; i++) {
|
|
217
|
+
if (a[i] !== b[i]) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private flattenAssociativeBranches(
|
|
226
|
+
key: "allOf" | "anyOf",
|
|
227
|
+
branches: any[]
|
|
228
|
+
): any[] {
|
|
229
|
+
const out: any[] = [];
|
|
230
|
+
|
|
231
|
+
for (let i = 0; i < branches.length; i++) {
|
|
232
|
+
const item = branches[i];
|
|
233
|
+
if (
|
|
234
|
+
this.isPlainObject(item) &&
|
|
235
|
+
Object.keys(item).length === 1 &&
|
|
236
|
+
Array.isArray(item[key])
|
|
237
|
+
) {
|
|
238
|
+
const nested = this.flattenAssociativeBranches(key, item[key]);
|
|
239
|
+
for (let j = 0; j < nested.length; j++) {
|
|
240
|
+
out.push(nested[j]);
|
|
241
|
+
}
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
out.push(item);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private flattenSingleWrapperOneOf(branches: any[]): any[] {
|
|
251
|
+
let current = branches;
|
|
252
|
+
|
|
253
|
+
while (current.length === 1) {
|
|
254
|
+
const item = current[0];
|
|
255
|
+
if (
|
|
256
|
+
this.isPlainObject(item) &&
|
|
257
|
+
Object.keys(item).length === 1 &&
|
|
258
|
+
Array.isArray(item.oneOf)
|
|
259
|
+
) {
|
|
260
|
+
current = item.oneOf;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return current;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private normalizeSchemaForCompile(schema: Record<string, any>): Record<string, any> {
|
|
270
|
+
let normalized = schema;
|
|
271
|
+
const schemaKeys = Object.keys(schema);
|
|
272
|
+
const hasOnlyKey = (key: string) =>
|
|
273
|
+
schemaKeys.length === 1 && schemaKeys[0] === key;
|
|
274
|
+
|
|
275
|
+
const setNormalized = (key: string, value: any) => {
|
|
276
|
+
if (normalized === schema) {
|
|
277
|
+
normalized = { ...schema };
|
|
278
|
+
}
|
|
279
|
+
normalized[key] = value;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (Array.isArray(schema.allOf)) {
|
|
283
|
+
const flattenedAllOf = this.flattenAssociativeBranches(
|
|
284
|
+
"allOf",
|
|
285
|
+
schema.allOf
|
|
286
|
+
).filter(
|
|
287
|
+
(item) =>
|
|
288
|
+
!(
|
|
289
|
+
this.isPlainObject(item) && Object.keys(item).length === 0
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (
|
|
294
|
+
hasOnlyKey("allOf") &&
|
|
295
|
+
flattenedAllOf.length === 1 &&
|
|
296
|
+
this.isPlainObject(flattenedAllOf[0])
|
|
297
|
+
) {
|
|
298
|
+
return flattenedAllOf[0];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!this.shallowArrayEquals(flattenedAllOf, schema.allOf)) {
|
|
302
|
+
setNormalized("allOf", flattenedAllOf);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (Array.isArray(schema.anyOf)) {
|
|
307
|
+
const flattenedAnyOf = this.flattenAssociativeBranches(
|
|
308
|
+
"anyOf",
|
|
309
|
+
schema.anyOf
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
if (
|
|
313
|
+
hasOnlyKey("anyOf") &&
|
|
314
|
+
flattenedAnyOf.length === 1 &&
|
|
315
|
+
this.isPlainObject(flattenedAnyOf[0])
|
|
316
|
+
) {
|
|
317
|
+
return flattenedAnyOf[0];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (!this.shallowArrayEquals(flattenedAnyOf, schema.anyOf)) {
|
|
321
|
+
setNormalized("anyOf", flattenedAnyOf);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (Array.isArray(schema.oneOf)) {
|
|
326
|
+
const flattenedOneOf = this.flattenSingleWrapperOneOf(schema.oneOf);
|
|
327
|
+
|
|
328
|
+
if (
|
|
329
|
+
hasOnlyKey("oneOf") &&
|
|
330
|
+
flattenedOneOf.length === 1 &&
|
|
331
|
+
this.isPlainObject(flattenedOneOf[0])
|
|
332
|
+
) {
|
|
333
|
+
return flattenedOneOf[0];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!this.shallowArrayEquals(flattenedOneOf, schema.oneOf)) {
|
|
337
|
+
setNormalized("oneOf", flattenedOneOf);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return normalized;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private markSchemaHasRef(schema: CompiledSchema) {
|
|
345
|
+
if ((schema as any)._hasRef === true) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
Object.defineProperty(schema, "_hasRef", {
|
|
350
|
+
value: true,
|
|
351
|
+
enumerable: false,
|
|
352
|
+
configurable: false,
|
|
353
|
+
writable: false
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private shouldSkipKeyword(schema: Record<string, any>, key: string): boolean {
|
|
358
|
+
const value = schema[key];
|
|
359
|
+
|
|
360
|
+
switch (key) {
|
|
361
|
+
case "required":
|
|
362
|
+
return Array.isArray(value) && value.length === 0;
|
|
363
|
+
case "uniqueItems":
|
|
364
|
+
return value === false;
|
|
365
|
+
case "properties":
|
|
366
|
+
case "patternProperties":
|
|
367
|
+
case "dependencies":
|
|
368
|
+
return (
|
|
369
|
+
this.isPlainObject(value) &&
|
|
370
|
+
Object.keys(value).length === 0
|
|
371
|
+
);
|
|
372
|
+
case "propertyNames":
|
|
373
|
+
case "items":
|
|
374
|
+
return value === true;
|
|
375
|
+
case "additionalProperties":
|
|
376
|
+
if (value === true) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
value === false &&
|
|
382
|
+
this.isPlainObject(schema.patternProperties) &&
|
|
383
|
+
Object.keys(schema.patternProperties).length > 0
|
|
384
|
+
);
|
|
385
|
+
case "additionalItems":
|
|
386
|
+
return value === true || !Array.isArray(schema.items);
|
|
387
|
+
case "allOf": {
|
|
388
|
+
if (!Array.isArray(value)) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (value.length === 0) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
for (let i = 0; i < value.length; i++) {
|
|
397
|
+
if (this.isTrivialAlwaysValidSubschema(value[i])) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
case "anyOf": {
|
|
407
|
+
if (!Array.isArray(value)) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for (let i = 0; i < value.length; i++) {
|
|
412
|
+
if (this.isTrivialAlwaysValidSubschema(value[i])) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
default:
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private hasRequiredDefaults(schema: Record<string, any>): boolean {
|
|
425
|
+
const properties = schema.properties;
|
|
426
|
+
if (!this.isPlainObject(properties)) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const keys = Object.keys(properties);
|
|
431
|
+
for (let i = 0; i < keys.length; i++) {
|
|
432
|
+
const subSchema = properties[keys[i]];
|
|
433
|
+
if (this.isPlainObject(subSchema) && "default" in subSchema) {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private isDefaultTypeValidator(type: string, validator: TypeFunction): boolean {
|
|
442
|
+
return (Types as Record<string, TypeFunction | false>)[type] === validator;
|
|
443
|
+
}
|
|
444
|
+
|
|
175
445
|
private compileSchema(schema: Partial<CompiledSchema> | any): CompiledSchema {
|
|
176
|
-
if (!
|
|
446
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
177
447
|
if (schema === true) {
|
|
178
448
|
schema = { anyOf: [{}] }; // Always valid
|
|
179
449
|
} else if (schema === false) {
|
|
@@ -183,13 +453,20 @@ export class SchemaShield {
|
|
|
183
453
|
}
|
|
184
454
|
}
|
|
185
455
|
|
|
186
|
-
|
|
456
|
+
schema = this.normalizeSchemaForCompile(schema);
|
|
457
|
+
|
|
458
|
+
const compiledSchema: CompiledSchema = deepCloneUnfreeze(
|
|
459
|
+
schema
|
|
460
|
+
) as CompiledSchema;
|
|
461
|
+
|
|
462
|
+
let schemaHasRef = false;
|
|
187
463
|
|
|
188
464
|
if (typeof schema.$id === "string") {
|
|
189
465
|
this.idRegistry.set(schema.$id, compiledSchema);
|
|
190
466
|
}
|
|
191
467
|
|
|
192
468
|
if ("$ref" in schema) {
|
|
469
|
+
schemaHasRef = true;
|
|
193
470
|
const refValidator = this.getKeyword("$ref");
|
|
194
471
|
if (refValidator) {
|
|
195
472
|
const defineError = getDefinedErrorFunctionForKey(
|
|
@@ -209,6 +486,8 @@ export class SchemaShield {
|
|
|
209
486
|
)
|
|
210
487
|
);
|
|
211
488
|
}
|
|
489
|
+
|
|
490
|
+
this.markSchemaHasRef(compiledSchema);
|
|
212
491
|
return compiledSchema;
|
|
213
492
|
}
|
|
214
493
|
|
|
@@ -227,12 +506,19 @@ export class SchemaShield {
|
|
|
227
506
|
|
|
228
507
|
const typeFunctions: TypeFunction[] = [];
|
|
229
508
|
const typeNames: string[] = [];
|
|
509
|
+
const defaultTypeNames: string[] = [];
|
|
510
|
+
let allTypesDefault = true;
|
|
230
511
|
|
|
231
512
|
for (const type of types) {
|
|
232
513
|
const validator = this.getType(type);
|
|
233
514
|
if (validator) {
|
|
234
515
|
typeFunctions.push(validator);
|
|
235
516
|
typeNames.push(validator.name);
|
|
517
|
+
if (this.isDefaultTypeValidator(type, validator)) {
|
|
518
|
+
defaultTypeNames.push(type);
|
|
519
|
+
} else {
|
|
520
|
+
allTypesDefault = false;
|
|
521
|
+
}
|
|
236
522
|
}
|
|
237
523
|
}
|
|
238
524
|
|
|
@@ -247,7 +533,134 @@ export class SchemaShield {
|
|
|
247
533
|
let combinedTypeValidator: ValidateFunction;
|
|
248
534
|
let typeMethodName = "";
|
|
249
535
|
|
|
250
|
-
if (typeFunctions.length === 1) {
|
|
536
|
+
if (typeFunctions.length === 1 && allTypesDefault) {
|
|
537
|
+
const singleTypeName = defaultTypeNames[0];
|
|
538
|
+
typeMethodName = singleTypeName;
|
|
539
|
+
|
|
540
|
+
switch (singleTypeName) {
|
|
541
|
+
case "object":
|
|
542
|
+
combinedTypeValidator = (data) => {
|
|
543
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
544
|
+
return defineTypeError("Invalid type", { data });
|
|
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
|
+
}
|
|
599
|
+
} else if (typeFunctions.length > 1 && allTypesDefault) {
|
|
600
|
+
typeMethodName = defaultTypeNames.join("_OR_");
|
|
601
|
+
|
|
602
|
+
const allowsObject = defaultTypeNames.includes("object");
|
|
603
|
+
const allowsArray = defaultTypeNames.includes("array");
|
|
604
|
+
const allowsString = defaultTypeNames.includes("string");
|
|
605
|
+
const allowsNumber = defaultTypeNames.includes("number");
|
|
606
|
+
const allowsInteger = defaultTypeNames.includes("integer");
|
|
607
|
+
const allowsBoolean = defaultTypeNames.includes("boolean");
|
|
608
|
+
const allowsNull = defaultTypeNames.includes("null");
|
|
609
|
+
|
|
610
|
+
combinedTypeValidator = (data) => {
|
|
611
|
+
const dataType = typeof data;
|
|
612
|
+
|
|
613
|
+
if (dataType === "number") {
|
|
614
|
+
if (allowsNumber || (allowsInteger && Number.isInteger(data))) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return defineTypeError("Invalid type", { data });
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (dataType === "string") {
|
|
622
|
+
if (allowsString) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return defineTypeError("Invalid type", { data });
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (dataType === "boolean") {
|
|
630
|
+
if (allowsBoolean) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return defineTypeError("Invalid type", { data });
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (dataType === "object") {
|
|
638
|
+
if (data === null) {
|
|
639
|
+
if (allowsNull) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return defineTypeError("Invalid type", { data });
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (Array.isArray(data)) {
|
|
647
|
+
if (allowsArray) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return defineTypeError("Invalid type", { data });
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (allowsObject) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return defineTypeError("Invalid type", { data });
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return defineTypeError("Invalid type", { data });
|
|
662
|
+
};
|
|
663
|
+
} else if (typeFunctions.length === 1) {
|
|
251
664
|
typeMethodName = typeNames[0];
|
|
252
665
|
const singleTypeFn = typeFunctions[0];
|
|
253
666
|
combinedTypeValidator = (data) => {
|
|
@@ -267,12 +680,9 @@ export class SchemaShield {
|
|
|
267
680
|
};
|
|
268
681
|
}
|
|
269
682
|
|
|
270
|
-
const typeAdapter: KeywordFunction = (_s, data) =>
|
|
271
|
-
combinedTypeValidator(data);
|
|
272
|
-
|
|
273
683
|
validators.push({
|
|
274
|
-
|
|
275
|
-
|
|
684
|
+
name: typeMethodName,
|
|
685
|
+
validate: getNamedFunction(typeMethodName, combinedTypeValidator)
|
|
276
686
|
});
|
|
277
687
|
activeNames.push(typeMethodName);
|
|
278
688
|
}
|
|
@@ -282,26 +692,37 @@ export class SchemaShield {
|
|
|
282
692
|
// In here we create an array of keys putting the require keyword last
|
|
283
693
|
// This is to ensure required properties are checked after defaults are applied
|
|
284
694
|
const keyOrder = required
|
|
285
|
-
?
|
|
695
|
+
? this.hasRequiredDefaults(schema)
|
|
696
|
+
? [...Object.keys(otherKeys), "required"]
|
|
697
|
+
: ["required", ...Object.keys(otherKeys)]
|
|
286
698
|
: Object.keys(otherKeys);
|
|
699
|
+
|
|
287
700
|
for (const key of keyOrder) {
|
|
288
701
|
const keywordFn = this.getKeyword(key);
|
|
289
702
|
|
|
290
|
-
if (keywordFn) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
schema[key],
|
|
294
|
-
this.failFast
|
|
295
|
-
);
|
|
296
|
-
const fnName = keywordFn.name || key;
|
|
297
|
-
|
|
298
|
-
validators.push({
|
|
299
|
-
fn: keywordFn as KeywordFunction,
|
|
300
|
-
defineError
|
|
301
|
-
});
|
|
703
|
+
if (!keywordFn) {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
302
706
|
|
|
303
|
-
|
|
707
|
+
if (this.shouldSkipKeyword(schema, key)) {
|
|
708
|
+
continue;
|
|
304
709
|
}
|
|
710
|
+
|
|
711
|
+
const defineError = getDefinedErrorFunctionForKey(
|
|
712
|
+
key,
|
|
713
|
+
schema[key],
|
|
714
|
+
this.failFast
|
|
715
|
+
);
|
|
716
|
+
const fnName = keywordFn.name || key;
|
|
717
|
+
|
|
718
|
+
validators.push({
|
|
719
|
+
name: fnName,
|
|
720
|
+
validate: getNamedFunction<ValidateFunction>(fnName, (data) =>
|
|
721
|
+
(keywordFn as KeywordFunction)(compiledSchema, data, defineError, this)
|
|
722
|
+
)
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
activeNames.push(fnName);
|
|
305
726
|
}
|
|
306
727
|
|
|
307
728
|
const literalKeywords = ["enum", "const", "default", "examples"];
|
|
@@ -309,45 +730,68 @@ export class SchemaShield {
|
|
|
309
730
|
if (literalKeywords.includes(key)) {
|
|
310
731
|
continue;
|
|
311
732
|
}
|
|
312
|
-
|
|
733
|
+
|
|
734
|
+
if (
|
|
735
|
+
schema[key] &&
|
|
736
|
+
typeof schema[key] === "object" &&
|
|
737
|
+
!Array.isArray(schema[key])
|
|
738
|
+
) {
|
|
313
739
|
if (key === "properties") {
|
|
314
740
|
for (const subKey of Object.keys(schema[key])) {
|
|
315
|
-
|
|
741
|
+
const compiledSubSchema = this.compileSchema(
|
|
316
742
|
schema[key][subKey]
|
|
317
743
|
);
|
|
744
|
+
|
|
745
|
+
if ((compiledSubSchema as any)._hasRef === true) {
|
|
746
|
+
schemaHasRef = true;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
compiledSchema[key][subKey] = compiledSubSchema;
|
|
318
750
|
}
|
|
319
751
|
continue;
|
|
320
752
|
}
|
|
321
|
-
|
|
753
|
+
const compiledSubSchema = this.compileSchema(schema[key]);
|
|
754
|
+
if ((compiledSubSchema as any)._hasRef === true) {
|
|
755
|
+
schemaHasRef = true;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
compiledSchema[key] = compiledSubSchema;
|
|
322
759
|
continue;
|
|
323
760
|
}
|
|
324
761
|
|
|
325
762
|
if (Array.isArray(schema[key])) {
|
|
326
763
|
for (let i = 0; i < schema[key].length; i++) {
|
|
327
764
|
if (this.isSchemaLike(schema[key][i])) {
|
|
328
|
-
|
|
765
|
+
const compiledSubSchema = this.compileSchema(schema[key][i]);
|
|
766
|
+
if ((compiledSubSchema as any)._hasRef === true) {
|
|
767
|
+
schemaHasRef = true;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
compiledSchema[key][i] = compiledSubSchema;
|
|
329
771
|
}
|
|
330
772
|
}
|
|
331
773
|
continue;
|
|
332
774
|
}
|
|
333
775
|
}
|
|
334
776
|
|
|
777
|
+
if (schemaHasRef) {
|
|
778
|
+
this.markSchemaHasRef(compiledSchema);
|
|
779
|
+
}
|
|
780
|
+
|
|
335
781
|
if (validators.length === 0) {
|
|
336
782
|
return compiledSchema;
|
|
337
783
|
}
|
|
338
784
|
|
|
339
785
|
if (validators.length === 1) {
|
|
340
786
|
const v = validators[0];
|
|
341
|
-
compiledSchema.$validate = getNamedFunction(
|
|
342
|
-
v.fn(compiledSchema, data, v.defineError, this)
|
|
343
|
-
);
|
|
787
|
+
compiledSchema.$validate = getNamedFunction(v.name, v.validate);
|
|
344
788
|
} else {
|
|
345
789
|
const compositeName = "Validate_" + activeNames.join("_AND_");
|
|
346
790
|
|
|
347
791
|
const masterValidator: ValidateFunction = (data) => {
|
|
348
792
|
for (let i = 0; i < validators.length; i++) {
|
|
349
793
|
const v = validators[i];
|
|
350
|
-
const error = v.
|
|
794
|
+
const error = v.validate(data);
|
|
351
795
|
if (error) {
|
|
352
796
|
return error;
|
|
353
797
|
}
|
|
@@ -365,7 +809,11 @@ export class SchemaShield {
|
|
|
365
809
|
}
|
|
366
810
|
|
|
367
811
|
isSchemaLike(subSchema: any): boolean {
|
|
368
|
-
if (
|
|
812
|
+
if (
|
|
813
|
+
subSchema &&
|
|
814
|
+
typeof subSchema === "object" &&
|
|
815
|
+
!Array.isArray(subSchema)
|
|
816
|
+
) {
|
|
369
817
|
if ("type" in subSchema) {
|
|
370
818
|
return true;
|
|
371
819
|
}
|