@oh-my-pi/omptype 17.2.7 → 17.2.8
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 +23 -0
- package/dist/js/compile.js +414 -219
- package/dist/js/errors.js +236 -41
- package/dist/js/from-json-schema.js +234 -0
- package/dist/js/index.js +1 -0
- package/dist/js/interp.js +514 -132
- package/dist/js/ir.js +972 -202
- package/dist/js/json-schema.js +73 -34
- package/dist/js/keywords.js +108 -1
- package/dist/js/type.js +2156 -172
- package/dist/js/typebox.js +41 -32
- package/dist/js/zod.js +2 -2
- package/dist/types/compile.d.ts +1 -1
- package/dist/types/errors.d.ts +19 -18
- package/dist/types/from-json-schema.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/infer.d.ts +45 -6
- package/dist/types/interp.d.ts +9 -2
- package/dist/types/ir.d.ts +57 -6
- package/dist/types/json-schema.d.ts +10 -1
- package/dist/types/type.d.ts +324 -48
- package/dist/types/typebox.d.ts +78 -50
- package/package.json +5 -1
- package/src/compile.ts +598 -243
- package/src/errors.ts +247 -49
- package/src/from-json-schema.ts +231 -0
- package/src/index.ts +1 -0
- package/src/infer.ts +107 -32
- package/src/interp.ts +457 -118
- package/src/ir.ts +981 -212
- package/src/json-schema.ts +82 -34
- package/src/keywords.ts +111 -1
- package/src/type.ts +2760 -271
- package/src/typebox.ts +141 -98
- package/src/zod.ts +1 -1
package/dist/js/interp.js
CHANGED
|
@@ -9,13 +9,37 @@
|
|
|
9
9
|
* - failure returns an `OmpErrors` with a single fast-fail entry
|
|
10
10
|
*/
|
|
11
11
|
import { MISSING, OmpErrors } from "./errors.js";
|
|
12
|
-
import { expectedOf, hasMorph } from "./ir.js";
|
|
12
|
+
import { expectedOf, hasAlias, hasMorph } from "./ir.js";
|
|
13
13
|
const own = Object.prototype.hasOwnProperty;
|
|
14
|
-
/**
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/** Return an independent runtime value for a prevalidated static default. */
|
|
15
|
+
export function materializeDefault(payload) {
|
|
16
|
+
if (payload === null || typeof payload !== "object")
|
|
17
|
+
return payload;
|
|
18
|
+
if (payload instanceof Date)
|
|
19
|
+
return new Date(payload);
|
|
20
|
+
return structuredClone(payload);
|
|
21
|
+
}
|
|
22
|
+
let activeVisits;
|
|
23
|
+
let activeChecks;
|
|
24
|
+
/**
|
|
25
|
+
* Validate `value` against `ir`; returns output value or `OmpErrors`.
|
|
26
|
+
* `path` seeds the traversal location so nested step callbacks observe
|
|
27
|
+
* absolute ctx.path values when a compiled parent delegates a subtree;
|
|
28
|
+
* resulting error paths are then already absolute.
|
|
29
|
+
*/
|
|
30
|
+
export function walk(ir, value, path = []) {
|
|
31
|
+
const previousVisits = activeVisits;
|
|
32
|
+
const previousChecks = activeChecks;
|
|
33
|
+
// Created lazily by visit/checks only when a recursive-alias node is reached.
|
|
34
|
+
activeVisits = undefined;
|
|
35
|
+
activeChecks = undefined;
|
|
36
|
+
try {
|
|
37
|
+
return visit(ir, value, path);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
activeVisits = previousVisits;
|
|
41
|
+
activeChecks = previousChecks;
|
|
42
|
+
}
|
|
19
43
|
}
|
|
20
44
|
function fail(path, expected, data) {
|
|
21
45
|
const storedPath = path.length === 0 ? undefined : path.length === 1 ? path[0] : [...path];
|
|
@@ -23,6 +47,28 @@ function fail(path, expected, data) {
|
|
|
23
47
|
}
|
|
24
48
|
/** Pure predicate used for union-member scanning (no morphs, no errors). */
|
|
25
49
|
function checks(ir, v) {
|
|
50
|
+
// Cycle guards are only needed when the subtree can revisit nodes through
|
|
51
|
+
// recursive aliases; plain schemas skip the WeakMap bookkeeping entirely.
|
|
52
|
+
if (typeof v !== "object" || v === null || !hasAlias(ir))
|
|
53
|
+
return checkNode(ir, v);
|
|
54
|
+
activeChecks ??= new WeakMap();
|
|
55
|
+
const visits = activeChecks;
|
|
56
|
+
let visited = visits.get(v);
|
|
57
|
+
if (visited?.has(ir))
|
|
58
|
+
return true;
|
|
59
|
+
if (visited === undefined) {
|
|
60
|
+
visited = new Set();
|
|
61
|
+
visits.set(v, visited);
|
|
62
|
+
}
|
|
63
|
+
visited.add(ir);
|
|
64
|
+
try {
|
|
65
|
+
return checkNode(ir, v);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
visited.delete(ir);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function checkNode(ir, v) {
|
|
26
72
|
switch (ir.k) {
|
|
27
73
|
case "unknown":
|
|
28
74
|
return true;
|
|
@@ -65,8 +111,8 @@ function checks(ir, v) {
|
|
|
65
111
|
return false;
|
|
66
112
|
if (ir.max !== undefined && v.length > ir.max)
|
|
67
113
|
return false;
|
|
68
|
-
for (const
|
|
69
|
-
if (!checks(ir.el,
|
|
114
|
+
for (const element of v)
|
|
115
|
+
if (!checks(ir.el, element))
|
|
70
116
|
return false;
|
|
71
117
|
return true;
|
|
72
118
|
}
|
|
@@ -105,7 +151,7 @@ function checks(ir, v) {
|
|
|
105
151
|
return true;
|
|
106
152
|
}
|
|
107
153
|
case "object": {
|
|
108
|
-
if (typeof v !== "object" || v === null
|
|
154
|
+
if (typeof v !== "object" || v === null)
|
|
109
155
|
return false;
|
|
110
156
|
const rec = v;
|
|
111
157
|
for (const p of ir.props) {
|
|
@@ -118,25 +164,35 @@ function checks(ir, v) {
|
|
|
118
164
|
if (!checks(p.val, rec[p.key]))
|
|
119
165
|
return false;
|
|
120
166
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
167
|
+
for (const key in rec) {
|
|
168
|
+
if (!own.call(rec, key))
|
|
169
|
+
continue;
|
|
170
|
+
if (ir.index !== undefined && !checks(ir.index, rec[key]))
|
|
171
|
+
return false;
|
|
172
|
+
let patternMatched = false;
|
|
173
|
+
if (ir.patternIndexes !== undefined) {
|
|
174
|
+
for (const pattern of ir.patternIndexes) {
|
|
175
|
+
if (!checks(pattern.key, key))
|
|
176
|
+
continue;
|
|
177
|
+
patternMatched = true;
|
|
178
|
+
if (!checks(pattern.val, rec[key]))
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (ir.extras === "reject" &&
|
|
183
|
+
ir.index === undefined &&
|
|
184
|
+
!patternMatched &&
|
|
185
|
+
!ir.props.some(prop => prop.key === key)) {
|
|
186
|
+
return false;
|
|
125
187
|
}
|
|
126
188
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
declared = true;
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (!declared)
|
|
139
|
-
return false;
|
|
189
|
+
for (const key of Object.getOwnPropertySymbols(rec)) {
|
|
190
|
+
if (!Object.prototype.propertyIsEnumerable.call(rec, key))
|
|
191
|
+
continue;
|
|
192
|
+
if (ir.symbolIndex !== undefined && !checks(ir.symbolIndex, rec[key]))
|
|
193
|
+
return false;
|
|
194
|
+
if (ir.extras === "reject" && ir.symbolIndex === undefined && !ir.props.some(prop => prop.key === key)) {
|
|
195
|
+
return false;
|
|
140
196
|
}
|
|
141
197
|
}
|
|
142
198
|
return true;
|
|
@@ -147,7 +203,7 @@ function checks(ir, v) {
|
|
|
147
203
|
if (!checks(ir.base, v))
|
|
148
204
|
return false;
|
|
149
205
|
try {
|
|
150
|
-
return ir.pred(v);
|
|
206
|
+
return ir.pred(v) === true;
|
|
151
207
|
}
|
|
152
208
|
catch {
|
|
153
209
|
return false;
|
|
@@ -161,6 +217,37 @@ function checks(ir, v) {
|
|
|
161
217
|
}
|
|
162
218
|
}
|
|
163
219
|
function visit(ir, v, path) {
|
|
220
|
+
if (typeof v !== "object" || v === null || !hasAlias(ir))
|
|
221
|
+
return visitFinish(ir, v, path);
|
|
222
|
+
activeVisits ??= new WeakMap();
|
|
223
|
+
const visits = activeVisits;
|
|
224
|
+
let visited = visits.get(v);
|
|
225
|
+
if (visited?.has(ir))
|
|
226
|
+
return v;
|
|
227
|
+
if (visited === undefined) {
|
|
228
|
+
visited = new Set();
|
|
229
|
+
visits.set(v, visited);
|
|
230
|
+
}
|
|
231
|
+
visited.add(ir);
|
|
232
|
+
try {
|
|
233
|
+
return visitFinish(ir, v, path);
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
visited.delete(ir);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Run the node visitor, then apply node-local error configuration. */
|
|
240
|
+
function visitFinish(ir, v, path) {
|
|
241
|
+
const out = visitNode(ir, v, path);
|
|
242
|
+
if (!(out instanceof OmpErrors) || ir.cfg === undefined)
|
|
243
|
+
return out;
|
|
244
|
+
for (const error of out) {
|
|
245
|
+
if (error.path.length !== path.length)
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
return out.configure(ir.cfg);
|
|
249
|
+
}
|
|
250
|
+
function visitNode(ir, v, path) {
|
|
164
251
|
switch (ir.k) {
|
|
165
252
|
case "alias":
|
|
166
253
|
return visit(ir.resolve(), v, path);
|
|
@@ -169,7 +256,10 @@ function visit(ir, v, path) {
|
|
|
169
256
|
if (base instanceof OmpErrors)
|
|
170
257
|
return base;
|
|
171
258
|
try {
|
|
172
|
-
|
|
259
|
+
const result = ir.pred(base);
|
|
260
|
+
if (result instanceof OmpErrors)
|
|
261
|
+
return path.length === 0 ? result : prefixAll(result, path);
|
|
262
|
+
return result ? base : fail(path, ir.expected, base);
|
|
173
263
|
}
|
|
174
264
|
catch {
|
|
175
265
|
return fail(path, ir.expected, base);
|
|
@@ -198,7 +288,7 @@ function visit(ir, v, path) {
|
|
|
198
288
|
return output;
|
|
199
289
|
}
|
|
200
290
|
case "sub": {
|
|
201
|
-
const out = ir.schema.run(v);
|
|
291
|
+
const out = ir.schema.run(v, path);
|
|
202
292
|
if (out instanceof OmpErrors) {
|
|
203
293
|
return path.length === 0 ? out : prefixAll(out, path);
|
|
204
294
|
}
|
|
@@ -213,43 +303,48 @@ function visit(ir, v, path) {
|
|
|
213
303
|
return v;
|
|
214
304
|
}
|
|
215
305
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
306
|
+
let targeted;
|
|
307
|
+
let targetCount = 0;
|
|
308
|
+
for (const member of ir.members) {
|
|
309
|
+
if (member.k === "sub" || hasMorph(member)) {
|
|
310
|
+
const out = visit(member, v, path);
|
|
219
311
|
if (!(out instanceof OmpErrors))
|
|
220
312
|
return out;
|
|
313
|
+
if (kindMatches(unwrapBase(member), v)) {
|
|
314
|
+
targetCount++;
|
|
315
|
+
targeted ??= out;
|
|
316
|
+
}
|
|
221
317
|
}
|
|
222
318
|
}
|
|
319
|
+
if (targetCount === 1 && targeted !== undefined)
|
|
320
|
+
return targeted;
|
|
223
321
|
return ir.members.some(canRefineUnionFailure) ? unionFail(ir, v, path) : fail(path, expectedOf(ir), v);
|
|
224
322
|
}
|
|
225
323
|
case "array": {
|
|
226
324
|
if (!Array.isArray(v))
|
|
227
325
|
return fail(path, "an array", v);
|
|
228
326
|
if (ir.min !== undefined && v.length < ir.min)
|
|
229
|
-
return fail(path, `at least length ${ir.min}`, v);
|
|
327
|
+
return fail(path, `at least length ${ir.min}`, v.length);
|
|
230
328
|
if (ir.max !== undefined && v.length > ir.max)
|
|
231
|
-
return fail(path, `at most length ${ir.max}`, v);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return err instanceof OmpErrors ? err : fail([...path, i], expectedOf(ir.el), v[i]);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return v;
|
|
242
|
-
}
|
|
243
|
-
const out = new Array(v.length);
|
|
244
|
-
for (let i = 0; i < v.length; i++) {
|
|
245
|
-
path.push(i);
|
|
246
|
-
const el = visit(ir.el, v[i], path);
|
|
329
|
+
return fail(path, `at most length ${ir.max}`, v.length);
|
|
330
|
+
const morph = hasMorph(ir.el);
|
|
331
|
+
const out = morph ? new Array(v.length) : v;
|
|
332
|
+
let errors;
|
|
333
|
+
for (let index = 0; index < v.length; index++) {
|
|
334
|
+
path.push(index);
|
|
335
|
+
const element = visit(ir.el, v[index], path);
|
|
247
336
|
path.pop();
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
337
|
+
if (element instanceof OmpErrors) {
|
|
338
|
+
if (errors)
|
|
339
|
+
errors.append(element);
|
|
340
|
+
else
|
|
341
|
+
errors = element;
|
|
342
|
+
}
|
|
343
|
+
else if (morph) {
|
|
344
|
+
out[index] = element;
|
|
345
|
+
}
|
|
251
346
|
}
|
|
252
|
-
return out;
|
|
347
|
+
return errors ?? out;
|
|
253
348
|
}
|
|
254
349
|
case "tuple": {
|
|
255
350
|
if (!Array.isArray(v))
|
|
@@ -268,26 +363,51 @@ function visit(ir, v, path) {
|
|
|
268
363
|
const prefixCount = Math.min(ir.prefix.length, postfixStart);
|
|
269
364
|
const morph = hasMorph(ir);
|
|
270
365
|
const output = morph ? [...v] : v;
|
|
366
|
+
let errors;
|
|
271
367
|
for (let index = 0; index < prefixCount; index++) {
|
|
272
368
|
path.push(index);
|
|
273
369
|
const item = visit(ir.prefix[index].val, v[index], path);
|
|
274
370
|
path.pop();
|
|
275
|
-
if (item instanceof OmpErrors)
|
|
276
|
-
|
|
277
|
-
|
|
371
|
+
if (item instanceof OmpErrors) {
|
|
372
|
+
if (errors)
|
|
373
|
+
errors.append(item);
|
|
374
|
+
else
|
|
375
|
+
errors = item;
|
|
376
|
+
}
|
|
377
|
+
else if (morph) {
|
|
278
378
|
output[index] = item;
|
|
379
|
+
}
|
|
279
380
|
}
|
|
280
381
|
for (let index = prefixCount; index < ir.prefix.length; index++) {
|
|
281
382
|
const item = ir.prefix[index];
|
|
282
383
|
if (item.hasDefault && morph) {
|
|
283
384
|
const payload = item.def;
|
|
284
|
-
|
|
385
|
+
if (item.defFactory && typeof payload === "function") {
|
|
386
|
+
path.push(index);
|
|
387
|
+
const resolved = visit(item.val, payload(), path);
|
|
388
|
+
path.pop();
|
|
389
|
+
if (resolved instanceof OmpErrors) {
|
|
390
|
+
if (errors)
|
|
391
|
+
errors.append(resolved);
|
|
392
|
+
else
|
|
393
|
+
errors = resolved;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
output[index] = resolved;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
output[index] = materializeDefault(payload);
|
|
401
|
+
}
|
|
285
402
|
}
|
|
286
403
|
else if (!item.opt) {
|
|
287
404
|
path.push(index);
|
|
288
405
|
const error = fail(path, expectedOf(item.val), MISSING);
|
|
289
406
|
path.pop();
|
|
290
|
-
|
|
407
|
+
if (errors)
|
|
408
|
+
errors.append(error);
|
|
409
|
+
else
|
|
410
|
+
errors = error;
|
|
291
411
|
}
|
|
292
412
|
}
|
|
293
413
|
if (ir.variadic !== undefined) {
|
|
@@ -295,10 +415,15 @@ function visit(ir, v, path) {
|
|
|
295
415
|
path.push(index);
|
|
296
416
|
const item = visit(ir.variadic, v[index], path);
|
|
297
417
|
path.pop();
|
|
298
|
-
if (item instanceof OmpErrors)
|
|
299
|
-
|
|
300
|
-
|
|
418
|
+
if (item instanceof OmpErrors) {
|
|
419
|
+
if (errors)
|
|
420
|
+
errors.append(item);
|
|
421
|
+
else
|
|
422
|
+
errors = item;
|
|
423
|
+
}
|
|
424
|
+
else if (morph) {
|
|
301
425
|
output[index] = item;
|
|
426
|
+
}
|
|
302
427
|
}
|
|
303
428
|
}
|
|
304
429
|
for (let index = 0; index < ir.postfix.length; index++) {
|
|
@@ -306,21 +431,30 @@ function visit(ir, v, path) {
|
|
|
306
431
|
path.push(inputIndex);
|
|
307
432
|
const item = visit(ir.postfix[index], v[inputIndex], path);
|
|
308
433
|
path.pop();
|
|
309
|
-
if (item instanceof OmpErrors)
|
|
310
|
-
|
|
311
|
-
|
|
434
|
+
if (item instanceof OmpErrors) {
|
|
435
|
+
if (errors)
|
|
436
|
+
errors.append(item);
|
|
437
|
+
else
|
|
438
|
+
errors = item;
|
|
439
|
+
}
|
|
440
|
+
else if (morph) {
|
|
312
441
|
output[inputIndex] = item;
|
|
442
|
+
}
|
|
313
443
|
}
|
|
314
|
-
return output;
|
|
444
|
+
return errors ?? output;
|
|
315
445
|
}
|
|
316
446
|
case "object": {
|
|
317
|
-
if (typeof v !== "object" || v === null
|
|
447
|
+
if (typeof v !== "object" || v === null)
|
|
318
448
|
return fail(path, "an object", v);
|
|
319
449
|
const rec = v;
|
|
320
450
|
const morph = hasMorph(ir);
|
|
321
451
|
let out;
|
|
452
|
+
let errors;
|
|
322
453
|
if (morph) {
|
|
323
|
-
if (ir.extras === "delete" &&
|
|
454
|
+
if (ir.extras === "delete" &&
|
|
455
|
+
ir.index === undefined &&
|
|
456
|
+
ir.symbolIndex === undefined &&
|
|
457
|
+
ir.patternIndexes === undefined) {
|
|
324
458
|
out = {};
|
|
325
459
|
}
|
|
326
460
|
else {
|
|
@@ -330,59 +464,189 @@ function visit(ir, v, path) {
|
|
|
330
464
|
for (const p of ir.props) {
|
|
331
465
|
if (!(p.key in rec)) {
|
|
332
466
|
if (p.hasDefault && out) {
|
|
333
|
-
// defFactory guarantees a callable default payload
|
|
334
467
|
const payload = p.def;
|
|
335
|
-
|
|
468
|
+
if (p.defFactory && typeof payload === "function") {
|
|
469
|
+
path.push(p.key);
|
|
470
|
+
const resolved = visit(p.val, payload(), path);
|
|
471
|
+
path.pop();
|
|
472
|
+
if (resolved instanceof OmpErrors) {
|
|
473
|
+
if (errors)
|
|
474
|
+
errors.append(resolved);
|
|
475
|
+
else
|
|
476
|
+
errors = resolved;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
out[p.key] = resolved;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
out[p.key] = materializeDefault(payload);
|
|
484
|
+
}
|
|
336
485
|
continue;
|
|
337
486
|
}
|
|
338
487
|
if (p.opt || p.hasDefault)
|
|
339
488
|
continue;
|
|
340
489
|
path.push(p.key);
|
|
341
|
-
const
|
|
490
|
+
const error = fail(path, expectedOf(p.val), MISSING);
|
|
342
491
|
path.pop();
|
|
343
|
-
|
|
492
|
+
if (errors)
|
|
493
|
+
errors.append(error);
|
|
494
|
+
else
|
|
495
|
+
errors = error;
|
|
496
|
+
continue;
|
|
344
497
|
}
|
|
345
498
|
path.push(p.key);
|
|
346
|
-
const
|
|
499
|
+
const result = visit(p.val, rec[p.key], path);
|
|
347
500
|
path.pop();
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
continue;
|
|
357
|
-
path.push(key);
|
|
358
|
-
const res = visit(ir.index, rec[key], path);
|
|
359
|
-
path.pop();
|
|
360
|
-
if (res instanceof OmpErrors)
|
|
361
|
-
return res;
|
|
362
|
-
if (out)
|
|
363
|
-
out[key] = res;
|
|
501
|
+
if (result instanceof OmpErrors) {
|
|
502
|
+
if (errors)
|
|
503
|
+
errors.append(result);
|
|
504
|
+
else
|
|
505
|
+
errors = result;
|
|
506
|
+
}
|
|
507
|
+
else if (out) {
|
|
508
|
+
out[p.key] = result;
|
|
364
509
|
}
|
|
365
510
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
511
|
+
for (const key in rec) {
|
|
512
|
+
if (!own.call(rec, key))
|
|
513
|
+
continue;
|
|
514
|
+
let indexed = false;
|
|
515
|
+
if (ir.index !== undefined) {
|
|
516
|
+
indexed = true;
|
|
517
|
+
path.push(key);
|
|
518
|
+
const result = visit(ir.index, rec[key], path);
|
|
519
|
+
path.pop();
|
|
520
|
+
if (result instanceof OmpErrors) {
|
|
521
|
+
if (errors)
|
|
522
|
+
errors.append(result);
|
|
523
|
+
else
|
|
524
|
+
errors = result;
|
|
376
525
|
}
|
|
377
|
-
if (
|
|
526
|
+
else if (out) {
|
|
527
|
+
out[key] = result;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (ir.patternIndexes !== undefined) {
|
|
531
|
+
for (const pattern of ir.patternIndexes) {
|
|
532
|
+
if (!checks(pattern.key, key))
|
|
533
|
+
continue;
|
|
534
|
+
indexed = true;
|
|
378
535
|
path.push(key);
|
|
379
|
-
const
|
|
536
|
+
const result = visit(pattern.val, rec[key], path);
|
|
380
537
|
path.pop();
|
|
381
|
-
|
|
538
|
+
if (result instanceof OmpErrors) {
|
|
539
|
+
if (errors)
|
|
540
|
+
errors.append(result);
|
|
541
|
+
else
|
|
542
|
+
errors = result;
|
|
543
|
+
}
|
|
544
|
+
else if (out) {
|
|
545
|
+
out[key] = result;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (ir.extras === "reject" && !indexed && !ir.props.some(prop => prop.key === key)) {
|
|
550
|
+
path.push(key);
|
|
551
|
+
const error = fail(path, "removed", rec[key]);
|
|
552
|
+
path.pop();
|
|
553
|
+
if (errors)
|
|
554
|
+
errors.append(error);
|
|
555
|
+
else
|
|
556
|
+
errors = error;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
for (const key of Object.getOwnPropertySymbols(rec)) {
|
|
560
|
+
if (!Object.prototype.propertyIsEnumerable.call(rec, key))
|
|
561
|
+
continue;
|
|
562
|
+
if (ir.symbolIndex !== undefined) {
|
|
563
|
+
path.push(key);
|
|
564
|
+
const result = visit(ir.symbolIndex, rec[key], path);
|
|
565
|
+
path.pop();
|
|
566
|
+
if (result instanceof OmpErrors) {
|
|
567
|
+
if (errors)
|
|
568
|
+
errors.append(result);
|
|
569
|
+
else
|
|
570
|
+
errors = result;
|
|
571
|
+
}
|
|
572
|
+
else if (out) {
|
|
573
|
+
out[key] = result;
|
|
382
574
|
}
|
|
383
575
|
}
|
|
576
|
+
else if (ir.extras === "reject" && !ir.props.some(prop => prop.key === key)) {
|
|
577
|
+
path.push(key);
|
|
578
|
+
const error = fail(path, "removed", rec[key]);
|
|
579
|
+
path.pop();
|
|
580
|
+
if (errors)
|
|
581
|
+
errors.append(error);
|
|
582
|
+
else
|
|
583
|
+
errors = error;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return errors ?? out ?? v;
|
|
587
|
+
}
|
|
588
|
+
case "string": {
|
|
589
|
+
if (typeof v !== "string")
|
|
590
|
+
return fail(path, "a string", v);
|
|
591
|
+
if (ir.min !== undefined && v.length < ir.min)
|
|
592
|
+
return fail(path, `at least length ${ir.min}`, v.length);
|
|
593
|
+
if (ir.max !== undefined && v.length > ir.max)
|
|
594
|
+
return fail(path, `at most length ${ir.max}`, v.length);
|
|
595
|
+
if (ir.url && !URL.canParse(v))
|
|
596
|
+
return fail(path, "a URL string", v);
|
|
597
|
+
return v;
|
|
598
|
+
}
|
|
599
|
+
case "number": {
|
|
600
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
601
|
+
return fail(path, ir.int ? "an integer" : "a number", v);
|
|
602
|
+
let errors;
|
|
603
|
+
const add = (expected) => {
|
|
604
|
+
const error = fail(path, expected, v);
|
|
605
|
+
if (errors)
|
|
606
|
+
errors.append(error);
|
|
607
|
+
else
|
|
608
|
+
errors = error;
|
|
609
|
+
};
|
|
610
|
+
if (ir.int && !Number.isInteger(v))
|
|
611
|
+
add("an integer");
|
|
612
|
+
if (ir.divisor !== undefined && v % ir.divisor !== 0)
|
|
613
|
+
add(`a number divisible by ${ir.divisor}`);
|
|
614
|
+
if (ir.min !== undefined && (ir.xmin ? v <= ir.min : v < ir.min)) {
|
|
615
|
+
add(ir.min === 0
|
|
616
|
+
? ir.xmin
|
|
617
|
+
? "positive"
|
|
618
|
+
: "non-negative"
|
|
619
|
+
: `a number ${ir.xmin ? "more than" : "at least"} ${ir.min}`);
|
|
620
|
+
}
|
|
621
|
+
if (ir.max !== undefined && (ir.xmax ? v >= ir.max : v > ir.max)) {
|
|
622
|
+
add(ir.max === 0
|
|
623
|
+
? ir.xmax
|
|
624
|
+
? "negative"
|
|
625
|
+
: "non-positive"
|
|
626
|
+
: `a number ${ir.xmax ? "less than" : "at most"} ${ir.max}`);
|
|
384
627
|
}
|
|
385
|
-
return
|
|
628
|
+
return errors ?? v;
|
|
629
|
+
}
|
|
630
|
+
case "lit": {
|
|
631
|
+
if (checks(ir, v))
|
|
632
|
+
return v;
|
|
633
|
+
if ((typeof ir.v === "object" && ir.v !== null) || typeof ir.v === "function") {
|
|
634
|
+
let expected = "the specified reference";
|
|
635
|
+
try {
|
|
636
|
+
const serialized = JSON.stringify(ir.v);
|
|
637
|
+
if (serialized !== undefined) {
|
|
638
|
+
expected = `reference equal to ${serialized}`;
|
|
639
|
+
if (typeof v === "object" && v !== null && JSON.stringify(v) === serialized) {
|
|
640
|
+
expected += " (serialized to the same value)";
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
// Cyclic values still get a useful reference-identity expectation.
|
|
646
|
+
}
|
|
647
|
+
return fail(path, expected, v);
|
|
648
|
+
}
|
|
649
|
+
return fail(path, expectedOf(ir), v);
|
|
386
650
|
}
|
|
387
651
|
default:
|
|
388
652
|
return checks(ir, v) ? v : fail(path, expectedOf(ir), v);
|
|
@@ -395,14 +659,7 @@ function prefixAll(errs, path) {
|
|
|
395
659
|
}
|
|
396
660
|
/** True when a union failure can be replaced with a more specific nested error. */
|
|
397
661
|
export function canRefineUnionFailure(member) {
|
|
398
|
-
const base = member
|
|
399
|
-
if (member.k === "sub") {
|
|
400
|
-
return (base.k === "array" ||
|
|
401
|
-
base.k === "object" ||
|
|
402
|
-
base.k === "anyobject" ||
|
|
403
|
-
base.k === "string" ||
|
|
404
|
-
base.k === "number");
|
|
405
|
-
}
|
|
662
|
+
const base = unwrapBase(member);
|
|
406
663
|
if (base.k === "array" || base.k === "object")
|
|
407
664
|
return true;
|
|
408
665
|
if (base.k === "string")
|
|
@@ -418,45 +675,170 @@ export function canRefineUnionFailure(member) {
|
|
|
418
675
|
*/
|
|
419
676
|
export function unionFail(ir, v, path, expected) {
|
|
420
677
|
let best;
|
|
421
|
-
for (const
|
|
422
|
-
const base =
|
|
678
|
+
for (const member of ir.members) {
|
|
679
|
+
const base = unwrapBase(member);
|
|
423
680
|
if (!kindMatches(base, v))
|
|
424
681
|
continue;
|
|
425
682
|
if (best !== undefined) {
|
|
426
683
|
best = undefined;
|
|
427
684
|
break;
|
|
428
685
|
}
|
|
429
|
-
best =
|
|
686
|
+
best = member;
|
|
430
687
|
}
|
|
431
|
-
if (best === undefined)
|
|
432
|
-
|
|
433
|
-
|
|
688
|
+
if (best === undefined) {
|
|
689
|
+
const discriminated = discriminateFailure(ir.members, v, path);
|
|
690
|
+
if (discriminated !== undefined)
|
|
691
|
+
return discriminated;
|
|
692
|
+
}
|
|
693
|
+
if (best !== undefined) {
|
|
434
694
|
const out = visit(best, v, path);
|
|
435
695
|
if (out instanceof OmpErrors)
|
|
436
696
|
return out;
|
|
437
697
|
}
|
|
698
|
+
if (ir.members.every(member => unwrapBase(member).k === "object")) {
|
|
699
|
+
const branches = ir.members.flatMap(member => {
|
|
700
|
+
const result = visit(member, v, path);
|
|
701
|
+
return result instanceof OmpErrors ? [[...result]] : [];
|
|
702
|
+
});
|
|
703
|
+
if (branches.length !== 0) {
|
|
704
|
+
const common = branches[0].filter((entry, index, first) => first.findIndex(candidate => pathsEqual(candidate.path, entry.path)) === index &&
|
|
705
|
+
branches.every(branch => branch.some(candidate => pathsEqual(candidate.path, entry.path))));
|
|
706
|
+
const alternatives = [];
|
|
707
|
+
if (common.length !== 0) {
|
|
708
|
+
for (const entry of common) {
|
|
709
|
+
const expectations = new Set();
|
|
710
|
+
for (const branch of branches) {
|
|
711
|
+
for (const candidate of branch) {
|
|
712
|
+
if (pathsEqual(candidate.path, entry.path)) {
|
|
713
|
+
expectations.add(candidate.expected.endsWith(" instance") ? "an object" : candidate.expected);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
alternatives.push(new OmpErrors(entry.path, [...expectations].join(" or "), entry.data, { preserveActual: true }));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
for (const branch of branches) {
|
|
722
|
+
for (const entry of branch) {
|
|
723
|
+
alternatives.push(new OmpErrors(entry.path, entry.expected.endsWith(" instance") ? "an object" : entry.expected, entry.data, { preserveActual: true }));
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const combined = alternatives[0];
|
|
728
|
+
for (let index = 1; index < alternatives.length; index++)
|
|
729
|
+
combined.append(alternatives[index]);
|
|
730
|
+
return alternatives.length === 1 ? combined : combined.asAlternatives();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
438
733
|
return fail(path, expected ?? expectedOf(ir), v);
|
|
439
734
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
735
|
+
function unwrapBase(member, seen = new Set()) {
|
|
736
|
+
if (seen.has(member))
|
|
737
|
+
return member;
|
|
738
|
+
seen.add(member);
|
|
739
|
+
if (member.k === "sub")
|
|
740
|
+
return unwrapBase(member.schema.ir, seen);
|
|
741
|
+
if (member.k === "alias")
|
|
742
|
+
return unwrapBase(member.resolve(), seen);
|
|
743
|
+
if (member.k === "refine")
|
|
744
|
+
return unwrapBase(member.base, seen);
|
|
745
|
+
return member;
|
|
746
|
+
}
|
|
747
|
+
function collectDiscriminants(member, prefix = [], seen = new Set()) {
|
|
748
|
+
if (seen.has(member))
|
|
749
|
+
return [];
|
|
750
|
+
seen.add(member);
|
|
751
|
+
if (member.k === "alias")
|
|
752
|
+
return collectDiscriminants(member.resolve(), prefix, seen);
|
|
753
|
+
if (member.k === "sub")
|
|
754
|
+
return collectDiscriminants(member.schema.ir, prefix, seen);
|
|
755
|
+
if (member.k === "refine")
|
|
756
|
+
return collectDiscriminants(member.base, prefix, seen);
|
|
757
|
+
if (member.k !== "object")
|
|
758
|
+
return [];
|
|
759
|
+
const result = [];
|
|
760
|
+
for (const property of member.props) {
|
|
761
|
+
const propertyPath = [...prefix, property.key];
|
|
762
|
+
const value = unwrapBase(property.val);
|
|
763
|
+
if (value.k === "lit")
|
|
764
|
+
result.push({ path: propertyPath, value: value.v });
|
|
765
|
+
else
|
|
766
|
+
result.push(...collectDiscriminants(property.val, propertyPath, new Set(seen)));
|
|
767
|
+
}
|
|
768
|
+
return result;
|
|
769
|
+
}
|
|
770
|
+
function pathsEqual(left, right) {
|
|
771
|
+
return left.length === right.length && left.every((key, index) => key === right[index]);
|
|
772
|
+
}
|
|
773
|
+
function valueAtPath(value, path) {
|
|
774
|
+
let cursor = value;
|
|
775
|
+
for (const key of path) {
|
|
776
|
+
if ((typeof cursor !== "object" && typeof cursor !== "function") || cursor === null || !(key in cursor)) {
|
|
777
|
+
return { present: false };
|
|
778
|
+
}
|
|
779
|
+
cursor = cursor[key];
|
|
780
|
+
}
|
|
781
|
+
return { present: true, value: cursor };
|
|
782
|
+
}
|
|
783
|
+
function discriminateFailure(members, value, path) {
|
|
784
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
443
785
|
return undefined;
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
for (const
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
continue;
|
|
450
|
-
for (const p of base.props) {
|
|
451
|
-
if (p.val.k !== "lit" || rec[p.key] !== p.val.v)
|
|
786
|
+
const byMember = members.map(member => collectDiscriminants(member));
|
|
787
|
+
const candidates = [];
|
|
788
|
+
for (const discriminants of byMember) {
|
|
789
|
+
for (const discriminant of discriminants) {
|
|
790
|
+
if (candidates.some(candidate => pathsEqual(candidate.path, discriminant.path)))
|
|
452
791
|
continue;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
792
|
+
const values = [];
|
|
793
|
+
let declared = 0;
|
|
794
|
+
for (const branch of byMember) {
|
|
795
|
+
const match = branch.find(candidate => pathsEqual(candidate.path, discriminant.path));
|
|
796
|
+
if (match === undefined)
|
|
797
|
+
continue;
|
|
798
|
+
declared++;
|
|
799
|
+
if (!values.some(candidate => Object.is(candidate, match.value)))
|
|
800
|
+
values.push(match.value);
|
|
801
|
+
}
|
|
802
|
+
if (values.length > 1)
|
|
803
|
+
candidates.push({ path: discriminant.path, distinct: values.length, declared });
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
candidates.sort((left, right) => right.distinct - left.distinct || right.declared - left.declared);
|
|
807
|
+
for (const candidate of candidates) {
|
|
808
|
+
const actual = valueAtPath(value, candidate.path);
|
|
809
|
+
const exact = [];
|
|
810
|
+
const defaults = [];
|
|
811
|
+
const expectedMembers = [];
|
|
812
|
+
for (let index = 0; index < members.length; index++) {
|
|
813
|
+
const discriminant = byMember[index].find(item => pathsEqual(item.path, candidate.path));
|
|
814
|
+
if (discriminant === undefined) {
|
|
815
|
+
defaults.push(members[index]);
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
expectedMembers.push({ k: "lit", v: discriminant.value });
|
|
819
|
+
if (actual.present && Object.is(actual.value, discriminant.value))
|
|
820
|
+
exact.push(members[index]);
|
|
821
|
+
}
|
|
822
|
+
if (!actual.present) {
|
|
823
|
+
if (defaults.length !== 0 && defaults.length < members.length) {
|
|
824
|
+
return discriminateFailure(defaults, value, path);
|
|
825
|
+
}
|
|
826
|
+
return fail([...path, ...candidate.path], expectedOf({ k: "union", members: expectedMembers }), undefined);
|
|
827
|
+
}
|
|
828
|
+
if (exact.length === 0) {
|
|
829
|
+
if (defaults.length !== 0)
|
|
830
|
+
return discriminateFailure(defaults, value, path);
|
|
831
|
+
return fail([...path, ...candidate.path], expectedOf({ k: "union", members: expectedMembers }), actual.value);
|
|
832
|
+
}
|
|
833
|
+
if (exact.length === 1) {
|
|
834
|
+
const result = visit(exact[0], value, path);
|
|
835
|
+
return result instanceof OmpErrors ? result : undefined;
|
|
457
836
|
}
|
|
837
|
+
const nested = discriminateFailure(exact, value, path);
|
|
838
|
+
if (nested !== undefined)
|
|
839
|
+
return nested;
|
|
458
840
|
}
|
|
459
|
-
return
|
|
841
|
+
return undefined;
|
|
460
842
|
}
|
|
461
843
|
/** True when a value's runtime shape could only be aimed at this member. */
|
|
462
844
|
function kindMatches(base, v) {
|