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.
Files changed (39) hide show
  1. package/README.md +38 -12
  2. package/dist/formats.d.ts.map +1 -1
  3. package/dist/index.d.ts +14 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1445 -447
  6. package/dist/index.min.js +1 -1
  7. package/dist/index.min.js.map +1 -1
  8. package/dist/index.mjs +1445 -447
  9. package/dist/keywords/array-keywords.d.ts.map +1 -1
  10. package/dist/keywords/object-keywords.d.ts.map +1 -1
  11. package/dist/keywords/other-keywords.d.ts.map +1 -1
  12. package/dist/keywords/string-keywords.d.ts.map +1 -1
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/utils/deep-freeze.d.ts +5 -0
  15. package/dist/utils/deep-freeze.d.ts.map +1 -0
  16. package/dist/utils/has-changed.d.ts +2 -0
  17. package/dist/utils/has-changed.d.ts.map +1 -0
  18. package/dist/utils/index.d.ts +5 -0
  19. package/dist/utils/index.d.ts.map +1 -0
  20. package/dist/{utils.d.ts → utils/main-utils.d.ts} +3 -6
  21. package/dist/utils/main-utils.d.ts.map +1 -0
  22. package/dist/utils/pattern-matcher.d.ts +3 -0
  23. package/dist/utils/pattern-matcher.d.ts.map +1 -0
  24. package/lib/formats.ts +402 -84
  25. package/lib/index.ts +494 -46
  26. package/lib/keywords/array-keywords.ts +215 -21
  27. package/lib/keywords/number-keywords.ts +1 -1
  28. package/lib/keywords/object-keywords.ts +218 -113
  29. package/lib/keywords/other-keywords.ts +229 -76
  30. package/lib/keywords/string-keywords.ts +97 -7
  31. package/lib/types.ts +4 -5
  32. package/lib/utils/deep-freeze.ts +208 -0
  33. package/lib/utils/has-changed.ts +51 -0
  34. package/lib/utils/index.ts +4 -0
  35. package/lib/utils/main-utils.ts +190 -0
  36. package/lib/utils/pattern-matcher.ts +66 -0
  37. package/package.json +1 -1
  38. package/dist/utils.d.ts.map +0 -1
  39. 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
- fn: KeywordFunction;
58
- defineError: DefineErrorFunction;
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
- this.linkReferences(compiledSchema);
148
+ if ((compiledSchema as any)._hasRef === true) {
149
+ this.linkReferences(compiledSchema);
150
+ }
146
151
 
147
152
  if (!compiledSchema.$validate) {
148
- if (this.isSchemaLike(schema) === false) {
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 ? deepClone(data) : data;
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 (!isObject(schema)) {
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
- const compiledSchema: CompiledSchema = deepClone(schema) as CompiledSchema;
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
- fn: getNamedFunction(typeMethodName, typeAdapter),
275
- defineError: defineTypeError
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
- ? [...Object.keys(otherKeys), "required"]
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
- const defineError = getDefinedErrorFunctionForKey(
292
- key,
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
- activeNames.push(fnName);
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
- if (isObject(schema[key])) {
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
- compiledSchema[key][subKey] = this.compileSchema(
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
- compiledSchema[key] = this.compileSchema(schema[key]);
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
- compiledSchema[key][i] = this.compileSchema(schema[key][i]);
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(activeNames[0], (data) =>
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.fn(compiledSchema, data, v.defineError, this);
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 (isObject(subSchema)) {
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
  }