@json-schema-engine/core 0.0.1
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/coverage.d.ts +30 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +81 -0
- package/dist/coverage.js.map +1 -0
- package/dist/cursor.d.ts +16 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +28 -0
- package/dist/cursor.js.map +1 -0
- package/dist/dialect.d.ts +316 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +135 -0
- package/dist/dialect.js.map +1 -0
- package/dist/engine.d.ts +203 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +493 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +370 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +49 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +128 -0
- package/dist/json.js.map +1 -0
- package/dist/keywords/applicator.d.ts +57 -0
- package/dist/keywords/applicator.d.ts.map +1 -0
- package/dist/keywords/applicator.js +789 -0
- package/dist/keywords/applicator.js.map +1 -0
- package/dist/keywords/core.d.ts +42 -0
- package/dist/keywords/core.d.ts.map +1 -0
- package/dist/keywords/core.js +139 -0
- package/dist/keywords/core.js.map +1 -0
- package/dist/keywords/format.d.ts +43 -0
- package/dist/keywords/format.d.ts.map +1 -0
- package/dist/keywords/format.js +101 -0
- package/dist/keywords/format.js.map +1 -0
- package/dist/keywords/metaschemas2019.d.ts +3 -0
- package/dist/keywords/metaschemas2019.d.ts.map +1 -0
- package/dist/keywords/metaschemas2019.js +406 -0
- package/dist/keywords/metaschemas2019.js.map +1 -0
- package/dist/keywords/metaschemas2020.d.ts +3 -0
- package/dist/keywords/metaschemas2020.d.ts.map +1 -0
- package/dist/keywords/metaschemas2020.js +441 -0
- package/dist/keywords/metaschemas2020.js.map +1 -0
- package/dist/keywords/metaschemas6.d.ts +3 -0
- package/dist/keywords/metaschemas6.d.ts.map +1 -0
- package/dist/keywords/metaschemas6.js +221 -0
- package/dist/keywords/metaschemas6.js.map +1 -0
- package/dist/keywords/metaschemas7.d.ts +3 -0
- package/dist/keywords/metaschemas7.d.ts.map +1 -0
- package/dist/keywords/metaschemas7.js +248 -0
- package/dist/keywords/metaschemas7.js.map +1 -0
- package/dist/keywords/unevaluated.d.ts +14 -0
- package/dist/keywords/unevaluated.d.ts.map +1 -0
- package/dist/keywords/unevaluated.js +296 -0
- package/dist/keywords/unevaluated.js.map +1 -0
- package/dist/keywords/validation.d.ts +21 -0
- package/dist/keywords/validation.d.ts.map +1 -0
- package/dist/keywords/validation.js +332 -0
- package/dist/keywords/validation.js.map +1 -0
- package/dist/keywords/vocab2019.d.ts +36 -0
- package/dist/keywords/vocab2019.d.ts.map +1 -0
- package/dist/keywords/vocab2019.js +585 -0
- package/dist/keywords/vocab2019.js.map +1 -0
- package/dist/keywords/vocab2020.d.ts +20 -0
- package/dist/keywords/vocab2020.d.ts.map +1 -0
- package/dist/keywords/vocab2020.js +55 -0
- package/dist/keywords/vocab2020.js.map +1 -0
- package/dist/keywords/vocab7.d.ts +46 -0
- package/dist/keywords/vocab7.d.ts.map +1 -0
- package/dist/keywords/vocab7.js +305 -0
- package/dist/keywords/vocab7.js.map +1 -0
- package/dist/loader.d.ts +41 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +13 -0
- package/dist/loader.js.map +1 -0
- package/dist/lowering.d.ts +371 -0
- package/dist/lowering.d.ts.map +1 -0
- package/dist/lowering.js +59 -0
- package/dist/lowering.js.map +1 -0
- package/dist/output.d.ts +251 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +357 -0
- package/dist/output.js.map +1 -0
- package/dist/records.d.ts +71 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +150 -0
- package/dist/records.js.map +1 -0
- package/dist/ref.d.ts +10 -0
- package/dist/ref.d.ts.map +1 -0
- package/dist/ref.js +5 -0
- package/dist/ref.js.map +1 -0
- package/dist/regex.d.ts +45 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +163 -0
- package/dist/regex.js.map +1 -0
- package/dist/registry.d.ts +141 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +407 -0
- package/dist/registry.js.map +1 -0
- package/dist/result.d.ts +109 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +112 -0
- package/dist/result.js.map +1 -0
- package/dist/uri.d.ts +18 -0
- package/dist/uri.d.ts.map +1 -0
- package/dist/uri.js +27 -0
- package/dist/uri.js.map +1 -0
- package/dist/walk.d.ts +25 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +65 -0
- package/dist/walk.js.map +1 -0
- package/package.json +32 -0
- package/src/coverage.ts +88 -0
- package/src/cursor.ts +42 -0
- package/src/dialect.ts +428 -0
- package/src/engine.ts +741 -0
- package/src/index.ts +699 -0
- package/src/json.ts +137 -0
- package/src/keywords/applicator.ts +895 -0
- package/src/keywords/core.ts +168 -0
- package/src/keywords/format.ts +143 -0
- package/src/keywords/metaschemas2019.ts +410 -0
- package/src/keywords/metaschemas2020.ts +443 -0
- package/src/keywords/metaschemas6.ts +226 -0
- package/src/keywords/metaschemas7.ts +253 -0
- package/src/keywords/unevaluated.ts +329 -0
- package/src/keywords/validation.ts +627 -0
- package/src/keywords/vocab2019.ts +676 -0
- package/src/keywords/vocab2020.ts +82 -0
- package/src/keywords/vocab7.ts +393 -0
- package/src/loader.ts +60 -0
- package/src/lowering.ts +414 -0
- package/src/output.ts +651 -0
- package/src/records.ts +226 -0
- package/src/ref.ts +14 -0
- package/src/regex.ts +171 -0
- package/src/registry.ts +481 -0
- package/src/result.ts +252 -0
- package/src/uri.ts +35 -0
- package/src/walk.ts +88 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// Unevaluated vocabulary: the channel-consumer keyword class. Phase 1: runs
|
|
2
|
+
// after every other keyword in the same schema object has merged.
|
|
3
|
+
import { isObject } from "../json.js";
|
|
4
|
+
import { lowerIR } from "../lowering.js";
|
|
5
|
+
import { childCursor } from "../cursor.js";
|
|
6
|
+
import { SELF } from "./core.js";
|
|
7
|
+
import { properties, patternProperties, additionalProperties, prefixItems, items, contains, } from "./applicator.js";
|
|
8
|
+
/** 2020-12 unevaluated vocabulary URI. */
|
|
9
|
+
export const VOCAB_UNEVALUATED = "https://json-schema.org/draft/2020-12/vocab/unevaluated";
|
|
10
|
+
/**
|
|
11
|
+
* EXEMPLAR (consumer class): reads visible dependency records (engine.ts
|
|
12
|
+
* visibility rule), applies the subschema to properties nobody evaluated,
|
|
13
|
+
* and produces like any other applicator.
|
|
14
|
+
*/
|
|
15
|
+
export const unevaluatedProperties = {
|
|
16
|
+
id: `${VOCAB_UNEVALUATED}#unevaluatedProperties`,
|
|
17
|
+
phase: 1,
|
|
18
|
+
analyze: () => ({
|
|
19
|
+
subschemas: SELF.subschemas,
|
|
20
|
+
consumes: [
|
|
21
|
+
properties.id,
|
|
22
|
+
patternProperties.id,
|
|
23
|
+
additionalProperties.id,
|
|
24
|
+
`${VOCAB_UNEVALUATED}#unevaluatedProperties`,
|
|
25
|
+
],
|
|
26
|
+
produces: [`${VOCAB_UNEVALUATED}#unevaluatedProperties`],
|
|
27
|
+
evaluatesNames: { kind: "all" },
|
|
28
|
+
applications: [
|
|
29
|
+
{ path: [], mode: "childSweep", conditional: false, asserts: true },
|
|
30
|
+
],
|
|
31
|
+
}),
|
|
32
|
+
// Static-coverage path only (D9a): the planner classifies this schema
|
|
33
|
+
// object as interpreted when any coverage contributor is dynamic, so
|
|
34
|
+
// lower() is never called with a null coverage.
|
|
35
|
+
lower: (_value, lctx) => {
|
|
36
|
+
// Runtime-coverage path (phase B activates it): fold the unit's runtime
|
|
37
|
+
// channel into an evaluated-name set and sweep the names it does not
|
|
38
|
+
// cover, mirroring evaluate()'s seen-set skip. The object-gated
|
|
39
|
+
// collectedNames produce is the same one the static path emits — its
|
|
40
|
+
// attempted-apply accumulator is exactly the interpreter's `matched`.
|
|
41
|
+
if (lctx.runtimeCoverage()) {
|
|
42
|
+
const f = lctx.binding();
|
|
43
|
+
const b = lctx.binding();
|
|
44
|
+
lctx.emit(lowerIR.when(lowerIR.typeIs(lctx.instance, "object"), [
|
|
45
|
+
{ kind: "coverageFold", half: "names", binding: f },
|
|
46
|
+
{
|
|
47
|
+
kind: "forEachKey",
|
|
48
|
+
target: lctx.instance,
|
|
49
|
+
binding: b,
|
|
50
|
+
body: [
|
|
51
|
+
lowerIR.when(lowerIR.not({
|
|
52
|
+
kind: "coverageCovers",
|
|
53
|
+
fold: f,
|
|
54
|
+
target: { kind: "binding", id: b },
|
|
55
|
+
}), [
|
|
56
|
+
{
|
|
57
|
+
kind: "apply",
|
|
58
|
+
apply: {
|
|
59
|
+
path: [],
|
|
60
|
+
cursor: {
|
|
61
|
+
kind: "child",
|
|
62
|
+
of: { kind: "here" },
|
|
63
|
+
segment: { kind: "binding", id: b },
|
|
64
|
+
},
|
|
65
|
+
fold: "allMustPass",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
]),
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{ kind: "produce", value: { kind: "collectedNames" } },
|
|
72
|
+
]));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const coverage = lctx.staticCoverage();
|
|
76
|
+
if (coverage === null) {
|
|
77
|
+
throw new Error("unevaluatedProperties lowering requires static coverage (planner bug)");
|
|
78
|
+
}
|
|
79
|
+
// The sweep is statically vacuous when sibling coverage is total, but the
|
|
80
|
+
// produce is not: evaluate() emits an (empty) names annotation for every
|
|
81
|
+
// object regardless. So the object-type guard always wraps a produce; the
|
|
82
|
+
// sweep is added only when some name can still be unevaluated.
|
|
83
|
+
const body = [];
|
|
84
|
+
if (!coverage.coversAllNames) {
|
|
85
|
+
const b = lctx.binding();
|
|
86
|
+
const covered = [
|
|
87
|
+
...coverage.names.map((n) => lowerIR.cmp("===", { kind: "binding", id: b }, lowerIR.constant(n))),
|
|
88
|
+
...coverage.patterns.map((p) => lowerIR.regexTest(p, { kind: "binding", id: b })),
|
|
89
|
+
];
|
|
90
|
+
body.push({
|
|
91
|
+
kind: "forEachKey",
|
|
92
|
+
target: lctx.instance,
|
|
93
|
+
binding: b,
|
|
94
|
+
body: [
|
|
95
|
+
lowerIR.when(covered.length === 0
|
|
96
|
+
? lowerIR.constant(true)
|
|
97
|
+
: lowerIR.not(lowerIR.or(...covered)), [
|
|
98
|
+
{
|
|
99
|
+
kind: "apply",
|
|
100
|
+
apply: {
|
|
101
|
+
path: [],
|
|
102
|
+
cursor: {
|
|
103
|
+
kind: "child",
|
|
104
|
+
of: { kind: "here" },
|
|
105
|
+
segment: { kind: "binding", id: b },
|
|
106
|
+
},
|
|
107
|
+
fold: "allMustPass",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
]),
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
body.push({ kind: "produce", value: { kind: "collectedNames" } });
|
|
115
|
+
// Produce iff the instance is an object (nothing otherwise), matching
|
|
116
|
+
// evaluate()'s object-type guard before ctx.produce.
|
|
117
|
+
lctx.emit(lowerIR.when(lowerIR.typeIs(lctx.instance, "object"), body));
|
|
118
|
+
},
|
|
119
|
+
evaluate: (_value, cursor, ctx) => {
|
|
120
|
+
if (!isObject(cursor.value))
|
|
121
|
+
return true;
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
for (const p of ctx.visible([
|
|
124
|
+
properties.id,
|
|
125
|
+
patternProperties.id,
|
|
126
|
+
additionalProperties.id,
|
|
127
|
+
unevaluatedProperties.id,
|
|
128
|
+
])) {
|
|
129
|
+
for (const name of p.data)
|
|
130
|
+
seen.add(name);
|
|
131
|
+
}
|
|
132
|
+
let ok = true;
|
|
133
|
+
const matched = [];
|
|
134
|
+
for (const name of Object.keys(cursor.value)) {
|
|
135
|
+
if (seen.has(name))
|
|
136
|
+
continue;
|
|
137
|
+
matched.push(name);
|
|
138
|
+
if (!ctx.apply(["unevaluatedProperties"], childCursor(cursor, name, cursor.value[name])))
|
|
139
|
+
ok = false;
|
|
140
|
+
}
|
|
141
|
+
// Dependency data only from an accepting keyword (Appendix D; see
|
|
142
|
+
// applicator.ts properties).
|
|
143
|
+
if (ok)
|
|
144
|
+
ctx.produce(matched);
|
|
145
|
+
return ok;
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
/** Same consumer shape as {@link unevaluatedProperties}, over array indexes not covered by `prefixItems`/`items`/`contains`. */
|
|
149
|
+
export const unevaluatedItems = {
|
|
150
|
+
id: `${VOCAB_UNEVALUATED}#unevaluatedItems`,
|
|
151
|
+
phase: 1,
|
|
152
|
+
analyze: () => ({
|
|
153
|
+
subschemas: SELF.subschemas,
|
|
154
|
+
consumes: [
|
|
155
|
+
prefixItems.id,
|
|
156
|
+
items.id,
|
|
157
|
+
contains.id,
|
|
158
|
+
`${VOCAB_UNEVALUATED}#unevaluatedItems`,
|
|
159
|
+
],
|
|
160
|
+
produces: [`${VOCAB_UNEVALUATED}#unevaluatedItems`],
|
|
161
|
+
evaluatesIndexes: { kind: "all" },
|
|
162
|
+
applications: [
|
|
163
|
+
{ path: [], mode: "childSweep", conditional: false, asserts: true },
|
|
164
|
+
],
|
|
165
|
+
}),
|
|
166
|
+
// Static-coverage path only (D9a), same discipline as
|
|
167
|
+
// unevaluatedProperties: the planner classifies this schema object as
|
|
168
|
+
// interpreted whenever any index-coverage contributor is dynamic (e.g. a
|
|
169
|
+
// sibling `contains`, whose coverage is instance-dependent), so lower() is
|
|
170
|
+
// never called with a null/incomplete coverage.
|
|
171
|
+
lower: (_value, lctx) => {
|
|
172
|
+
// Runtime-coverage path (phase B activates it): fold the unit's runtime
|
|
173
|
+
// channel into a coveredPrefix/coveredIdx summary over this array's
|
|
174
|
+
// length and sweep the indexes it does not cover, mirroring evaluate()'s
|
|
175
|
+
// skip loop. The appliedTrue produce is the same one the static path
|
|
176
|
+
// emits (any attempted apply -> true, the interpreter's `applied` flag).
|
|
177
|
+
if (lctx.runtimeCoverage()) {
|
|
178
|
+
const f = lctx.binding();
|
|
179
|
+
const b = lctx.binding();
|
|
180
|
+
lctx.emit(lowerIR.when(lowerIR.typeIs(lctx.instance, "array"), [
|
|
181
|
+
{ kind: "coverageFold", half: "indexes", binding: f },
|
|
182
|
+
{
|
|
183
|
+
kind: "forEachIndex",
|
|
184
|
+
target: lctx.instance,
|
|
185
|
+
binding: b,
|
|
186
|
+
start: 0,
|
|
187
|
+
body: [
|
|
188
|
+
lowerIR.when(lowerIR.not({
|
|
189
|
+
kind: "coverageCovers",
|
|
190
|
+
fold: f,
|
|
191
|
+
target: { kind: "binding", id: b },
|
|
192
|
+
}), [
|
|
193
|
+
{
|
|
194
|
+
kind: "apply",
|
|
195
|
+
apply: {
|
|
196
|
+
path: [],
|
|
197
|
+
cursor: {
|
|
198
|
+
kind: "child",
|
|
199
|
+
of: { kind: "here" },
|
|
200
|
+
segment: { kind: "binding", id: b },
|
|
201
|
+
},
|
|
202
|
+
fold: "allMustPass",
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
]),
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
kind: "produce",
|
|
210
|
+
value: { kind: "collectedIndexes", render: "appliedTrue" },
|
|
211
|
+
},
|
|
212
|
+
]));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const coverage = lctx.staticCoverage();
|
|
216
|
+
if (coverage === null) {
|
|
217
|
+
throw new Error("unevaluatedItems lowering requires static coverage (planner bug)");
|
|
218
|
+
}
|
|
219
|
+
if (coverage.coversAllIndexes)
|
|
220
|
+
return; // statically vacuous
|
|
221
|
+
const b = lctx.binding();
|
|
222
|
+
lctx.emit(lowerIR.when(lowerIR.typeIs(lctx.instance, "array"), [
|
|
223
|
+
{
|
|
224
|
+
kind: "forEachIndex",
|
|
225
|
+
target: lctx.instance,
|
|
226
|
+
binding: b,
|
|
227
|
+
start: coverage.prefixCount,
|
|
228
|
+
body: [
|
|
229
|
+
{
|
|
230
|
+
kind: "apply",
|
|
231
|
+
apply: {
|
|
232
|
+
path: [],
|
|
233
|
+
cursor: {
|
|
234
|
+
kind: "child",
|
|
235
|
+
of: { kind: "here" },
|
|
236
|
+
segment: { kind: "binding", id: b },
|
|
237
|
+
},
|
|
238
|
+
fold: "allMustPass",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
]));
|
|
244
|
+
// Annotation: true iff it applied to any unevaluated index.
|
|
245
|
+
lctx.emit({
|
|
246
|
+
kind: "produce",
|
|
247
|
+
value: { kind: "collectedIndexes", render: "appliedTrue" },
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
evaluate: (_value, cursor, ctx) => {
|
|
251
|
+
if (!Array.isArray(cursor.value))
|
|
252
|
+
return true;
|
|
253
|
+
const length = cursor.value.length;
|
|
254
|
+
let coveredPrefix = 0;
|
|
255
|
+
const coveredIdx = new Set();
|
|
256
|
+
for (const p of ctx.visible([
|
|
257
|
+
prefixItems.id,
|
|
258
|
+
items.id,
|
|
259
|
+
contains.id,
|
|
260
|
+
unevaluatedItems.id,
|
|
261
|
+
])) {
|
|
262
|
+
if (p.behaviorId === contains.id) {
|
|
263
|
+
if (p.data === true)
|
|
264
|
+
coveredPrefix = length;
|
|
265
|
+
else
|
|
266
|
+
for (const i of p.data)
|
|
267
|
+
coveredIdx.add(i);
|
|
268
|
+
}
|
|
269
|
+
else if (p.data === true) {
|
|
270
|
+
coveredPrefix = length;
|
|
271
|
+
}
|
|
272
|
+
else if (p.behaviorId === prefixItems.id) {
|
|
273
|
+
coveredPrefix = Math.max(coveredPrefix, p.data + 1);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
let ok = true;
|
|
277
|
+
let applied = false;
|
|
278
|
+
for (let i = coveredPrefix; i < length; i++) {
|
|
279
|
+
if (coveredIdx.has(i))
|
|
280
|
+
continue;
|
|
281
|
+
applied = true;
|
|
282
|
+
if (!ctx.apply(["unevaluatedItems"], childCursor(cursor, i, cursor.value[i]))) {
|
|
283
|
+
ok = false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (applied && ok)
|
|
287
|
+
ctx.produce(true);
|
|
288
|
+
return ok;
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
/** The 2020-12 unevaluated vocabulary's keyword behaviors, by name. */
|
|
292
|
+
export const unevaluatedVocabulary = {
|
|
293
|
+
unevaluatedProperties,
|
|
294
|
+
unevaluatedItems,
|
|
295
|
+
};
|
|
296
|
+
//# sourceMappingURL=unevaluated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unevaluated.js","sourceRoot":"","sources":["../../src/keywords/unevaluated.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { KeywordBehavior } from "../dialect.js";
|
|
2
|
+
/** 2020-12 validation vocabulary URI. */
|
|
3
|
+
export declare const VOCAB_VALIDATION = "https://json-schema.org/draft/2020-12/vocab/validation";
|
|
4
|
+
/**
|
|
5
|
+
* EXEMPLAR (assertion class): inspect the instance, report one error on
|
|
6
|
+
* failure, return the verdict. `pattern` compiles its regex through the
|
|
7
|
+
* context so a caller-supplied engine and the per-engine cache apply
|
|
8
|
+
* (see regex.ts), and declares the pattern via analyze() so
|
|
9
|
+
* `rejectUnsafeRegex` can screen it at registration.
|
|
10
|
+
*/
|
|
11
|
+
export declare const pattern: KeywordBehavior;
|
|
12
|
+
/**
|
|
13
|
+
* `instance` is an exact multiple of `divisor` (`multipleOf`'s predicate,
|
|
14
|
+
* shared by both tiers). Scales both operands to integers by the same power
|
|
15
|
+
* of ten before the modulus so binary-float rounding doesn't misfire (e.g.
|
|
16
|
+
* 0.0075 % 0.0001 in raw floats).
|
|
17
|
+
*/
|
|
18
|
+
export declare function isMultipleOf(instance: number, divisor: number): boolean;
|
|
19
|
+
/** The 2020-12 validation vocabulary's keyword behaviors, by name. */
|
|
20
|
+
export declare const validationVocabulary: Record<string, KeywordBehavior>;
|
|
21
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/keywords/validation.ts"],"names":[],"mappings":"AAYA,OAAO,EAAe,eAAe,EAAkB,MAAM,eAAe,CAAC;AAS7E,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,2DAC6B,CAAC;AA8D3D;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,eA4BrB,CAAC;AAsBF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAYvE;AAED,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CA+chE,CAAC"}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// Validation vocabulary: pure assertions. Assertions never produce and
|
|
2
|
+
// never descend.
|
|
3
|
+
import { isObject, jsonTypeOf, jsonEqual, codePointLength, firstDuplicatePair, } from "../json.js";
|
|
4
|
+
import { lowerIR, } from "../lowering.js";
|
|
5
|
+
/** 2020-12 validation vocabulary URI. */
|
|
6
|
+
export const VOCAB_VALIDATION = "https://json-schema.org/draft/2020-12/vocab/validation";
|
|
7
|
+
const id = (name) => `${VOCAB_VALIDATION}#${name}`;
|
|
8
|
+
const assertion = (name, test, message, params) => ({
|
|
9
|
+
id: id(name),
|
|
10
|
+
evaluate: (value, cursor, ctx) => {
|
|
11
|
+
if (test(value, cursor.value))
|
|
12
|
+
return true;
|
|
13
|
+
ctx.error(message(value), params?.(value));
|
|
14
|
+
return false;
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Shared shape for the numeric/string/array/object "guard, then compare"
|
|
19
|
+
* assertions (minLength/maxLength/minimum/maximum/exclusiveMinimum/
|
|
20
|
+
* exclusiveMaximum/minItems/maxItems/minProperties/maxProperties): the
|
|
21
|
+
* keyword is vacuously true unless the instance has the guarded type, in
|
|
22
|
+
* which case a comparison against the (hoisted) keyword value must hold.
|
|
23
|
+
* Mirrors each assertion's `test` above exactly — same guard, same
|
|
24
|
+
* direction of comparison — via `when(and(guard, not(cmp)), [fail])`.
|
|
25
|
+
*/
|
|
26
|
+
const guardedCompare = (guardType, measure, op, message) => (value, lctx) => {
|
|
27
|
+
lctx.emit(lowerIR.when(lowerIR.and(lowerIR.typeIs(lctx.instance, guardType), lowerIR.not(lowerIR.cmp(op, measure(lctx), lowerIR.constant(value)))), [lowerIR.failWith({ limit: lowerIR.constant(value) }, message(value))]));
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The `limit` params shape shared by every guard-then-compare bounds
|
|
31
|
+
* keyword: minLength/maxLength, minItems/maxItems, minProperties/
|
|
32
|
+
* maxProperties, and minimum/maximum/exclusiveMinimum/exclusiveMaximum.
|
|
33
|
+
*/
|
|
34
|
+
const limitParams = (value) => ({ limit: value });
|
|
35
|
+
const arrayLengthMeasure = (lctx) => lowerIR.helper("lengthOf", lctx.instance);
|
|
36
|
+
const propertyCountMeasure = (lctx) => lowerIR.helper("lengthOf", lowerIR.helper("keysOf", lctx.instance));
|
|
37
|
+
const numberMeasure = (lctx) => lctx.instance;
|
|
38
|
+
const typeMatches = (t, v) => t === "integer"
|
|
39
|
+
? typeof v === "number" && Number.isInteger(v)
|
|
40
|
+
: jsonTypeOf(v) === t;
|
|
41
|
+
/**
|
|
42
|
+
* EXEMPLAR (assertion class): inspect the instance, report one error on
|
|
43
|
+
* failure, return the verdict. `pattern` compiles its regex through the
|
|
44
|
+
* context so a caller-supplied engine and the per-engine cache apply
|
|
45
|
+
* (see regex.ts), and declares the pattern via analyze() so
|
|
46
|
+
* `rejectUnsafeRegex` can screen it at registration.
|
|
47
|
+
*/
|
|
48
|
+
export const pattern = {
|
|
49
|
+
id: id("pattern"),
|
|
50
|
+
analyze: (value) => ({
|
|
51
|
+
regexes: typeof value === "string" ? [value] : [],
|
|
52
|
+
}),
|
|
53
|
+
evaluate: (value, cursor, ctx) => {
|
|
54
|
+
const instance = cursor.value;
|
|
55
|
+
if (typeof instance !== "string")
|
|
56
|
+
return true;
|
|
57
|
+
if (ctx.compileRegex(value).test(instance))
|
|
58
|
+
return true;
|
|
59
|
+
ctx.error("does not match required pattern", { pattern: value });
|
|
60
|
+
return false;
|
|
61
|
+
},
|
|
62
|
+
lower: (value, lctx) => {
|
|
63
|
+
lctx.emit(lowerIR.when(lowerIR.and(lowerIR.typeIs(lctx.instance, "string"), lowerIR.not(lowerIR.regexTest(value, lctx.instance))), [
|
|
64
|
+
lowerIR.failWith({ pattern: lowerIR.constant(value) }, "does not match required pattern"),
|
|
65
|
+
]));
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
// Number of digits after the decimal point in `n`'s shortest representation,
|
|
69
|
+
// including exponential notation (1e-8 has 8). `%` on the raw floats fails
|
|
70
|
+
// suite cases like 0.0075 % 0.0001 (binary rounding); scaling both operands
|
|
71
|
+
// to integers by the same power of ten sidesteps that at the cost of this
|
|
72
|
+
// string inspection.
|
|
73
|
+
function decimalDigits(n) {
|
|
74
|
+
if (!Number.isFinite(n))
|
|
75
|
+
return 0;
|
|
76
|
+
const s = Math.abs(n).toString();
|
|
77
|
+
const eIndex = s.indexOf("e");
|
|
78
|
+
if (eIndex !== -1) {
|
|
79
|
+
const mantissa = s.slice(0, eIndex);
|
|
80
|
+
const exponent = Number(s.slice(eIndex + 1));
|
|
81
|
+
const dot = mantissa.indexOf(".");
|
|
82
|
+
const mantissaDigits = dot === -1 ? 0 : mantissa.length - dot - 1;
|
|
83
|
+
return Math.max(0, mantissaDigits - exponent);
|
|
84
|
+
}
|
|
85
|
+
const dot = s.indexOf(".");
|
|
86
|
+
return dot === -1 ? 0 : s.length - dot - 1;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* `instance` is an exact multiple of `divisor` (`multipleOf`'s predicate,
|
|
90
|
+
* shared by both tiers). Scales both operands to integers by the same power
|
|
91
|
+
* of ten before the modulus so binary-float rounding doesn't misfire (e.g.
|
|
92
|
+
* 0.0075 % 0.0001 in raw floats).
|
|
93
|
+
*/
|
|
94
|
+
export function isMultipleOf(instance, divisor) {
|
|
95
|
+
const scale = 10 ** Math.max(decimalDigits(instance), decimalDigits(divisor));
|
|
96
|
+
const scaledInstance = instance * scale;
|
|
97
|
+
const scaledDivisor = divisor * scale;
|
|
98
|
+
// Suite case: the scaling itself can overflow to Infinity for huge
|
|
99
|
+
// instances against a small divisor; that must read as non-multiple, not
|
|
100
|
+
// throw or silently misvalidate.
|
|
101
|
+
if (Number.isFinite(scaledInstance) && Number.isFinite(scaledDivisor)) {
|
|
102
|
+
return Math.round(scaledInstance) % Math.round(scaledDivisor) === 0;
|
|
103
|
+
}
|
|
104
|
+
const quotient = instance / divisor;
|
|
105
|
+
return Number.isFinite(quotient) && Number.isInteger(quotient);
|
|
106
|
+
}
|
|
107
|
+
/** The 2020-12 validation vocabulary's keyword behaviors, by name. */
|
|
108
|
+
export const validationVocabulary = {
|
|
109
|
+
type: {
|
|
110
|
+
id: id("type"),
|
|
111
|
+
evaluate: (value, cursor, ctx) => {
|
|
112
|
+
const ok = Array.isArray(value)
|
|
113
|
+
? value.some((t) => typeMatches(t, cursor.value))
|
|
114
|
+
: typeMatches(value, cursor.value);
|
|
115
|
+
if (!ok)
|
|
116
|
+
ctx.error(`expected type ${JSON.stringify(value)}`, {
|
|
117
|
+
expected: value,
|
|
118
|
+
});
|
|
119
|
+
return ok;
|
|
120
|
+
},
|
|
121
|
+
lower: (value, lctx) => {
|
|
122
|
+
const types = (Array.isArray(value) ? value : [value]);
|
|
123
|
+
lctx.emit(lowerIR.when(lowerIR.not(lowerIR.typeIs(lctx.instance, ...types)), [
|
|
124
|
+
lowerIR.failWith({ expected: lowerIR.constant(value) }, `expected type ${JSON.stringify(value)}`),
|
|
125
|
+
]));
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
enum: {
|
|
129
|
+
...assertion("enum", (value, instance) => value.some((x) => jsonEqual(x, instance)), () => "not one of the allowed values", (value) => ({ allowedValues: value })),
|
|
130
|
+
lower: (value, lctx) => {
|
|
131
|
+
const alternatives = value;
|
|
132
|
+
const params = { allowedValues: lowerIR.constant(value) };
|
|
133
|
+
// An empty enum can never match (some() over zero alternatives is
|
|
134
|
+
// false); guard explicitly since lowerIR.or() with zero parts has no
|
|
135
|
+
// meaningful "no alternatives matched" expression to negate.
|
|
136
|
+
lctx.emit(alternatives.length === 0
|
|
137
|
+
? lowerIR.failWith(params, "not one of the allowed values")
|
|
138
|
+
: lowerIR.when(lowerIR.not(lowerIR.or(...alternatives.map((x) => lowerIR.helper("jsonEqual", lowerIR.constant(x), lctx.instance)))), [lowerIR.failWith(params, "not one of the allowed values")]));
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
const: {
|
|
142
|
+
...assertion("const", (value, instance) => jsonEqual(value, instance), () => "does not equal the required constant", (value) => ({ allowedValue: value })),
|
|
143
|
+
lower: (value, lctx) => {
|
|
144
|
+
lctx.emit(lowerIR.when(lowerIR.not(lowerIR.helper("jsonEqual", lowerIR.constant(value), lctx.instance)), [
|
|
145
|
+
lowerIR.failWith({ allowedValue: lowerIR.constant(value) }, "does not equal the required constant"),
|
|
146
|
+
]));
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
pattern,
|
|
150
|
+
minLength: {
|
|
151
|
+
...assertion("minLength", (value, instance) => typeof instance !== "string" ||
|
|
152
|
+
codePointLength(instance) >= value, (value) => `must be at least ${value} characters`, limitParams),
|
|
153
|
+
// Violation = code points < n. UTF-16 units bound points from above
|
|
154
|
+
// (points <= units) and below (points >= units/2), so units alone decide
|
|
155
|
+
// outside [n, 2n) — codePointLength runs only in that window (D9).
|
|
156
|
+
lower: (value, lctx) => {
|
|
157
|
+
const n = value;
|
|
158
|
+
const len = lowerIR.helper("lengthOf", lctx.instance);
|
|
159
|
+
const cpl = lowerIR.helper("codePointLength", lctx.instance);
|
|
160
|
+
lctx.emit(lowerIR.when(lowerIR.and(lowerIR.typeIs(lctx.instance, "string"), lowerIR.or(lowerIR.cmp("<", len, lowerIR.constant(n)), lowerIR.and(lowerIR.cmp("<", len, lowerIR.constant(2 * n)), lowerIR.cmp("<", cpl, lowerIR.constant(n))))), [
|
|
161
|
+
lowerIR.failWith({ limit: lowerIR.constant(n) }, `must be at least ${n} characters`),
|
|
162
|
+
]));
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
maxLength: {
|
|
166
|
+
...assertion("maxLength", (value, instance) => typeof instance !== "string" ||
|
|
167
|
+
codePointLength(instance) <= value, (value) => `must be at most ${value} characters`, limitParams),
|
|
168
|
+
// Violation = code points > n; units <= n implies points <= n, so the
|
|
169
|
+
// expensive count runs only when units exceed the bound (D9).
|
|
170
|
+
lower: (value, lctx) => {
|
|
171
|
+
const n = value;
|
|
172
|
+
lctx.emit(lowerIR.when(lowerIR.and(lowerIR.typeIs(lctx.instance, "string"), lowerIR.cmp(">", lowerIR.helper("lengthOf", lctx.instance), lowerIR.constant(n)), lowerIR.cmp(">", lowerIR.helper("codePointLength", lctx.instance), lowerIR.constant(n))), [
|
|
173
|
+
lowerIR.failWith({ limit: lowerIR.constant(n) }, `must be at most ${n} characters`),
|
|
174
|
+
]));
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
minimum: {
|
|
178
|
+
...assertion("minimum", (value, instance) => typeof instance !== "number" || instance >= value, (value) => `must be >= ${value}`, limitParams),
|
|
179
|
+
lower: guardedCompare("number", numberMeasure, ">=", (value) => `must be >= ${value}`),
|
|
180
|
+
},
|
|
181
|
+
maximum: {
|
|
182
|
+
...assertion("maximum", (value, instance) => typeof instance !== "number" || instance <= value, (value) => `must be <= ${value}`, limitParams),
|
|
183
|
+
lower: guardedCompare("number", numberMeasure, "<=", (value) => `must be <= ${value}`),
|
|
184
|
+
},
|
|
185
|
+
exclusiveMinimum: {
|
|
186
|
+
...assertion("exclusiveMinimum", (value, instance) => typeof instance !== "number" || instance > value, (value) => `must be > ${value}`, limitParams),
|
|
187
|
+
lower: guardedCompare("number", numberMeasure, ">", (value) => `must be > ${value}`),
|
|
188
|
+
},
|
|
189
|
+
exclusiveMaximum: {
|
|
190
|
+
...assertion("exclusiveMaximum", (value, instance) => typeof instance !== "number" || instance < value, (value) => `must be < ${value}`, limitParams),
|
|
191
|
+
lower: guardedCompare("number", numberMeasure, "<", (value) => `must be < ${value}`),
|
|
192
|
+
},
|
|
193
|
+
minItems: {
|
|
194
|
+
...assertion("minItems", (value, instance) => !Array.isArray(instance) || instance.length >= value, (value) => `must have at least ${value} items`, limitParams),
|
|
195
|
+
lower: guardedCompare("array", arrayLengthMeasure, ">=", (value) => `must have at least ${value} items`),
|
|
196
|
+
},
|
|
197
|
+
maxItems: {
|
|
198
|
+
...assertion("maxItems", (value, instance) => !Array.isArray(instance) || instance.length <= value, (value) => `must have at most ${value} items`, limitParams),
|
|
199
|
+
lower: guardedCompare("array", arrayLengthMeasure, "<=", (value) => `must have at most ${value} items`),
|
|
200
|
+
},
|
|
201
|
+
minProperties: {
|
|
202
|
+
...assertion("minProperties", (value, instance) => !isObject(instance) ||
|
|
203
|
+
Object.keys(instance).length >= value, (value) => `must have at least ${value} properties`, limitParams),
|
|
204
|
+
lower: guardedCompare("object", propertyCountMeasure, ">=", (value) => `must have at least ${value} properties`),
|
|
205
|
+
},
|
|
206
|
+
maxProperties: {
|
|
207
|
+
...assertion("maxProperties", (value, instance) => !isObject(instance) ||
|
|
208
|
+
Object.keys(instance).length <= value, (value) => `must have at most ${value} properties`, limitParams),
|
|
209
|
+
lower: guardedCompare("object", propertyCountMeasure, "<=", (value) => `must have at most ${value} properties`),
|
|
210
|
+
},
|
|
211
|
+
required: {
|
|
212
|
+
id: id("required"),
|
|
213
|
+
evaluate: (value, cursor, ctx) => {
|
|
214
|
+
if (!isObject(cursor.value))
|
|
215
|
+
return true;
|
|
216
|
+
let ok = true;
|
|
217
|
+
for (const name of value) {
|
|
218
|
+
if (!Object.hasOwn(cursor.value, name)) {
|
|
219
|
+
ctx.error(`missing required property '${name}'`, {
|
|
220
|
+
missingProperty: name,
|
|
221
|
+
});
|
|
222
|
+
ok = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return ok;
|
|
226
|
+
},
|
|
227
|
+
lower: (value, lctx) => {
|
|
228
|
+
lctx.emit(lowerIR.when(lowerIR.typeIs(lctx.instance, "object"), [
|
|
229
|
+
...value.map((name) => lowerIR.when(lowerIR.not({
|
|
230
|
+
kind: "hasOwn",
|
|
231
|
+
target: lctx.instance,
|
|
232
|
+
key: name,
|
|
233
|
+
}), [
|
|
234
|
+
lowerIR.failWith({ missingProperty: lowerIR.constant(name) }, `missing required property '${name}'`),
|
|
235
|
+
])),
|
|
236
|
+
]));
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
multipleOf: {
|
|
240
|
+
...assertion("multipleOf", (value, instance) => typeof instance !== "number" || isMultipleOf(instance, value), (value) => `must be a multiple of ${value}`, (value) => ({ multipleOf: value })),
|
|
241
|
+
lower: (value, lctx) => {
|
|
242
|
+
lctx.emit(lowerIR.when(lowerIR.and(lowerIR.typeIs(lctx.instance, "number"), lowerIR.not(lowerIR.helper("isMultipleOf", lctx.instance, lowerIR.constant(value)))), [
|
|
243
|
+
lowerIR.failWith({ multipleOf: lowerIR.constant(value) }, `must be a multiple of ${value}`),
|
|
244
|
+
]));
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
uniqueItems: {
|
|
248
|
+
id: id("uniqueItems"),
|
|
249
|
+
evaluate: (value, cursor, ctx) => {
|
|
250
|
+
if (value !== true || !Array.isArray(cursor.value))
|
|
251
|
+
return true;
|
|
252
|
+
const pair = firstDuplicatePair(cursor.value);
|
|
253
|
+
if (pair === null)
|
|
254
|
+
return true;
|
|
255
|
+
const [j, i] = pair;
|
|
256
|
+
ctx.error(`items at ${j} and ${i} are not unique`, {
|
|
257
|
+
duplicates: [j, i],
|
|
258
|
+
});
|
|
259
|
+
return false;
|
|
260
|
+
},
|
|
261
|
+
// evaluate() returns true (vacuously) whenever `value !== true` — mirror
|
|
262
|
+
// that by emitting nothing at all when the keyword value isn't literally
|
|
263
|
+
// `true` (the message text can't cite specific indexes at compile time,
|
|
264
|
+
// but the assertion itself does not depend on them: any duplicate fails).
|
|
265
|
+
lower: (value, lctx) => {
|
|
266
|
+
if (value !== true)
|
|
267
|
+
return;
|
|
268
|
+
// The pair lookup in the message runs only on the failure path
|
|
269
|
+
// (D9e), so the duplicate scan's cost is not doubled for valid data.
|
|
270
|
+
const pair = lowerIR.helper("firstDuplicatePair", lctx.instance);
|
|
271
|
+
lctx.emit(lowerIR.when(lowerIR.and(lowerIR.typeIs(lctx.instance, "array"), lowerIR.helper("hasDuplicateItems", lctx.instance)), [
|
|
272
|
+
lowerIR.failWith({ duplicates: pair }, "items at ", { kind: "item", target: pair, index: lowerIR.constant(0) }, " and ", { kind: "item", target: pair, index: lowerIR.constant(1) }, " are not unique"),
|
|
273
|
+
]));
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
dependentRequired: {
|
|
277
|
+
id: id("dependentRequired"),
|
|
278
|
+
evaluate: (value, cursor, ctx) => {
|
|
279
|
+
if (!isObject(cursor.value))
|
|
280
|
+
return true;
|
|
281
|
+
const instance = cursor.value;
|
|
282
|
+
let ok = true;
|
|
283
|
+
for (const [name, deps] of Object.entries(value)) {
|
|
284
|
+
if (!Object.hasOwn(instance, name))
|
|
285
|
+
continue;
|
|
286
|
+
for (const dep of deps) {
|
|
287
|
+
if (!Object.hasOwn(instance, dep)) {
|
|
288
|
+
ctx.error(`'${name}' requires '${dep}' to be present`, {
|
|
289
|
+
property: name,
|
|
290
|
+
missingProperty: dep,
|
|
291
|
+
});
|
|
292
|
+
ok = false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return ok;
|
|
297
|
+
},
|
|
298
|
+
lower: (value, lctx) => {
|
|
299
|
+
lctx.emit(lowerIR.when(lowerIR.typeIs(lctx.instance, "object"), [
|
|
300
|
+
...Object.entries(value).map(([name, deps]) => lowerIR.when({ kind: "hasOwn", target: lctx.instance, key: name }, deps.map((dep) => lowerIR.when(lowerIR.not({
|
|
301
|
+
kind: "hasOwn",
|
|
302
|
+
target: lctx.instance,
|
|
303
|
+
key: dep,
|
|
304
|
+
}), [
|
|
305
|
+
lowerIR.failWith({
|
|
306
|
+
property: lowerIR.constant(name),
|
|
307
|
+
missingProperty: lowerIR.constant(dep),
|
|
308
|
+
}, `'${name}' requires '${dep}' to be present`),
|
|
309
|
+
])))),
|
|
310
|
+
]));
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
// minContains/maxContains have no assertion of their own; `contains` reads
|
|
314
|
+
// them as inert siblings (applicator.ts), the same way `if` drives `then`/
|
|
315
|
+
// `else`. They must still be registered so unknown-keyword handling and the
|
|
316
|
+
// registration walk don't treat them as annotation-only or absent.
|
|
317
|
+
minContains: {
|
|
318
|
+
id: id("minContains"),
|
|
319
|
+
evaluate: () => true,
|
|
320
|
+
lower: () => {
|
|
321
|
+
/* contains reads this sibling directly (lctx.schema.minContains) */
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
maxContains: {
|
|
325
|
+
id: id("maxContains"),
|
|
326
|
+
evaluate: () => true,
|
|
327
|
+
lower: () => {
|
|
328
|
+
/* contains reads this sibling directly (lctx.schema.maxContains) */
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
//# sourceMappingURL=validation.js.map
|