ata-validator 1.43.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/index.js +54 -0
- package/lib/clone-emit.js +5 -2
- package/lib/data-position-cache.js +12 -6
- package/lib/draft7.js +5 -7
- package/lib/formats.js +8 -2
- package/lib/interpreter.js +44 -51
- package/lib/js-compiler.js +197 -76
- package/lib/levenshtein.js +34 -22
- package/lib/native-positions.js +100 -0
- package/lib/plan-compiler.js +23 -25
- package/lib/rejections.js +14 -7
- package/lib/suggestions.js +27 -20
- package/lib/unique-items.js +139 -0
- package/lib/validator-core.js +71 -10
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -244,21 +244,21 @@ is the most common way to get a misleading number out of this library.
|
|
|
244
244
|
|
|
245
245
|
| | compiled with `ata build` | runtime `new Validator(schema)` |
|
|
246
246
|
|---|---|---|
|
|
247
|
-
| In a bundle, gzipped | **2.
|
|
248
|
-
| Time to a served request | **3.5 ms** |
|
|
247
|
+
| In a bundle, gzipped | **2.1 KB** | 112.8 KB |
|
|
248
|
+
| Time to a served request | **3.5 ms** | 8.6 ms |
|
|
249
249
|
| Schema known when | build time | any time |
|
|
250
250
|
|
|
251
251
|
The bundle row is the ten-field user schema in
|
|
252
252
|
`tests/fixtures/error-dx/user.schema.json`, every export of the compiled module against
|
|
253
|
-
`new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.
|
|
254
|
-
The startup row is a Hono route on Bun 1.4, median of
|
|
253
|
+
`new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.44.0.
|
|
254
|
+
The startup row is a Hono route on Bun 1.4, the median of three rounds of best-of-seven, from
|
|
255
255
|
`benchmark/bundle`, against 3.5 ms for the same app doing no validation at all, so the
|
|
256
256
|
compiled path costs nothing measurable to start. The runtime figure is what it is because
|
|
257
257
|
a schema that arrives at run time can use any keyword, so the whole engine has to be
|
|
258
258
|
there. The compiled module imports nothing and contains only the checks your schema asks
|
|
259
259
|
for.
|
|
260
260
|
|
|
261
|
-
**On a server, use whichever fits your schemas.**
|
|
261
|
+
**On a server, use whichever fits your schemas.** 113 KB of JavaScript on a Node or Bun
|
|
262
262
|
process is not a cost anyone notices, and the runtime API is the simpler thing to reach
|
|
263
263
|
for. Speed is the same either way once warm.
|
|
264
264
|
|
|
@@ -491,7 +491,7 @@ const v = new Validator(schema, {
|
|
|
491
491
|
|
|
492
492
|
### Build-time compile (`ata compile`)
|
|
493
493
|
|
|
494
|
-
The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. For the ten-field user schema in `tests/fixtures/error-dx/user.schema.json` the module is 2.
|
|
494
|
+
The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. For the ten-field user schema in `tests/fixtures/error-dx/user.schema.json` the module is 2.1 KB gzipped, full error detail included, against 112.8 KB for the runtime bundled for the browser (ata 1.44.0).
|
|
495
495
|
|
|
496
496
|
```bash
|
|
497
497
|
npx ata compile schemas/user.json -o src/generated/user.validator.mjs
|
|
@@ -533,13 +533,13 @@ npx ata build 'schemas/*.json' --out-dir build/validators --check
|
|
|
533
533
|
Run with `--watch` during development for incremental rebuilds.
|
|
534
534
|
|
|
535
535
|
Bundle sizes for the 10-field user schema in `tests/fixtures/error-dx/user.schema.json`,
|
|
536
|
-
minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.
|
|
536
|
+
minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.44.0:
|
|
537
537
|
|
|
538
538
|
| What the app imports | Size | Notes |
|
|
539
539
|
|---|---|---|
|
|
540
|
-
| `Validator` from `ata-validator` |
|
|
540
|
+
| `Validator` from `ata-validator` | 112.8 KB | The compiler ships with it, because a runtime schema can use any keyword |
|
|
541
541
|
| `isValid` from the compiled module | **1.3 KB** | Nothing else is reachable, so the error collector is dropped |
|
|
542
|
-
| `validate` from the compiled module | **
|
|
542
|
+
| `validate` from the compiled module | **1.9 KB** | Adds the detailed error collector |
|
|
543
543
|
|
|
544
544
|
`--abort-early` makes the generated source about three times smaller, and after
|
|
545
545
|
bundling it makes no difference: importing only `isValid` already leaves the error
|
|
@@ -575,7 +575,7 @@ through a `setFormats()` export the module carries. `docs/API.md` has the
|
|
|
575
575
|
details.
|
|
576
576
|
|
|
577
577
|
**Fastify startup, 10 route schemas, from a cold process to the first validated request:
|
|
578
|
-
ajv 19.
|
|
578
|
+
ajv 19.4 ms, ata 2.7 ms, no build step required.** ata registers in 0.2 ms of that and
|
|
579
579
|
compiles on the first request, so counting only registration would overstate the gap.
|
|
580
580
|
Reproduce with `node benchmark/bench_fastify_boot.mjs`.
|
|
581
581
|
|
package/index.js
CHANGED
|
@@ -434,6 +434,9 @@ function installCodegenPaths (ctx) {
|
|
|
434
434
|
}
|
|
435
435
|
return nativeText ? getNative().rawFastValidate(this._fastSlot, Buffer.from(jsonStr)) : undefined;
|
|
436
436
|
};
|
|
437
|
+
// The parsed document is left on `_lastParsed` for the rich layer around
|
|
438
|
+
// validateJSON, which hands it to the rejection so reading the errors does
|
|
439
|
+
// not parse the text again (see LazyJsonRejection).
|
|
437
440
|
const validateText = (jsonStr) => {
|
|
438
441
|
let obj;
|
|
439
442
|
try {
|
|
@@ -442,6 +445,7 @@ function installCodegenPaths (ctx) {
|
|
|
442
445
|
if (!(e instanceof SyntaxError)) throw e;
|
|
443
446
|
return _jsonSyntaxRejection(e);
|
|
444
447
|
}
|
|
448
|
+
this._lastParsed = obj;
|
|
445
449
|
return jsonValidateFn(obj);
|
|
446
450
|
};
|
|
447
451
|
this.validateJSON = useSimdjsonForLarge && !preprocess
|
|
@@ -878,8 +882,58 @@ function buildParse (self, decline, extended) {
|
|
|
878
882
|
// preprocess pass, the JSON-text scanner, the parse() copy and the
|
|
879
883
|
// ahead-of-time bundle methods. parse() and the bundle methods load on first
|
|
880
884
|
// use.
|
|
885
|
+
// Cold start. A validator's first COLD_CALLS calls are answered by an
|
|
886
|
+
// interpreted twin, built from the same schema and options with
|
|
887
|
+
// `engine: 'interpreter'`, which the test suite holds to the same answers and
|
|
888
|
+
// errors as the generated code. Generating code and having V8 compile it is
|
|
889
|
+
// the largest part of a first call on a large schema: from process start to
|
|
890
|
+
// one rejection with its error read, SchemaStore's SARIF schema took 87 ms
|
|
891
|
+
// with code generated at the first call and 31 ms with the twin answering.
|
|
892
|
+
// A validator still in use after that compiles as before, so a server reaches
|
|
893
|
+
// the generated code's speed within its first requests, and a command line
|
|
894
|
+
// tool or a short-lived function never pays to compile. Only where compiling
|
|
895
|
+
// costs enough to matter: a schema whose size with its references followed
|
|
896
|
+
// passes COLD_MIN characters (expandedChars in lib/js-compiler.js). A small
|
|
897
|
+
// schema compiles in about a millisecond and is generated at its first call
|
|
898
|
+
// as before, which also keeps every test that compares the generated code
|
|
899
|
+
// with the interpreter comparing the generated code. Not for a validator with
|
|
900
|
+
// an extension (_extendValidate, _extendChecks), which the twin would not
|
|
901
|
+
// carry. Returns the twin to answer this call, or null to compile.
|
|
902
|
+
const COLD_CALLS = 64;
|
|
903
|
+
const COLD_MIN = 16 * 1024;
|
|
904
|
+
// Whether the schema may expand past `limit`, answered from its text where
|
|
905
|
+
// that settles it. Without a `$ref` nothing expands, and expandedChars counts
|
|
906
|
+
// at most 8 for each character of the text, so a short schema is decided
|
|
907
|
+
// without walking it. The text is the one _ensureCompiled needs anyway. Walking
|
|
908
|
+
// every small route schema here cost a Fastify boot of ten routes 0.15 ms.
|
|
909
|
+
function mayExpandPast (self, limit) {
|
|
910
|
+
const str = self._schemaStr || (self._schemaStr = JSON.stringify(self._schemaObj));
|
|
911
|
+
if (str === undefined) return false;
|
|
912
|
+
if (str.length * 8 <= limit && !str.includes('"$ref"')) return false;
|
|
913
|
+
return jsCompiler.expandedChars(self._schemaObj, limit) > limit;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function coldTwin (self, Validator) {
|
|
917
|
+
const n = self._coldCalls === undefined ? 0 : self._coldCalls;
|
|
918
|
+
if (n >= COLD_CALLS || self._validateTail !== null || self._verdictTail !== null) return null;
|
|
919
|
+
let t = self._twin;
|
|
920
|
+
if (t === undefined) {
|
|
921
|
+
t = null;
|
|
922
|
+
if (!self._interpretOnly && !self._initialized && mayExpandPast(self, COLD_MIN)) {
|
|
923
|
+
try { t = new Validator(self._rawSchema, Object.assign({}, self._options, { engine: 'interpreter' })); } catch { t = null; }
|
|
924
|
+
}
|
|
925
|
+
self._twin = t;
|
|
926
|
+
}
|
|
927
|
+
if (t === null) return null;
|
|
928
|
+
self._coldCalls = n + 1;
|
|
929
|
+
return t;
|
|
930
|
+
}
|
|
931
|
+
|
|
881
932
|
core._registerCodegen({
|
|
882
933
|
jsCompiler,
|
|
934
|
+
coldTwin,
|
|
935
|
+
// The native locator for error frames, loaded with the first frame.
|
|
936
|
+
nativePositions: () => require('./lib/native-positions').nativeTargeted,
|
|
883
937
|
installPaths: installCodegenPaths,
|
|
884
938
|
compileVerdict: installCodegenPaths.compileVerdict,
|
|
885
939
|
installScanner: installCodegenPaths.installScanner,
|
package/lib/clone-emit.js
CHANGED
|
@@ -78,8 +78,11 @@ function inlineRefsForClone(root) {
|
|
|
78
78
|
for (const k of Object.keys(cur)) {
|
|
79
79
|
if (strip && k === 'default') continue;
|
|
80
80
|
const v = cur[k];
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// A property named "__proto__" is an own key of the copy: assigned, it
|
|
82
|
+
// set the copy's prototype and the property was gone from parse().
|
|
83
|
+
const w = (_INLINE_DATA_KEYS.has(k) || k === '$defs' || k === 'definitions') ? v : walk(v, active, strip);
|
|
84
|
+
if (k === '__proto__') Object.defineProperty(out, k, { value: w, writable: true, enumerable: true, configurable: true });
|
|
85
|
+
else out[k] = w;
|
|
83
86
|
}
|
|
84
87
|
if (siblingDefault !== undefined) out.default = siblingDefault;
|
|
85
88
|
return out;
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
const { buildDataPositionMap, buildTargetedPositionMap } = require('./data-positions');
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
7
|
* Memoize the position map for the duration of a single validate() call.
|
|
7
8
|
* Caller passes the original buffer/string. Identity-keyed: same reference
|
|
8
9
|
* == same map. No global state, caller holds the cache instance.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
// `nativeTargeted`, when given, is a faster source of the same entries: the
|
|
13
|
+
// full entry point passes lib/native-positions.js, lite passes nothing.
|
|
14
|
+
function createCache (nativeTargeted) {
|
|
15
|
+
const _nativeTargeted = typeof nativeTargeted === 'function' ? nativeTargeted : null;
|
|
12
16
|
const wm = new WeakMap();
|
|
13
17
|
const sm = new Map(); // strings can't go in WeakMap; clear after each validate
|
|
14
18
|
let lastInput = null;
|
|
@@ -32,11 +36,13 @@ function createCache () {
|
|
|
32
36
|
? sm.get(input)
|
|
33
37
|
: (Buffer.isBuffer(input) ? wm.get(input) : undefined);
|
|
34
38
|
if (already) return already;
|
|
35
|
-
let filtered;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
let filtered = _nativeTargeted === null ? null : _nativeTargeted(input, wanted);
|
|
40
|
+
if (filtered === null) {
|
|
41
|
+
try {
|
|
42
|
+
filtered = buildTargetedPositionMap(input, wanted);
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
47
|
let full;
|
|
42
48
|
const fallback = (key) => {
|
package/lib/draft7.js
CHANGED
|
@@ -49,13 +49,11 @@ function _normalize(schema) {
|
|
|
49
49
|
// dependencies → dependentSchemas + dependentRequired
|
|
50
50
|
if (schema.dependencies) {
|
|
51
51
|
for (const [key, value] of Object.entries(schema.dependencies)) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
schema.dependentSchemas[key] = value
|
|
58
|
-
}
|
|
52
|
+
// defineProperty: a dependency on a property named "__proto__" is an
|
|
53
|
+
// own key here, where assignment would set the map's prototype.
|
|
54
|
+
const target = Array.isArray(value) ? 'dependentRequired' : 'dependentSchemas'
|
|
55
|
+
if (!schema[target]) schema[target] = {}
|
|
56
|
+
Object.defineProperty(schema[target], key, { value, writable: true, enumerable: true, configurable: true })
|
|
59
57
|
}
|
|
60
58
|
delete schema.dependencies
|
|
61
59
|
}
|
package/lib/formats.js
CHANGED
|
@@ -382,12 +382,18 @@ function uriChar (c) {
|
|
|
382
382
|
function isHex (c) {
|
|
383
383
|
return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70);
|
|
384
384
|
}
|
|
385
|
+
// The same set through the URI_CHAR and HEX_CHAR tables: one load per
|
|
386
|
+
// character, where uriChar() was a call and up to eleven comparisons, and on
|
|
387
|
+
// SchemaStore's SARIF samples, full of URIs, that loop was a sixth of what the
|
|
388
|
+
// interpreted engine spent. A "%" at the end reads past the string, NaN,
|
|
389
|
+
// which fails the bound as it failed isHex().
|
|
385
390
|
function uriChars (s, from) {
|
|
386
391
|
for (let i = from; i < s.length; i++) {
|
|
387
392
|
const c = s.charCodeAt(i);
|
|
388
|
-
if (
|
|
393
|
+
if (c > 127 || URI_CHAR[c] === 0) return false;
|
|
389
394
|
if (c === 37) { // '%'
|
|
390
|
-
|
|
395
|
+
const h1 = s.charCodeAt(i + 1), h2 = s.charCodeAt(i + 2);
|
|
396
|
+
if (!(h1 <= 127) || !(h2 <= 127) || HEX_CHAR[h1] === 0 || HEX_CHAR[h2] === 0) return false;
|
|
391
397
|
i += 2;
|
|
392
398
|
}
|
|
393
399
|
}
|
package/lib/interpreter.js
CHANGED
|
@@ -183,26 +183,22 @@ function resolveRef(ref, fromBase, state) {
|
|
|
183
183
|
|
|
184
184
|
// ---------- value helpers ----------
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
const ak = Object.keys(a);
|
|
198
|
-
const bk = Object.keys(b);
|
|
199
|
-
if (ak.length !== bk.length) return false;
|
|
200
|
-
for (const k of ak) {
|
|
201
|
-
if (!(k in b) || !deepEqual(a[k], b[k])) return false;
|
|
202
|
-
}
|
|
203
|
-
return true;
|
|
186
|
+
// uniqueItems: the functions the code generator emits as _uq and _uqp
|
|
187
|
+
// (lib/unique-items.js), so both engines run the same code.
|
|
188
|
+
const { allDistinct, duplicatePair, _deq } = require('./unique-items');
|
|
189
|
+
|
|
190
|
+
// Whether `data` deep-equals one of the node's enum values (see enumPrims).
|
|
191
|
+
function enumMember(P, data) {
|
|
192
|
+
if (data === null || typeof data !== 'object') return P.enumPrims.has(data);
|
|
193
|
+
const objs = P.enumObjs;
|
|
194
|
+
for (let i = 0; i < objs.length; i++) if (deepEqual(objs[i], data)) return true;
|
|
195
|
+
return false;
|
|
204
196
|
}
|
|
205
197
|
|
|
198
|
+
// JSON equality: the same function the code generator emits as `_deq`, so the
|
|
199
|
+
// engines agree on what equal means (an object's own keys, in any order).
|
|
200
|
+
const deepEqual = _deq;
|
|
201
|
+
|
|
206
202
|
function codePointLength(s) {
|
|
207
203
|
let n = 0;
|
|
208
204
|
for (let i = 0; i < s.length; i++) {
|
|
@@ -375,6 +371,21 @@ class Plan {
|
|
|
375
371
|
}
|
|
376
372
|
this.hasType = schema.type !== undefined;
|
|
377
373
|
this.enum = schema.enum !== undefined ? schema.enum : null;
|
|
374
|
+
// Membership for enumMember(): the primitive values in a Set, so a long
|
|
375
|
+
// enum answers in one lookup, and the object and array values, which only
|
|
376
|
+
// deepEqual can compare, in a list. Scanning every value with deepEqual
|
|
377
|
+
// was a fifth to a third of what validating SchemaStore's samples cost.
|
|
378
|
+
// NaN stays out of the Set: deepEqual never matches it, and a Set would.
|
|
379
|
+
this.enumPrims = null;
|
|
380
|
+
this.enumObjs = null;
|
|
381
|
+
if (Array.isArray(this.enum)) {
|
|
382
|
+
this.enumPrims = new Set();
|
|
383
|
+
this.enumObjs = [];
|
|
384
|
+
for (const v of this.enum) {
|
|
385
|
+
if (v !== null && typeof v === 'object') this.enumObjs.push(v);
|
|
386
|
+
else if (!(typeof v === 'number' && v !== v)) this.enumPrims.add(v);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
378
389
|
this.hasConst = schema.const !== undefined;
|
|
379
390
|
this.const = schema.const;
|
|
380
391
|
|
|
@@ -524,9 +535,7 @@ function evalLeafV(P, data) {
|
|
|
524
535
|
const bits = dataBits(data);
|
|
525
536
|
if (P.hasType && (bits & P.typeMask) === 0) return false;
|
|
526
537
|
if (P.enum !== null) {
|
|
527
|
-
|
|
528
|
-
for (let i = 0; i < P.enum.length; i++) { if (deepEqual(P.enum[i], data)) { found = true; break; } }
|
|
529
|
-
if (!found) return false;
|
|
538
|
+
if (!enumMember(P, data)) return false;
|
|
530
539
|
}
|
|
531
540
|
if (P.hasConst && !deepEqual(P.const, data)) return false;
|
|
532
541
|
if (P.hasNumber && Number.isFinite(data)) {
|
|
@@ -545,13 +554,7 @@ function evalLeafV(P, data) {
|
|
|
545
554
|
if (P.hasArray && bits === T_ARRAY) {
|
|
546
555
|
if (P.minItems !== undefined && data.length < P.minItems) return false;
|
|
547
556
|
if (P.maxItems !== undefined && data.length > P.maxItems) return false;
|
|
548
|
-
if (P.uniqueItems)
|
|
549
|
-
for (let i = 0; i < data.length; i++) {
|
|
550
|
-
for (let j = i + 1; j < data.length; j++) {
|
|
551
|
-
if (deepEqual(data[i], data[j])) return false;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
557
|
+
if (P.uniqueItems && !allDistinct(data)) return false;
|
|
555
558
|
}
|
|
556
559
|
if (P.hasObject && bits === T_OBJECT) {
|
|
557
560
|
if (P.required !== null) {
|
|
@@ -635,9 +638,7 @@ function evalLeaf(P, data, errors, instancePath, schemaPath) {
|
|
|
635
638
|
valid = false;
|
|
636
639
|
}
|
|
637
640
|
if (P.enum !== null) {
|
|
638
|
-
|
|
639
|
-
for (let i = 0; i < P.enum.length; i++) { if (deepEqual(P.enum[i], data)) { found = true; break; } }
|
|
640
|
-
if (!found) {
|
|
641
|
+
if (!enumMember(P, data)) {
|
|
641
642
|
if (errors !== NOERRORS) errors.push(err('enum', 'enum', instancePath, schemaPath + '/enum', { allowedValues: P.enum }, 'must be equal to one of the allowed values'));
|
|
642
643
|
valid = false;
|
|
643
644
|
}
|
|
@@ -663,14 +664,11 @@ function evalLeaf(P, data, errors, instancePath, schemaPath) {
|
|
|
663
664
|
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; }
|
|
664
665
|
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; }
|
|
665
666
|
if (P.uniqueItems) {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
break outer;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
667
|
+
const pair = duplicatePair(data);
|
|
668
|
+
if (pair !== null) {
|
|
669
|
+
const i = pair[0], j = pair[1];
|
|
670
|
+
if (errors !== NOERRORS) errors.push(err('uniqueItems', 'uniqueItems', instancePath, schemaPath + '/uniqueItems', { i, j }, `must NOT have duplicate items (items ## ${j} and ${i} are identical)`));
|
|
671
|
+
valid = false;
|
|
674
672
|
}
|
|
675
673
|
}
|
|
676
674
|
}
|
|
@@ -924,9 +922,7 @@ class Interpreter {
|
|
|
924
922
|
valid = false;
|
|
925
923
|
}
|
|
926
924
|
if (P.enum !== null) {
|
|
927
|
-
|
|
928
|
-
for (let i = 0; i < P.enum.length; i++) { if (deepEqual(P.enum[i], data)) { found = true; break; } }
|
|
929
|
-
if (!found) {
|
|
925
|
+
if (!enumMember(P, data)) {
|
|
930
926
|
if (errors !== NOERRORS) errors.push(err('enum', 'enum', instancePath, schemaPath + '/enum', { allowedValues: P.enum }, 'must be equal to one of the allowed values'));
|
|
931
927
|
valid = false;
|
|
932
928
|
}
|
|
@@ -958,14 +954,11 @@ class Interpreter {
|
|
|
958
954
|
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; }
|
|
959
955
|
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; }
|
|
960
956
|
if (P.uniqueItems) {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
break outer;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
957
|
+
const pair = duplicatePair(data);
|
|
958
|
+
if (pair !== null) {
|
|
959
|
+
const i = pair[0], j = pair[1];
|
|
960
|
+
if (errors !== NOERRORS) errors.push(err('uniqueItems', 'uniqueItems', instancePath, schemaPath + '/uniqueItems', { i, j }, `must NOT have duplicate items (items ## ${j} and ${i} are identical)`));
|
|
961
|
+
valid = false;
|
|
969
962
|
}
|
|
970
963
|
}
|
|
971
964
|
const prefix = P.prefixItems;
|
|
@@ -1246,7 +1239,7 @@ function err(code, keyword, instancePath, schemaPath, params, message) {
|
|
|
1246
1239
|
|
|
1247
1240
|
const { compileInterpreter } = require('./plan-compiler').install({
|
|
1248
1241
|
Plan, NOERRORS, err, evalLeaf, evalLeafV, runCustomV, dataBits, escapePointer,
|
|
1249
|
-
deepEqual, multipleOfOk, cpAtLeast, cpAtMost,
|
|
1242
|
+
deepEqual, enumMember, allDistinct, multipleOfOk, cpAtLeast, cpAtMost,
|
|
1250
1243
|
T_STRING, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri,
|
|
1251
1244
|
});
|
|
1252
1245
|
|