ata-validator 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +9 -0
- package/index.d.ts +3 -3
- package/index.js +121 -45
- package/lib/aot.js +1 -1
- package/lib/interpreter.js +249 -45
- package/lib/js-compiler.js +3 -2
- package/lib/plan-compiler.js +545 -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,18 @@ 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 single-resource schemas without
|
|
611
|
+
// unevaluated keywords; everything else stays on eval(). Compiled lazily so
|
|
612
|
+
// construction stays cheap for schemas that never validate.
|
|
613
|
+
_fastRoot() {
|
|
614
|
+
if (this._fast === undefined) {
|
|
615
|
+
try { this._fast = compileInterpreter(this); } catch { this._fast = null; }
|
|
616
|
+
}
|
|
617
|
+
return this._fast;
|
|
444
618
|
}
|
|
445
619
|
|
|
446
620
|
pattern(src) {
|
|
@@ -473,14 +647,19 @@ class Interpreter {
|
|
|
473
647
|
}
|
|
474
648
|
|
|
475
649
|
// $ref resolution depends on the base URI in effect, which can differ
|
|
476
|
-
// between visits of the same node; cache per (plan, base).
|
|
650
|
+
// between visits of the same node; cache per (plan, base). Nearly every
|
|
651
|
+
// plan only ever sees one base, so the last resolution lives in two plain
|
|
652
|
+
// fields and the Map exists only for the rare second base. The cached
|
|
653
|
+
// entry carries the already-planned child, so a visit does no Map lookups.
|
|
477
654
|
resolveRefCached(P, base) {
|
|
655
|
+
if (P.refBase0 === base) return P.refRes0;
|
|
478
656
|
let cache = P.refCache;
|
|
479
|
-
|
|
480
|
-
let r = cache.get(base);
|
|
657
|
+
let r = cache !== null ? cache.get(base) : undefined;
|
|
481
658
|
if (r === undefined) {
|
|
482
|
-
|
|
483
|
-
|
|
659
|
+
const raw = resolveRef(P.ref, base, this.state);
|
|
660
|
+
r = { child: raw.node === undefined ? undefined : this.node(raw.node), base: raw.base };
|
|
661
|
+
if (P.refBase0 === undefined) { P.refBase0 = base; P.refRes0 = r; }
|
|
662
|
+
else { if (cache === null) cache = P.refCache = new Map(); cache.set(base, r); }
|
|
484
663
|
}
|
|
485
664
|
return r;
|
|
486
665
|
}
|
|
@@ -508,7 +687,21 @@ class Interpreter {
|
|
|
508
687
|
return this.plan(schema);
|
|
509
688
|
}
|
|
510
689
|
|
|
690
|
+
// Verdict only: same walk as validate(), no error construction at all.
|
|
691
|
+
isValid(data) {
|
|
692
|
+
const fast = this._fastRoot();
|
|
693
|
+
if (fast !== null) return fast.v(data, []);
|
|
694
|
+
const dynScope = [this.state.rootBase];
|
|
695
|
+
return this.eval(this.rootNode, data, this.state.rootBase, dynScope, NOERRORS, '', '#', [], DISCARD);
|
|
696
|
+
}
|
|
697
|
+
|
|
511
698
|
validate(data) {
|
|
699
|
+
const fast = this._fastRoot();
|
|
700
|
+
if (fast !== null) {
|
|
701
|
+
const errors = [];
|
|
702
|
+
const valid = fast.c(data, errors, '', '#', []);
|
|
703
|
+
return valid ? { valid: true, data, errors: [] } : { valid: false, errors };
|
|
704
|
+
}
|
|
512
705
|
const errors = [];
|
|
513
706
|
const dynScope = [this.state.rootBase];
|
|
514
707
|
// Nothing reads the root's annotations, so they are discarded from the start.
|
|
@@ -522,9 +715,10 @@ class Interpreter {
|
|
|
522
715
|
eval(P, data, base, dynScope, errors, instancePath, schemaPath, stack, sink) {
|
|
523
716
|
if (P === true) return true;
|
|
524
717
|
if (P === false) {
|
|
525
|
-
errors.push(err('false schema', 'not', instancePath, schemaPath, {}, 'boolean schema is false'));
|
|
718
|
+
if (errors !== NOERRORS) errors.push(err('false schema', 'not', instancePath, schemaPath, {}, 'boolean schema is false'));
|
|
526
719
|
return false;
|
|
527
720
|
}
|
|
721
|
+
if (P.leaf) return evalLeaf(P, data, errors, instancePath, schemaPath);
|
|
528
722
|
const schema = P.schema;
|
|
529
723
|
|
|
530
724
|
// Only a reference can lead evaluation back to a schema it is already
|
|
@@ -542,9 +736,13 @@ class Interpreter {
|
|
|
542
736
|
if (P.nodeBase !== null && P.nodeBase !== base) base = P.nodeBase;
|
|
543
737
|
// Entering a schema resource extends the dynamic scope, whether evaluation
|
|
544
738
|
// lands on the resource's root or on a subschema inside it: a reference
|
|
545
|
-
// to first#/$defs/stuff still passes through resource `first`.
|
|
739
|
+
// to first#/$defs/stuff still passes through resource `first`. The scope
|
|
740
|
+
// is pushed in place and popped on the way out; evaluation is depth-first
|
|
741
|
+
// and nothing retains the array.
|
|
742
|
+
let scopePushed = false;
|
|
546
743
|
if (dynScope[dynScope.length - 1] !== base) {
|
|
547
|
-
dynScope
|
|
744
|
+
dynScope.push(base);
|
|
745
|
+
scopePushed = true;
|
|
548
746
|
}
|
|
549
747
|
|
|
550
748
|
let valid = true;
|
|
@@ -556,13 +754,13 @@ class Interpreter {
|
|
|
556
754
|
|
|
557
755
|
// $ref / $dynamicRef: in-place applicators, annotations flow through
|
|
558
756
|
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}`));
|
|
757
|
+
const { child, base: refBase } = this.resolveRefCached(P, base);
|
|
758
|
+
if (child === undefined) {
|
|
759
|
+
if (errors !== NOERRORS) errors.push(err('$ref', '$ref', instancePath, schemaPath + '/$ref', { ref: P.ref }, `cannot resolve $ref ${P.ref}`));
|
|
562
760
|
valid = false;
|
|
563
761
|
} else {
|
|
564
762
|
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
565
|
-
if (!this.eval(
|
|
763
|
+
if (!this.eval(child, data, refBase, dynScope, errors, instancePath, schemaPath + '/$ref', stack, sub)) valid = false;
|
|
566
764
|
else if (collect) mergeAnnotations(local, sub);
|
|
567
765
|
}
|
|
568
766
|
}
|
|
@@ -597,7 +795,7 @@ class Interpreter {
|
|
|
597
795
|
}
|
|
598
796
|
}
|
|
599
797
|
if (node === undefined) {
|
|
600
|
-
errors.push(err('$dynamicRef', '$dynamicRef', instancePath, schemaPath + '/$dynamicRef', { ref }, `cannot resolve $dynamicRef ${ref}`));
|
|
798
|
+
if (errors !== NOERRORS) errors.push(err('$dynamicRef', '$dynamicRef', instancePath, schemaPath + '/$dynamicRef', { ref }, `cannot resolve $dynamicRef ${ref}`));
|
|
601
799
|
valid = false;
|
|
602
800
|
} else {
|
|
603
801
|
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
@@ -610,48 +808,48 @@ class Interpreter {
|
|
|
610
808
|
|
|
611
809
|
// type / enum / const
|
|
612
810
|
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 ')}`));
|
|
811
|
+
if (errors !== NOERRORS) errors.push(err('type', 'type', instancePath, schemaPath + '/type', { type: schema.type }, `must be ${P.typeNames.join(' or ')}`));
|
|
614
812
|
valid = false;
|
|
615
813
|
}
|
|
616
814
|
if (P.enum !== null) {
|
|
617
815
|
let found = false;
|
|
618
816
|
for (let i = 0; i < P.enum.length; i++) { if (deepEqual(P.enum[i], data)) { found = true; break; } }
|
|
619
817
|
if (!found) {
|
|
620
|
-
errors.push(err('enum', 'enum', instancePath, schemaPath + '/enum', { allowedValues: P.enum }, 'must be equal to one of the allowed values'));
|
|
818
|
+
if (errors !== NOERRORS) errors.push(err('enum', 'enum', instancePath, schemaPath + '/enum', { allowedValues: P.enum }, 'must be equal to one of the allowed values'));
|
|
621
819
|
valid = false;
|
|
622
820
|
}
|
|
623
821
|
}
|
|
624
822
|
if (P.hasConst && !deepEqual(P.const, data)) {
|
|
625
|
-
errors.push(err('const', 'const', instancePath, schemaPath + '/const', { allowedValue: P.const }, 'must be equal to constant'));
|
|
823
|
+
if (errors !== NOERRORS) errors.push(err('const', 'const', instancePath, schemaPath + '/const', { allowedValue: P.const }, 'must be equal to constant'));
|
|
626
824
|
valid = false;
|
|
627
825
|
}
|
|
628
826
|
|
|
629
827
|
// numbers
|
|
630
828
|
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; }
|
|
829
|
+
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; }
|
|
830
|
+
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; }
|
|
831
|
+
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; }
|
|
832
|
+
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; }
|
|
833
|
+
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
834
|
}
|
|
637
835
|
|
|
638
836
|
// strings
|
|
639
837
|
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; }
|
|
838
|
+
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; }
|
|
839
|
+
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; }
|
|
840
|
+
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; }
|
|
841
|
+
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
842
|
}
|
|
645
843
|
|
|
646
844
|
// arrays
|
|
647
845
|
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; }
|
|
846
|
+
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; }
|
|
847
|
+
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
848
|
if (P.uniqueItems) {
|
|
651
849
|
outer: for (let i = 0; i < data.length; i++) {
|
|
652
850
|
for (let j = i + 1; j < data.length; j++) {
|
|
653
851
|
if (deepEqual(data[i], data[j])) {
|
|
654
|
-
errors.push(err('uniqueItems', 'uniqueItems', instancePath, schemaPath + '/uniqueItems', { i, j }, 'must NOT have duplicate items'));
|
|
852
|
+
if (errors !== NOERRORS) errors.push(err('uniqueItems', 'uniqueItems', instancePath, schemaPath + '/uniqueItems', { i, j }, 'must NOT have duplicate items'));
|
|
655
853
|
valid = false;
|
|
656
854
|
break outer;
|
|
657
855
|
}
|
|
@@ -676,12 +874,12 @@ class Interpreter {
|
|
|
676
874
|
if (P.contains !== undefined) {
|
|
677
875
|
const matched = [];
|
|
678
876
|
for (let i = 0; i < data.length; i++) {
|
|
679
|
-
const scratch = [];
|
|
877
|
+
const scratch = errors === NOERRORS ? NOERRORS : [];
|
|
680
878
|
if (this.eval(P.contains, data[i], base, dynScope, scratch, instancePath + '/' + i, schemaPath + '/contains', stack, DISCARD)) matched.push(i);
|
|
681
879
|
}
|
|
682
880
|
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; }
|
|
881
|
+
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; }
|
|
882
|
+
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
883
|
if (collect && matched.length) {
|
|
686
884
|
if (!local.items) local.items = new Set();
|
|
687
885
|
for (const i of matched) local.items.add(i);
|
|
@@ -696,16 +894,16 @@ class Interpreter {
|
|
|
696
894
|
const req = P.required;
|
|
697
895
|
for (let i = 0; i < req.length; i++) {
|
|
698
896
|
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; }
|
|
897
|
+
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
898
|
}
|
|
701
899
|
}
|
|
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; }
|
|
900
|
+
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; }
|
|
901
|
+
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
902
|
if (P.dependentRequired !== null) {
|
|
705
903
|
for (const [key, deps] of P.dependentRequired) {
|
|
706
904
|
if (Object.hasOwn(data, key)) {
|
|
707
905
|
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; }
|
|
906
|
+
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
907
|
}
|
|
710
908
|
}
|
|
711
909
|
}
|
|
@@ -782,7 +980,7 @@ class Interpreter {
|
|
|
782
980
|
}
|
|
783
981
|
if (P.anyOf !== null) {
|
|
784
982
|
let any = false;
|
|
785
|
-
const scratch = [];
|
|
983
|
+
const scratch = errors === NOERRORS ? NOERRORS : [];
|
|
786
984
|
for (let i = 0; i < P.anyOf.length; i++) {
|
|
787
985
|
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
788
986
|
if (this.eval(P.anyOf[i], data, base, dynScope, scratch, instancePath, schemaPath + '/anyOf/' + i, stack, sub)) {
|
|
@@ -791,15 +989,15 @@ class Interpreter {
|
|
|
791
989
|
}
|
|
792
990
|
}
|
|
793
991
|
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'));
|
|
992
|
+
if (errors !== NOERRORS) for (const e of scratch) errors.push(e);
|
|
993
|
+
if (errors !== NOERRORS) errors.push(err('anyOf', 'anyOf', instancePath, schemaPath + '/anyOf', {}, 'must match a schema in anyOf'));
|
|
796
994
|
valid = false;
|
|
797
995
|
}
|
|
798
996
|
}
|
|
799
997
|
if (P.oneOf !== null) {
|
|
800
998
|
let count = 0;
|
|
801
999
|
let winner = null;
|
|
802
|
-
const scratch = [];
|
|
1000
|
+
const scratch = errors === NOERRORS ? NOERRORS : [];
|
|
803
1001
|
for (let i = 0; i < P.oneOf.length; i++) {
|
|
804
1002
|
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
805
1003
|
if (this.eval(P.oneOf[i], data, base, dynScope, scratch, instancePath, schemaPath + '/oneOf/' + i, stack, sub)) {
|
|
@@ -810,20 +1008,20 @@ class Interpreter {
|
|
|
810
1008
|
if (count === 1) { if (collect) mergeAnnotations(local, winner); }
|
|
811
1009
|
else {
|
|
812
1010
|
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'));
|
|
1011
|
+
if (errors !== NOERRORS) errors.push(err('oneOf', 'oneOf', instancePath, schemaPath + '/oneOf', { passingSchemas: count }, 'must match exactly one schema in oneOf'));
|
|
814
1012
|
valid = false;
|
|
815
1013
|
}
|
|
816
1014
|
}
|
|
817
1015
|
if (P.not !== undefined) {
|
|
818
|
-
const scratch = [];
|
|
1016
|
+
const scratch = errors === NOERRORS ? NOERRORS : [];
|
|
819
1017
|
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'));
|
|
1018
|
+
if (errors !== NOERRORS) errors.push(err('not', 'not', instancePath, schemaPath + '/not', {}, 'must NOT be valid'));
|
|
821
1019
|
valid = false;
|
|
822
1020
|
}
|
|
823
1021
|
}
|
|
824
1022
|
if (P.if !== undefined) {
|
|
825
1023
|
const ifSub = collect ? { props: null, items: null } : DISCARD;
|
|
826
|
-
const ifScratch = [];
|
|
1024
|
+
const ifScratch = errors === NOERRORS ? NOERRORS : [];
|
|
827
1025
|
const ifOk = this.eval(P.if, data, base, dynScope, ifScratch, instancePath, schemaPath + '/if', stack, ifSub);
|
|
828
1026
|
if (ifOk) {
|
|
829
1027
|
if (collect) mergeAnnotations(local, ifSub);
|
|
@@ -860,6 +1058,7 @@ class Interpreter {
|
|
|
860
1058
|
}
|
|
861
1059
|
}
|
|
862
1060
|
|
|
1061
|
+
if (scopePushed) dynScope.pop();
|
|
863
1062
|
if (tracked) stack.length -= 2;
|
|
864
1063
|
if (valid && collect && sink !== DISCARD) mergeAnnotations(sink, local);
|
|
865
1064
|
return valid;
|
|
@@ -880,6 +1079,11 @@ function err(code, keyword, instancePath, schemaPath, params, message) {
|
|
|
880
1079
|
return { keyword, instancePath, schemaPath, params, message };
|
|
881
1080
|
}
|
|
882
1081
|
|
|
1082
|
+
const { compileInterpreter } = require('./plan-compiler').install({
|
|
1083
|
+
Plan, NOERRORS, err, evalLeaf, evalLeafV, deepEqual, dataBits, escapePointer,
|
|
1084
|
+
T_STRING, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri,
|
|
1085
|
+
});
|
|
1086
|
+
|
|
883
1087
|
function createInterpreter(schema, options) {
|
|
884
1088
|
return new Interpreter(schema, options);
|
|
885
1089
|
}
|
package/lib/js-compiler.js
CHANGED
|
@@ -53,7 +53,8 @@ function buildErrorLiteral (opts) {
|
|
|
53
53
|
function _cpLen(s) {
|
|
54
54
|
const len = s.length;
|
|
55
55
|
for (let i = 0; i < len; i++) {
|
|
56
|
-
|
|
56
|
+
// Wraparound test: one compare classifies a high surrogate.
|
|
57
|
+
if (((s.charCodeAt(i) - 0xD800) >>> 0) < 0x400) {
|
|
57
58
|
// Found a high surrogate — count code points the slow way
|
|
58
59
|
let n = 0; for (const _ of s) n++; return n;
|
|
59
60
|
}
|
|
@@ -354,7 +355,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
354
355
|
}
|
|
355
356
|
if (schema.maxLength !== undefined) {
|
|
356
357
|
const max = schema.maxLength
|
|
357
|
-
checks.push((d) => typeof d !== 'string' || d.length <= max || _cpLen(d) <= max)
|
|
358
|
+
checks.push((d) => typeof d !== 'string' || d.length <= max || (d.length <= 2 * max + 1 && _cpLen(d) <= max))
|
|
358
359
|
}
|
|
359
360
|
if (schema.pattern) {
|
|
360
361
|
try {
|