ata-validator 1.7.0 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,969 @@
1
+ 'use strict';
2
+
3
+ // Compiles interpreter Plans into a tree of specialized closures. No source
4
+ // generation, no `new Function`: this is plain closure composition, so it
5
+ // runs wherever the interpreter runs, CSP included. The payoff over eval()
6
+ // is that every keyword branch is decided once at compile time and every
7
+ // child call is a direct monomorphic call, which is the same advantage the
8
+ // code generator has, minus the codegen.
9
+ //
10
+ // Everything eval() decides at run time from the schema path is decided
11
+ // here at compile time instead, because the schema path is static:
12
+ // - the base URI in effect, so `$ref` targets resolve once;
13
+ // - the dynamic scope, kept as the ordered list of resources entered so
14
+ // far, so `$dynamicRef` targets resolve once too. eval() pushes a
15
+ // resource every time the base changes, which can repeat entries; the
16
+ // outermost-first anchor search gives the same answer on the list with
17
+ // repeats removed, and that list is finite, so compilation terminates
18
+ // on cyclic schemas;
19
+ // - whether a node's annotations are read by anyone. A node under an
20
+ // in-place applicator of a collecting node collects; one under a child
21
+ // applicator (properties, items, not, ...) does not, exactly as eval()
22
+ // hands those the discard sink;
23
+ // - whether the schema can recurse at all. eval() guards every reference
24
+ // against revisiting a (schema, data) pair; that guard can only fire
25
+ // when the compiled graph has a cycle, which compilation notices, so an
26
+ // acyclic schema compiles without the guard and without the stack.
27
+ // A `$ref` or `$dynamicRef` that does not resolve compiles into the same
28
+ // runtime rejection eval() produces, so nothing declines. Error output must
29
+ // be byte-for-byte what eval() produces; tests/test_plan_compiler.js diffs
30
+ // the two over the official suite.
31
+ //
32
+ // Compiled pairs: `c(data, errors, instancePath, schemaPath, stack[, rec])`
33
+ // collects errors and returns the verdict; `v(data, stack[, rec])` returns
34
+ // the verdict alone, with no path strings and no error objects, and returns
35
+ // on the first failure. `errors` may be the NOERRORS sentinel, in which case
36
+ // the collect variant also stops at the first failure.
37
+ //
38
+ // Annotations live in a record {props, n, x}: the property names evaluated
39
+ // so far, the count of items evaluated contiguously from index 0, and any
40
+ // further item indexes (from `contains`). A node that reads annotations
41
+ // (unevaluated*) owns a fresh record per call; every in-place child writes
42
+ // straight into the record it is handed. eval() keeps a failed child's
43
+ // annotations out of its parent by merging only on success; here the
44
+ // caller notes the record's lengths before a child that may fail without
45
+ // failing the caller, and truncates back on failure. Same result, no
46
+ // allocation per child.
47
+
48
+ function install(deps) {
49
+ const { Plan, NOERRORS, err, evalLeaf, evalLeafV, dataBits, escapePointer,
50
+ deepEqual, multipleOfOk, cpAtLeast, cpAtMost,
51
+ T_STRING, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri } = deps;
52
+
53
+ const TRUE_FN = () => true;
54
+ const TRUE_PAIR = { v: TRUE_FN, c: TRUE_FN };
55
+ const FALSE_PAIR = {
56
+ v: () => false,
57
+ c: (data, errors, instancePath, schemaPath) => {
58
+ if (errors !== NOERRORS) errors.push(err('false schema', 'not', instancePath, schemaPath, {}, 'boolean schema is false'));
59
+ return false;
60
+ },
61
+ };
62
+
63
+ // Static $dynamicRef target, mirroring the `$dynamicRef` branch of eval()
64
+ // with the compile-time scope list in place of the runtime one.
65
+ function dynTarget(interp, ref, base, chain) {
66
+ const state = interp.state;
67
+ let { node, base: refBase } = resolveRef(ref, base, state);
68
+ const [, fragment] = splitFragment(resolveUri(base, ref));
69
+ if (fragment && !fragment.startsWith('/')) {
70
+ const initialDyn = state.dynamicAnchors.get(refBase);
71
+ const bookended = node !== undefined && initialDyn && initialDyn.get(fragment) === node;
72
+ if (bookended || !interp.bookending) {
73
+ for (let i = 0; i < chain.length; i++) {
74
+ const dyn = state.dynamicAnchors.get(chain[i]);
75
+ if (dyn && dyn.has(fragment)) { node = dyn.get(fragment); refBase = chain[i]; break; }
76
+ }
77
+ }
78
+ }
79
+ return { node, base: refBase };
80
+ }
81
+
82
+ // ---- annotation records ----
83
+ function fresh() { return { props: null, n: 0, x: null, keys: null }; }
84
+ // Object.keys once per record: every node that shares a record sees the
85
+ // same data object.
86
+ function keysOf(rec, data) {
87
+ if (rec.keys === null) rec.keys = Object.keys(data);
88
+ return rec.keys;
89
+ }
90
+ function hasProp(rec, key) {
91
+ const p = rec.props;
92
+ if (p === null) return false;
93
+ for (let i = 0; i < p.length; i++) if (p[i] === key) return true;
94
+ return false;
95
+ }
96
+ function addProp(rec, key) {
97
+ if (rec.props === null) rec.props = [key];
98
+ else rec.props.push(key);
99
+ }
100
+ function hasItem(rec, i) {
101
+ if (i < rec.n) return true;
102
+ const x = rec.x;
103
+ if (x === null) return false;
104
+ for (let j = 0; j < x.length; j++) if (x[j] === i) return true;
105
+ return false;
106
+ }
107
+ function addItem(rec, i) {
108
+ if (i < rec.n) return;
109
+ if (i === rec.n) { rec.n = i + 1; return; }
110
+ if (rec.x === null) rec.x = [i];
111
+ else rec.x.push(i);
112
+ }
113
+ function markRange(rec, from, to) {
114
+ if (from >= to) return;
115
+ if (from <= rec.n) { if (to > rec.n) rec.n = to; return; }
116
+ if (rec.x === null) rec.x = [];
117
+ for (let i = from; i < to; i++) rec.x.push(i);
118
+ }
119
+ function mergeRec(target, from) {
120
+ if (from.props !== null) {
121
+ if (target.props === null) target.props = from.props;
122
+ else for (let i = 0; i < from.props.length; i++) target.props.push(from.props[i]);
123
+ }
124
+ if (from.n > target.n) target.n = from.n;
125
+ if (from.x !== null) {
126
+ if (target.x === null) target.x = from.x;
127
+ else for (let i = 0; i < from.x.length; i++) target.x.push(from.x[i]);
128
+ }
129
+ }
130
+ // Rollback marks: the two array lengths and the contiguous count.
131
+ function pLen(rec) { return rec.props === null ? 0 : rec.props.length; }
132
+ function xLen(rec) { return rec.x === null ? 0 : rec.x.length; }
133
+ function undo(rec, pl, n0, xl) {
134
+ if (rec.props !== null) rec.props.length = pl;
135
+ rec.n = n0;
136
+ if (rec.x !== null) rec.x.length = xl;
137
+ }
138
+
139
+ // The value-level keywords of one node, compiled. evalLeafV() answers the
140
+ // same question by re-reading a dozen presence flags off the plan on every
141
+ // call, which is work the schema already decided: a plan's keywords never
142
+ // change. So each present keyword becomes one closure over its own operand
143
+ // and the node keeps only those, in the same order evalLeafV() runs them.
144
+ // Checks take (data, bits) because the type of the value is worth computing
145
+ // once per node rather than once per keyword; `needsBits` stays false when
146
+ // nothing in the node asks for it.
147
+ function compileLeafV(P) {
148
+ const checks = [];
149
+ let needsBits = false;
150
+
151
+ if (P.hasType) {
152
+ const mask = P.typeMask;
153
+ needsBits = true;
154
+ checks.push((data, bits) => (bits & mask) !== 0);
155
+ }
156
+ if (P.enum !== null) {
157
+ const vals = P.enum;
158
+ if (vals.length === 0) checks.push(() => false);
159
+ else checks.push((data) => {
160
+ for (let i = 0; i < vals.length; i++) if (deepEqual(vals[i], data)) return true;
161
+ return false;
162
+ });
163
+ }
164
+ if (P.hasConst) {
165
+ const c = P.const;
166
+ checks.push((data) => deepEqual(c, data));
167
+ }
168
+ if (P.hasNumber) {
169
+ const nums = [];
170
+ if (P.minimum !== undefined) { const m = P.minimum; nums.push((d) => d >= m); }
171
+ if (P.maximum !== undefined) { const m = P.maximum; nums.push((d) => d <= m); }
172
+ if (P.exclusiveMinimum !== undefined) { const m = P.exclusiveMinimum; nums.push((d) => d > m); }
173
+ if (P.exclusiveMaximum !== undefined) { const m = P.exclusiveMaximum; nums.push((d) => d < m); }
174
+ if (P.multipleOf !== undefined) { const m = P.multipleOf; nums.push((d) => multipleOfOk(d, m)); }
175
+ const run = combineValue(nums);
176
+ if (run !== null) checks.push((data) => typeof data !== 'number' || run(data));
177
+ }
178
+ if (P.hasString) {
179
+ const strs = [];
180
+ if (P.minLength !== undefined) { const m = P.minLength; strs.push((d) => cpAtLeast(d, m)); }
181
+ if (P.maxLength !== undefined) { const m = P.maxLength; strs.push((d) => cpAtMost(d, m)); }
182
+ if (P.pattern !== null) { const re = P.pattern; strs.push((d) => re.test(d)); }
183
+ if (P.formatFn !== null) { const f = P.formatFn; strs.push((d) => f(d)); }
184
+ const run = combineValue(strs);
185
+ if (run !== null) { needsBits = true; checks.push((data, bits) => bits !== T_STRING || run(data)); }
186
+ }
187
+ // `hasArray` also covers prefixItems/items/contains, which are not leaf
188
+ // keywords; only the three value-level ones belong here, exactly as
189
+ // evalLeafV() reads them.
190
+ if (P.minItems !== undefined || P.maxItems !== undefined || P.uniqueItems) {
191
+ const arrs = [];
192
+ if (P.minItems !== undefined) { const m = P.minItems; arrs.push((d) => d.length >= m); }
193
+ if (P.maxItems !== undefined) { const m = P.maxItems; arrs.push((d) => d.length <= m); }
194
+ if (P.uniqueItems) {
195
+ arrs.push((d) => {
196
+ for (let i = 0; i < d.length; i++) {
197
+ for (let j = i + 1; j < d.length; j++) if (deepEqual(d[i], d[j])) return false;
198
+ }
199
+ return true;
200
+ });
201
+ }
202
+ const run = combineValue(arrs);
203
+ if (run !== null) { needsBits = true; checks.push((data, bits) => bits !== T_ARRAY || run(data)); }
204
+ }
205
+ if (P.required !== null || P.minProperties !== undefined || P.maxProperties !== undefined || P.dependentRequired !== null) {
206
+ const objs = [];
207
+ if (P.required !== null) {
208
+ const req = P.required;
209
+ if (req.length === 1) { const k = req[0]; objs.push((d) => Object.hasOwn(d, k)); }
210
+ else objs.push((d) => {
211
+ for (let i = 0; i < req.length; i++) if (!Object.hasOwn(d, req[i])) return false;
212
+ return true;
213
+ });
214
+ }
215
+ if (P.minProperties !== undefined) { const m = P.minProperties; objs.push((d) => Object.keys(d).length >= m); }
216
+ if (P.maxProperties !== undefined) { const m = P.maxProperties; objs.push((d) => Object.keys(d).length <= m); }
217
+ if (P.dependentRequired !== null) {
218
+ const entries = P.dependentRequired;
219
+ objs.push((d) => {
220
+ for (let i = 0; i < entries.length; i++) {
221
+ const [key, deps] = entries[i];
222
+ if (!Object.hasOwn(d, key)) continue;
223
+ for (let j = 0; j < deps.length; j++) if (!Object.hasOwn(d, deps[j])) return false;
224
+ }
225
+ return true;
226
+ });
227
+ }
228
+ const run = combineValue(objs);
229
+ if (run !== null) { needsBits = true; checks.push((data, bits) => bits !== T_OBJECT || run(data)); }
230
+ }
231
+
232
+ if (checks.length === 0) return TRUE_FN;
233
+ if (!needsBits) {
234
+ if (checks.length === 1) { const a = checks[0]; return (data) => a(data, 0); }
235
+ return (data) => {
236
+ for (let i = 0; i < checks.length; i++) if (!checks[i](data, 0)) return false;
237
+ return true;
238
+ };
239
+ }
240
+ if (checks.length === 1) { const a = checks[0]; return (data) => a(data, dataBits(data)); }
241
+ if (checks.length === 2) {
242
+ const [a, b] = checks;
243
+ return (data) => { const bits = dataBits(data); return a(data, bits) && b(data, bits); };
244
+ }
245
+ return (data) => {
246
+ const bits = dataBits(data);
247
+ for (let i = 0; i < checks.length; i++) if (!checks[i](data, bits)) return false;
248
+ return true;
249
+ };
250
+ }
251
+
252
+ // Folds a group's checks into one call, or null when the group is empty.
253
+ function combineValue(fns) {
254
+ if (fns.length === 0) return null;
255
+ if (fns.length === 1) return fns[0];
256
+ if (fns.length === 2) { const [a, b] = fns; return (d) => a(d) && b(d); }
257
+ return (d) => {
258
+ for (let i = 0; i < fns.length; i++) if (!fns[i](d)) return false;
259
+ return true;
260
+ };
261
+ }
262
+
263
+ function compileNode(ctx, node, base, chain, ann) {
264
+ if (node === true) return TRUE_PAIR;
265
+ if (node === false) return FALSE_PAIR;
266
+ if (!(node instanceof Plan)) return TRUE_PAIR;
267
+ const interp = ctx.interp;
268
+ const P = node;
269
+ if (P.nodeBase !== null && P.nodeBase !== base) base = P.nodeBase;
270
+ if (chain.indexOf(base) === -1) chain = chain.concat(base);
271
+ const collect = ann || P.hasUnevaluated;
272
+
273
+ let perKey = ctx.memo.get(P);
274
+ if (perKey === undefined) { perKey = new Map(); ctx.memo.set(P, perKey); }
275
+ const key = (ann ? 'a ' : 'p ') + base + ' ' + chain.join(' ');
276
+ const cached = perKey.get(key);
277
+ if (cached !== undefined) {
278
+ if (cached.open) ctx.cyclic = true;
279
+ return cached;
280
+ }
281
+ // Cycles: register trampolines before compiling children.
282
+ const box = { v: null, c: null };
283
+ const pair = {
284
+ open: true,
285
+ v: (d, st, rec) => box.v(d, st, rec),
286
+ c: (d, e, ip, sp, st, rec) => box.c(d, e, ip, sp, st, rec),
287
+ };
288
+ perKey.set(key, pair);
289
+
290
+ // In-place children collect when this node does; child applicators
291
+ // never do. `rec` is threaded as the trailing argument of every step
292
+ // and is undefined on non-collecting nodes, where nothing reads it.
293
+ const inPlace = (n, b) => compileNode(ctx, n, b, chain, collect);
294
+ const child = (n) => compileNode(ctx, n, base, chain, false);
295
+
296
+ const steps = []; // collect variant: (data, errors, ip, sp, stack, rec)
297
+ const vsteps = []; // verdict variant: (data, stack, rec)
298
+
299
+ // $ref / $dynamicRef, resolved at compile time.
300
+ for (let which = 0; which < 2; which++) {
301
+ let t, seg;
302
+ if (which === 0) {
303
+ if (P.ref === null) continue;
304
+ t = resolveRef(P.ref, base, interp.state);
305
+ seg = '/$ref';
306
+ } else {
307
+ if (P.dynamicRef === null) continue;
308
+ t = dynTarget(interp, P.dynamicRef, base, chain);
309
+ seg = '/$dynamicRef';
310
+ }
311
+ if (t.node === undefined) {
312
+ // Reported when reached, exactly as eval() does; a metaschema-sized
313
+ // tree with one dangling reference in a branch the data never
314
+ // enters still compiles.
315
+ const ref = which === 0 ? P.ref : P.dynamicRef;
316
+ const kw = which === 0 ? '$ref' : '$dynamicRef';
317
+ steps.push((data, errors, instancePath, schemaPath) => {
318
+ if (errors !== NOERRORS) errors.push(err(kw, kw, instancePath, schemaPath + seg, { ref }, `cannot resolve ${kw} ${ref}`));
319
+ return false;
320
+ });
321
+ vsteps.push(() => false);
322
+ continue;
323
+ }
324
+ const target = inPlace(interp.node(t.node), t.base);
325
+ let cstep, vstep;
326
+ if (collect) {
327
+ cstep = (data, errors, instancePath, schemaPath, stack, rec) => {
328
+ const pl = pLen(rec), n0 = rec.n, xl = xLen(rec);
329
+ const ok = target.c(data, errors, instancePath, schemaPath + seg, stack, rec);
330
+ if (!ok) undo(rec, pl, n0, xl);
331
+ return ok;
332
+ };
333
+ vstep = (data, stack, rec) => target.v(data, stack, rec);
334
+ } else {
335
+ cstep = (data, errors, instancePath, schemaPath, stack) => target.c(data, errors, instancePath, schemaPath + seg, stack);
336
+ vstep = target.v;
337
+ }
338
+ if (ctx.guard) {
339
+ // The cycle guard mirrors eval(): a (schema, data) pair already on
340
+ // the stack is a fixed point.
341
+ const schema = P.schema;
342
+ const ic = cstep, iv = vstep;
343
+ cstep = (data, errors, instancePath, schemaPath, stack, rec) => {
344
+ for (let i = stack.length - 2; i >= 0; i -= 2) {
345
+ if (stack[i] === schema && stack[i + 1] === data) return true;
346
+ }
347
+ stack.push(schema, data);
348
+ const ok = ic(data, errors, instancePath, schemaPath, stack, rec);
349
+ stack.length -= 2;
350
+ return ok;
351
+ };
352
+ vstep = (data, stack, rec) => {
353
+ for (let i = stack.length - 2; i >= 0; i -= 2) {
354
+ if (stack[i] === schema && stack[i + 1] === data) return true;
355
+ }
356
+ stack.push(schema, data);
357
+ const ok = iv(data, stack, rec);
358
+ stack.length -= 2;
359
+ return ok;
360
+ };
361
+ }
362
+ steps.push(cstep);
363
+ vsteps.push(vstep);
364
+ }
365
+
366
+ // Every value-level keyword in one step, reusing the leaf evaluator so
367
+ // the two paths cannot drift.
368
+ if (P.hasType || P.enum !== null || P.hasConst || P.hasNumber || P.hasString ||
369
+ P.minItems !== undefined || P.maxItems !== undefined || P.uniqueItems ||
370
+ P.required !== null || P.minProperties !== undefined || P.maxProperties !== undefined ||
371
+ P.dependentRequired !== null) {
372
+ steps.push((data, errors, instancePath, schemaPath) => evalLeaf(P, data, errors, instancePath, schemaPath));
373
+ vsteps.push(compileLeafV(P));
374
+ }
375
+
376
+ // Arrays
377
+ if (P.prefixItems !== null) {
378
+ const fns = P.prefixItems.map(child);
379
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
380
+ if (dataBits(data) !== T_ARRAY) return true;
381
+ let ok = true;
382
+ const n = Math.min(fns.length, data.length);
383
+ for (let i = 0; i < n; i++) {
384
+ if (!fns[i].c(data[i], errors, instancePath + '/' + i, schemaPath + '/prefixItems/' + i, stack)) {
385
+ ok = false;
386
+ if (errors === NOERRORS) return false;
387
+ }
388
+ }
389
+ if (collect) markRange(rec, 0, n);
390
+ return ok;
391
+ });
392
+ vsteps.push((data, stack, rec) => {
393
+ if (dataBits(data) !== T_ARRAY) return true;
394
+ const n = Math.min(fns.length, data.length);
395
+ for (let i = 0; i < n; i++) if (!fns[i].v(data[i], stack)) return false;
396
+ if (collect) markRange(rec, 0, n);
397
+ return true;
398
+ });
399
+ }
400
+ if (P.items !== undefined) {
401
+ const fn = child(P.items);
402
+ const start = P.prefixItems !== null ? P.prefixItems.length : 0;
403
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
404
+ if (dataBits(data) !== T_ARRAY) return true;
405
+ let ok = true;
406
+ for (let i = start; i < data.length; i++) {
407
+ if (!fn.c(data[i], errors, instancePath + '/' + i, schemaPath + '/items', stack)) {
408
+ ok = false;
409
+ if (errors === NOERRORS) return false;
410
+ }
411
+ }
412
+ if (collect) markRange(rec, start, data.length);
413
+ return ok;
414
+ });
415
+ const fv = fn.v;
416
+ vsteps.push((data, stack, rec) => {
417
+ if (dataBits(data) !== T_ARRAY) return true;
418
+ for (let i = start; i < data.length; i++) if (!fv(data[i], stack)) return false;
419
+ if (collect) markRange(rec, start, data.length);
420
+ return true;
421
+ });
422
+ }
423
+ if (P.contains !== undefined) {
424
+ const fn = child(P.contains);
425
+ const minC = P.minContains !== undefined ? P.minContains : 1;
426
+ const maxC = P.maxContains;
427
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
428
+ if (dataBits(data) !== T_ARRAY) return true;
429
+ let matched = 0;
430
+ for (let i = 0; i < data.length; i++) {
431
+ if (fn.v(data[i], stack)) {
432
+ matched++;
433
+ if (collect) addItem(rec, i);
434
+ }
435
+ }
436
+ let ok = true;
437
+ if (matched < minC) {
438
+ if (errors !== NOERRORS) errors.push(err('contains', 'contains', instancePath, schemaPath + '/contains', { minContains: minC }, `must contain at least ${minC} valid item(s)`));
439
+ ok = false;
440
+ }
441
+ if (maxC !== undefined && matched > maxC) {
442
+ if (errors !== NOERRORS) errors.push(err('maxContains', 'maxContains', instancePath, schemaPath + '/maxContains', { limit: maxC }, `must NOT contain more than ${maxC} valid item(s)`));
443
+ ok = false;
444
+ }
445
+ return ok;
446
+ });
447
+ if (collect) {
448
+ // Every matching index is an annotation, so the scan runs to the end.
449
+ vsteps.push((data, stack, rec) => {
450
+ if (dataBits(data) !== T_ARRAY) return true;
451
+ let matched = 0;
452
+ for (let i = 0; i < data.length; i++) {
453
+ if (fn.v(data[i], stack)) { matched++; addItem(rec, i); }
454
+ }
455
+ return matched >= minC && (maxC === undefined || matched <= maxC);
456
+ });
457
+ } else {
458
+ vsteps.push((data, stack) => {
459
+ if (dataBits(data) !== T_ARRAY) return true;
460
+ let matched = 0;
461
+ for (let i = 0; i < data.length; i++) {
462
+ if (fn.v(data[i], stack)) { matched++; if (maxC === undefined && matched >= minC) return true; }
463
+ }
464
+ return matched >= minC && (maxC === undefined || matched <= maxC);
465
+ });
466
+ }
467
+ }
468
+
469
+ // Objects
470
+ if (P.properties !== null || P.patternProperties !== null || P.additionalProperties !== undefined || P.propertyNames !== undefined) {
471
+ const props = P.properties;
472
+ // Declared properties: a linear scan over a short list beats a Map
473
+ // lookup, and most schemas declare a handful.
474
+ const propKeys = props !== null ? [...props.keys()] : null;
475
+ const propList = props !== null
476
+ ? [...props.values()].map((entry) => ({ fn: child(entry.node), seg: entry.seg, schemaSeg: entry.schemaSeg }))
477
+ : null;
478
+ const propMap = props !== null && props.size > 8 ? new Map(propKeys.map((k, i) => [k, propList[i]])) : null;
479
+ const lookup = propMap !== null
480
+ ? (key) => propMap.get(key)
481
+ : propKeys !== null
482
+ ? (key) => { for (let i = 0; i < propKeys.length; i++) if (propKeys[i] === key) return propList[i]; return undefined; }
483
+ : null;
484
+ const patterns = P.patternProperties !== null
485
+ ? P.patternProperties.map((e) => ({ re: e.re, src: e.src, fn: child(e.node) }))
486
+ : null;
487
+ const apFn = P.additionalProperties !== undefined ? child(P.additionalProperties) : null;
488
+ const pnFn = P.propertyNames !== undefined ? child(P.propertyNames) : null;
489
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
490
+ if (dataBits(data) !== T_OBJECT) return true;
491
+ let ok = true;
492
+ const keys = collect ? keysOf(rec, data) : Object.keys(data);
493
+ if (pnFn !== null) {
494
+ for (const k of keys) {
495
+ if (!pnFn.c(k, errors, instancePath + '/' + escapePointer(k), schemaPath + '/propertyNames', stack)) {
496
+ ok = false;
497
+ if (errors === NOERRORS) return false;
498
+ }
499
+ }
500
+ }
501
+ for (let k = 0; k < keys.length; k++) {
502
+ const key = keys[k];
503
+ let evaluated = false;
504
+ const prop = lookup !== null ? lookup(key) : undefined;
505
+ if (prop !== undefined) {
506
+ if (!prop.fn.c(data[key], errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack)) {
507
+ ok = false;
508
+ if (errors === NOERRORS) return false;
509
+ }
510
+ evaluated = true;
511
+ }
512
+ if (patterns !== null) {
513
+ for (let pi = 0; pi < patterns.length; pi++) {
514
+ const pp = patterns[pi];
515
+ if (pp.re.test(key)) {
516
+ if (!pp.fn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/patternProperties/' + escapePointer(pp.src), stack)) {
517
+ ok = false;
518
+ if (errors === NOERRORS) return false;
519
+ }
520
+ evaluated = true;
521
+ }
522
+ }
523
+ }
524
+ if (!evaluated && apFn !== null) {
525
+ if (!apFn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/additionalProperties', stack)) {
526
+ ok = false;
527
+ if (errors === NOERRORS) return false;
528
+ }
529
+ evaluated = true;
530
+ }
531
+ if (evaluated && collect) addProp(rec, key);
532
+ }
533
+ return ok;
534
+ });
535
+ if (patterns === null && apFn === null && pnFn === null && propKeys.length <= 8) {
536
+ // Only declared properties matter and there are few: walk the
537
+ // schema's list and look each one up, no key array. Own-property
538
+ // test on purpose, so an inherited name (`toString`, `__proto__`) is
539
+ // not mistaken for data. A long list walks the data's keys instead,
540
+ // since the data usually carries far fewer.
541
+ const n = propKeys.length;
542
+ vsteps.push((data, stack, rec) => {
543
+ if (dataBits(data) !== T_OBJECT) return true;
544
+ for (let i = 0; i < n; i++) {
545
+ const key = propKeys[i];
546
+ if (!Object.hasOwn(data, key)) continue;
547
+ if (!propList[i].fn.v(data[key], stack)) return false;
548
+ if (collect) addProp(rec, key);
549
+ }
550
+ return true;
551
+ });
552
+ } else vsteps.push((data, stack, rec) => {
553
+ if (dataBits(data) !== T_OBJECT) return true;
554
+ const keys = collect ? keysOf(rec, data) : Object.keys(data);
555
+ if (pnFn !== null) {
556
+ for (const k of keys) if (!pnFn.v(k, stack)) return false;
557
+ }
558
+ for (let k = 0; k < keys.length; k++) {
559
+ const key = keys[k];
560
+ let evaluated = false;
561
+ const prop = lookup !== null ? lookup(key) : undefined;
562
+ if (prop !== undefined) {
563
+ if (!prop.fn.v(data[key], stack)) return false;
564
+ evaluated = true;
565
+ }
566
+ if (patterns !== null) {
567
+ for (let pi = 0; pi < patterns.length; pi++) {
568
+ const pp = patterns[pi];
569
+ if (pp.re.test(key)) {
570
+ if (!pp.fn.v(data[key], stack)) return false;
571
+ evaluated = true;
572
+ }
573
+ }
574
+ }
575
+ if (!evaluated && apFn !== null) {
576
+ if (!apFn.v(data[key], stack)) return false;
577
+ evaluated = true;
578
+ }
579
+ if (evaluated && collect) addProp(rec, key);
580
+ }
581
+ return true;
582
+ });
583
+ }
584
+ if (P.dependentSchemas !== null) {
585
+ const entries = P.dependentSchemas.map(([k, v]) => [k, inPlace(v, base), escapePointer(k)]);
586
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
587
+ if (dataBits(data) !== T_OBJECT) return true;
588
+ let ok = true;
589
+ for (const [k, fn, ek] of entries) {
590
+ if (Object.hasOwn(data, k)) {
591
+ if (collect) {
592
+ const pl = pLen(rec), n0 = rec.n, xl = xLen(rec);
593
+ if (!fn.c(data, errors, instancePath, schemaPath + '/dependentSchemas/' + ek, stack, rec)) {
594
+ undo(rec, pl, n0, xl);
595
+ ok = false;
596
+ if (errors === NOERRORS) return false;
597
+ }
598
+ } else if (!fn.c(data, errors, instancePath, schemaPath + '/dependentSchemas/' + ek, stack)) {
599
+ ok = false;
600
+ if (errors === NOERRORS) return false;
601
+ }
602
+ }
603
+ }
604
+ return ok;
605
+ });
606
+ vsteps.push((data, stack, rec) => {
607
+ if (dataBits(data) !== T_OBJECT) return true;
608
+ for (const [k, fn] of entries) {
609
+ if (Object.hasOwn(data, k) && !fn.v(data, stack, rec)) return false;
610
+ }
611
+ return true;
612
+ });
613
+ }
614
+ if (P.propertyDependencies !== null) {
615
+ const entries = P.propertyDependencies.map(([k, choices]) => {
616
+ const m = new Map();
617
+ for (const [value, v] of choices) m.set(value, inPlace(v, base));
618
+ return [k, m];
619
+ });
620
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
621
+ if (dataBits(data) !== T_OBJECT) return true;
622
+ let ok = true;
623
+ for (const [k, choices] of entries) {
624
+ if (!Object.hasOwn(data, k)) continue;
625
+ const value = data[k];
626
+ if (typeof value !== 'string') continue;
627
+ const fn = choices.get(value);
628
+ if (fn === undefined) continue;
629
+ const branchPath = schemaPath + '/propertyDependencies/' + escapePointer(k) + '/' + escapePointer(value);
630
+ if (collect) {
631
+ const pl = pLen(rec), n0 = rec.n, xl = xLen(rec);
632
+ if (!fn.c(data, errors, instancePath, branchPath, stack, rec)) {
633
+ undo(rec, pl, n0, xl);
634
+ ok = false;
635
+ if (errors === NOERRORS) return false;
636
+ }
637
+ } else if (!fn.c(data, errors, instancePath, branchPath, stack)) {
638
+ ok = false;
639
+ if (errors === NOERRORS) return false;
640
+ }
641
+ }
642
+ return ok;
643
+ });
644
+ vsteps.push((data, stack, rec) => {
645
+ if (dataBits(data) !== T_OBJECT) return true;
646
+ for (const [k, choices] of entries) {
647
+ if (!Object.hasOwn(data, k)) continue;
648
+ const value = data[k];
649
+ if (typeof value !== 'string') continue;
650
+ const fn = choices.get(value);
651
+ if (fn !== undefined && !fn.v(data, stack, rec)) return false;
652
+ }
653
+ return true;
654
+ });
655
+ }
656
+
657
+ // In-place applicators
658
+ if (P.allOf !== null) {
659
+ const fns = P.allOf.map((v) => inPlace(v, base));
660
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
661
+ let ok = true;
662
+ for (let i = 0; i < fns.length; i++) {
663
+ if (collect) {
664
+ const pl = pLen(rec), n0 = rec.n, xl = xLen(rec);
665
+ if (!fns[i].c(data, errors, instancePath, schemaPath + '/allOf/' + i, stack, rec)) {
666
+ undo(rec, pl, n0, xl);
667
+ ok = false;
668
+ if (errors === NOERRORS) return false;
669
+ }
670
+ } else if (!fns[i].c(data, errors, instancePath, schemaPath + '/allOf/' + i, stack)) {
671
+ ok = false;
672
+ if (errors === NOERRORS) return false;
673
+ }
674
+ }
675
+ return ok;
676
+ });
677
+ vsteps.push((data, stack, rec) => {
678
+ for (let i = 0; i < fns.length; i++) if (!fns[i].v(data, stack, rec)) return false;
679
+ return true;
680
+ });
681
+ }
682
+ if (P.anyOf !== null) {
683
+ const fns = P.anyOf.map((v) => inPlace(v, base));
684
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
685
+ const scratch = errors === NOERRORS ? NOERRORS : [];
686
+ let any = false;
687
+ for (let i = 0; i < fns.length; i++) {
688
+ if (collect) {
689
+ const pl = pLen(rec), n0 = rec.n, xl = xLen(rec);
690
+ if (fns[i].c(data, scratch, instancePath, schemaPath + '/anyOf/' + i, stack, rec)) any = true;
691
+ else undo(rec, pl, n0, xl);
692
+ } else if (fns[i].c(data, scratch, instancePath, schemaPath + '/anyOf/' + i, stack)) any = true;
693
+ }
694
+ if (!any) {
695
+ if (errors !== NOERRORS) {
696
+ for (const e of scratch) errors.push(e);
697
+ errors.push(err('anyOf', 'anyOf', instancePath, schemaPath + '/anyOf', {}, 'must match a schema in anyOf'));
698
+ }
699
+ return false;
700
+ }
701
+ return true;
702
+ });
703
+ if (collect) {
704
+ // Every passing branch contributes annotations, so all of them run.
705
+ vsteps.push((data, stack, rec) => {
706
+ let any = false;
707
+ for (let i = 0; i < fns.length; i++) {
708
+ const pl = pLen(rec), n0 = rec.n, xl = xLen(rec);
709
+ if (fns[i].v(data, stack, rec)) any = true;
710
+ else undo(rec, pl, n0, xl);
711
+ }
712
+ return any;
713
+ });
714
+ } else {
715
+ vsteps.push((data, stack) => {
716
+ for (let i = 0; i < fns.length; i++) if (fns[i].v(data, stack)) return true;
717
+ return false;
718
+ });
719
+ }
720
+ }
721
+ if (P.oneOf !== null) {
722
+ const fns = P.oneOf.map((v) => inPlace(v, base));
723
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
724
+ const scratch = errors === NOERRORS ? NOERRORS : [];
725
+ let count = 0;
726
+ // With a record: the winner's annotations stay, every other branch's
727
+ // are undone, and a second winner undoes everything since the node
728
+ // fails and merges nothing.
729
+ const pl0 = collect ? pLen(rec) : 0, n00 = collect ? rec.n : 0, xl0 = collect ? xLen(rec) : 0;
730
+ for (let i = 0; i < fns.length; i++) {
731
+ if (collect) {
732
+ const pl = pLen(rec), n0 = rec.n, xl = xLen(rec);
733
+ if (fns[i].c(data, scratch, instancePath, schemaPath + '/oneOf/' + i, stack, rec)) count++;
734
+ else undo(rec, pl, n0, xl);
735
+ } else if (fns[i].c(data, scratch, instancePath, schemaPath + '/oneOf/' + i, stack)) count++;
736
+ }
737
+ if (count !== 1) {
738
+ if (collect) undo(rec, pl0, n00, xl0);
739
+ if (errors !== NOERRORS) {
740
+ if (count === 0) for (const e of scratch) errors.push(e);
741
+ errors.push(err('oneOf', 'oneOf', instancePath, schemaPath + '/oneOf', { passingSchemas: count }, 'must match exactly one schema in oneOf'));
742
+ }
743
+ return false;
744
+ }
745
+ return true;
746
+ });
747
+ if (collect) {
748
+ vsteps.push((data, stack, rec) => {
749
+ let count = 0;
750
+ for (let i = 0; i < fns.length; i++) {
751
+ const pl = pLen(rec), n0 = rec.n, xl = xLen(rec);
752
+ if (fns[i].v(data, stack, rec)) { count++; if (count > 1) return false; }
753
+ else undo(rec, pl, n0, xl);
754
+ }
755
+ return count === 1;
756
+ });
757
+ } else {
758
+ vsteps.push((data, stack) => {
759
+ let count = 0;
760
+ for (let i = 0; i < fns.length; i++) {
761
+ if (fns[i].v(data, stack)) { count++; if (count > 1) return false; }
762
+ }
763
+ return count === 1;
764
+ });
765
+ }
766
+ }
767
+ if (P.not !== undefined) {
768
+ const fn = child(P.not);
769
+ steps.push((data, errors, instancePath, schemaPath, stack) => {
770
+ if (fn.v(data, stack)) {
771
+ if (errors !== NOERRORS) errors.push(err('not', 'not', instancePath, schemaPath + '/not', {}, 'must NOT be valid'));
772
+ return false;
773
+ }
774
+ return true;
775
+ });
776
+ vsteps.push((data, stack) => !fn.v(data, stack));
777
+ }
778
+ if (P.if !== undefined) {
779
+ const ifFn = inPlace(P.if, base);
780
+ const thenFn = P.then !== undefined ? inPlace(P.then, base) : null;
781
+ const elseFn = P.else !== undefined ? inPlace(P.else, base) : null;
782
+ // The `if` branch keeps its annotations only when it passes; then and
783
+ // else fail the node when they fail, and the collect variant undoes
784
+ // theirs so the node's own unevaluated* sees what eval() sees.
785
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
786
+ let pl = collect ? pLen(rec) : 0, n0 = collect ? rec.n : 0, xl = collect ? xLen(rec) : 0;
787
+ let ok;
788
+ if (ifFn.v(data, stack, rec)) {
789
+ if (thenFn === null) return true;
790
+ if (collect) { pl = pLen(rec); n0 = rec.n; xl = xLen(rec); }
791
+ ok = thenFn.c(data, errors, instancePath, schemaPath + '/then', stack, rec);
792
+ } else {
793
+ if (collect) undo(rec, pl, n0, xl);
794
+ if (elseFn === null) return true;
795
+ ok = elseFn.c(data, errors, instancePath, schemaPath + '/else', stack, rec);
796
+ }
797
+ if (!ok && collect) undo(rec, pl, n0, xl);
798
+ return ok;
799
+ });
800
+ vsteps.push((data, stack, rec) => {
801
+ const pl = collect ? pLen(rec) : 0, n0 = collect ? rec.n : 0, xl = collect ? xLen(rec) : 0;
802
+ if (ifFn.v(data, stack, rec)) {
803
+ if (thenFn !== null) return thenFn.v(data, stack, rec);
804
+ return true;
805
+ }
806
+ if (collect) undo(rec, pl, n0, xl);
807
+ if (elseFn !== null) return elseFn.v(data, stack, rec);
808
+ return true;
809
+ });
810
+ }
811
+
812
+ // unevaluated*: run last, against the annotations of everything above.
813
+ if (P.unevaluatedProperties !== undefined) {
814
+ const fn = child(P.unevaluatedProperties);
815
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
816
+ if (dataBits(data) !== T_OBJECT) return true;
817
+ let ok = true;
818
+ const keys = keysOf(rec, data);
819
+ for (let k = 0; k < keys.length; k++) {
820
+ const key = keys[k];
821
+ if (hasProp(rec, key)) continue;
822
+ if (!fn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/unevaluatedProperties', stack)) {
823
+ ok = false;
824
+ if (errors === NOERRORS) return false;
825
+ }
826
+ addProp(rec, key);
827
+ }
828
+ return ok;
829
+ });
830
+ if (fn === FALSE_PAIR) {
831
+ // Every key must already be evaluated; on success there is nothing
832
+ // new to record.
833
+ vsteps.push((data, stack, rec) => {
834
+ if (dataBits(data) !== T_OBJECT) return true;
835
+ const keys = keysOf(rec, data);
836
+ if (keys.length === 0) return true;
837
+ if (rec.props === null) return false;
838
+ for (let k = 0; k < keys.length; k++) if (!hasProp(rec, keys[k])) return false;
839
+ return true;
840
+ });
841
+ } else {
842
+ vsteps.push((data, stack, rec) => {
843
+ if (dataBits(data) !== T_OBJECT) return true;
844
+ const keys = keysOf(rec, data);
845
+ for (let k = 0; k < keys.length; k++) {
846
+ const key = keys[k];
847
+ if (hasProp(rec, key)) continue;
848
+ if (!fn.v(data[key], stack)) return false;
849
+ addProp(rec, key);
850
+ }
851
+ return true;
852
+ });
853
+ }
854
+ }
855
+ if (P.unevaluatedItems !== undefined) {
856
+ const fn = child(P.unevaluatedItems);
857
+ steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
858
+ if (dataBits(data) !== T_ARRAY) return true;
859
+ let ok = true;
860
+ for (let i = 0; i < data.length; i++) {
861
+ if (hasItem(rec, i)) continue;
862
+ if (!fn.c(data[i], errors, instancePath + '/' + i, schemaPath + '/unevaluatedItems', stack)) {
863
+ ok = false;
864
+ if (errors === NOERRORS) return false;
865
+ }
866
+ }
867
+ if (data.length > rec.n) rec.n = data.length;
868
+ return ok;
869
+ });
870
+ if (fn === FALSE_PAIR) {
871
+ vsteps.push((data, stack, rec) => {
872
+ if (dataBits(data) !== T_ARRAY) return true;
873
+ for (let i = rec.n; i < data.length; i++) if (!hasItem(rec, i)) return false;
874
+ return true;
875
+ });
876
+ } else {
877
+ vsteps.push((data, stack, rec) => {
878
+ if (dataBits(data) !== T_ARRAY) return true;
879
+ for (let i = 0; i < data.length; i++) {
880
+ if (hasItem(rec, i)) continue;
881
+ if (!fn.v(data[i], stack)) return false;
882
+ }
883
+ if (data.length > rec.n) rec.n = data.length;
884
+ return true;
885
+ });
886
+ }
887
+ }
888
+
889
+ let cfn;
890
+ if (steps.length === 0) cfn = TRUE_FN;
891
+ else if (steps.length === 1) cfn = steps[0];
892
+ else {
893
+ const arr = steps;
894
+ cfn = (d, e, ip, sp, st, rec) => {
895
+ let ok = true;
896
+ for (let i = 0; i < arr.length; i++) {
897
+ if (!arr[i](d, e, ip, sp, st, rec)) {
898
+ if (e === NOERRORS) return false;
899
+ ok = false;
900
+ }
901
+ }
902
+ return ok;
903
+ };
904
+ }
905
+ let vfn;
906
+ if (vsteps.length === 0) vfn = TRUE_FN;
907
+ else if (vsteps.length === 1) vfn = vsteps[0];
908
+ else if (vsteps.length === 2) {
909
+ const [a, b] = vsteps;
910
+ vfn = (d, st, rec) => a(d, st, rec) && b(d, st, rec);
911
+ } else {
912
+ const arr = vsteps;
913
+ vfn = (d, st, rec) => {
914
+ for (let i = 0; i < arr.length; i++) if (!arr[i](d, st, rec)) return false;
915
+ return true;
916
+ };
917
+ }
918
+ if (P.hasUnevaluated) {
919
+ // This node reads annotations, so it owns a record that starts empty
920
+ // (siblings' annotations are not its to see) and hands it up on
921
+ // success when a caller reads it.
922
+ const inner = cfn;
923
+ const innerV = vfn;
924
+ if (ann) {
925
+ cfn = (d, e, ip, sp, st, rec) => {
926
+ const own = fresh();
927
+ const ok = inner(d, e, ip, sp, st, own);
928
+ if (ok) mergeRec(rec, own);
929
+ return ok;
930
+ };
931
+ vfn = (d, st, rec) => {
932
+ const own = fresh();
933
+ const ok = innerV(d, st, own);
934
+ if (ok) mergeRec(rec, own);
935
+ return ok;
936
+ };
937
+ } else {
938
+ cfn = (d, e, ip, sp, st) => inner(d, e, ip, sp, st, fresh());
939
+ vfn = (d, st) => innerV(d, st, fresh());
940
+ }
941
+ }
942
+ box.c = cfn;
943
+ box.v = vfn;
944
+ pair.c = cfn;
945
+ pair.v = vfn;
946
+ pair.open = false;
947
+ return pair;
948
+ }
949
+
950
+ // Entry point: every schema the interpreter accepts compiles. The first
951
+ // pass carries the cycle guard and reports whether the graph has a cycle;
952
+ // an acyclic schema is compiled again without the guard, since eval()'s
953
+ // guard can never fire on it.
954
+ function compileInterpreter(interp) {
955
+ const root = interp.rootNode;
956
+ const base = interp.state.rootBase;
957
+ let ctx = { interp, memo: new Map(), guard: true, cyclic: false };
958
+ let pair = compileNode(ctx, root, base, [base], false);
959
+ if (!ctx.cyclic) {
960
+ ctx = { interp, memo: new Map(), guard: false, cyclic: false };
961
+ pair = compileNode(ctx, root, base, [base], false);
962
+ }
963
+ return { v: pair.v, c: pair.c, cyclic: ctx.guard };
964
+ }
965
+
966
+ return { compileInterpreter };
967
+ }
968
+
969
+ module.exports = { install };