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.
- package/CHANGELOG.md +29 -0
- package/README.md +13 -3
- package/index.d.ts +3 -3
- package/index.js +141 -47
- package/lib/aot.js +1 -1
- package/lib/interpreter.js +251 -45
- package/lib/js-compiler.js +99 -44
- package/lib/plan-compiler.js +969 -0
- package/lib/version.js +1 -1
- package/package.json +8 -8
package/lib/interpreter.js
CHANGED
|
@@ -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
|
-
|
|
209
|
-
if (
|
|
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
|
-
|
|
480
|
-
let r = cache.get(base);
|
|
658
|
+
let r = cache !== null ? cache.get(base) : undefined;
|
|
481
659
|
if (r === undefined) {
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
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 {
|
|
560
|
-
if (
|
|
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(
|
|
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 &&
|
|
641
|
-
if (P.maxLength !== undefined &&
|
|
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
|
}
|