ata-validator 1.7.1 → 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 +15 -0
- package/README.md +4 -3
- package/index.js +20 -2
- package/lib/interpreter.js +8 -6
- package/lib/js-compiler.js +96 -42
- package/lib/plan-compiler.js +651 -227
- package/lib/version.js +1 -1
- package/package.json +8 -8
package/lib/plan-compiler.js
CHANGED
|
@@ -7,23 +7,47 @@
|
|
|
7
7
|
// child call is a direct monomorphic call, which is the same advantage the
|
|
8
8
|
// code generator has, minus the codegen.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
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
|
|
17
29
|
// be byte-for-byte what eval() produces; tests/test_plan_compiler.js diffs
|
|
18
30
|
// the two over the official suite.
|
|
19
31
|
//
|
|
20
|
-
// Compiled
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
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.
|
|
24
47
|
|
|
25
48
|
function install(deps) {
|
|
26
|
-
const { Plan, NOERRORS, err, evalLeaf, evalLeafV,
|
|
49
|
+
const { Plan, NOERRORS, err, evalLeaf, evalLeafV, dataBits, escapePointer,
|
|
50
|
+
deepEqual, multipleOfOk, cpAtLeast, cpAtMost,
|
|
27
51
|
T_STRING, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri } = deps;
|
|
28
52
|
|
|
29
53
|
const TRUE_FN = () => true;
|
|
@@ -36,136 +60,307 @@ function install(deps) {
|
|
|
36
60
|
},
|
|
37
61
|
};
|
|
38
62
|
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!(node instanceof Plan)) return true;
|
|
45
|
-
const P = node;
|
|
46
|
-
if (P.nodeBase !== null && P.nodeBase !== base) base = P.nodeBase;
|
|
47
|
-
const key = P;
|
|
48
|
-
const prev = seen.get(key);
|
|
49
|
-
if (prev !== undefined) { if (prev === base || prev === true) return true; return false; }
|
|
50
|
-
seen.set(key, base);
|
|
51
|
-
if (P.hasUnevaluated) return false;
|
|
52
|
-
if (P.ref !== null) {
|
|
53
|
-
const t = resolveRef(P.ref, base, interp.state);
|
|
54
|
-
if (t.node === undefined || !compilable(interp, interp.node(t.node), t.base, seen)) return false;
|
|
55
|
-
}
|
|
56
|
-
if (P.dynamicRef !== null) {
|
|
57
|
-
// $dynamicRef stays compiled only in a single-resource schema, where
|
|
58
|
-
// the dynamic scope is a constant. Anything larger keeps eval().
|
|
59
|
-
if (interp.state.resources.size !== 1) return false;
|
|
60
|
-
const t = dynTarget(interp, P.dynamicRef);
|
|
61
|
-
if (t === undefined || !compilable(interp, interp.node(t), base, seen)) return false;
|
|
62
|
-
}
|
|
63
|
-
const kids = [];
|
|
64
|
-
if (P.properties !== null) for (const v of P.properties.values()) kids.push(v.node);
|
|
65
|
-
if (P.patternProperties !== null) for (const e of P.patternProperties) kids.push(e.node);
|
|
66
|
-
if (P.additionalProperties !== undefined) kids.push(P.additionalProperties);
|
|
67
|
-
if (P.propertyNames !== undefined) kids.push(P.propertyNames);
|
|
68
|
-
if (P.dependentSchemas !== null) for (const [, v] of P.dependentSchemas) kids.push(v);
|
|
69
|
-
if (P.propertyDependencies !== null) for (const [, m] of P.propertyDependencies) for (const v of m.values()) kids.push(v);
|
|
70
|
-
if (P.prefixItems !== null) for (const v of P.prefixItems) kids.push(v);
|
|
71
|
-
if (P.items !== undefined) kids.push(P.items);
|
|
72
|
-
if (P.contains !== undefined) kids.push(P.contains);
|
|
73
|
-
if (P.allOf !== null) for (const v of P.allOf) kids.push(v);
|
|
74
|
-
if (P.anyOf !== null) for (const v of P.anyOf) kids.push(v);
|
|
75
|
-
if (P.oneOf !== null) for (const v of P.oneOf) kids.push(v);
|
|
76
|
-
if (P.not !== undefined) kids.push(P.not);
|
|
77
|
-
if (P.if !== undefined) kids.push(P.if);
|
|
78
|
-
if (P.then !== undefined) kids.push(P.then);
|
|
79
|
-
if (P.else !== undefined) kids.push(P.else);
|
|
80
|
-
for (const k of kids) if (!compilable(interp, k, base, seen)) return false;
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Static $dynamicRef target: with a single resource the dynamic scope is
|
|
85
|
-
// always [rootBase], so the outermost-scope search collapses to a lookup.
|
|
86
|
-
function dynTarget(interp, ref) {
|
|
87
|
-
const base = interp.state.rootBase;
|
|
88
|
-
let { node } = resolveRef(ref, base, interp.state);
|
|
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);
|
|
89
68
|
const [, fragment] = splitFragment(resolveUri(base, ref));
|
|
90
69
|
if (fragment && !fragment.startsWith('/')) {
|
|
91
|
-
const
|
|
92
|
-
const bookended = node !== undefined &&
|
|
70
|
+
const initialDyn = state.dynamicAnchors.get(refBase);
|
|
71
|
+
const bookended = node !== undefined && initialDyn && initialDyn.get(fragment) === node;
|
|
93
72
|
if (bookended || !interp.bookending) {
|
|
94
|
-
|
|
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
|
+
});
|
|
95
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
|
+
};
|
|
96
239
|
}
|
|
97
|
-
return
|
|
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
|
+
};
|
|
98
250
|
}
|
|
99
251
|
|
|
100
|
-
|
|
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) {
|
|
101
264
|
if (node === true) return TRUE_PAIR;
|
|
102
265
|
if (node === false) return FALSE_PAIR;
|
|
103
266
|
if (!(node instanceof Plan)) return TRUE_PAIR;
|
|
267
|
+
const interp = ctx.interp;
|
|
104
268
|
const P = node;
|
|
105
269
|
if (P.nodeBase !== null && P.nodeBase !== base) base = P.nodeBase;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
}
|
|
112
281
|
// Cycles: register trampolines before compiling children.
|
|
113
282
|
const box = { v: null, c: null };
|
|
114
283
|
const pair = {
|
|
115
|
-
|
|
116
|
-
|
|
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),
|
|
117
287
|
};
|
|
118
|
-
|
|
288
|
+
perKey.set(key, pair);
|
|
119
289
|
|
|
120
|
-
|
|
121
|
-
|
|
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);
|
|
122
295
|
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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);
|
|
169
364
|
}
|
|
170
365
|
|
|
171
366
|
// Every value-level keyword in one step, reusing the leaf evaluator so
|
|
@@ -175,13 +370,13 @@ function install(deps) {
|
|
|
175
370
|
P.required !== null || P.minProperties !== undefined || P.maxProperties !== undefined ||
|
|
176
371
|
P.dependentRequired !== null) {
|
|
177
372
|
steps.push((data, errors, instancePath, schemaPath) => evalLeaf(P, data, errors, instancePath, schemaPath));
|
|
178
|
-
vsteps.push((
|
|
373
|
+
vsteps.push(compileLeafV(P));
|
|
179
374
|
}
|
|
180
375
|
|
|
181
376
|
// Arrays
|
|
182
377
|
if (P.prefixItems !== null) {
|
|
183
|
-
const fns = P.prefixItems.map(
|
|
184
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
378
|
+
const fns = P.prefixItems.map(child);
|
|
379
|
+
steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
|
|
185
380
|
if (dataBits(data) !== T_ARRAY) return true;
|
|
186
381
|
let ok = true;
|
|
187
382
|
const n = Math.min(fns.length, data.length);
|
|
@@ -191,19 +386,21 @@ function install(deps) {
|
|
|
191
386
|
if (errors === NOERRORS) return false;
|
|
192
387
|
}
|
|
193
388
|
}
|
|
389
|
+
if (collect) markRange(rec, 0, n);
|
|
194
390
|
return ok;
|
|
195
391
|
});
|
|
196
|
-
vsteps.push((data, stack) => {
|
|
392
|
+
vsteps.push((data, stack, rec) => {
|
|
197
393
|
if (dataBits(data) !== T_ARRAY) return true;
|
|
198
394
|
const n = Math.min(fns.length, data.length);
|
|
199
395
|
for (let i = 0; i < n; i++) if (!fns[i].v(data[i], stack)) return false;
|
|
396
|
+
if (collect) markRange(rec, 0, n);
|
|
200
397
|
return true;
|
|
201
398
|
});
|
|
202
399
|
}
|
|
203
400
|
if (P.items !== undefined) {
|
|
204
|
-
const fn =
|
|
401
|
+
const fn = child(P.items);
|
|
205
402
|
const start = P.prefixItems !== null ? P.prefixItems.length : 0;
|
|
206
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
403
|
+
steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
|
|
207
404
|
if (dataBits(data) !== T_ARRAY) return true;
|
|
208
405
|
let ok = true;
|
|
209
406
|
for (let i = start; i < data.length; i++) {
|
|
@@ -212,24 +409,29 @@ function install(deps) {
|
|
|
212
409
|
if (errors === NOERRORS) return false;
|
|
213
410
|
}
|
|
214
411
|
}
|
|
412
|
+
if (collect) markRange(rec, start, data.length);
|
|
215
413
|
return ok;
|
|
216
414
|
});
|
|
217
415
|
const fv = fn.v;
|
|
218
|
-
vsteps.push((data, stack) => {
|
|
416
|
+
vsteps.push((data, stack, rec) => {
|
|
219
417
|
if (dataBits(data) !== T_ARRAY) return true;
|
|
220
418
|
for (let i = start; i < data.length; i++) if (!fv(data[i], stack)) return false;
|
|
419
|
+
if (collect) markRange(rec, start, data.length);
|
|
221
420
|
return true;
|
|
222
421
|
});
|
|
223
422
|
}
|
|
224
423
|
if (P.contains !== undefined) {
|
|
225
|
-
const fn =
|
|
424
|
+
const fn = child(P.contains);
|
|
226
425
|
const minC = P.minContains !== undefined ? P.minContains : 1;
|
|
227
426
|
const maxC = P.maxContains;
|
|
228
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
427
|
+
steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
|
|
229
428
|
if (dataBits(data) !== T_ARRAY) return true;
|
|
230
429
|
let matched = 0;
|
|
231
430
|
for (let i = 0; i < data.length; i++) {
|
|
232
|
-
if (fn.v(data[i], stack))
|
|
431
|
+
if (fn.v(data[i], stack)) {
|
|
432
|
+
matched++;
|
|
433
|
+
if (collect) addItem(rec, i);
|
|
434
|
+
}
|
|
233
435
|
}
|
|
234
436
|
let ok = true;
|
|
235
437
|
if (matched < minC) {
|
|
@@ -242,35 +444,55 @@ function install(deps) {
|
|
|
242
444
|
}
|
|
243
445
|
return ok;
|
|
244
446
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
}
|
|
253
467
|
}
|
|
254
468
|
|
|
255
469
|
// Objects
|
|
256
470
|
if (P.properties !== null || P.patternProperties !== null || P.additionalProperties !== undefined || P.propertyNames !== undefined) {
|
|
257
471
|
const props = P.properties;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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;
|
|
262
484
|
const patterns = P.patternProperties !== null
|
|
263
|
-
? P.patternProperties.map((e) => ({ re: e.re, src: e.src, fn:
|
|
485
|
+
? P.patternProperties.map((e) => ({ re: e.re, src: e.src, fn: child(e.node) }))
|
|
264
486
|
: null;
|
|
265
|
-
const apFn = P.additionalProperties !== undefined ?
|
|
266
|
-
const pnFn = P.propertyNames !== undefined ?
|
|
267
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
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) => {
|
|
268
490
|
if (dataBits(data) !== T_OBJECT) return true;
|
|
269
491
|
let ok = true;
|
|
270
|
-
const keys = Object.keys(data);
|
|
492
|
+
const keys = collect ? keysOf(rec, data) : Object.keys(data);
|
|
271
493
|
if (pnFn !== null) {
|
|
272
|
-
for (const
|
|
273
|
-
if (!pnFn.c(
|
|
494
|
+
for (const k of keys) {
|
|
495
|
+
if (!pnFn.c(k, errors, instancePath + '/' + escapePointer(k), schemaPath + '/propertyNames', stack)) {
|
|
274
496
|
ok = false;
|
|
275
497
|
if (errors === NOERRORS) return false;
|
|
276
498
|
}
|
|
@@ -279,7 +501,7 @@ function install(deps) {
|
|
|
279
501
|
for (let k = 0; k < keys.length; k++) {
|
|
280
502
|
const key = keys[k];
|
|
281
503
|
let evaluated = false;
|
|
282
|
-
const prop =
|
|
504
|
+
const prop = lookup !== null ? lookup(key) : undefined;
|
|
283
505
|
if (prop !== undefined) {
|
|
284
506
|
if (!prop.fn.c(data[key], errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack)) {
|
|
285
507
|
ok = false;
|
|
@@ -304,20 +526,39 @@ function install(deps) {
|
|
|
304
526
|
ok = false;
|
|
305
527
|
if (errors === NOERRORS) return false;
|
|
306
528
|
}
|
|
529
|
+
evaluated = true;
|
|
307
530
|
}
|
|
531
|
+
if (evaluated && collect) addProp(rec, key);
|
|
308
532
|
}
|
|
309
533
|
return ok;
|
|
310
534
|
});
|
|
311
|
-
|
|
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) => {
|
|
312
553
|
if (dataBits(data) !== T_OBJECT) return true;
|
|
313
|
-
const keys = Object.keys(data);
|
|
554
|
+
const keys = collect ? keysOf(rec, data) : Object.keys(data);
|
|
314
555
|
if (pnFn !== null) {
|
|
315
|
-
for (const
|
|
556
|
+
for (const k of keys) if (!pnFn.v(k, stack)) return false;
|
|
316
557
|
}
|
|
317
558
|
for (let k = 0; k < keys.length; k++) {
|
|
318
559
|
const key = keys[k];
|
|
319
560
|
let evaluated = false;
|
|
320
|
-
const prop =
|
|
561
|
+
const prop = lookup !== null ? lookup(key) : undefined;
|
|
321
562
|
if (prop !== undefined) {
|
|
322
563
|
if (!prop.fn.v(data[key], stack)) return false;
|
|
323
564
|
evaluated = true;
|
|
@@ -333,19 +574,28 @@ function install(deps) {
|
|
|
333
574
|
}
|
|
334
575
|
if (!evaluated && apFn !== null) {
|
|
335
576
|
if (!apFn.v(data[key], stack)) return false;
|
|
577
|
+
evaluated = true;
|
|
336
578
|
}
|
|
579
|
+
if (evaluated && collect) addProp(rec, key);
|
|
337
580
|
}
|
|
338
581
|
return true;
|
|
339
582
|
});
|
|
340
583
|
}
|
|
341
584
|
if (P.dependentSchemas !== null) {
|
|
342
|
-
const entries = P.dependentSchemas.map(([
|
|
343
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
585
|
+
const entries = P.dependentSchemas.map(([k, v]) => [k, inPlace(v, base), escapePointer(k)]);
|
|
586
|
+
steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
|
|
344
587
|
if (dataBits(data) !== T_OBJECT) return true;
|
|
345
588
|
let ok = true;
|
|
346
|
-
for (const [
|
|
347
|
-
if (Object.hasOwn(data,
|
|
348
|
-
if (
|
|
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)) {
|
|
349
599
|
ok = false;
|
|
350
600
|
if (errors === NOERRORS) return false;
|
|
351
601
|
}
|
|
@@ -353,45 +603,52 @@ function install(deps) {
|
|
|
353
603
|
}
|
|
354
604
|
return ok;
|
|
355
605
|
});
|
|
356
|
-
vsteps.push((data, stack) => {
|
|
606
|
+
vsteps.push((data, stack, rec) => {
|
|
357
607
|
if (dataBits(data) !== T_OBJECT) return true;
|
|
358
|
-
for (const [
|
|
359
|
-
if (Object.hasOwn(data,
|
|
608
|
+
for (const [k, fn] of entries) {
|
|
609
|
+
if (Object.hasOwn(data, k) && !fn.v(data, stack, rec)) return false;
|
|
360
610
|
}
|
|
361
611
|
return true;
|
|
362
612
|
});
|
|
363
613
|
}
|
|
364
614
|
if (P.propertyDependencies !== null) {
|
|
365
|
-
const entries = P.propertyDependencies.map(([
|
|
615
|
+
const entries = P.propertyDependencies.map(([k, choices]) => {
|
|
366
616
|
const m = new Map();
|
|
367
|
-
for (const [value, v] of choices) m.set(value,
|
|
368
|
-
return [
|
|
617
|
+
for (const [value, v] of choices) m.set(value, inPlace(v, base));
|
|
618
|
+
return [k, m];
|
|
369
619
|
});
|
|
370
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
620
|
+
steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
|
|
371
621
|
if (dataBits(data) !== T_OBJECT) return true;
|
|
372
622
|
let ok = true;
|
|
373
|
-
for (const [
|
|
374
|
-
if (!Object.hasOwn(data,
|
|
375
|
-
const value = data[
|
|
623
|
+
for (const [k, choices] of entries) {
|
|
624
|
+
if (!Object.hasOwn(data, k)) continue;
|
|
625
|
+
const value = data[k];
|
|
376
626
|
if (typeof value !== 'string') continue;
|
|
377
627
|
const fn = choices.get(value);
|
|
378
628
|
if (fn === undefined) continue;
|
|
379
|
-
const branchPath = schemaPath + '/propertyDependencies/' + escapePointer(
|
|
380
|
-
if (
|
|
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)) {
|
|
381
638
|
ok = false;
|
|
382
639
|
if (errors === NOERRORS) return false;
|
|
383
640
|
}
|
|
384
641
|
}
|
|
385
642
|
return ok;
|
|
386
643
|
});
|
|
387
|
-
vsteps.push((data, stack) => {
|
|
644
|
+
vsteps.push((data, stack, rec) => {
|
|
388
645
|
if (dataBits(data) !== T_OBJECT) return true;
|
|
389
|
-
for (const [
|
|
390
|
-
if (!Object.hasOwn(data,
|
|
391
|
-
const value = data[
|
|
646
|
+
for (const [k, choices] of entries) {
|
|
647
|
+
if (!Object.hasOwn(data, k)) continue;
|
|
648
|
+
const value = data[k];
|
|
392
649
|
if (typeof value !== 'string') continue;
|
|
393
650
|
const fn = choices.get(value);
|
|
394
|
-
if (fn !== undefined && !fn.v(data, stack)) return false;
|
|
651
|
+
if (fn !== undefined && !fn.v(data, stack, rec)) return false;
|
|
395
652
|
}
|
|
396
653
|
return true;
|
|
397
654
|
});
|
|
@@ -399,29 +656,40 @@ function install(deps) {
|
|
|
399
656
|
|
|
400
657
|
// In-place applicators
|
|
401
658
|
if (P.allOf !== null) {
|
|
402
|
-
const fns = P.allOf.map((v) =>
|
|
403
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
659
|
+
const fns = P.allOf.map((v) => inPlace(v, base));
|
|
660
|
+
steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
|
|
404
661
|
let ok = true;
|
|
405
662
|
for (let i = 0; i < fns.length; i++) {
|
|
406
|
-
if (
|
|
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)) {
|
|
407
671
|
ok = false;
|
|
408
672
|
if (errors === NOERRORS) return false;
|
|
409
673
|
}
|
|
410
674
|
}
|
|
411
675
|
return ok;
|
|
412
676
|
});
|
|
413
|
-
vsteps.push((data, stack) => {
|
|
414
|
-
for (let i = 0; i < fns.length; i++) if (!fns[i].v(data, stack)) return false;
|
|
677
|
+
vsteps.push((data, stack, rec) => {
|
|
678
|
+
for (let i = 0; i < fns.length; i++) if (!fns[i].v(data, stack, rec)) return false;
|
|
415
679
|
return true;
|
|
416
680
|
});
|
|
417
681
|
}
|
|
418
682
|
if (P.anyOf !== null) {
|
|
419
|
-
const fns = P.anyOf.map((v) =>
|
|
420
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
683
|
+
const fns = P.anyOf.map((v) => inPlace(v, base));
|
|
684
|
+
steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
|
|
421
685
|
const scratch = errors === NOERRORS ? NOERRORS : [];
|
|
422
686
|
let any = false;
|
|
423
687
|
for (let i = 0; i < fns.length; i++) {
|
|
424
|
-
if (
|
|
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;
|
|
425
693
|
}
|
|
426
694
|
if (!any) {
|
|
427
695
|
if (errors !== NOERRORS) {
|
|
@@ -432,20 +700,42 @@ function install(deps) {
|
|
|
432
700
|
}
|
|
433
701
|
return true;
|
|
434
702
|
});
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
+
}
|
|
439
720
|
}
|
|
440
721
|
if (P.oneOf !== null) {
|
|
441
|
-
const fns = P.oneOf.map((v) =>
|
|
442
|
-
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
722
|
+
const fns = P.oneOf.map((v) => inPlace(v, base));
|
|
723
|
+
steps.push((data, errors, instancePath, schemaPath, stack, rec) => {
|
|
443
724
|
const scratch = errors === NOERRORS ? NOERRORS : [];
|
|
444
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;
|
|
445
730
|
for (let i = 0; i < fns.length; i++) {
|
|
446
|
-
if (
|
|
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++;
|
|
447
736
|
}
|
|
448
737
|
if (count !== 1) {
|
|
738
|
+
if (collect) undo(rec, pl0, n00, xl0);
|
|
449
739
|
if (errors !== NOERRORS) {
|
|
450
740
|
if (count === 0) for (const e of scratch) errors.push(e);
|
|
451
741
|
errors.push(err('oneOf', 'oneOf', instancePath, schemaPath + '/oneOf', { passingSchemas: count }, 'must match exactly one schema in oneOf'));
|
|
@@ -454,16 +744,28 @@ function install(deps) {
|
|
|
454
744
|
}
|
|
455
745
|
return true;
|
|
456
746
|
});
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
+
}
|
|
464
766
|
}
|
|
465
767
|
if (P.not !== undefined) {
|
|
466
|
-
const fn =
|
|
768
|
+
const fn = child(P.not);
|
|
467
769
|
steps.push((data, errors, instancePath, schemaPath, stack) => {
|
|
468
770
|
if (fn.v(data, stack)) {
|
|
469
771
|
if (errors !== NOERRORS) errors.push(err('not', 'not', instancePath, schemaPath + '/not', {}, 'must NOT be valid'));
|
|
@@ -474,36 +776,125 @@ function install(deps) {
|
|
|
474
776
|
vsteps.push((data, stack) => !fn.v(data, stack));
|
|
475
777
|
}
|
|
476
778
|
if (P.if !== undefined) {
|
|
477
|
-
const ifFn =
|
|
478
|
-
const thenFn = P.then !== undefined ?
|
|
479
|
-
const elseFn = P.else !== undefined ?
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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);
|
|
485
796
|
}
|
|
486
|
-
|
|
797
|
+
if (!ok && collect) undo(rec, pl, n0, xl);
|
|
798
|
+
return ok;
|
|
487
799
|
});
|
|
488
|
-
vsteps.push((data, stack) => {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
return
|
|
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;
|
|
493
805
|
}
|
|
806
|
+
if (collect) undo(rec, pl, n0, xl);
|
|
807
|
+
if (elseFn !== null) return elseFn.v(data, stack, rec);
|
|
494
808
|
return true;
|
|
495
809
|
});
|
|
496
810
|
}
|
|
497
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
|
+
|
|
498
889
|
let cfn;
|
|
499
890
|
if (steps.length === 0) cfn = TRUE_FN;
|
|
500
891
|
else if (steps.length === 1) cfn = steps[0];
|
|
501
892
|
else {
|
|
502
893
|
const arr = steps;
|
|
503
|
-
cfn = (d, e, ip, sp, st) => {
|
|
894
|
+
cfn = (d, e, ip, sp, st, rec) => {
|
|
504
895
|
let ok = true;
|
|
505
896
|
for (let i = 0; i < arr.length; i++) {
|
|
506
|
-
if (!arr[i](d, e, ip, sp, st)) {
|
|
897
|
+
if (!arr[i](d, e, ip, sp, st, rec)) {
|
|
507
898
|
if (e === NOERRORS) return false;
|
|
508
899
|
ok = false;
|
|
509
900
|
}
|
|
@@ -516,27 +907,60 @@ function install(deps) {
|
|
|
516
907
|
else if (vsteps.length === 1) vfn = vsteps[0];
|
|
517
908
|
else if (vsteps.length === 2) {
|
|
518
909
|
const [a, b] = vsteps;
|
|
519
|
-
vfn = (d, st) => a(d, st) && b(d, st);
|
|
910
|
+
vfn = (d, st, rec) => a(d, st, rec) && b(d, st, rec);
|
|
520
911
|
} else {
|
|
521
912
|
const arr = vsteps;
|
|
522
|
-
vfn = (d, st) => {
|
|
523
|
-
for (let i = 0; i < arr.length; i++) if (!arr[i](d, st)) return false;
|
|
913
|
+
vfn = (d, st, rec) => {
|
|
914
|
+
for (let i = 0; i < arr.length; i++) if (!arr[i](d, st, rec)) return false;
|
|
524
915
|
return true;
|
|
525
916
|
};
|
|
526
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
|
+
}
|
|
527
942
|
box.c = cfn;
|
|
528
943
|
box.v = vfn;
|
|
529
944
|
pair.c = cfn;
|
|
530
945
|
pair.v = vfn;
|
|
531
|
-
|
|
946
|
+
pair.open = false;
|
|
532
947
|
return pair;
|
|
533
948
|
}
|
|
534
949
|
|
|
535
|
-
// Entry point:
|
|
536
|
-
//
|
|
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.
|
|
537
954
|
function compileInterpreter(interp) {
|
|
538
|
-
|
|
539
|
-
|
|
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 };
|
|
540
964
|
}
|
|
541
965
|
|
|
542
966
|
return { compileInterpreter };
|