ata-validator 1.7.0 → 1.7.2

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.
@@ -205,13 +205,27 @@ function deepEqual(a, b) {
205
205
  function codePointLength(s) {
206
206
  let n = 0;
207
207
  for (let i = 0; i < s.length; i++) {
208
- const c = s.charCodeAt(i);
209
- if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) i++;
208
+ // Wraparound test: one compare classifies a high surrogate.
209
+ if (((s.charCodeAt(i) - 0xd800) >>> 0) < 0x400 && i + 1 < s.length) i++;
210
210
  n++;
211
211
  }
212
212
  return n;
213
213
  }
214
214
 
215
+ // The code point count of a UTF-16 string sits between ceil(length / 2) and
216
+ // length, so most length bounds are decided without counting anything.
217
+ function cpAtLeast(s, min) {
218
+ if (s.length >= 2 * min) return true;
219
+ if (s.length < min) return false;
220
+ return codePointLength(s) >= min;
221
+ }
222
+
223
+ function cpAtMost(s, max) {
224
+ if (s.length <= max) return true;
225
+ if (s.length > 2 * max + 1) return false;
226
+ return codePointLength(s) <= max;
227
+ }
228
+
215
229
  function typeMatches(type, d) {
216
230
  switch (type) {
217
231
  case 'string': return typeof d === 'string';
@@ -256,6 +270,11 @@ const FORMAT_CHECKS = {
256
270
  // it sees this object, so it is never mutated.
257
271
  const DISCARD = { props: null, items: null };
258
272
 
273
+ // Verdict-only error sink. When eval() sees this identity it neither builds
274
+ // error objects nor formats messages; every push site is guarded on it. The
275
+ // array is frozen so a missed guard throws in tests instead of hiding.
276
+ const NOERRORS = Object.freeze([]);
277
+
259
278
  function mergeAnnotations(target, from) {
260
279
  if (from.props && from.props.size) {
261
280
  if (!target.props) target.props = new Set();
@@ -331,6 +350,8 @@ class Plan {
331
350
  // evaluation time and cached per base URI.
332
351
  const child = (sub) => interp.node(sub);
333
352
  this.refCache = null;
353
+ this.refBase0 = undefined;
354
+ this.refRes0 = null;
334
355
  this.dynCache = null;
335
356
  this.ref = typeof schema.$ref === 'string' ? schema.$ref : null;
336
357
  this.dynamicRef = typeof schema.$dynamicRef === 'string' ? schema.$dynamicRef : null;
@@ -425,7 +446,148 @@ class Plan {
425
446
  const nodeBase = interp.state.nodeBase.get(schema);
426
447
  this.nodeBase = nodeBase !== undefined ? nodeBase : null;
427
448
  this.isResourceRoot = nodeBase !== undefined && interp.state.resources.get(nodeBase) === schema;
449
+
450
+ // A leaf plan checks only value-level keywords: no references, no
451
+ // subschemas, no annotations, no resource boundary. Those skip the whole
452
+ // eval() prologue (cycle guard, scope, annotation sinks) and run through
453
+ // evalLeaf(). Most schemas are mostly leaves.
454
+ this.leaf = !this.tracked && !this.hasApplicators && !this.hasUnevaluated &&
455
+ this.properties === null && this.patternProperties === null &&
456
+ this.additionalProperties === undefined && this.propertyNames === undefined &&
457
+ this.dependentSchemas === null && this.propertyDependencies === null &&
458
+ this.prefixItems === null && this.items === undefined && this.contains === undefined &&
459
+ this.nodeBase === null;
460
+ }
461
+ }
462
+
463
+
464
+ // Verdict-only twin of evalLeaf: no error objects, no path strings, first
465
+ // failure returns. Kept adjacent so the two cannot drift.
466
+ function evalLeafV(P, data) {
467
+ const bits = dataBits(data);
468
+ if (P.hasType && (bits & P.typeMask) === 0) return false;
469
+ if (P.enum !== null) {
470
+ let found = false;
471
+ for (let i = 0; i < P.enum.length; i++) { if (deepEqual(P.enum[i], data)) { found = true; break; } }
472
+ if (!found) return false;
473
+ }
474
+ if (P.hasConst && !deepEqual(P.const, data)) return false;
475
+ if (P.hasNumber && typeof data === 'number') {
476
+ if (P.minimum !== undefined && !(data >= P.minimum)) return false;
477
+ if (P.maximum !== undefined && !(data <= P.maximum)) return false;
478
+ if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) return false;
479
+ if (P.exclusiveMaximum !== undefined && !(data < P.exclusiveMaximum)) return false;
480
+ if (P.multipleOf !== undefined && !multipleOfOk(data, P.multipleOf)) return false;
481
+ }
482
+ if (P.hasString && bits === T_STRING) {
483
+ if (P.minLength !== undefined && !cpAtLeast(data, P.minLength)) return false;
484
+ if (P.maxLength !== undefined && !cpAtMost(data, P.maxLength)) return false;
485
+ if (P.pattern !== null && !P.pattern.test(data)) return false;
486
+ if (P.formatFn !== null && !P.formatFn(data)) return false;
487
+ }
488
+ if (P.hasArray && bits === T_ARRAY) {
489
+ if (P.minItems !== undefined && data.length < P.minItems) return false;
490
+ if (P.maxItems !== undefined && data.length > P.maxItems) return false;
491
+ if (P.uniqueItems) {
492
+ for (let i = 0; i < data.length; i++) {
493
+ for (let j = i + 1; j < data.length; j++) {
494
+ if (deepEqual(data[i], data[j])) return false;
495
+ }
496
+ }
497
+ }
498
+ }
499
+ if (P.hasObject && bits === T_OBJECT) {
500
+ if (P.required !== null) {
501
+ const req = P.required;
502
+ for (let i = 0; i < req.length; i++) {
503
+ if (!Object.hasOwn(data, req[i])) return false;
504
+ }
505
+ }
506
+ if (P.minProperties !== undefined && Object.keys(data).length < P.minProperties) return false;
507
+ if (P.maxProperties !== undefined && Object.keys(data).length > P.maxProperties) return false;
508
+ if (P.dependentRequired !== null) {
509
+ for (const [key, deps] of P.dependentRequired) {
510
+ if (Object.hasOwn(data, key)) {
511
+ for (const dep of deps) if (!Object.hasOwn(data, dep)) return false;
512
+ }
513
+ }
514
+ }
515
+ }
516
+ return true;
517
+ }
518
+
519
+ // Value-level checks only; the caller guarantees P.leaf. The bodies mirror
520
+ // the corresponding sections of eval() exactly, minus the prologue.
521
+ function evalLeaf(P, data, errors, instancePath, schemaPath) {
522
+ let valid = true;
523
+ const schema = P.schema;
524
+ const bits = dataBits(data);
525
+ if (P.hasType && (bits & P.typeMask) === 0) {
526
+ if (errors !== NOERRORS) errors.push(err('type', 'type', instancePath, schemaPath + '/type', { type: schema.type }, `must be ${P.typeNames.join(' or ')}`));
527
+ valid = false;
528
+ }
529
+ if (P.enum !== null) {
530
+ let found = false;
531
+ for (let i = 0; i < P.enum.length; i++) { if (deepEqual(P.enum[i], data)) { found = true; break; } }
532
+ if (!found) {
533
+ if (errors !== NOERRORS) errors.push(err('enum', 'enum', instancePath, schemaPath + '/enum', { allowedValues: P.enum }, 'must be equal to one of the allowed values'));
534
+ valid = false;
535
+ }
536
+ }
537
+ if (P.hasConst && !deepEqual(P.const, data)) {
538
+ if (errors !== NOERRORS) errors.push(err('const', 'const', instancePath, schemaPath + '/const', { allowedValue: P.const }, 'must be equal to constant'));
539
+ valid = false;
540
+ }
541
+ if (P.hasNumber && typeof data === 'number') {
542
+ if (P.minimum !== undefined && !(data >= P.minimum)) { errors === NOERRORS || errors.push(err('minimum', 'minimum', instancePath, schemaPath + '/minimum', { comparison: '>=', limit: P.minimum }, `must be >= ${P.minimum}`)); valid = false; }
543
+ if (P.maximum !== undefined && !(data <= P.maximum)) { errors === NOERRORS || errors.push(err('maximum', 'maximum', instancePath, schemaPath + '/maximum', { comparison: '<=', limit: P.maximum }, `must be <= ${P.maximum}`)); valid = false; }
544
+ if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) { errors === NOERRORS || errors.push(err('exclusiveMinimum', 'exclusiveMinimum', instancePath, schemaPath + '/exclusiveMinimum', { comparison: '>', limit: P.exclusiveMinimum }, `must be > ${P.exclusiveMinimum}`)); valid = false; }
545
+ if (P.exclusiveMaximum !== undefined && !(data < P.exclusiveMaximum)) { errors === NOERRORS || errors.push(err('exclusiveMaximum', 'exclusiveMaximum', instancePath, schemaPath + '/exclusiveMaximum', { comparison: '<', limit: P.exclusiveMaximum }, `must be < ${P.exclusiveMaximum}`)); valid = false; }
546
+ if (P.multipleOf !== undefined && !multipleOfOk(data, P.multipleOf)) { errors === NOERRORS || errors.push(err('multipleOf', 'multipleOf', instancePath, schemaPath + '/multipleOf', { multipleOf: P.multipleOf }, `must be multiple of ${P.multipleOf}`)); valid = false; }
547
+ }
548
+ if (P.hasString && bits === T_STRING) {
549
+ if (P.minLength !== undefined && !cpAtLeast(data, P.minLength)) { errors === NOERRORS || errors.push(err('minLength', 'minLength', instancePath, schemaPath + '/minLength', { limit: P.minLength }, `must NOT have fewer than ${P.minLength} characters`)); valid = false; }
550
+ if (P.maxLength !== undefined && !cpAtMost(data, P.maxLength)) { errors === NOERRORS || errors.push(err('maxLength', 'maxLength', instancePath, schemaPath + '/maxLength', { limit: P.maxLength }, `must NOT have more than ${P.maxLength} characters`)); valid = false; }
551
+ if (P.pattern !== null && !P.pattern.test(data)) { errors === NOERRORS || errors.push(err('pattern', 'pattern', instancePath, schemaPath + '/pattern', { pattern: P.patternSource }, `must match pattern "${P.patternSource}"`)); valid = false; }
552
+ if (P.formatFn !== null && !P.formatFn(data)) { errors === NOERRORS || errors.push(err('format', 'format', instancePath, schemaPath + '/format', { format: P.format }, `must match format "${P.format}"`)); valid = false; }
553
+ }
554
+ if (P.hasArray && bits === T_ARRAY) {
555
+ if (P.minItems !== undefined && data.length < P.minItems) { errors === NOERRORS || errors.push(err('minItems', 'minItems', instancePath, schemaPath + '/minItems', { limit: P.minItems }, `must NOT have fewer than ${P.minItems} items`)); valid = false; }
556
+ if (P.maxItems !== undefined && data.length > P.maxItems) { errors === NOERRORS || errors.push(err('maxItems', 'maxItems', instancePath, schemaPath + '/maxItems', { limit: P.maxItems }, `must NOT have more than ${P.maxItems} items`)); valid = false; }
557
+ if (P.uniqueItems) {
558
+ outer: for (let i = 0; i < data.length; i++) {
559
+ for (let j = i + 1; j < data.length; j++) {
560
+ if (deepEqual(data[i], data[j])) {
561
+ if (errors !== NOERRORS) errors.push(err('uniqueItems', 'uniqueItems', instancePath, schemaPath + '/uniqueItems', { i, j }, 'must NOT have duplicate items'));
562
+ valid = false;
563
+ break outer;
564
+ }
565
+ }
566
+ }
567
+ }
568
+ }
569
+ if (P.hasObject && bits === T_OBJECT) {
570
+ const keys = Object.keys(data);
571
+ if (P.required !== null) {
572
+ const req = P.required;
573
+ for (let i = 0; i < req.length; i++) {
574
+ const key = req[i];
575
+ if (!Object.hasOwn(data, key)) { errors === NOERRORS || errors.push(err('required', 'required', instancePath, schemaPath + '/required', { missingProperty: key }, `must have required property '${key}'`)); valid = false; }
576
+ }
577
+ }
578
+ if (P.minProperties !== undefined && keys.length < P.minProperties) { errors === NOERRORS || errors.push(err('minProperties', 'minProperties', instancePath, schemaPath + '/minProperties', { limit: P.minProperties }, `must NOT have fewer than ${P.minProperties} properties`)); valid = false; }
579
+ if (P.maxProperties !== undefined && keys.length > P.maxProperties) { errors === NOERRORS || errors.push(err('maxProperties', 'maxProperties', instancePath, schemaPath + '/maxProperties', { limit: P.maxProperties }, `must NOT have more than ${P.maxProperties} properties`)); valid = false; }
580
+ if (P.dependentRequired !== null) {
581
+ for (const [key, deps] of P.dependentRequired) {
582
+ if (Object.hasOwn(data, key)) {
583
+ for (const dep of deps) {
584
+ if (!Object.hasOwn(data, dep)) { errors === NOERRORS || errors.push(err('required', 'required', instancePath, schemaPath + '/dependentRequired', { missingProperty: dep }, `must have required property '${dep}'`)); valid = false; }
585
+ }
586
+ }
587
+ }
588
+ }
428
589
  }
590
+ return valid;
429
591
  }
430
592
 
431
593
  class Interpreter {
@@ -441,6 +603,19 @@ class Interpreter {
441
603
  // Plans are built on first visit; a schema node is never re-read after.
442
604
  this.plans = new Map();
443
605
  this.rootNode = this.node(rootSchema);
606
+ // Compiled closure tree: undefined = not attempted, null = out of scope.
607
+ this._fast = undefined;
608
+ }
609
+
610
+ // The closure-tree compiler covers every schema the interpreter accepts;
611
+ // eval() stays as the reference the compiled tree is diffed against.
612
+ // Compiled lazily so construction stays cheap for schemas that never
613
+ // validate.
614
+ _fastRoot() {
615
+ if (this._fast === undefined) {
616
+ try { this._fast = compileInterpreter(this); } catch { this._fast = null; }
617
+ }
618
+ return this._fast;
444
619
  }
445
620
 
446
621
  pattern(src) {
@@ -473,14 +648,19 @@ class Interpreter {
473
648
  }
474
649
 
475
650
  // $ref resolution depends on the base URI in effect, which can differ
476
- // between visits of the same node; cache per (plan, base).
651
+ // between visits of the same node; cache per (plan, base). Nearly every
652
+ // plan only ever sees one base, so the last resolution lives in two plain
653
+ // fields and the Map exists only for the rare second base. The cached
654
+ // entry carries the already-planned child, so a visit does no Map lookups.
477
655
  resolveRefCached(P, base) {
656
+ if (P.refBase0 === base) return P.refRes0;
478
657
  let cache = P.refCache;
479
- if (cache === null) cache = P.refCache = new Map();
480
- let r = cache.get(base);
658
+ let r = cache !== null ? cache.get(base) : undefined;
481
659
  if (r === undefined) {
482
- r = resolveRef(P.ref, base, this.state);
483
- cache.set(base, r);
660
+ const raw = resolveRef(P.ref, base, this.state);
661
+ r = { child: raw.node === undefined ? undefined : this.node(raw.node), base: raw.base };
662
+ if (P.refBase0 === undefined) { P.refBase0 = base; P.refRes0 = r; }
663
+ else { if (cache === null) cache = P.refCache = new Map(); cache.set(base, r); }
484
664
  }
485
665
  return r;
486
666
  }
@@ -508,7 +688,21 @@ class Interpreter {
508
688
  return this.plan(schema);
509
689
  }
510
690
 
691
+ // Verdict only: same walk as validate(), no error construction at all.
692
+ isValid(data) {
693
+ const fast = this._fastRoot();
694
+ if (fast !== null) return fast.v(data, fast.cyclic ? [] : null);
695
+ const dynScope = [this.state.rootBase];
696
+ return this.eval(this.rootNode, data, this.state.rootBase, dynScope, NOERRORS, '', '#', [], DISCARD);
697
+ }
698
+
511
699
  validate(data) {
700
+ const fast = this._fastRoot();
701
+ if (fast !== null) {
702
+ const errors = [];
703
+ const valid = fast.c(data, errors, '', '#', fast.cyclic ? [] : null);
704
+ return valid ? { valid: true, data, errors: [] } : { valid: false, errors };
705
+ }
512
706
  const errors = [];
513
707
  const dynScope = [this.state.rootBase];
514
708
  // Nothing reads the root's annotations, so they are discarded from the start.
@@ -522,9 +716,10 @@ class Interpreter {
522
716
  eval(P, data, base, dynScope, errors, instancePath, schemaPath, stack, sink) {
523
717
  if (P === true) return true;
524
718
  if (P === false) {
525
- errors.push(err('false schema', 'not', instancePath, schemaPath, {}, 'boolean schema is false'));
719
+ if (errors !== NOERRORS) errors.push(err('false schema', 'not', instancePath, schemaPath, {}, 'boolean schema is false'));
526
720
  return false;
527
721
  }
722
+ if (P.leaf) return evalLeaf(P, data, errors, instancePath, schemaPath);
528
723
  const schema = P.schema;
529
724
 
530
725
  // Only a reference can lead evaluation back to a schema it is already
@@ -542,9 +737,13 @@ class Interpreter {
542
737
  if (P.nodeBase !== null && P.nodeBase !== base) base = P.nodeBase;
543
738
  // Entering a schema resource extends the dynamic scope, whether evaluation
544
739
  // lands on the resource's root or on a subschema inside it: a reference
545
- // to first#/$defs/stuff still passes through resource `first`.
740
+ // to first#/$defs/stuff still passes through resource `first`. The scope
741
+ // is pushed in place and popped on the way out; evaluation is depth-first
742
+ // and nothing retains the array.
743
+ let scopePushed = false;
546
744
  if (dynScope[dynScope.length - 1] !== base) {
547
- dynScope = dynScope.concat(base);
745
+ dynScope.push(base);
746
+ scopePushed = true;
548
747
  }
549
748
 
550
749
  let valid = true;
@@ -556,13 +755,13 @@ class Interpreter {
556
755
 
557
756
  // $ref / $dynamicRef: in-place applicators, annotations flow through
558
757
  if (P.ref !== null) {
559
- const { node, base: refBase } = this.resolveRefCached(P, base);
560
- if (node === undefined) {
561
- errors.push(err('$ref', '$ref', instancePath, schemaPath + '/$ref', { ref: P.ref }, `cannot resolve $ref ${P.ref}`));
758
+ const { child, base: refBase } = this.resolveRefCached(P, base);
759
+ if (child === undefined) {
760
+ if (errors !== NOERRORS) errors.push(err('$ref', '$ref', instancePath, schemaPath + '/$ref', { ref: P.ref }, `cannot resolve $ref ${P.ref}`));
562
761
  valid = false;
563
762
  } else {
564
763
  const sub = collect ? { props: null, items: null } : DISCARD;
565
- if (!this.eval(this.node(node), data, refBase, dynScope, errors, instancePath, schemaPath + '/$ref', stack, sub)) valid = false;
764
+ if (!this.eval(child, data, refBase, dynScope, errors, instancePath, schemaPath + '/$ref', stack, sub)) valid = false;
566
765
  else if (collect) mergeAnnotations(local, sub);
567
766
  }
568
767
  }
@@ -597,7 +796,7 @@ class Interpreter {
597
796
  }
598
797
  }
599
798
  if (node === undefined) {
600
- errors.push(err('$dynamicRef', '$dynamicRef', instancePath, schemaPath + '/$dynamicRef', { ref }, `cannot resolve $dynamicRef ${ref}`));
799
+ if (errors !== NOERRORS) errors.push(err('$dynamicRef', '$dynamicRef', instancePath, schemaPath + '/$dynamicRef', { ref }, `cannot resolve $dynamicRef ${ref}`));
601
800
  valid = false;
602
801
  } else {
603
802
  const sub = collect ? { props: null, items: null } : DISCARD;
@@ -610,48 +809,48 @@ class Interpreter {
610
809
 
611
810
  // type / enum / const
612
811
  if (P.hasType && (bits & P.typeMask) === 0) {
613
- errors.push(err('type', 'type', instancePath, schemaPath + '/type', { type: schema.type }, `must be ${P.typeNames.join(' or ')}`));
812
+ if (errors !== NOERRORS) errors.push(err('type', 'type', instancePath, schemaPath + '/type', { type: schema.type }, `must be ${P.typeNames.join(' or ')}`));
614
813
  valid = false;
615
814
  }
616
815
  if (P.enum !== null) {
617
816
  let found = false;
618
817
  for (let i = 0; i < P.enum.length; i++) { if (deepEqual(P.enum[i], data)) { found = true; break; } }
619
818
  if (!found) {
620
- errors.push(err('enum', 'enum', instancePath, schemaPath + '/enum', { allowedValues: P.enum }, 'must be equal to one of the allowed values'));
819
+ if (errors !== NOERRORS) errors.push(err('enum', 'enum', instancePath, schemaPath + '/enum', { allowedValues: P.enum }, 'must be equal to one of the allowed values'));
621
820
  valid = false;
622
821
  }
623
822
  }
624
823
  if (P.hasConst && !deepEqual(P.const, data)) {
625
- errors.push(err('const', 'const', instancePath, schemaPath + '/const', { allowedValue: P.const }, 'must be equal to constant'));
824
+ if (errors !== NOERRORS) errors.push(err('const', 'const', instancePath, schemaPath + '/const', { allowedValue: P.const }, 'must be equal to constant'));
626
825
  valid = false;
627
826
  }
628
827
 
629
828
  // numbers
630
829
  if (P.hasNumber && typeof data === 'number') {
631
- if (P.minimum !== undefined && !(data >= P.minimum)) { errors.push(err('minimum', 'minimum', instancePath, schemaPath + '/minimum', { comparison: '>=', limit: P.minimum }, `must be >= ${P.minimum}`)); valid = false; }
632
- if (P.maximum !== undefined && !(data <= P.maximum)) { errors.push(err('maximum', 'maximum', instancePath, schemaPath + '/maximum', { comparison: '<=', limit: P.maximum }, `must be <= ${P.maximum}`)); valid = false; }
633
- if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) { errors.push(err('exclusiveMinimum', 'exclusiveMinimum', instancePath, schemaPath + '/exclusiveMinimum', { comparison: '>', limit: P.exclusiveMinimum }, `must be > ${P.exclusiveMinimum}`)); valid = false; }
634
- if (P.exclusiveMaximum !== undefined && !(data < P.exclusiveMaximum)) { errors.push(err('exclusiveMaximum', 'exclusiveMaximum', instancePath, schemaPath + '/exclusiveMaximum', { comparison: '<', limit: P.exclusiveMaximum }, `must be < ${P.exclusiveMaximum}`)); valid = false; }
635
- if (P.multipleOf !== undefined && !multipleOfOk(data, P.multipleOf)) { errors.push(err('multipleOf', 'multipleOf', instancePath, schemaPath + '/multipleOf', { multipleOf: P.multipleOf }, `must be multiple of ${P.multipleOf}`)); valid = false; }
830
+ if (P.minimum !== undefined && !(data >= P.minimum)) { errors === NOERRORS || errors.push(err('minimum', 'minimum', instancePath, schemaPath + '/minimum', { comparison: '>=', limit: P.minimum }, `must be >= ${P.minimum}`)); valid = false; }
831
+ if (P.maximum !== undefined && !(data <= P.maximum)) { errors === NOERRORS || errors.push(err('maximum', 'maximum', instancePath, schemaPath + '/maximum', { comparison: '<=', limit: P.maximum }, `must be <= ${P.maximum}`)); valid = false; }
832
+ if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) { errors === NOERRORS || errors.push(err('exclusiveMinimum', 'exclusiveMinimum', instancePath, schemaPath + '/exclusiveMinimum', { comparison: '>', limit: P.exclusiveMinimum }, `must be > ${P.exclusiveMinimum}`)); valid = false; }
833
+ if (P.exclusiveMaximum !== undefined && !(data < P.exclusiveMaximum)) { errors === NOERRORS || errors.push(err('exclusiveMaximum', 'exclusiveMaximum', instancePath, schemaPath + '/exclusiveMaximum', { comparison: '<', limit: P.exclusiveMaximum }, `must be < ${P.exclusiveMaximum}`)); valid = false; }
834
+ if (P.multipleOf !== undefined && !multipleOfOk(data, P.multipleOf)) { errors === NOERRORS || errors.push(err('multipleOf', 'multipleOf', instancePath, schemaPath + '/multipleOf', { multipleOf: P.multipleOf }, `must be multiple of ${P.multipleOf}`)); valid = false; }
636
835
  }
637
836
 
638
837
  // strings
639
838
  if (P.hasString && bits === T_STRING) {
640
- if (P.minLength !== undefined && codePointLength(data) < P.minLength) { errors.push(err('minLength', 'minLength', instancePath, schemaPath + '/minLength', { limit: P.minLength }, `must NOT have fewer than ${P.minLength} characters`)); valid = false; }
641
- if (P.maxLength !== undefined && codePointLength(data) > P.maxLength) { errors.push(err('maxLength', 'maxLength', instancePath, schemaPath + '/maxLength', { limit: P.maxLength }, `must NOT have more than ${P.maxLength} characters`)); valid = false; }
642
- if (P.pattern !== null && !P.pattern.test(data)) { errors.push(err('pattern', 'pattern', instancePath, schemaPath + '/pattern', { pattern: P.patternSource }, `must match pattern "${P.patternSource}"`)); valid = false; }
643
- if (P.formatFn !== null && !P.formatFn(data)) { errors.push(err('format', 'format', instancePath, schemaPath + '/format', { format: P.format }, `must match format "${P.format}"`)); valid = false; }
839
+ if (P.minLength !== undefined && !cpAtLeast(data, P.minLength)) { errors === NOERRORS || errors.push(err('minLength', 'minLength', instancePath, schemaPath + '/minLength', { limit: P.minLength }, `must NOT have fewer than ${P.minLength} characters`)); valid = false; }
840
+ if (P.maxLength !== undefined && !cpAtMost(data, P.maxLength)) { errors === NOERRORS || errors.push(err('maxLength', 'maxLength', instancePath, schemaPath + '/maxLength', { limit: P.maxLength }, `must NOT have more than ${P.maxLength} characters`)); valid = false; }
841
+ if (P.pattern !== null && !P.pattern.test(data)) { errors === NOERRORS || errors.push(err('pattern', 'pattern', instancePath, schemaPath + '/pattern', { pattern: P.patternSource }, `must match pattern "${P.patternSource}"`)); valid = false; }
842
+ if (P.formatFn !== null && !P.formatFn(data)) { errors === NOERRORS || errors.push(err('format', 'format', instancePath, schemaPath + '/format', { format: P.format }, `must match format "${P.format}"`)); valid = false; }
644
843
  }
645
844
 
646
845
  // arrays
647
846
  if (P.hasArray && bits === T_ARRAY) {
648
- if (P.minItems !== undefined && data.length < P.minItems) { errors.push(err('minItems', 'minItems', instancePath, schemaPath + '/minItems', { limit: P.minItems }, `must NOT have fewer than ${P.minItems} items`)); valid = false; }
649
- if (P.maxItems !== undefined && data.length > P.maxItems) { errors.push(err('maxItems', 'maxItems', instancePath, schemaPath + '/maxItems', { limit: P.maxItems }, `must NOT have more than ${P.maxItems} items`)); valid = false; }
847
+ if (P.minItems !== undefined && data.length < P.minItems) { errors === NOERRORS || errors.push(err('minItems', 'minItems', instancePath, schemaPath + '/minItems', { limit: P.minItems }, `must NOT have fewer than ${P.minItems} items`)); valid = false; }
848
+ if (P.maxItems !== undefined && data.length > P.maxItems) { errors === NOERRORS || errors.push(err('maxItems', 'maxItems', instancePath, schemaPath + '/maxItems', { limit: P.maxItems }, `must NOT have more than ${P.maxItems} items`)); valid = false; }
650
849
  if (P.uniqueItems) {
651
850
  outer: for (let i = 0; i < data.length; i++) {
652
851
  for (let j = i + 1; j < data.length; j++) {
653
852
  if (deepEqual(data[i], data[j])) {
654
- errors.push(err('uniqueItems', 'uniqueItems', instancePath, schemaPath + '/uniqueItems', { i, j }, 'must NOT have duplicate items'));
853
+ if (errors !== NOERRORS) errors.push(err('uniqueItems', 'uniqueItems', instancePath, schemaPath + '/uniqueItems', { i, j }, 'must NOT have duplicate items'));
655
854
  valid = false;
656
855
  break outer;
657
856
  }
@@ -676,12 +875,12 @@ class Interpreter {
676
875
  if (P.contains !== undefined) {
677
876
  const matched = [];
678
877
  for (let i = 0; i < data.length; i++) {
679
- const scratch = [];
878
+ const scratch = errors === NOERRORS ? NOERRORS : [];
680
879
  if (this.eval(P.contains, data[i], base, dynScope, scratch, instancePath + '/' + i, schemaPath + '/contains', stack, DISCARD)) matched.push(i);
681
880
  }
682
881
  const minC = P.minContains !== undefined ? P.minContains : 1;
683
- if (matched.length < minC) { errors.push(err('contains', 'contains', instancePath, schemaPath + '/contains', { minContains: minC }, `must contain at least ${minC} valid item(s)`)); valid = false; }
684
- if (P.maxContains !== undefined && matched.length > P.maxContains) { errors.push(err('maxContains', 'maxContains', instancePath, schemaPath + '/maxContains', { limit: P.maxContains }, `must NOT contain more than ${P.maxContains} valid item(s)`)); valid = false; }
882
+ if (matched.length < minC) { errors === NOERRORS || errors.push(err('contains', 'contains', instancePath, schemaPath + '/contains', { minContains: minC }, `must contain at least ${minC} valid item(s)`)); valid = false; }
883
+ if (P.maxContains !== undefined && matched.length > P.maxContains) { errors === NOERRORS || errors.push(err('maxContains', 'maxContains', instancePath, schemaPath + '/maxContains', { limit: P.maxContains }, `must NOT contain more than ${P.maxContains} valid item(s)`)); valid = false; }
685
884
  if (collect && matched.length) {
686
885
  if (!local.items) local.items = new Set();
687
886
  for (const i of matched) local.items.add(i);
@@ -696,16 +895,16 @@ class Interpreter {
696
895
  const req = P.required;
697
896
  for (let i = 0; i < req.length; i++) {
698
897
  const key = req[i];
699
- if (!Object.hasOwn(data, key)) { errors.push(err('required', 'required', instancePath, schemaPath + '/required', { missingProperty: key }, `must have required property '${key}'`)); valid = false; }
898
+ if (!Object.hasOwn(data, key)) { errors === NOERRORS || errors.push(err('required', 'required', instancePath, schemaPath + '/required', { missingProperty: key }, `must have required property '${key}'`)); valid = false; }
700
899
  }
701
900
  }
702
- if (P.minProperties !== undefined && keys.length < P.minProperties) { errors.push(err('minProperties', 'minProperties', instancePath, schemaPath + '/minProperties', { limit: P.minProperties }, `must NOT have fewer than ${P.minProperties} properties`)); valid = false; }
703
- if (P.maxProperties !== undefined && keys.length > P.maxProperties) { errors.push(err('maxProperties', 'maxProperties', instancePath, schemaPath + '/maxProperties', { limit: P.maxProperties }, `must NOT have more than ${P.maxProperties} properties`)); valid = false; }
901
+ if (P.minProperties !== undefined && keys.length < P.minProperties) { errors === NOERRORS || errors.push(err('minProperties', 'minProperties', instancePath, schemaPath + '/minProperties', { limit: P.minProperties }, `must NOT have fewer than ${P.minProperties} properties`)); valid = false; }
902
+ if (P.maxProperties !== undefined && keys.length > P.maxProperties) { errors === NOERRORS || errors.push(err('maxProperties', 'maxProperties', instancePath, schemaPath + '/maxProperties', { limit: P.maxProperties }, `must NOT have more than ${P.maxProperties} properties`)); valid = false; }
704
903
  if (P.dependentRequired !== null) {
705
904
  for (const [key, deps] of P.dependentRequired) {
706
905
  if (Object.hasOwn(data, key)) {
707
906
  for (const dep of deps) {
708
- if (!Object.hasOwn(data, dep)) { errors.push(err('required', 'required', instancePath, schemaPath + '/dependentRequired', { missingProperty: dep }, `must have required property '${dep}'`)); valid = false; }
907
+ if (!Object.hasOwn(data, dep)) { errors === NOERRORS || errors.push(err('required', 'required', instancePath, schemaPath + '/dependentRequired', { missingProperty: dep }, `must have required property '${dep}'`)); valid = false; }
709
908
  }
710
909
  }
711
910
  }
@@ -782,7 +981,7 @@ class Interpreter {
782
981
  }
783
982
  if (P.anyOf !== null) {
784
983
  let any = false;
785
- const scratch = [];
984
+ const scratch = errors === NOERRORS ? NOERRORS : [];
786
985
  for (let i = 0; i < P.anyOf.length; i++) {
787
986
  const sub = collect ? { props: null, items: null } : DISCARD;
788
987
  if (this.eval(P.anyOf[i], data, base, dynScope, scratch, instancePath, schemaPath + '/anyOf/' + i, stack, sub)) {
@@ -791,15 +990,15 @@ class Interpreter {
791
990
  }
792
991
  }
793
992
  if (!any) {
794
- for (const e of scratch) errors.push(e);
795
- errors.push(err('anyOf', 'anyOf', instancePath, schemaPath + '/anyOf', {}, 'must match a schema in anyOf'));
993
+ if (errors !== NOERRORS) for (const e of scratch) errors.push(e);
994
+ if (errors !== NOERRORS) errors.push(err('anyOf', 'anyOf', instancePath, schemaPath + '/anyOf', {}, 'must match a schema in anyOf'));
796
995
  valid = false;
797
996
  }
798
997
  }
799
998
  if (P.oneOf !== null) {
800
999
  let count = 0;
801
1000
  let winner = null;
802
- const scratch = [];
1001
+ const scratch = errors === NOERRORS ? NOERRORS : [];
803
1002
  for (let i = 0; i < P.oneOf.length; i++) {
804
1003
  const sub = collect ? { props: null, items: null } : DISCARD;
805
1004
  if (this.eval(P.oneOf[i], data, base, dynScope, scratch, instancePath, schemaPath + '/oneOf/' + i, stack, sub)) {
@@ -810,20 +1009,20 @@ class Interpreter {
810
1009
  if (count === 1) { if (collect) mergeAnnotations(local, winner); }
811
1010
  else {
812
1011
  if (count === 0) for (const e of scratch) errors.push(e);
813
- errors.push(err('oneOf', 'oneOf', instancePath, schemaPath + '/oneOf', { passingSchemas: count }, 'must match exactly one schema in oneOf'));
1012
+ if (errors !== NOERRORS) errors.push(err('oneOf', 'oneOf', instancePath, schemaPath + '/oneOf', { passingSchemas: count }, 'must match exactly one schema in oneOf'));
814
1013
  valid = false;
815
1014
  }
816
1015
  }
817
1016
  if (P.not !== undefined) {
818
- const scratch = [];
1017
+ const scratch = errors === NOERRORS ? NOERRORS : [];
819
1018
  if (this.eval(P.not, data, base, dynScope, scratch, instancePath, schemaPath + '/not', stack, DISCARD)) {
820
- errors.push(err('not', 'not', instancePath, schemaPath + '/not', {}, 'must NOT be valid'));
1019
+ if (errors !== NOERRORS) errors.push(err('not', 'not', instancePath, schemaPath + '/not', {}, 'must NOT be valid'));
821
1020
  valid = false;
822
1021
  }
823
1022
  }
824
1023
  if (P.if !== undefined) {
825
1024
  const ifSub = collect ? { props: null, items: null } : DISCARD;
826
- const ifScratch = [];
1025
+ const ifScratch = errors === NOERRORS ? NOERRORS : [];
827
1026
  const ifOk = this.eval(P.if, data, base, dynScope, ifScratch, instancePath, schemaPath + '/if', stack, ifSub);
828
1027
  if (ifOk) {
829
1028
  if (collect) mergeAnnotations(local, ifSub);
@@ -860,6 +1059,7 @@ class Interpreter {
860
1059
  }
861
1060
  }
862
1061
 
1062
+ if (scopePushed) dynScope.pop();
863
1063
  if (tracked) stack.length -= 2;
864
1064
  if (valid && collect && sink !== DISCARD) mergeAnnotations(sink, local);
865
1065
  return valid;
@@ -880,6 +1080,12 @@ function err(code, keyword, instancePath, schemaPath, params, message) {
880
1080
  return { keyword, instancePath, schemaPath, params, message };
881
1081
  }
882
1082
 
1083
+ const { compileInterpreter } = require('./plan-compiler').install({
1084
+ Plan, NOERRORS, err, evalLeaf, evalLeafV, dataBits, escapePointer,
1085
+ deepEqual, multipleOfOk, cpAtLeast, cpAtMost,
1086
+ T_STRING, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri,
1087
+ });
1088
+
883
1089
  function createInterpreter(schema, options) {
884
1090
  return new Interpreter(schema, options);
885
1091
  }