@amritk/validation 0.4.1 → 0.6.0
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/AI.md +11 -0
- package/README.md +58 -4
- package/dist/parsers/generators/generate-parser-function.js +1 -1
- package/dist/parsers/generators/generate-strict-assertion.js +6 -7
- package/dist/validators/generators/build-schema.d.ts +1 -1
- package/dist/validators/generators/build-schema.js +70 -216
- package/dist/validators/generators/collect-validator-imports.d.ts +7 -0
- package/dist/validators/generators/collect-validator-imports.js +6 -3
- package/dist/validators/generators/generate-files.d.ts +6 -0
- package/dist/validators/generators/generate-files.js +34 -9
- package/dist/validators/generators/generate-repair-function.d.ts +13 -17
- package/dist/validators/generators/generate-repair-function.js +224 -118
- package/dist/validators/generators/generate-repair-walk.d.ts +24 -0
- package/dist/validators/generators/generate-repair-walk.js +335 -0
- package/dist/validators/generators/generate-validator-function.d.ts +19 -1
- package/dist/validators/generators/generate-validator-function.js +65 -36
- package/dist/validators/generators/repair-context.d.ts +39 -0
- package/dist/validators/generators/repair-context.js +35 -0
- package/dist/validators/generators/repair-runtime.d.ts +25 -0
- package/dist/validators/generators/repair-runtime.js +1071 -0
- package/dist/validators/generators/repair-walkable.d.ts +33 -0
- package/dist/validators/generators/repair-walkable.js +148 -0
- package/package.json +3 -3
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { regexFlagsFor } from "@amritk/helpers/escape-regex-pattern";
|
|
2
|
+
import { readKey } from "@amritk/helpers/read-key";
|
|
3
|
+
import { refToName } from "@amritk/helpers/ref-to-name";
|
|
4
|
+
import { isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
5
|
+
import { namedTest, schemaMap, subschema } from "./repair-context.js";
|
|
6
|
+
import { isReferenceSchema, leafKeywords } from "./repair-walkable.js";
|
|
7
|
+
const repairWalkNameFor = (typeName) => `walk${typeName}Repair`;
|
|
8
|
+
const SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
|
|
9
|
+
const typeCondition = (type) => {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case "string":
|
|
12
|
+
return `typeof v === 'string'`;
|
|
13
|
+
case "number":
|
|
14
|
+
return `(typeof v === 'number' && (!run.finite || Number.isFinite(v)))`;
|
|
15
|
+
case "integer":
|
|
16
|
+
return `Number.isInteger(v)`;
|
|
17
|
+
case "boolean":
|
|
18
|
+
return `typeof v === 'boolean'`;
|
|
19
|
+
case "null":
|
|
20
|
+
return `v === null`;
|
|
21
|
+
case "array":
|
|
22
|
+
return `Array.isArray(v)`;
|
|
23
|
+
default:
|
|
24
|
+
return `(typeof v === 'object' && v !== null && !Array.isArray(v) && (!run.plain || isPlainObject(v)))`;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const equalsCondition = (value) => {
|
|
28
|
+
if (typeof value === "number" && Number.isNaN(value))
|
|
29
|
+
return `Number.isNaN(v)`;
|
|
30
|
+
if (value !== null && typeof value === "object")
|
|
31
|
+
return `valuesEqual(v, ${JSON.stringify(value)})`;
|
|
32
|
+
return `(v as unknown) === ${JSON.stringify(value)}`;
|
|
33
|
+
};
|
|
34
|
+
const ownChecks = (node, ctx) => {
|
|
35
|
+
const checks = [];
|
|
36
|
+
const type = readKey(node, "type");
|
|
37
|
+
if (typeof type === "string") {
|
|
38
|
+
checks.push({
|
|
39
|
+
condition: typeCondition(type),
|
|
40
|
+
report: `found.push(walkError(run, 'type', ${JSON.stringify(`must be ${type}`)}, ${JSON.stringify({ type })}))`
|
|
41
|
+
});
|
|
42
|
+
} else if (Array.isArray(type)) {
|
|
43
|
+
const types = type;
|
|
44
|
+
checks.push({
|
|
45
|
+
condition: `(${types.map(typeCondition).join(" || ")})`,
|
|
46
|
+
report: `found.push(walkError(run, 'type', ${JSON.stringify(`must be ${types.join(" or ")}`)}, ${JSON.stringify({ type })}))`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const values = readKey(node, "enum");
|
|
50
|
+
if (Array.isArray(values)) {
|
|
51
|
+
const label = values.map((value) => JSON.stringify(value)).join(", ");
|
|
52
|
+
checks.push({
|
|
53
|
+
condition: values.length === 0 ? "false" : `(${values.map(equalsCondition).join(" || ")})`,
|
|
54
|
+
report: `found.push(walkError(run, 'enum', ${JSON.stringify(`must be one of: ${label}`)}, ${JSON.stringify({ allowedValues: values })}))`
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (Object.hasOwn(node, "const")) {
|
|
58
|
+
const value = node["const"];
|
|
59
|
+
checks.push({
|
|
60
|
+
condition: equalsCondition(value),
|
|
61
|
+
report: `found.push(walkError(run, 'const', ${JSON.stringify(`must be ${JSON.stringify(value)}`)}, ${JSON.stringify({ allowedValue: value })}))`
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const leaf = leafKeywords(node);
|
|
65
|
+
if (leaf !== null) {
|
|
66
|
+
const test = namedTest(leaf, ctx.repair);
|
|
67
|
+
if (test !== true) {
|
|
68
|
+
const explain = ctx.repair.matcher.explain(leaf);
|
|
69
|
+
checks.push({
|
|
70
|
+
condition: test === false ? "false" : `${test}(v)`,
|
|
71
|
+
report: explain === null ? `found.push(walkError(run, 'false schema', 'boolean schema is false', {}))` : `for (const error of ${explain}(v, walkPath(run))) found.push(error)`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return checks;
|
|
76
|
+
};
|
|
77
|
+
const coercibleTypes = (node) => {
|
|
78
|
+
const type = readKey(node, "type");
|
|
79
|
+
const types = typeof type === "string" ? [type] : Array.isArray(type) ? type : [];
|
|
80
|
+
return types.length > 0 && types.every((entry) => SCALAR_TYPES.has(entry)) ? JSON.stringify(types) : null;
|
|
81
|
+
};
|
|
82
|
+
const walkChild = (walker, key, write) => [
|
|
83
|
+
`run.path.push(${key})`,
|
|
84
|
+
`const n = ${walker}(c, run)`,
|
|
85
|
+
`run.path.pop()`,
|
|
86
|
+
`if (n !== c) ${write}`
|
|
87
|
+
];
|
|
88
|
+
const inlineTypeId = (node, typeSuffix) => {
|
|
89
|
+
const branchName = (key) => {
|
|
90
|
+
const branch = readKey(node, key);
|
|
91
|
+
if (typeof branch !== "object" || branch === null)
|
|
92
|
+
return null;
|
|
93
|
+
const ref = readKey(branch, "$ref");
|
|
94
|
+
return typeof ref === "string" && Object.keys(branch).length === 1 ? refToName(ref, typeSuffix) : null;
|
|
95
|
+
};
|
|
96
|
+
const thenName = branchName("then");
|
|
97
|
+
const elseName = branchName("else");
|
|
98
|
+
const text = JSON.stringify(node);
|
|
99
|
+
let hash = 2166136261;
|
|
100
|
+
for (let i = 0; i < text.length; i++)
|
|
101
|
+
hash = Math.imul(hash ^ text.charCodeAt(i), 16777619);
|
|
102
|
+
const digest = (hash >>> 0).toString(16).padStart(8, "0");
|
|
103
|
+
return thenName !== null && elseName !== null ? `${thenName}|${elseName}#${digest}` : `inline#${digest}`;
|
|
104
|
+
};
|
|
105
|
+
const falseWalker = (ctx, walkers) => {
|
|
106
|
+
const existing = walkers.get(false);
|
|
107
|
+
if (existing)
|
|
108
|
+
return existing;
|
|
109
|
+
const drop = ctx.lookupFor(false);
|
|
110
|
+
const name = "_walkFalse";
|
|
111
|
+
walkers.set(false, name);
|
|
112
|
+
ctx.repair.helpers.push([
|
|
113
|
+
`const ${name}: RepairWalk = (v, run) =>`,
|
|
114
|
+
` walkRepair(run, v, [walkError(run, 'false schema', 'boolean schema is false', {})], ${drop}, () => false, null)`
|
|
115
|
+
].join("\n"));
|
|
116
|
+
return name;
|
|
117
|
+
};
|
|
118
|
+
const emitWalk = (schema, ctx, walkers, isRoot) => {
|
|
119
|
+
if (schema === false)
|
|
120
|
+
return falseWalker(ctx, walkers);
|
|
121
|
+
if (!isSchemaObject(schema))
|
|
122
|
+
return null;
|
|
123
|
+
if (walkers.has(schema))
|
|
124
|
+
return walkers.get(schema) ?? null;
|
|
125
|
+
const node = schema;
|
|
126
|
+
const helpers = ctx.repair.helpers;
|
|
127
|
+
const ref = readKey(node, "$ref");
|
|
128
|
+
const refWalker = typeof ref === "string" ? repairWalkNameFor(refToName(ref, ctx.repair.typeSuffix)) : null;
|
|
129
|
+
const checks = ownChecks(node, ctx);
|
|
130
|
+
const properties = schemaMap(node, "properties") ?? {};
|
|
131
|
+
const required = readKey(node, "required")?.filter((key) => typeof key === "string");
|
|
132
|
+
const hasObjectPart = Object.keys(properties).length > 0 || required !== void 0 && required.length > 0 || readKey(node, "additionalProperties") !== void 0 || readKey(node, "patternProperties") !== void 0;
|
|
133
|
+
const items = subschema(node, "items");
|
|
134
|
+
const hasConditional = readKey(node, "if") !== void 0 || Array.isArray(readKey(node, "allOf"));
|
|
135
|
+
const ownsObjectType = readKey(node, "type") === "object";
|
|
136
|
+
if (refWalker !== null && checks.length === 0 && !hasObjectPart && items === void 0 && !hasConditional && !isRoot) {
|
|
137
|
+
walkers.set(schema, refWalker);
|
|
138
|
+
return refWalker;
|
|
139
|
+
}
|
|
140
|
+
const id = ctx.repair.next();
|
|
141
|
+
const name = `_walk${id}`;
|
|
142
|
+
walkers.set(schema, name);
|
|
143
|
+
const typeId = JSON.stringify(isRoot ? ctx.typeName : inlineTypeId(node, ctx.repair.typeSuffix));
|
|
144
|
+
const lookup = ctx.lookupFor(schema);
|
|
145
|
+
const body = [];
|
|
146
|
+
if (refWalker !== null) {
|
|
147
|
+
body.push(`v = ${refWalker}(v, run)`, `if (v === REPAIR_DROP) return v`);
|
|
148
|
+
}
|
|
149
|
+
if (checks.length > 0) {
|
|
150
|
+
const ok = checks.map((check) => check.condition).join(" && ");
|
|
151
|
+
const okName = `_walkOk${id}`;
|
|
152
|
+
const fixName = `_walkFix${id}`;
|
|
153
|
+
helpers.push(`const ${okName} = (v: unknown, ${/\brun\b/.test(ok) ? "run" : "_run"}: RepairRun): boolean => ${ok}`);
|
|
154
|
+
helpers.push([
|
|
155
|
+
`const ${fixName} = (v: unknown, run: RepairRun): unknown => {`,
|
|
156
|
+
` const found: ValidationError[] = []`,
|
|
157
|
+
...checks.map((check) => ` if (!(${check.condition})) ${check.report}`),
|
|
158
|
+
` return walkRepair(run, v, found, ${lookup ?? "null"}, ${okName}, ${coercibleTypes(node) ?? "null"})`,
|
|
159
|
+
`}`
|
|
160
|
+
].join("\n"));
|
|
161
|
+
body.push(`if (!(${ok})) {`, ` const fixed = ${fixName}(v, run)`, ` if (fixed !== v) return fixed === REPAIR_DROP ? fixed : walkVerified(run, fixed, ${name})`, `}`);
|
|
162
|
+
}
|
|
163
|
+
const container = [];
|
|
164
|
+
if (hasObjectPart || ownsObjectType)
|
|
165
|
+
container.push(...objectPart(node, properties, required ?? [], lookup, id, ctx, walkers));
|
|
166
|
+
if (items !== void 0)
|
|
167
|
+
container.push(...arrayPart(items, ctx, walkers));
|
|
168
|
+
const conditional = conditionalPart(node, name, typeId, lookup, ctx, walkers);
|
|
169
|
+
if (container.length > 0) {
|
|
170
|
+
body.push(`if (++run.work > run.maxWork || run.path.length > run.maxDepth) return walkHalt(run, v)`);
|
|
171
|
+
body.push(...container);
|
|
172
|
+
}
|
|
173
|
+
body.push(...conditional);
|
|
174
|
+
if (body.length === 0) {
|
|
175
|
+
walkers.set(schema, null);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const bodyName = container.length > 0 ? `_walkBody${id}` : name;
|
|
179
|
+
helpers.push([`const ${bodyName}: RepairWalk = (v, run) => {`, ...body.map((line) => ` ${line}`), ` return v`, `}`].join("\n"));
|
|
180
|
+
if (container.length > 0) {
|
|
181
|
+
helpers.push(`const ${name}: RepairWalk = (v, run) => (run.memo === null ? ${bodyName}(v, run) : walkMemo(run, v, ${bodyName}))`);
|
|
182
|
+
}
|
|
183
|
+
return name;
|
|
184
|
+
};
|
|
185
|
+
const objectPart = (node, properties, required, lookup, id, ctx, walkers) => {
|
|
186
|
+
const inner = [];
|
|
187
|
+
const write = (key) => `o = walkSet(run, o, o !== src, ${key}, n)`;
|
|
188
|
+
const requiredSet = new Set(required);
|
|
189
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
190
|
+
const walker = emitWalk(child, ctx, walkers, false);
|
|
191
|
+
const quoted = JSON.stringify(key);
|
|
192
|
+
if (requiredSet.has(key)) {
|
|
193
|
+
if (walker === null) {
|
|
194
|
+
inner.push(`if (!Object.hasOwn(o, ${quoted})) o = walkMissing(run, o, o !== src, ${quoted}, ${lookup ?? "null"}, null)`);
|
|
195
|
+
} else {
|
|
196
|
+
inner.push(`if (Object.hasOwn(o, ${quoted})) {`, ` const c = o[${quoted}]`, ...walkChild(walker, quoted, write(quoted)).map((line) => ` ${line}`), `} else o = walkMissing(run, o, o !== src, ${quoted}, ${lookup ?? "null"}, ${walker})`);
|
|
197
|
+
}
|
|
198
|
+
} else if (walker !== null) {
|
|
199
|
+
inner.push(`{`, ` const c = o[${quoted}]`, ` if (c !== undefined && Object.hasOwn(o, ${quoted})) {`, ...walkChild(walker, quoted, write(quoted)).map((line) => ` ${line}`), ` }`, `}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
for (const key of required) {
|
|
203
|
+
if (Object.hasOwn(properties, key))
|
|
204
|
+
continue;
|
|
205
|
+
const quoted = JSON.stringify(key);
|
|
206
|
+
inner.push(`if (!Object.hasOwn(o, ${quoted})) o = walkMissing(run, o, o !== src, ${quoted}, ${lookup ?? "null"}, null)`);
|
|
207
|
+
}
|
|
208
|
+
inner.push(...mapPart(node, properties, id, ctx, walkers));
|
|
209
|
+
return [
|
|
210
|
+
`if (typeof v === 'object' && v !== null && !Array.isArray(v)) {`,
|
|
211
|
+
` const src = v as Record<string, unknown>`,
|
|
212
|
+
` let o = src`,
|
|
213
|
+
` if (run.plain) o = walkProto(run, o, false)`,
|
|
214
|
+
...inner.map((line) => ` ${line}`),
|
|
215
|
+
` v = o`,
|
|
216
|
+
`}`
|
|
217
|
+
];
|
|
218
|
+
};
|
|
219
|
+
const mapPart = (node, properties, id, ctx, walkers) => {
|
|
220
|
+
const patterns = Object.entries(schemaMap(node, "patternProperties") ?? {}).map(([source, child]) => ({
|
|
221
|
+
source,
|
|
222
|
+
walker: emitWalk(child, ctx, walkers, false)
|
|
223
|
+
}));
|
|
224
|
+
const additional = subschema(node, "additionalProperties");
|
|
225
|
+
const additionalWalker = additional === void 0 || additional === false ? null : emitWalk(additional, ctx, walkers, false);
|
|
226
|
+
const closed = additional === false;
|
|
227
|
+
const patternsMatter = patterns.some((pattern) => pattern.walker !== null) || closed || additionalWalker !== null;
|
|
228
|
+
if (!patternsMatter)
|
|
229
|
+
return [];
|
|
230
|
+
const helpers = ctx.repair.helpers;
|
|
231
|
+
const lines = [`for (const k in src) {`, ` if (!Object.hasOwn(src, k)) continue`];
|
|
232
|
+
const declared = Object.keys(properties);
|
|
233
|
+
if (declared.length > 0) {
|
|
234
|
+
const setName = `_walkDeclared${id}`;
|
|
235
|
+
helpers.push(`const ${setName} = new Set(${JSON.stringify(declared)})`);
|
|
236
|
+
lines.push(` if (${setName}.has(k)) continue`);
|
|
237
|
+
}
|
|
238
|
+
lines.push(` if (o !== src && !Object.hasOwn(o, k)) continue`);
|
|
239
|
+
const needsMatched = patterns.length > 0 && (closed || additionalWalker !== null);
|
|
240
|
+
if (needsMatched)
|
|
241
|
+
lines.push(` let matched = false`);
|
|
242
|
+
patterns.forEach((pattern, index) => {
|
|
243
|
+
const regexName = `_walkPattern${id}_${index}`;
|
|
244
|
+
helpers.push(`const ${regexName} = new RegExp(${JSON.stringify(pattern.source)}, ${JSON.stringify(regexFlagsFor(pattern.source))})`);
|
|
245
|
+
const walk = pattern.walker === null ? [] : [
|
|
246
|
+
`const c = o[k]`,
|
|
247
|
+
...walkChild(pattern.walker, "k", `{`),
|
|
248
|
+
` o = walkSet(run, o, o !== src, k, n)`,
|
|
249
|
+
` if (n === REPAIR_DROP) continue`,
|
|
250
|
+
`}`
|
|
251
|
+
];
|
|
252
|
+
if (!needsMatched && walk.length === 0)
|
|
253
|
+
return;
|
|
254
|
+
lines.push(` if (${regexName}.test(k)) {`);
|
|
255
|
+
if (needsMatched)
|
|
256
|
+
lines.push(` matched = true`);
|
|
257
|
+
lines.push(...walk.map((line) => ` ${line}`), ` }`);
|
|
258
|
+
});
|
|
259
|
+
const otherwise = needsMatched ? `if (!matched) ` : "";
|
|
260
|
+
if (closed) {
|
|
261
|
+
lines.push(` ${otherwise}o = walkExtra(run, o, o !== src, k)`);
|
|
262
|
+
} else if (additionalWalker !== null) {
|
|
263
|
+
lines.push(` ${otherwise}{`, ` const c = o[k]`, ...walkChild(additionalWalker, "k", `o = walkSet(run, o, o !== src, k, n)`).map((line) => ` ${line}`), ` }`);
|
|
264
|
+
}
|
|
265
|
+
lines.push(`}`);
|
|
266
|
+
return lines;
|
|
267
|
+
};
|
|
268
|
+
const arrayPart = (items, ctx, walkers) => {
|
|
269
|
+
const walker = emitWalk(items, ctx, walkers, false);
|
|
270
|
+
if (walker === null)
|
|
271
|
+
return [];
|
|
272
|
+
return [
|
|
273
|
+
`if (Array.isArray(v)) {`,
|
|
274
|
+
` const src: unknown[] = v`,
|
|
275
|
+
` let a = src`,
|
|
276
|
+
` let dropped = false`,
|
|
277
|
+
` for (let i = 0; i < src.length; i++) {`,
|
|
278
|
+
` const c = src[i]`,
|
|
279
|
+
...walkChild(walker, "i", `{`).map((line) => ` ${line}`),
|
|
280
|
+
` if (n === REPAIR_DROP) dropped = true`,
|
|
281
|
+
` a = walkSetItem(run, a, a !== src, i, n)`,
|
|
282
|
+
` }`,
|
|
283
|
+
` }`,
|
|
284
|
+
` v = dropped ? walkCompact(a) : a`,
|
|
285
|
+
`}`
|
|
286
|
+
];
|
|
287
|
+
};
|
|
288
|
+
const conditionalPart = (node, self, typeId, lookup, ctx, walkers) => {
|
|
289
|
+
const lines = [];
|
|
290
|
+
const leave = lookup === null ? "return v" : "return walkUnscope(run, v)";
|
|
291
|
+
const branch = (schema) => {
|
|
292
|
+
if (schema === void 0)
|
|
293
|
+
return [];
|
|
294
|
+
const walker = emitWalk(schema, ctx, walkers, false);
|
|
295
|
+
const out = walker === null ? [] : [`v = ${walker}(v, run)`, `if (v === REPAIR_DROP) ${leave}`];
|
|
296
|
+
if (isReferenceSchema(schema, ctx.rootSchema))
|
|
297
|
+
out.push(`walkRef(run, v, ${self}, ${typeId})`);
|
|
298
|
+
return out;
|
|
299
|
+
};
|
|
300
|
+
const ifSchema = subschema(node, "if");
|
|
301
|
+
const thenSchema = subschema(node, "then");
|
|
302
|
+
const elseSchema = subschema(node, "else");
|
|
303
|
+
if (ifSchema !== void 0 && (thenSchema !== void 0 || elseSchema !== void 0)) {
|
|
304
|
+
const test = namedTest(ifSchema, ctx.repair);
|
|
305
|
+
if (test === true)
|
|
306
|
+
lines.push(...branch(thenSchema));
|
|
307
|
+
else if (test === false)
|
|
308
|
+
lines.push(...branch(elseSchema));
|
|
309
|
+
else {
|
|
310
|
+
const thenLines = branch(thenSchema);
|
|
311
|
+
const elseLines = branch(elseSchema);
|
|
312
|
+
if (thenLines.length > 0) {
|
|
313
|
+
lines.push(`if (${test}(v)) {`, ...thenLines.map((line) => ` ${line}`));
|
|
314
|
+
if (elseLines.length > 0)
|
|
315
|
+
lines.push(`} else {`, ...elseLines.map((line) => ` ${line}`));
|
|
316
|
+
lines.push(`}`);
|
|
317
|
+
} else if (elseLines.length > 0) {
|
|
318
|
+
lines.push(`if (!${test}(v)) {`, ...elseLines.map((line) => ` ${line}`), `}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const allOf = readKey(node, "allOf");
|
|
323
|
+
if (Array.isArray(allOf)) {
|
|
324
|
+
for (const entry of allOf)
|
|
325
|
+
lines.push(...branch(entry));
|
|
326
|
+
}
|
|
327
|
+
if (lines.length === 0 || lookup === null)
|
|
328
|
+
return lines;
|
|
329
|
+
return [`walkScope(run, ${lookup})`, ...lines, `walkUnscope(run, v)`];
|
|
330
|
+
};
|
|
331
|
+
const emitRepairWalk = (schema, ctx) => emitWalk(schema, ctx, /* @__PURE__ */ new Map(), true);
|
|
332
|
+
export {
|
|
333
|
+
emitRepairWalk,
|
|
334
|
+
repairWalkNameFor
|
|
335
|
+
};
|
|
@@ -26,7 +26,12 @@ export declare const generateBooleanGuard: (schema: JSONSchema, typeName: string
|
|
|
26
26
|
* fallback runs the whole validator and builds every error on the way to `false`,
|
|
27
27
|
* so there the coercer walks first instead.
|
|
28
28
|
*/
|
|
29
|
-
export declare const generateBooleanGuardSource: (schema: JSONSchema, typeName: string, unknownKeys?: UnknownKeysStrategy, formats?: ReadonlySet<string
|
|
29
|
+
export declare const generateBooleanGuardSource: (schema: JSONSchema, typeName: string, unknownKeys?: UnknownKeysStrategy, formats?: ReadonlySet<string>, refs?: {
|
|
30
|
+
/** The suffix `$ref`-derived names carry, so a ref's guard is called by its real name. */
|
|
31
|
+
readonly suffix?: string;
|
|
32
|
+
/** The whole document, for a ref that resolves inside it. */
|
|
33
|
+
readonly rootSchema?: Record<string, unknown>;
|
|
34
|
+
}) => {
|
|
30
35
|
code: string;
|
|
31
36
|
standalone: boolean;
|
|
32
37
|
};
|
|
@@ -96,11 +101,24 @@ export declare const createSubschemaMatcher: (suffix: string, rootSchema: Record
|
|
|
96
101
|
* tested, which on a union-heavy document was most of what coercing it cost.
|
|
97
102
|
*/
|
|
98
103
|
test: (sub: JSONSchema) => boolean | string;
|
|
104
|
+
/**
|
|
105
|
+
* The early-return body of {@link test}, or `null` where `test` would have to
|
|
106
|
+
* fall back to its buffered expression — a guard then calls the validator.
|
|
107
|
+
*/
|
|
108
|
+
guardBody: (sub: JSONSchema) => boolean | string | null;
|
|
99
109
|
/**
|
|
100
110
|
* The same test as a named function, hoisted with the rest and shared with
|
|
101
111
|
* every other test of the same subschema in this file — or a constant, or
|
|
102
112
|
* `null` for a body only the buffered form of {@link test} can express.
|
|
103
113
|
*/
|
|
104
114
|
named: (sub: JSONSchema) => boolean | string | null;
|
|
115
|
+
/**
|
|
116
|
+
* The name of a hoisted function that says why a value fails `sub` — every
|
|
117
|
+
* error `validateX` would report for it, rooted at the path it is handed — or
|
|
118
|
+
* `null` when `sub` has no checks to explain anything with. The repair walk
|
|
119
|
+
* calls it once a position has already failed, so it costs nothing on valid
|
|
120
|
+
* input.
|
|
121
|
+
*/
|
|
122
|
+
explain: (sub: JSONSchema) => string | null;
|
|
105
123
|
declarations: (text: string) => string[];
|
|
106
124
|
};
|
|
@@ -20,17 +20,13 @@ const checkerName = (typeName) => `check${typeName}`;
|
|
|
20
20
|
const propertyRead = (objVar, key) => safeAccessor(objVar, key);
|
|
21
21
|
const PROTOTYPE_MEMBERS = new Set(Object.getOwnPropertyNames(Object.prototype));
|
|
22
22
|
const protoLocalName = (key, depth) => `_own${depth}_${key}`;
|
|
23
|
+
const propertyLocalName = (key, index, depth) => PROTOTYPE_MEMBERS.has(key) ? protoLocalName(key, depth) : `_v${depth}_${index}`;
|
|
23
24
|
const hasOwnCheck = (objVar, key) => {
|
|
24
25
|
if (PROTOTYPE_MEMBERS.has(key))
|
|
25
26
|
return `Object.hasOwn(${objVar}, ${JSON.stringify(key)})`;
|
|
26
27
|
return `${JSON.stringify(key)} in ${objVar}`;
|
|
27
28
|
};
|
|
28
29
|
const missingCheck = (objVar, key) => `!(${hasOwnCheck(objVar, key)})`;
|
|
29
|
-
const typeofString = (type) => {
|
|
30
|
-
if (type === "integer")
|
|
31
|
-
return "number";
|
|
32
|
-
return type;
|
|
33
|
-
};
|
|
34
30
|
const constMismatchCondition = (accessor, value) => {
|
|
35
31
|
if (value === null || typeof value !== "object") {
|
|
36
32
|
return `${accessor} !== ${JSON.stringify(value)}`;
|
|
@@ -219,7 +215,7 @@ const generateMissingRequiredChecks = (schema, ctx) => {
|
|
|
219
215
|
}
|
|
220
216
|
return lines;
|
|
221
217
|
};
|
|
222
|
-
const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
|
|
218
|
+
const generatePropertyChecks = (key, index, propSchema, isRequired, suffix, ctx) => {
|
|
223
219
|
if (propSchema === false) {
|
|
224
220
|
const path = `\`${ctx.pathPrefix}/${pointerSegment(key)}\``;
|
|
225
221
|
const parentPath = ctx.depth === 0 ? "_path" : `\`${ctx.pathPrefix}\``;
|
|
@@ -239,18 +235,18 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
|
|
|
239
235
|
` }`
|
|
240
236
|
];
|
|
241
237
|
}
|
|
242
|
-
const raw =
|
|
238
|
+
const raw = propertyLocalName(key, index, ctx.depth);
|
|
243
239
|
const lines = [
|
|
244
|
-
...generatePropertyCheckLines(key, propSchema, isRequired, suffix, ctx),
|
|
240
|
+
...generatePropertyCheckLines(key, raw, propSchema, isRequired, suffix, ctx),
|
|
245
241
|
// `unevaluated*` is a sibling of everything else the property declares, and
|
|
246
242
|
// several of the branches above return early, so it is appended here rather
|
|
247
243
|
// than emitted inside them. The checks carry their own runtime type guard, so
|
|
248
244
|
// an absent optional property is inert and needs no presence gate.
|
|
249
245
|
...generateUnevaluatedChecks(raw, `\`${ctx.pathPrefix}/${pointerSegment(key)}\``, propSchema, suffix, ctx)
|
|
250
246
|
];
|
|
251
|
-
if (lines.
|
|
247
|
+
if (!readsBinding(raw, lines.join("\n")))
|
|
252
248
|
return lines;
|
|
253
|
-
return [` const ${
|
|
249
|
+
return [` const ${raw} = ${propertyRead(ctx.objVar, key)}`, ...lines];
|
|
254
250
|
};
|
|
255
251
|
const singleIfBlock = (lines) => {
|
|
256
252
|
if (lines.length < 3)
|
|
@@ -272,7 +268,11 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
|
|
|
272
268
|
const target = refToName(schema.$ref, suffix);
|
|
273
269
|
const delegate = ctx.sink === FAIL_FAST_SINK ? [` const _r = ${checkerName(target)}(${raw}, ${path})`, ` if (_r !== true) return _r`] : ctx.sink === MATCH_BOOL_SINK ? [` if (!${ctx.refGuard(target)}(${raw})) return false`] : [
|
|
274
270
|
` const _r = ${validatorName(target)}(${raw}, ${path})`,
|
|
275
|
-
|
|
271
|
+
// A loop rather than a spread: spreading passes every error as an
|
|
272
|
+
// argument, and a document with tens of thousands of errors under
|
|
273
|
+
// one reference overflows the engine's argument limit with a
|
|
274
|
+
// RangeError instead of returning them.
|
|
275
|
+
` if (_r !== true) for (const _e of _r.errors) ${ctx.sink}.push(_e)`
|
|
276
276
|
];
|
|
277
277
|
if (presence === "")
|
|
278
278
|
lines.push(` {`, ...delegate.map((line) => ` ${line}`), ` }`);
|
|
@@ -310,7 +310,7 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
|
|
|
310
310
|
const wrongType = wrongTypeCondition(typeTestAccessor(raw, ctx), t);
|
|
311
311
|
if (wrongType) {
|
|
312
312
|
lines.push(` if (${presence === "" ? wrongType : `${presence}(${wrongType})`}) {`);
|
|
313
|
-
lines.push(` ${pushError(ctx.sink, `'must be ${
|
|
313
|
+
lines.push(` ${pushError(ctx.sink, `'must be ${t}'`, path, "type", JSON.stringify({ type: t }))}`);
|
|
314
314
|
lines.push(` }`);
|
|
315
315
|
}
|
|
316
316
|
}
|
|
@@ -318,7 +318,7 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
|
|
|
318
318
|
if (typeArray) {
|
|
319
319
|
const allWrong = typeArray.map((t) => wrongTypeCondition(typeTestAccessor(raw, ctx), t)).filter((c) => c !== "").map((c) => `(${c})`).join(" && ");
|
|
320
320
|
if (allWrong) {
|
|
321
|
-
const label = typeArray.
|
|
321
|
+
const label = typeArray.join(" or ");
|
|
322
322
|
lines.push(` if (${presence === "" ? allWrong : `${presence}(${allWrong})`}) {`);
|
|
323
323
|
lines.push(` ${pushError(ctx.sink, JSON.stringify(`must be ${label}`), path, "type", JSON.stringify({ type: schema.type }))}`);
|
|
324
324
|
lines.push(` }`);
|
|
@@ -335,7 +335,7 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
|
|
|
335
335
|
}
|
|
336
336
|
return lines;
|
|
337
337
|
};
|
|
338
|
-
const generatePropertyCheckLines = (key, propSchema, isRequired, suffix, ctx) => {
|
|
338
|
+
const generatePropertyCheckLines = (key, raw, propSchema, isRequired, suffix, ctx) => {
|
|
339
339
|
const parentPath = ctx.depth === 0 ? "_path" : `\`${ctx.pathPrefix}\``;
|
|
340
340
|
const missing = [
|
|
341
341
|
` if (${missingCheck(ctx.objVar, key)}) {`,
|
|
@@ -346,7 +346,6 @@ const generatePropertyCheckLines = (key, propSchema, isRequired, suffix, ctx) =>
|
|
|
346
346
|
return [...missing, ` }`];
|
|
347
347
|
return [];
|
|
348
348
|
}
|
|
349
|
-
const raw = PROTOTYPE_MEMBERS.has(key) ? protoLocalName(key, ctx.depth) : propertyRead(ctx.objVar, key);
|
|
350
349
|
const path = `\`${ctx.pathPrefix}/${pointerSegment(key)}\``;
|
|
351
350
|
if (isRequired) {
|
|
352
351
|
const valueLines = generateKeywordChecks(key, raw, path, propSchema, suffix, ctx, "");
|
|
@@ -430,7 +429,7 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
|
|
|
430
429
|
if (hasRef(itemSchema) && !declaresKeywordOutside(itemSchema, ["$ref"])) {
|
|
431
430
|
const failFast = ctx.sink === FAIL_FAST_SINK;
|
|
432
431
|
const target = refToName(itemSchema.$ref, suffix);
|
|
433
|
-
const onFailure = failFast ? "return _ir" :
|
|
432
|
+
const onFailure = failFast ? "return _ir" : `for (const _e of _ir.errors) ${ctx.sink}.push(_e)`;
|
|
434
433
|
lines.push(` if (Array.isArray(${raw})) {`);
|
|
435
434
|
lines.push(` for (let ${iv} = ${firstTailIndex}; ${iv} < ${raw}.length; ${iv}++) {`);
|
|
436
435
|
if (ctx.sink === MATCH_BOOL_SINK) {
|
|
@@ -573,7 +572,7 @@ const generateValueCheckLines = (_key, raw, path, propSchema, suffix, ctx, requi
|
|
|
573
572
|
lines.push(...generateKeywordChecks("", raw, path, propSchema, suffix, valueCtx, presence));
|
|
574
573
|
return lines;
|
|
575
574
|
};
|
|
576
|
-
const NARROWABLE_REFERENCE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[A-Za-z_$][\w$]*\]|\[\d+\])*$/;
|
|
575
|
+
const NARROWABLE_REFERENCE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\["[A-Za-z_$][\w$]*"\]|\[[A-Za-z_$][\w$]*\]|\[\d+\])*$/;
|
|
577
576
|
const namedMatcher = (sub, suffix, ctx) => {
|
|
578
577
|
const key = JSON.stringify(sub);
|
|
579
578
|
const existing = ctx.namedMatchers.get(key);
|
|
@@ -710,14 +709,14 @@ const generateUnevaluatedChecks = (raw, path, schema, suffix, ctx) => {
|
|
|
710
709
|
}
|
|
711
710
|
return lines;
|
|
712
711
|
};
|
|
713
|
-
const branchErrorsReport = (guard, raw, explain, message, keyword, path, ctx) => [
|
|
712
|
+
const branchErrorsReport = (guard, raw, explain, total, message, keyword, path, ctx) => [
|
|
714
713
|
` if (${guard}) {`,
|
|
715
714
|
` ${pushError(ctx.sink, message, path, keyword)}`,
|
|
716
715
|
...explain.length === 0 ? [] : [
|
|
717
716
|
` const _bp = ${path}`,
|
|
718
717
|
` const _br: ValidationError[][] = []`,
|
|
719
718
|
` for (const _b of [${explain.map((name) => `${name}(${raw}, _bp)`).join(", ")}]) if (_b.length !== 0) _br.push(_b)`,
|
|
720
|
-
` for (const _e of selectBranchErrors(_br, _bp)) ${ctx.sink}.push(_e)`
|
|
719
|
+
` for (const _e of selectBranchErrors(_br, _bp${explain.length === total ? `, ${total}` : ""})) ${ctx.sink}.push(_e)`
|
|
721
720
|
],
|
|
722
721
|
` }`
|
|
723
722
|
];
|
|
@@ -734,7 +733,7 @@ const generateCombinatorChecks = (key, raw, path, schema, suffix, ctx) => {
|
|
|
734
733
|
const conds = schema.anyOf.map((b) => generateMatchesExpr(raw, b, suffix, ctx, true));
|
|
735
734
|
if (!conds.includes("true")) {
|
|
736
735
|
if (ctx.branchErrors) {
|
|
737
|
-
lines.push(...branchErrorsReport(`!(${conds.join(" || ")})`, raw, branchExplainers(schema.anyOf, suffix, ctx), `'must match a schema in anyOf'`, "anyOf", path, ctx));
|
|
736
|
+
lines.push(...branchErrorsReport(`!(${conds.join(" || ")})`, raw, branchExplainers(schema.anyOf, suffix, ctx), schema.anyOf.length, `'must match a schema in anyOf'`, "anyOf", path, ctx));
|
|
738
737
|
} else {
|
|
739
738
|
const condition = `!(${conds.join(" || ")})`;
|
|
740
739
|
if (LITERAL_CONDITION.test(condition))
|
|
@@ -756,7 +755,7 @@ const generateCombinatorChecks = (key, raw, path, schema, suffix, ctx) => {
|
|
|
756
755
|
const explain = branchExplainers(schema.oneOf, suffix, ctx);
|
|
757
756
|
lines.push(` {`);
|
|
758
757
|
lines.push(` const _n = ${conds.join(" + ")}`);
|
|
759
|
-
const report = branchErrorsReport("_n === 0", raw, explain, message, "oneOf", path, ctx).map((line) => ` ${line}`);
|
|
758
|
+
const report = branchErrorsReport("_n === 0", raw, explain, schema.oneOf.length, message, "oneOf", path, ctx).map((line) => ` ${line}`);
|
|
760
759
|
lines.push(...report.slice(0, -1));
|
|
761
760
|
lines.push(` } else if (_n !== 1) {`);
|
|
762
761
|
lines.push(` ${pushError(ctx.sink, message, path, "oneOf")}`);
|
|
@@ -869,8 +868,8 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
|
|
|
869
868
|
const required = new Set(hasRequired(propSchema) ? propSchema.required : []);
|
|
870
869
|
const properties = hasProperties(propSchema) ? propSchema.properties : {};
|
|
871
870
|
const innerLines = [];
|
|
872
|
-
for (const [childKey, childSchema] of Object.entries(properties)) {
|
|
873
|
-
innerLines.push(...generatePropertyChecks(childKey, childSchema, required.has(childKey), suffix, child));
|
|
871
|
+
for (const [index, [childKey, childSchema]] of Object.entries(properties).entries()) {
|
|
872
|
+
innerLines.push(...generatePropertyChecks(childKey, index, childSchema, required.has(childKey), suffix, child));
|
|
874
873
|
}
|
|
875
874
|
innerLines.push(...generateMissingRequiredChecks(propSchema, child));
|
|
876
875
|
innerLines.push(...generatePatternAndAdditionalChecks(propSchema, suffix, child));
|
|
@@ -1198,8 +1197,8 @@ const generateObjectValidator = (schema, typeName, suffix, rootSchema, unknownKe
|
|
|
1198
1197
|
const properties = hasProperties(schema) ? schema.properties : {};
|
|
1199
1198
|
const ctx = createRootContext(rootSchema, formats, branchErrors, failFast);
|
|
1200
1199
|
const propertyLines = [];
|
|
1201
|
-
for (const [key, propSchema] of Object.entries(properties)) {
|
|
1202
|
-
const checks = generatePropertyChecks(key, propSchema, required.has(key), suffix, ctx);
|
|
1200
|
+
for (const [index, [key, propSchema]] of Object.entries(properties).entries()) {
|
|
1201
|
+
const checks = generatePropertyChecks(key, index, propSchema, required.has(key), suffix, ctx);
|
|
1203
1202
|
if (checks.length > 0) {
|
|
1204
1203
|
propertyLines.push(...checks);
|
|
1205
1204
|
}
|
|
@@ -1472,18 +1471,38 @@ const typeDescribesEveryAcceptedValue = (schema) => {
|
|
|
1472
1471
|
return !IMPLICIT_OBJECT_KEYWORDS.some((keyword) => declaresKey(s, keyword));
|
|
1473
1472
|
};
|
|
1474
1473
|
const generateBooleanGuard = (schema, typeName, _suffix = "", unknownKeys = DEFAULT_UNKNOWN_KEYS, formats = NO_FORMATS) => generateBooleanGuardSource(schema, typeName, unknownKeys, formats).code;
|
|
1475
|
-
const generateBooleanGuardSource = (schema, typeName, unknownKeys = DEFAULT_UNKNOWN_KEYS, formats = NO_FORMATS) => {
|
|
1474
|
+
const generateBooleanGuardSource = (schema, typeName, unknownKeys = DEFAULT_UNKNOWN_KEYS, formats = NO_FORMATS, refs = {}) => {
|
|
1476
1475
|
const name = guardName(typeName);
|
|
1477
1476
|
const returns = typeDescribesEveryAcceptedValue(rewriteNullable(schema)) ? `input is ${typeName}` : "boolean";
|
|
1478
|
-
const fallback =
|
|
1477
|
+
const fallback = () => {
|
|
1478
|
+
const matcher = createSubschemaMatcher(refs.suffix ?? "", refs.rootSchema ?? (isSchemaObject(schema) ? schema : void 0), formats, "_guard");
|
|
1479
|
+
const body = matcher.guardBody(schema);
|
|
1480
|
+
if (typeof body !== "string" || !readsBinding("input", body)) {
|
|
1481
|
+
return {
|
|
1482
|
+
code: `export const ${name} = (input: unknown): ${returns} => ${validatorName(typeName)}(input) === true`,
|
|
1483
|
+
standalone: false
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
const declarations = matcher.declarations(body);
|
|
1487
|
+
const code = [
|
|
1488
|
+
...declarations.length > 0 ? [declarations.join("\n\n"), ""] : [],
|
|
1489
|
+
`export const ${name} = (input: unknown): ${returns} => {`,
|
|
1490
|
+
// Some checks build a path for a key or an index before they know whether
|
|
1491
|
+
// they fail. Nothing reports it here, so it is rooted at nothing.
|
|
1492
|
+
...readsBinding("_path", body) ? [` const _path = ''`] : [],
|
|
1493
|
+
body,
|
|
1494
|
+
`}`
|
|
1495
|
+
].join("\n");
|
|
1496
|
+
return { code, standalone: true };
|
|
1497
|
+
};
|
|
1479
1498
|
const rewritten = rewriteNullable(schema);
|
|
1480
1499
|
const ctx = createGuardContext(unknownKeys, formats);
|
|
1481
1500
|
if (declaresObjectType(rewritten)) {
|
|
1482
1501
|
if (!objectRootIsSelfContained(rewritten))
|
|
1483
|
-
return
|
|
1502
|
+
return fallback();
|
|
1484
1503
|
const block = booleanObjectParts(rewritten, "input", "obj", ctx);
|
|
1485
1504
|
if (block === null)
|
|
1486
|
-
return
|
|
1505
|
+
return fallback();
|
|
1487
1506
|
const body = isFlatGuardBlock(block) ? [` return (`, block.conditions.map((part) => ` ${part}`).join(" &&\n"), ` )`] : [...renderGuardBlock(block, "return false", " ", "lines", unknownKeys), ` return true`];
|
|
1488
1507
|
const code = [
|
|
1489
1508
|
`export const ${name} = (input: unknown): ${returns} => {`,
|
|
@@ -1497,7 +1516,7 @@ const generateBooleanGuardSource = (schema, typeName, unknownKeys = DEFAULT_UNKN
|
|
|
1497
1516
|
}
|
|
1498
1517
|
const expr = booleanLeafExpr(rewritten, "input", ctx);
|
|
1499
1518
|
if (expr === null)
|
|
1500
|
-
return
|
|
1519
|
+
return fallback();
|
|
1501
1520
|
return { code: `export const ${name} = (input: unknown): ${returns} => ${expr}`, standalone: true };
|
|
1502
1521
|
};
|
|
1503
1522
|
const generateScalarValidator = (schema, typeName, suffix, rootSchema, formats, branchErrors, failFast) => {
|
|
@@ -1584,7 +1603,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema, formats,
|
|
|
1584
1603
|
if (rootTypeArray2) {
|
|
1585
1604
|
const allWrong = rootTypeArray2.map((t) => wrongTypeCondition(typeTestAccessor("input", ctx), t)).filter((c) => c !== "").map((c) => `(${c})`).join(" && ");
|
|
1586
1605
|
if (allWrong) {
|
|
1587
|
-
const label = rootTypeArray2.
|
|
1606
|
+
const label = rootTypeArray2.join(" or ");
|
|
1588
1607
|
checks.push(` if (${allWrong}) {`);
|
|
1589
1608
|
checks.push(` ${pushError(ctx.sink, JSON.stringify(`must be ${label}`), rootPath, "type", JSON.stringify({ type: schema.type }))}`);
|
|
1590
1609
|
checks.push(` }`);
|
|
@@ -1594,7 +1613,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema, formats,
|
|
|
1594
1613
|
const wrongType = wrongTypeCondition(typeTestAccessor("input", ctx), t);
|
|
1595
1614
|
if (wrongType) {
|
|
1596
1615
|
checks.push(` if (${wrongType}) {`);
|
|
1597
|
-
checks.push(` ${pushError(ctx.sink, `'must be ${
|
|
1616
|
+
checks.push(` ${pushError(ctx.sink, `'must be ${t}'`, rootPath, "type", JSON.stringify({ type: t }))}`);
|
|
1598
1617
|
checks.push(` }`);
|
|
1599
1618
|
}
|
|
1600
1619
|
}
|
|
@@ -1616,7 +1635,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema, formats,
|
|
|
1616
1635
|
const checks = [];
|
|
1617
1636
|
const allWrong = rootTypeArray.map((t) => wrongTypeCondition(typeTestAccessor("input", ctx), t)).filter((c) => c !== "").map((c) => `(${c})`).join(" && ");
|
|
1618
1637
|
if (allWrong) {
|
|
1619
|
-
const label = rootTypeArray.
|
|
1638
|
+
const label = rootTypeArray.join(" or ");
|
|
1620
1639
|
checks.push(` if (${allWrong}) {`);
|
|
1621
1640
|
checks.push(` ${pushError(ctx.sink, JSON.stringify(`must be ${label}`), rootPath, "type", JSON.stringify({ type: schema.type }))}`);
|
|
1622
1641
|
checks.push(` }`);
|
|
@@ -1641,7 +1660,6 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema, formats,
|
|
|
1641
1660
|
if (hasType(schema)) {
|
|
1642
1661
|
const t = schema.type;
|
|
1643
1662
|
const wrongType = wrongTypeCondition("input", t);
|
|
1644
|
-
const typLabel = typeofString(t);
|
|
1645
1663
|
const rootCtx = createRootContext(rootSchema, formats, branchErrors, failFast);
|
|
1646
1664
|
const constraintLines = generateConstraintChecks("", "_root", "`${_path}`", schema, suffix, rootCtx);
|
|
1647
1665
|
if (!wrongType) {
|
|
@@ -1655,7 +1673,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema, formats,
|
|
|
1655
1673
|
return [
|
|
1656
1674
|
`export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
1657
1675
|
` if (${wrongType}) {`,
|
|
1658
|
-
` ${returnError(`'must be ${
|
|
1676
|
+
` ${returnError(`'must be ${t}'`, "_path", "type", JSON.stringify({ type: schema.type }))}`,
|
|
1659
1677
|
` }`,
|
|
1660
1678
|
` return true`,
|
|
1661
1679
|
`}`
|
|
@@ -1664,7 +1682,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema, formats,
|
|
|
1664
1682
|
return withHoisted(rootCtx.hoisted, [
|
|
1665
1683
|
`export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
1666
1684
|
` if (${wrongType}) {`,
|
|
1667
|
-
` ${returnError(`'must be ${
|
|
1685
|
+
` ${returnError(`'must be ${t}'`, "_path", "type", JSON.stringify({ type: schema.type }))}`,
|
|
1668
1686
|
` }`,
|
|
1669
1687
|
...readsBinding("_root", constraintLines.join("\n")) ? [` const _root: unknown = input`] : [],
|
|
1670
1688
|
...errorsBinding(failFast),
|
|
@@ -1796,12 +1814,23 @@ const createSubschemaMatcher = (suffix, rootSchema, formats, hoistNamespace, ref
|
|
|
1796
1814
|
return true`;
|
|
1797
1815
|
return ` return ${generateMatchesExpr("input", rewritten, suffix, ctx, true)}`;
|
|
1798
1816
|
},
|
|
1817
|
+
guardBody: (sub) => {
|
|
1818
|
+
if (sub === false)
|
|
1819
|
+
return false;
|
|
1820
|
+
const state = { on: false, blocked: false };
|
|
1821
|
+
const checks = generateValueChecks("", "input", "`${_path}`", rewriteNullable(sub), suffix, { ...ctx, sink: MATCH_BOOL_SINK, failFast: state, branchErrors: false }, true);
|
|
1822
|
+
if (checks.length === 0)
|
|
1823
|
+
return true;
|
|
1824
|
+
return state.blocked ? null : `${checks.join("\n")}
|
|
1825
|
+
return true`;
|
|
1826
|
+
},
|
|
1799
1827
|
named: (sub) => {
|
|
1800
1828
|
if (sub === false)
|
|
1801
1829
|
return false;
|
|
1802
1830
|
const name = namedMatcher(rewriteNullable(sub), suffix, ctx);
|
|
1803
1831
|
return name === "true" ? true : name;
|
|
1804
1832
|
},
|
|
1833
|
+
explain: (sub) => namedBranchErrors(rewriteNullable(sub), suffix, ctx),
|
|
1805
1834
|
declarations: (text) => keptHoisted(ctx.hoisted, text)
|
|
1806
1835
|
};
|
|
1807
1836
|
};
|