ata-validator 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +13 -3
- package/index.d.ts +3 -3
- package/index.js +141 -47
- package/lib/aot.js +1 -1
- package/lib/interpreter.js +251 -45
- package/lib/js-compiler.js +99 -44
- package/lib/plan-compiler.js +969 -0
- package/lib/version.js +1 -1
- package/package.json +8 -8
|
@@ -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 };
|