ata-validator 1.6.1 → 1.7.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/CHANGELOG.md +35 -0
- package/README.md +32 -14
- package/build.d.ts +12 -0
- package/index.d.ts +16 -4
- package/index.js +58 -13
- package/lib/aot-build.js +1 -0
- package/lib/aot.js +69 -19
- package/lib/buffer-gate.js +132 -0
- package/lib/draft7.js +24 -2
- package/lib/enrich-error.js +14 -4
- package/lib/error-codes.js +16 -9
- package/lib/interpreter.js +432 -200
- package/lib/js-compiler.js +240 -62
- package/lib/metaschemas.js +21 -0
- package/lib/safe-regex-source.js +1 -1
- package/lib/safe-regex.js +209 -29
- package/lib/version.js +1 -1
- package/package.json +8 -8
- package/t.d.ts +16 -5
package/lib/interpreter.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
// schemas the JS codegen cannot represent when the native engine is absent
|
|
5
5
|
// (browser, edge workers, ATA_NO_NATIVE). It walks the schema directly with
|
|
6
6
|
// full annotation tracking (unevaluatedProperties/Items), $id/$anchor
|
|
7
|
-
// resolution, and $dynamicRef dynamic-scope semantics.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// resolution, and $dynamicRef dynamic-scope semantics. Each schema node is
|
|
8
|
+
// resolved once into a fixed-shape Plan (type bitmask, compiled pattern,
|
|
9
|
+
// looked-up format, a flag per keyword group) so the walk itself reads no
|
|
10
|
+
// schema properties; codegen and the native engine remain the fast paths.
|
|
10
11
|
|
|
11
12
|
const { compileSafe } = require('./safe-regex');
|
|
12
13
|
|
|
@@ -135,6 +136,10 @@ function walkPointer(root, pointer) {
|
|
|
135
136
|
node = node.$defs;
|
|
136
137
|
} else if (part === '$defs' && node.definitions) {
|
|
137
138
|
node = node.definitions;
|
|
139
|
+
} else if (part === 'items' && Array.isArray(node.prefixItems)) {
|
|
140
|
+
// draft-07 array-form items is normalized to prefixItems; pointers
|
|
141
|
+
// written against the original document still land.
|
|
142
|
+
node = node.prefixItems;
|
|
138
143
|
} else {
|
|
139
144
|
return undefined;
|
|
140
145
|
}
|
|
@@ -246,7 +251,10 @@ const FORMAT_CHECKS = {
|
|
|
246
251
|
|
|
247
252
|
// ---------- evaluation ----------
|
|
248
253
|
|
|
249
|
-
|
|
254
|
+
// Sink for subschema results whose annotations the caller never reads
|
|
255
|
+
// (properties, items, propertyNames and friends). eval() skips the merge when
|
|
256
|
+
// it sees this object, so it is never mutated.
|
|
257
|
+
const DISCARD = { props: null, items: null };
|
|
250
258
|
|
|
251
259
|
function mergeAnnotations(target, from) {
|
|
252
260
|
if (from.props && from.props.size) {
|
|
@@ -259,6 +267,167 @@ function mergeAnnotations(target, from) {
|
|
|
259
267
|
}
|
|
260
268
|
}
|
|
261
269
|
|
|
270
|
+
// ---------- plans ----------
|
|
271
|
+
|
|
272
|
+
// Type bits, one per JSON Schema type. `integer` data also sets the `number`
|
|
273
|
+
// bit so a `number` constraint accepts it.
|
|
274
|
+
const T_STRING = 1;
|
|
275
|
+
const T_NUMBER = 2;
|
|
276
|
+
const T_INTEGER = 4;
|
|
277
|
+
const T_BOOLEAN = 8;
|
|
278
|
+
const T_NULL = 16;
|
|
279
|
+
const T_OBJECT = 32;
|
|
280
|
+
const T_ARRAY = 64;
|
|
281
|
+
const T_ANY = 127;
|
|
282
|
+
|
|
283
|
+
function typeBit(name) {
|
|
284
|
+
switch (name) {
|
|
285
|
+
case 'string': return T_STRING;
|
|
286
|
+
case 'number': return T_NUMBER;
|
|
287
|
+
case 'integer': return T_INTEGER;
|
|
288
|
+
case 'boolean': return T_BOOLEAN;
|
|
289
|
+
case 'null': return T_NULL;
|
|
290
|
+
case 'object': return T_OBJECT;
|
|
291
|
+
case 'array': return T_ARRAY;
|
|
292
|
+
default: return T_ANY; // unknown type names match everything
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function dataBits(d) {
|
|
297
|
+
switch (typeof d) {
|
|
298
|
+
case 'string': return T_STRING;
|
|
299
|
+
case 'number':
|
|
300
|
+
if (!isFinite(d)) return 0;
|
|
301
|
+
return Number.isInteger(d) ? T_NUMBER | T_INTEGER : T_NUMBER;
|
|
302
|
+
case 'boolean': return T_BOOLEAN;
|
|
303
|
+
case 'object':
|
|
304
|
+
if (d === null) return T_NULL;
|
|
305
|
+
return Array.isArray(d) ? T_ARRAY : T_OBJECT;
|
|
306
|
+
default: return 0;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function isObjectMap(v) {
|
|
311
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// A plan is the schema node with every keyword resolved once: the type list
|
|
315
|
+
// folded to a bitmask, the format looked up, patterns compiled, and a flag
|
|
316
|
+
// per section so eval() can skip whole blocks without touching the schema
|
|
317
|
+
// object. Plans have a fixed shape, which keeps eval() monomorphic where the
|
|
318
|
+
// raw schema objects are not.
|
|
319
|
+
class Plan {
|
|
320
|
+
// The plan is registered in the cache before `link` runs, so a schema
|
|
321
|
+
// object that contains itself structurally resolves to the same plan
|
|
322
|
+
// instead of recursing without end.
|
|
323
|
+
constructor(schema) {
|
|
324
|
+
this.schema = schema;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
link(interp) {
|
|
328
|
+
const schema = this.schema;
|
|
329
|
+
// Children are linked as plans (or booleans) up front, so a visit never
|
|
330
|
+
// goes back through the plan cache. Reference targets are resolved at
|
|
331
|
+
// evaluation time and cached per base URI.
|
|
332
|
+
const child = (sub) => interp.node(sub);
|
|
333
|
+
this.refCache = null;
|
|
334
|
+
this.dynCache = null;
|
|
335
|
+
this.ref = typeof schema.$ref === 'string' ? schema.$ref : null;
|
|
336
|
+
this.dynamicRef = typeof schema.$dynamicRef === 'string' ? schema.$dynamicRef : null;
|
|
337
|
+
this.tracked = this.ref !== null || this.dynamicRef !== null;
|
|
338
|
+
|
|
339
|
+
this.typeMask = 0;
|
|
340
|
+
this.typeNames = null;
|
|
341
|
+
if (schema.type !== undefined) {
|
|
342
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
343
|
+
let mask = 0;
|
|
344
|
+
for (const t of types) mask |= typeBit(t);
|
|
345
|
+
this.typeMask = mask;
|
|
346
|
+
this.typeNames = types;
|
|
347
|
+
}
|
|
348
|
+
this.hasType = schema.type !== undefined;
|
|
349
|
+
this.enum = schema.enum !== undefined ? schema.enum : null;
|
|
350
|
+
this.hasConst = schema.const !== undefined;
|
|
351
|
+
this.const = schema.const;
|
|
352
|
+
|
|
353
|
+
this.minimum = typeof schema.minimum === 'number' ? schema.minimum : undefined;
|
|
354
|
+
this.maximum = typeof schema.maximum === 'number' ? schema.maximum : undefined;
|
|
355
|
+
this.exclusiveMinimum = typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum : undefined;
|
|
356
|
+
this.exclusiveMaximum = typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum : undefined;
|
|
357
|
+
this.multipleOf = typeof schema.multipleOf === 'number' ? schema.multipleOf : undefined;
|
|
358
|
+
this.hasNumber = this.minimum !== undefined || this.maximum !== undefined || this.exclusiveMinimum !== undefined || this.exclusiveMaximum !== undefined || this.multipleOf !== undefined;
|
|
359
|
+
|
|
360
|
+
this.minLength = schema.minLength;
|
|
361
|
+
this.maxLength = schema.maxLength;
|
|
362
|
+
this.pattern = schema.pattern !== undefined ? interp.pattern(schema.pattern) : null;
|
|
363
|
+
this.patternSource = schema.pattern;
|
|
364
|
+
this.format = schema.format;
|
|
365
|
+
this.formatFn = null;
|
|
366
|
+
if (schema.format !== undefined) {
|
|
367
|
+
const uf = interp.userFormats;
|
|
368
|
+
const fc = (uf && typeof uf[schema.format] === 'function') ? uf[schema.format] : FORMAT_CHECKS[schema.format];
|
|
369
|
+
this.formatFn = fc || null;
|
|
370
|
+
}
|
|
371
|
+
this.hasString = this.minLength !== undefined || this.maxLength !== undefined || this.pattern !== null || this.formatFn !== null;
|
|
372
|
+
|
|
373
|
+
this.minItems = schema.minItems;
|
|
374
|
+
this.maxItems = schema.maxItems;
|
|
375
|
+
this.uniqueItems = schema.uniqueItems === true;
|
|
376
|
+
this.prefixItems = Array.isArray(schema.prefixItems) ? schema.prefixItems.map(child) : null;
|
|
377
|
+
this.items = schema.items !== undefined ? child(schema.items) : undefined;
|
|
378
|
+
this.contains = schema.contains !== undefined ? child(schema.contains) : undefined;
|
|
379
|
+
this.minContains = schema.minContains;
|
|
380
|
+
this.maxContains = schema.maxContains;
|
|
381
|
+
this.hasArray = this.minItems !== undefined || this.maxItems !== undefined || this.uniqueItems || this.prefixItems !== null || this.items !== undefined || this.contains !== undefined;
|
|
382
|
+
|
|
383
|
+
this.required = Array.isArray(schema.required) ? schema.required : null;
|
|
384
|
+
this.minProperties = schema.minProperties;
|
|
385
|
+
this.maxProperties = schema.maxProperties;
|
|
386
|
+
this.dependentRequired = isObjectMap(schema.dependentRequired) ? Object.entries(schema.dependentRequired) : null;
|
|
387
|
+
this.propertyNames = schema.propertyNames !== undefined ? child(schema.propertyNames) : undefined;
|
|
388
|
+
this.properties = null;
|
|
389
|
+
if (isObjectMap(schema.properties)) {
|
|
390
|
+
this.properties = new Map();
|
|
391
|
+
for (const key of Object.keys(schema.properties)) {
|
|
392
|
+
const ek = escapePointer(key);
|
|
393
|
+
// Escaped once here; the walk only concatenates.
|
|
394
|
+
this.properties.set(key, { node: child(schema.properties[key]), seg: '/' + ek, schemaSeg: '/properties/' + ek });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
this.patternProperties = null;
|
|
398
|
+
if (isObjectMap(schema.patternProperties)) {
|
|
399
|
+
this.patternProperties = Object.keys(schema.patternProperties).map((src) => ({ src, re: interp.pattern(src), node: child(schema.patternProperties[src]) }));
|
|
400
|
+
}
|
|
401
|
+
this.additionalProperties = schema.additionalProperties !== undefined ? child(schema.additionalProperties) : undefined;
|
|
402
|
+
this.dependentSchemas = isObjectMap(schema.dependentSchemas) ? Object.entries(schema.dependentSchemas).map(([k, v]) => [k, child(v)]) : null;
|
|
403
|
+
this.propertyDependencies = null;
|
|
404
|
+
if (isObjectMap(schema.propertyDependencies)) {
|
|
405
|
+
this.propertyDependencies = Object.entries(schema.propertyDependencies)
|
|
406
|
+
.filter(([, choices]) => isObjectMap(choices))
|
|
407
|
+
.map(([k, choices]) => [k, new Map(Object.keys(choices).map((v) => [v, child(choices[v])]))]);
|
|
408
|
+
}
|
|
409
|
+
this.hasObject = this.required !== null || this.minProperties !== undefined || this.maxProperties !== undefined || this.dependentRequired !== null || this.propertyNames !== undefined || this.properties !== null || this.patternProperties !== null || this.additionalProperties !== undefined || this.dependentSchemas !== null || this.propertyDependencies !== null;
|
|
410
|
+
|
|
411
|
+
this.allOf = Array.isArray(schema.allOf) ? schema.allOf.map(child) : null;
|
|
412
|
+
this.anyOf = Array.isArray(schema.anyOf) ? schema.anyOf.map(child) : null;
|
|
413
|
+
this.oneOf = Array.isArray(schema.oneOf) ? schema.oneOf.map(child) : null;
|
|
414
|
+
this.not = schema.not !== undefined ? child(schema.not) : undefined;
|
|
415
|
+
this.if = schema.if !== undefined ? child(schema.if) : undefined;
|
|
416
|
+
this.then = schema.then !== undefined ? child(schema.then) : undefined;
|
|
417
|
+
this.else = schema.else !== undefined ? child(schema.else) : undefined;
|
|
418
|
+
this.hasApplicators = this.allOf !== null || this.anyOf !== null || this.oneOf !== null || this.not !== undefined || this.if !== undefined;
|
|
419
|
+
|
|
420
|
+
this.unevaluatedProperties = schema.unevaluatedProperties !== undefined ? child(schema.unevaluatedProperties) : undefined;
|
|
421
|
+
this.unevaluatedItems = schema.unevaluatedItems !== undefined ? child(schema.unevaluatedItems) : undefined;
|
|
422
|
+
this.hasUnevaluated = this.unevaluatedProperties !== undefined || this.unevaluatedItems !== undefined;
|
|
423
|
+
|
|
424
|
+
// Resource bookkeeping, read once instead of two Map lookups per visit.
|
|
425
|
+
const nodeBase = interp.state.nodeBase.get(schema);
|
|
426
|
+
this.nodeBase = nodeBase !== undefined ? nodeBase : null;
|
|
427
|
+
this.isResourceRoot = nodeBase !== undefined && interp.state.resources.get(nodeBase) === schema;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
262
431
|
class Interpreter {
|
|
263
432
|
constructor(rootSchema, options) {
|
|
264
433
|
const opts = options || {};
|
|
@@ -269,6 +438,9 @@ class Interpreter {
|
|
|
269
438
|
// requirement. See the `$dynamicRef` branch in eval().
|
|
270
439
|
this.bookending = !opts.v1;
|
|
271
440
|
this.patternCache = new Map();
|
|
441
|
+
// Plans are built on first visit; a schema node is never re-read after.
|
|
442
|
+
this.plans = new Map();
|
|
443
|
+
this.rootNode = this.node(rootSchema);
|
|
272
444
|
}
|
|
273
445
|
|
|
274
446
|
pattern(src) {
|
|
@@ -290,57 +462,118 @@ class Interpreter {
|
|
|
290
462
|
return re;
|
|
291
463
|
}
|
|
292
464
|
|
|
465
|
+
plan(schema) {
|
|
466
|
+
let p = this.plans.get(schema);
|
|
467
|
+
if (p === undefined) {
|
|
468
|
+
p = new Plan(schema);
|
|
469
|
+
this.plans.set(schema, p);
|
|
470
|
+
p.link(this);
|
|
471
|
+
}
|
|
472
|
+
return p;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// $ref resolution depends on the base URI in effect, which can differ
|
|
476
|
+
// between visits of the same node; cache per (plan, base).
|
|
477
|
+
resolveRefCached(P, base) {
|
|
478
|
+
let cache = P.refCache;
|
|
479
|
+
if (cache === null) cache = P.refCache = new Map();
|
|
480
|
+
let r = cache.get(base);
|
|
481
|
+
if (r === undefined) {
|
|
482
|
+
r = resolveRef(P.ref, base, this.state);
|
|
483
|
+
cache.set(base, r);
|
|
484
|
+
}
|
|
485
|
+
return r;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// The static half of $dynamicRef (initial target and fragment) per base;
|
|
489
|
+
// the dynamic-scope search still runs on every visit.
|
|
490
|
+
resolveDynamicRefCached(P, base) {
|
|
491
|
+
let cache = P.dynCache;
|
|
492
|
+
if (cache === null) cache = P.dynCache = new Map();
|
|
493
|
+
let r = cache.get(base);
|
|
494
|
+
if (r === undefined) {
|
|
495
|
+
const { node, base: refBase } = resolveRef(P.dynamicRef, base, this.state);
|
|
496
|
+
const [, fragment] = splitFragment(resolveUri(base, P.dynamicRef));
|
|
497
|
+
r = { node, base: refBase, fragment, byAnchor: Boolean(fragment) && !fragment.startsWith('/') };
|
|
498
|
+
cache.set(base, r);
|
|
499
|
+
}
|
|
500
|
+
return r;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Maps any schema value to what eval() accepts: a boolean or a Plan.
|
|
504
|
+
// Non-object, non-boolean values validate everything, as before.
|
|
505
|
+
node(schema) {
|
|
506
|
+
if (schema === true || schema === false) return schema;
|
|
507
|
+
if (typeof schema !== 'object' || schema === null) return true;
|
|
508
|
+
return this.plan(schema);
|
|
509
|
+
}
|
|
510
|
+
|
|
293
511
|
validate(data) {
|
|
294
512
|
const errors = [];
|
|
295
513
|
const dynScope = [this.state.rootBase];
|
|
296
|
-
|
|
514
|
+
// Nothing reads the root's annotations, so they are discarded from the start.
|
|
515
|
+
const valid = this.eval(this.rootNode, data, this.state.rootBase, dynScope, errors, '', '#', [], DISCARD);
|
|
297
516
|
return valid ? { valid: true, data, errors: [] } : { valid: false, errors };
|
|
298
517
|
}
|
|
299
518
|
|
|
300
519
|
// Evaluates `schema` against `data`. Collected annotations for data are
|
|
301
520
|
// written into `sink` ({props, items}); errors append to `errors`.
|
|
302
521
|
// `stack` breaks infinite $ref recursion on identical (schema, data) pairs.
|
|
303
|
-
eval(
|
|
304
|
-
if (
|
|
305
|
-
if (
|
|
522
|
+
eval(P, data, base, dynScope, errors, instancePath, schemaPath, stack, sink) {
|
|
523
|
+
if (P === true) return true;
|
|
524
|
+
if (P === false) {
|
|
306
525
|
errors.push(err('false schema', 'not', instancePath, schemaPath, {}, 'boolean schema is false'));
|
|
307
526
|
return false;
|
|
308
527
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
528
|
+
const schema = P.schema;
|
|
529
|
+
|
|
530
|
+
// Only a reference can lead evaluation back to a schema it is already
|
|
531
|
+
// inside of, so the cycle guard is maintained on reference nodes alone.
|
|
532
|
+
// The frame is pushed in place and popped on the way out; the array is
|
|
533
|
+
// created per validate() call, so a throw leaves nothing to clean up.
|
|
534
|
+
const tracked = P.tracked;
|
|
535
|
+
if (tracked) {
|
|
536
|
+
for (let i = stack.length - 2; i >= 0; i -= 2) {
|
|
537
|
+
if (stack[i] === schema && stack[i + 1] === data) return true; // fixed point
|
|
538
|
+
}
|
|
539
|
+
stack.push(schema, data);
|
|
313
540
|
}
|
|
314
|
-
stack = stack.concat([[schema, data]]);
|
|
315
541
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
//
|
|
319
|
-
|
|
542
|
+
if (P.nodeBase !== null && P.nodeBase !== base) base = P.nodeBase;
|
|
543
|
+
// Entering a schema resource extends the dynamic scope, whether evaluation
|
|
544
|
+
// lands on the resource's root or on a subschema inside it: a reference
|
|
545
|
+
// to first#/$defs/stuff still passes through resource `first`.
|
|
546
|
+
if (dynScope[dynScope.length - 1] !== base) {
|
|
320
547
|
dynScope = dynScope.concat(base);
|
|
321
548
|
}
|
|
322
549
|
|
|
323
550
|
let valid = true;
|
|
324
|
-
|
|
551
|
+
// Annotations only matter if this node or an ancestor reads them. When
|
|
552
|
+
// nobody does, in-place children get the discard sink too and `local`
|
|
553
|
+
// stays unallocated.
|
|
554
|
+
const collect = sink !== DISCARD || P.hasUnevaluated;
|
|
555
|
+
const local = collect ? { props: null, items: null } : DISCARD;
|
|
325
556
|
|
|
326
557
|
// $ref / $dynamicRef: in-place applicators, annotations flow through
|
|
327
|
-
if (
|
|
328
|
-
const { node, base: refBase } =
|
|
558
|
+
if (P.ref !== null) {
|
|
559
|
+
const { node, base: refBase } = this.resolveRefCached(P, base);
|
|
329
560
|
if (node === undefined) {
|
|
330
|
-
errors.push(err('$ref', '$ref', instancePath, schemaPath + '/$ref', { ref:
|
|
561
|
+
errors.push(err('$ref', '$ref', instancePath, schemaPath + '/$ref', { ref: P.ref }, `cannot resolve $ref ${P.ref}`));
|
|
331
562
|
valid = false;
|
|
332
563
|
} else {
|
|
333
|
-
const sub = { props: null, items: null };
|
|
334
|
-
if (!this.eval(node, data, refBase, dynScope, errors, instancePath, schemaPath + '/$ref', stack, sub)) valid = false;
|
|
335
|
-
else mergeAnnotations(local, sub);
|
|
564
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
565
|
+
if (!this.eval(this.node(node), data, refBase, dynScope, errors, instancePath, schemaPath + '/$ref', stack, sub)) valid = false;
|
|
566
|
+
else if (collect) mergeAnnotations(local, sub);
|
|
336
567
|
}
|
|
337
568
|
}
|
|
338
569
|
|
|
339
|
-
if (
|
|
340
|
-
const ref =
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
570
|
+
if (P.dynamicRef !== null) {
|
|
571
|
+
const ref = P.dynamicRef;
|
|
572
|
+
const initial = this.resolveDynamicRefCached(P, base);
|
|
573
|
+
let node = initial.node;
|
|
574
|
+
let refBase = initial.base;
|
|
575
|
+
const fragment = initial.fragment;
|
|
576
|
+
if (initial.byAnchor) {
|
|
344
577
|
// Under 2020-12 the initial target must itself carry the matching
|
|
345
578
|
// $dynamicAnchor before the dynamic scope is consulted (bookending);
|
|
346
579
|
// otherwise the keyword behaves like $ref. v1 removes that
|
|
@@ -367,58 +600,54 @@ class Interpreter {
|
|
|
367
600
|
errors.push(err('$dynamicRef', '$dynamicRef', instancePath, schemaPath + '/$dynamicRef', { ref }, `cannot resolve $dynamicRef ${ref}`));
|
|
368
601
|
valid = false;
|
|
369
602
|
} else {
|
|
370
|
-
const sub = { props: null, items: null };
|
|
371
|
-
if (!this.eval(node, data, refBase, dynScope, errors, instancePath, schemaPath + '/$dynamicRef', stack, sub)) valid = false;
|
|
372
|
-
else mergeAnnotations(local, sub);
|
|
603
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
604
|
+
if (!this.eval(this.node(node), data, refBase, dynScope, errors, instancePath, schemaPath + '/$dynamicRef', stack, sub)) valid = false;
|
|
605
|
+
else if (collect) mergeAnnotations(local, sub);
|
|
373
606
|
}
|
|
374
607
|
}
|
|
375
608
|
|
|
609
|
+
const bits = dataBits(data);
|
|
610
|
+
|
|
376
611
|
// type / enum / const
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
errors.push(err('type', 'type', instancePath, schemaPath + '/type', { type: schema.type }, `must be ${types.join(' or ')}`));
|
|
381
|
-
valid = false;
|
|
382
|
-
}
|
|
612
|
+
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 ')}`));
|
|
614
|
+
valid = false;
|
|
383
615
|
}
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
-
|
|
616
|
+
if (P.enum !== null) {
|
|
617
|
+
let found = false;
|
|
618
|
+
for (let i = 0; i < P.enum.length; i++) { if (deepEqual(P.enum[i], data)) { found = true; break; } }
|
|
619
|
+
if (!found) {
|
|
620
|
+
errors.push(err('enum', 'enum', instancePath, schemaPath + '/enum', { allowedValues: P.enum }, 'must be equal to one of the allowed values'));
|
|
387
621
|
valid = false;
|
|
388
622
|
}
|
|
389
623
|
}
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
valid = false;
|
|
394
|
-
}
|
|
624
|
+
if (P.hasConst && !deepEqual(P.const, data)) {
|
|
625
|
+
errors.push(err('const', 'const', instancePath, schemaPath + '/const', { allowedValue: P.const }, 'must be equal to constant'));
|
|
626
|
+
valid = false;
|
|
395
627
|
}
|
|
396
628
|
|
|
397
629
|
// numbers
|
|
398
|
-
if (typeof data === 'number') {
|
|
399
|
-
if (
|
|
400
|
-
if (
|
|
401
|
-
if (
|
|
402
|
-
if (
|
|
403
|
-
if (
|
|
630
|
+
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; }
|
|
404
636
|
}
|
|
405
637
|
|
|
406
638
|
// strings
|
|
407
|
-
if (
|
|
408
|
-
if (
|
|
409
|
-
if (
|
|
410
|
-
if (
|
|
411
|
-
if (
|
|
412
|
-
const fc = (this.userFormats && typeof this.userFormats[schema.format] === 'function') ? this.userFormats[schema.format] : FORMAT_CHECKS[schema.format];
|
|
413
|
-
if (fc && !fc(data)) { errors.push(err('format', 'format', instancePath, schemaPath + '/format', { format: schema.format }, `must match format "${schema.format}"`)); valid = false; }
|
|
414
|
-
}
|
|
639
|
+
if (P.hasString && bits === T_STRING) {
|
|
640
|
+
if (P.minLength !== undefined && codePointLength(data) < P.minLength) { errors.push(err('minLength', 'minLength', instancePath, schemaPath + '/minLength', { limit: P.minLength }, `must NOT have fewer than ${P.minLength} characters`)); valid = false; }
|
|
641
|
+
if (P.maxLength !== undefined && codePointLength(data) > P.maxLength) { errors.push(err('maxLength', 'maxLength', instancePath, schemaPath + '/maxLength', { limit: P.maxLength }, `must NOT have more than ${P.maxLength} characters`)); valid = false; }
|
|
642
|
+
if (P.pattern !== null && !P.pattern.test(data)) { errors.push(err('pattern', 'pattern', instancePath, schemaPath + '/pattern', { pattern: P.patternSource }, `must match pattern "${P.patternSource}"`)); valid = false; }
|
|
643
|
+
if (P.formatFn !== null && !P.formatFn(data)) { errors.push(err('format', 'format', instancePath, schemaPath + '/format', { format: P.format }, `must match format "${P.format}"`)); valid = false; }
|
|
415
644
|
}
|
|
416
645
|
|
|
417
646
|
// arrays
|
|
418
|
-
if (
|
|
419
|
-
if (
|
|
420
|
-
if (
|
|
421
|
-
if (
|
|
647
|
+
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; }
|
|
650
|
+
if (P.uniqueItems) {
|
|
422
651
|
outer: for (let i = 0; i < data.length; i++) {
|
|
423
652
|
for (let j = i + 1; j < data.length; j++) {
|
|
424
653
|
if (deepEqual(data[i], data[j])) {
|
|
@@ -429,35 +658,31 @@ class Interpreter {
|
|
|
429
658
|
}
|
|
430
659
|
}
|
|
431
660
|
}
|
|
432
|
-
const prefix =
|
|
433
|
-
if (prefix) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (!this.eval(prefix[i], data[i], base, dynScope, errors, instancePath + '/' + i, schemaPath + '/prefixItems/' + i, stack,
|
|
437
|
-
if (!local.items) local.items = new Set();
|
|
438
|
-
local.items.add(i);
|
|
661
|
+
const prefix = P.prefixItems;
|
|
662
|
+
if (prefix !== null) {
|
|
663
|
+
const n = Math.min(prefix.length, data.length);
|
|
664
|
+
for (let i = 0; i < n; i++) {
|
|
665
|
+
if (!this.eval(prefix[i], data[i], base, dynScope, errors, instancePath + '/' + i, schemaPath + '/prefixItems/' + i, stack, DISCARD)) valid = false;
|
|
666
|
+
if (collect) { if (!local.items) local.items = new Set(); local.items.add(i); }
|
|
439
667
|
}
|
|
440
668
|
}
|
|
441
|
-
if (
|
|
442
|
-
const start = prefix ? prefix.length : 0;
|
|
669
|
+
if (P.items !== undefined) {
|
|
670
|
+
const start = prefix !== null ? prefix.length : 0;
|
|
443
671
|
for (let i = start; i < data.length; i++) {
|
|
444
|
-
|
|
445
|
-
if (!
|
|
446
|
-
if (!local.items) local.items = new Set();
|
|
447
|
-
local.items.add(i);
|
|
672
|
+
if (!this.eval(P.items, data[i], base, dynScope, errors, instancePath + '/' + i, schemaPath + '/items', stack, DISCARD)) valid = false;
|
|
673
|
+
if (collect) { if (!local.items) local.items = new Set(); local.items.add(i); }
|
|
448
674
|
}
|
|
449
675
|
}
|
|
450
|
-
if (
|
|
676
|
+
if (P.contains !== undefined) {
|
|
451
677
|
const matched = [];
|
|
452
678
|
for (let i = 0; i < data.length; i++) {
|
|
453
679
|
const scratch = [];
|
|
454
|
-
|
|
455
|
-
if (this.eval(schema.contains, data[i], base, dynScope, scratch, instancePath + '/' + i, schemaPath + '/contains', stack, sub)) matched.push(i);
|
|
680
|
+
if (this.eval(P.contains, data[i], base, dynScope, scratch, instancePath + '/' + i, schemaPath + '/contains', stack, DISCARD)) matched.push(i);
|
|
456
681
|
}
|
|
457
|
-
const minC =
|
|
682
|
+
const minC = P.minContains !== undefined ? P.minContains : 1;
|
|
458
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; }
|
|
459
|
-
if (
|
|
460
|
-
if (matched.length) {
|
|
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; }
|
|
685
|
+
if (collect && matched.length) {
|
|
461
686
|
if (!local.items) local.items = new Set();
|
|
462
687
|
for (const i of matched) local.items.add(i);
|
|
463
688
|
}
|
|
@@ -465,63 +690,65 @@ class Interpreter {
|
|
|
465
690
|
}
|
|
466
691
|
|
|
467
692
|
// objects
|
|
468
|
-
if (
|
|
693
|
+
if (P.hasObject && bits === T_OBJECT) {
|
|
469
694
|
const keys = Object.keys(data);
|
|
470
|
-
if (
|
|
471
|
-
|
|
472
|
-
|
|
695
|
+
if (P.required !== null) {
|
|
696
|
+
const req = P.required;
|
|
697
|
+
for (let i = 0; i < req.length; i++) {
|
|
698
|
+
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; }
|
|
473
700
|
}
|
|
474
701
|
}
|
|
475
|
-
if (
|
|
476
|
-
if (
|
|
477
|
-
if (
|
|
478
|
-
for (const [key, deps] of
|
|
479
|
-
if (Object.
|
|
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; }
|
|
704
|
+
if (P.dependentRequired !== null) {
|
|
705
|
+
for (const [key, deps] of P.dependentRequired) {
|
|
706
|
+
if (Object.hasOwn(data, key)) {
|
|
480
707
|
for (const dep of deps) {
|
|
481
|
-
if (!Object.
|
|
708
|
+
if (!Object.hasOwn(data, dep)) { errors.push(err('required', 'required', instancePath, schemaPath + '/dependentRequired', { missingProperty: dep }, `must have required property '${dep}'`)); valid = false; }
|
|
482
709
|
}
|
|
483
710
|
}
|
|
484
711
|
}
|
|
485
712
|
}
|
|
486
|
-
if (
|
|
713
|
+
if (P.propertyNames !== undefined) {
|
|
487
714
|
for (const key of keys) {
|
|
488
|
-
if (!this.eval(
|
|
715
|
+
if (!this.eval(P.propertyNames, key, base, dynScope, errors, instancePath + '/' + escapePointer(key), schemaPath + '/propertyNames', stack, DISCARD)) valid = false;
|
|
489
716
|
}
|
|
490
717
|
}
|
|
491
|
-
const props =
|
|
492
|
-
const patterns =
|
|
493
|
-
for (
|
|
718
|
+
const props = P.properties;
|
|
719
|
+
const patterns = P.patternProperties;
|
|
720
|
+
for (let k = 0; k < keys.length; k++) {
|
|
721
|
+
const key = keys[k];
|
|
494
722
|
let evaluated = false;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (!this.eval(
|
|
723
|
+
const prop = props !== null ? props.get(key) : undefined;
|
|
724
|
+
if (prop !== undefined) {
|
|
725
|
+
if (!this.eval(prop.node, data[key], base, dynScope, errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack, DISCARD)) valid = false;
|
|
498
726
|
evaluated = true;
|
|
499
727
|
}
|
|
500
|
-
if (patterns) {
|
|
501
|
-
for (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
if (!this.eval(
|
|
728
|
+
if (patterns !== null) {
|
|
729
|
+
for (let pi = 0; pi < patterns.length; pi++) {
|
|
730
|
+
const pp = patterns[pi];
|
|
731
|
+
if (pp.re.test(key)) {
|
|
732
|
+
if (!this.eval(pp.node, data[key], base, dynScope, errors, instancePath + '/' + escapePointer(key), schemaPath + '/patternProperties/' + escapePointer(pp.src), stack, DISCARD)) valid = false;
|
|
505
733
|
evaluated = true;
|
|
506
734
|
}
|
|
507
735
|
}
|
|
508
736
|
}
|
|
509
|
-
if (!evaluated &&
|
|
510
|
-
|
|
511
|
-
if (!this.eval(schema.additionalProperties, data[key], base, dynScope, errors, instancePath + '/' + escapePointer(key), schemaPath + '/additionalProperties', stack, sub)) valid = false;
|
|
737
|
+
if (!evaluated && P.additionalProperties !== undefined) {
|
|
738
|
+
if (!this.eval(P.additionalProperties, data[key], base, dynScope, errors, instancePath + '/' + escapePointer(key), schemaPath + '/additionalProperties', stack, DISCARD)) valid = false;
|
|
512
739
|
evaluated = true;
|
|
513
740
|
}
|
|
514
|
-
if (evaluated) {
|
|
741
|
+
if (evaluated && collect) {
|
|
515
742
|
if (!local.props) local.props = new Set();
|
|
516
743
|
local.props.add(key);
|
|
517
744
|
}
|
|
518
745
|
}
|
|
519
|
-
if (
|
|
520
|
-
for (const [key, dep] of
|
|
521
|
-
if (Object.
|
|
522
|
-
const sub = { props: null, items: null };
|
|
746
|
+
if (P.dependentSchemas !== null) {
|
|
747
|
+
for (const [key, dep] of P.dependentSchemas) {
|
|
748
|
+
if (Object.hasOwn(data, key)) {
|
|
749
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
523
750
|
if (!this.eval(dep, data, base, dynScope, errors, instancePath, schemaPath + '/dependentSchemas/' + escapePointer(key), stack, sub)) valid = false;
|
|
524
|
-
else mergeAnnotations(local, sub);
|
|
751
|
+
else if (collect) mergeAnnotations(local, sub);
|
|
525
752
|
}
|
|
526
753
|
}
|
|
527
754
|
}
|
|
@@ -529,119 +756,124 @@ class Interpreter {
|
|
|
529
756
|
// value rather than its presence. Only string values can match, since the
|
|
530
757
|
// inner keys are object keys. Equivalent to
|
|
531
758
|
// `if: { properties: { p: { const: v } }, required: [p] }, then: <schema>`.
|
|
532
|
-
if (
|
|
533
|
-
for (const [key, choices] of
|
|
534
|
-
if (!
|
|
535
|
-
if (!Object.prototype.hasOwnProperty.call(data, key)) continue;
|
|
759
|
+
if (P.propertyDependencies !== null) {
|
|
760
|
+
for (const [key, choices] of P.propertyDependencies) {
|
|
761
|
+
if (!Object.hasOwn(data, key)) continue;
|
|
536
762
|
const value = data[key];
|
|
537
763
|
if (typeof value !== 'string') continue;
|
|
538
|
-
|
|
539
|
-
|
|
764
|
+
const choice = choices.get(value);
|
|
765
|
+
if (choice === undefined) continue;
|
|
766
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
540
767
|
const branchPath = schemaPath + '/propertyDependencies/' + escapePointer(key) + '/' + escapePointer(value);
|
|
541
|
-
if (!this.eval(
|
|
542
|
-
else mergeAnnotations(local, sub);
|
|
768
|
+
if (!this.eval(choice, data, base, dynScope, errors, instancePath, branchPath, stack, sub)) valid = false;
|
|
769
|
+
else if (collect) mergeAnnotations(local, sub);
|
|
543
770
|
}
|
|
544
771
|
}
|
|
545
772
|
}
|
|
546
773
|
|
|
547
774
|
// in-place applicators
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
if (Array.isArray(schema.anyOf)) {
|
|
556
|
-
let any = false;
|
|
557
|
-
const scratch = [];
|
|
558
|
-
for (let i = 0; i < schema.anyOf.length; i++) {
|
|
559
|
-
const sub = { props: null, items: null };
|
|
560
|
-
if (this.eval(schema.anyOf[i], data, base, dynScope, scratch, instancePath, schemaPath + '/anyOf/' + i, stack, sub)) {
|
|
561
|
-
any = true;
|
|
562
|
-
mergeAnnotations(local, sub);
|
|
775
|
+
if (P.hasApplicators) {
|
|
776
|
+
if (P.allOf !== null) {
|
|
777
|
+
for (let i = 0; i < P.allOf.length; i++) {
|
|
778
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
779
|
+
if (!this.eval(P.allOf[i], data, base, dynScope, errors, instancePath, schemaPath + '/allOf/' + i, stack, sub)) valid = false;
|
|
780
|
+
else if (collect) mergeAnnotations(local, sub);
|
|
563
781
|
}
|
|
564
782
|
}
|
|
565
|
-
if (
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
winner = sub;
|
|
783
|
+
if (P.anyOf !== null) {
|
|
784
|
+
let any = false;
|
|
785
|
+
const scratch = [];
|
|
786
|
+
for (let i = 0; i < P.anyOf.length; i++) {
|
|
787
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
788
|
+
if (this.eval(P.anyOf[i], data, base, dynScope, scratch, instancePath, schemaPath + '/anyOf/' + i, stack, sub)) {
|
|
789
|
+
any = true;
|
|
790
|
+
if (collect) mergeAnnotations(local, sub);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
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'));
|
|
796
|
+
valid = false;
|
|
580
797
|
}
|
|
581
798
|
}
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
799
|
+
if (P.oneOf !== null) {
|
|
800
|
+
let count = 0;
|
|
801
|
+
let winner = null;
|
|
802
|
+
const scratch = [];
|
|
803
|
+
for (let i = 0; i < P.oneOf.length; i++) {
|
|
804
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
805
|
+
if (this.eval(P.oneOf[i], data, base, dynScope, scratch, instancePath, schemaPath + '/oneOf/' + i, stack, sub)) {
|
|
806
|
+
count++;
|
|
807
|
+
winner = sub;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (count === 1) { if (collect) mergeAnnotations(local, winner); }
|
|
811
|
+
else {
|
|
812
|
+
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'));
|
|
814
|
+
valid = false;
|
|
815
|
+
}
|
|
587
816
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
817
|
+
if (P.not !== undefined) {
|
|
818
|
+
const scratch = [];
|
|
819
|
+
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'));
|
|
821
|
+
valid = false;
|
|
822
|
+
}
|
|
594
823
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
824
|
+
if (P.if !== undefined) {
|
|
825
|
+
const ifSub = collect ? { props: null, items: null } : DISCARD;
|
|
826
|
+
const ifScratch = [];
|
|
827
|
+
const ifOk = this.eval(P.if, data, base, dynScope, ifScratch, instancePath, schemaPath + '/if', stack, ifSub);
|
|
828
|
+
if (ifOk) {
|
|
829
|
+
if (collect) mergeAnnotations(local, ifSub);
|
|
830
|
+
if (P.then !== undefined) {
|
|
831
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
832
|
+
if (!this.eval(P.then, data, base, dynScope, errors, instancePath, schemaPath + '/then', stack, sub)) valid = false;
|
|
833
|
+
else if (collect) mergeAnnotations(local, sub);
|
|
834
|
+
}
|
|
835
|
+
} else if (P.else !== undefined) {
|
|
836
|
+
const sub = collect ? { props: null, items: null } : DISCARD;
|
|
837
|
+
if (!this.eval(P.else, data, base, dynScope, errors, instancePath, schemaPath + '/else', stack, sub)) valid = false;
|
|
838
|
+
else if (collect) mergeAnnotations(local, sub);
|
|
606
839
|
}
|
|
607
|
-
} else if (schema.else !== undefined) {
|
|
608
|
-
const sub = { props: null, items: null };
|
|
609
|
-
if (!this.eval(schema.else, data, base, dynScope, errors, instancePath, schemaPath + '/else', stack, sub)) valid = false;
|
|
610
|
-
else mergeAnnotations(local, sub);
|
|
611
840
|
}
|
|
612
841
|
}
|
|
613
842
|
|
|
614
843
|
// unevaluated*: run last, against annotations from everything above
|
|
615
|
-
if (
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
844
|
+
if (P.hasUnevaluated) {
|
|
845
|
+
if (P.unevaluatedProperties !== undefined && bits === T_OBJECT) {
|
|
846
|
+
for (const key of Object.keys(data)) {
|
|
847
|
+
if (local.props && local.props.has(key)) continue;
|
|
848
|
+
if (!this.eval(P.unevaluatedProperties, data[key], base, dynScope, errors, instancePath + '/' + escapePointer(key), schemaPath + '/unevaluatedProperties', stack, DISCARD)) valid = false;
|
|
849
|
+
if (!local.props) local.props = new Set();
|
|
850
|
+
local.props.add(key);
|
|
851
|
+
}
|
|
622
852
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
local.items.add(i);
|
|
853
|
+
if (P.unevaluatedItems !== undefined && bits === T_ARRAY) {
|
|
854
|
+
for (let i = 0; i < data.length; i++) {
|
|
855
|
+
if (local.items && local.items.has(i)) continue;
|
|
856
|
+
if (!this.eval(P.unevaluatedItems, data[i], base, dynScope, errors, instancePath + '/' + i, schemaPath + '/unevaluatedItems', stack, DISCARD)) valid = false;
|
|
857
|
+
if (!local.items) local.items = new Set();
|
|
858
|
+
local.items.add(i);
|
|
859
|
+
}
|
|
631
860
|
}
|
|
632
861
|
}
|
|
633
862
|
|
|
634
|
-
if (
|
|
863
|
+
if (tracked) stack.length -= 2;
|
|
864
|
+
if (valid && collect && sink !== DISCARD) mergeAnnotations(sink, local);
|
|
635
865
|
return valid;
|
|
636
866
|
}
|
|
637
867
|
}
|
|
638
868
|
|
|
639
|
-
function NO_ANNOTATIONS_SINK() {
|
|
640
|
-
return { props: null, items: null };
|
|
641
|
-
}
|
|
642
|
-
|
|
643
869
|
function escapePointer(s) {
|
|
644
|
-
|
|
870
|
+
if (typeof s !== 'string') s = String(s);
|
|
871
|
+
// Keys almost never contain `~` or `/`; skip the two regex passes when so.
|
|
872
|
+
for (let i = 0; i < s.length; i++) {
|
|
873
|
+
const c = s.charCodeAt(i);
|
|
874
|
+
if (c === 0x7e || c === 0x2f) return s.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
875
|
+
}
|
|
876
|
+
return s;
|
|
645
877
|
}
|
|
646
878
|
|
|
647
879
|
function err(code, keyword, instancePath, schemaPath, params, message) {
|