@amritk/validation 0.5.0 → 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 +50 -3
- package/dist/validators/generators/build-schema.d.ts +1 -1
- package/dist/validators/generators/build-schema.js +32 -217
- 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 +53 -18
- 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 +1 -1
|
@@ -1,157 +1,263 @@
|
|
|
1
1
|
import { regexFlagsFor } from "@amritk/helpers/escape-regex-pattern";
|
|
2
2
|
import { getDefaultValue } from "@amritk/helpers/get-default-value";
|
|
3
|
-
import { readKey } from "@amritk/helpers/read-key";
|
|
3
|
+
import { declaresKey, readKey } from "@amritk/helpers/read-key";
|
|
4
4
|
import { refToName } from "@amritk/helpers/ref-to-name";
|
|
5
5
|
import { isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
6
6
|
import { NO_FORMATS } from "./enforced-keywords.js";
|
|
7
|
+
import { emitRepairWalk, repairWalkNameFor } from "./generate-repair-walk.js";
|
|
7
8
|
import { createSubschemaMatcher } from "./generate-validator-function.js";
|
|
9
|
+
import { namedTest, schemaMap, subschema } from "./repair-context.js";
|
|
8
10
|
const repairLookupNameFor = (ref, typeSuffix) => `repair${refToName(ref, typeSuffix)}At`;
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
if (
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
const isObjectish = (node) => {
|
|
12
|
+
const type = readKey(node, "type");
|
|
13
|
+
if (type === "object")
|
|
14
|
+
return true;
|
|
15
|
+
return type === void 0 && (declaresKey(node, "properties") || declaresKey(node, "required"));
|
|
16
|
+
};
|
|
17
|
+
const namesItsValue = (node) => declaresKey(node, "default") || declaresKey(node, "const") || declaresKey(node, "enum");
|
|
18
|
+
const requiredKeys = (node) => {
|
|
19
|
+
const required = readKey(node, "required");
|
|
20
|
+
return Array.isArray(required) ? required.filter((key) => typeof key === "string") : [];
|
|
21
|
+
};
|
|
22
|
+
const dropLookup = (ctx) => {
|
|
23
|
+
if (ctx.drop !== null)
|
|
24
|
+
return ctx.drop;
|
|
25
|
+
ctx.drop = "_repairDrop";
|
|
26
|
+
ctx.helpers.push(`const _repairDrop: RepairLookup = (segments, i) => (i === segments.length ? REPAIR_DROP : null)`);
|
|
27
|
+
return ctx.drop;
|
|
28
|
+
};
|
|
29
|
+
const conditionalBranches = (node, ctx) => {
|
|
30
|
+
const ifSchema = subschema(node, "if");
|
|
31
|
+
if (ifSchema === void 0)
|
|
32
|
+
return null;
|
|
33
|
+
const thenSchema = subschema(node, "then");
|
|
34
|
+
const elseSchema = subschema(node, "else");
|
|
35
|
+
if (thenSchema === void 0 && elseSchema === void 0)
|
|
36
|
+
return null;
|
|
37
|
+
const test = namedTest(ifSchema, ctx);
|
|
38
|
+
const thenLookup = test !== false && thenSchema !== void 0 ? emitLookup(thenSchema, ctx) : null;
|
|
39
|
+
const elseLookup = test !== true && elseSchema !== void 0 ? emitLookup(elseSchema, ctx) : null;
|
|
40
|
+
return thenLookup === null && elseLookup === null ? null : { test, thenLookup, elseLookup };
|
|
41
|
+
};
|
|
42
|
+
const conditionalAnswer = (node, ctx) => {
|
|
43
|
+
const parts = [];
|
|
44
|
+
const call = (lookup) => lookup === null ? "null" : `${lookup}(segments, i, value)`;
|
|
45
|
+
const branches = conditionalBranches(node, ctx);
|
|
46
|
+
if (branches !== null) {
|
|
47
|
+
const { test, thenLookup, elseLookup } = branches;
|
|
48
|
+
if (test === true) {
|
|
49
|
+
if (thenLookup !== null)
|
|
50
|
+
parts.push(call(thenLookup));
|
|
51
|
+
} else if (test === false) {
|
|
52
|
+
if (elseLookup !== null)
|
|
53
|
+
parts.push(call(elseLookup));
|
|
54
|
+
} else {
|
|
55
|
+
parts.push(`(${test}(value) ? ${call(thenLookup)} : ${call(elseLookup)})`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const allOf = readKey(node, "allOf");
|
|
59
|
+
if (Array.isArray(allOf)) {
|
|
60
|
+
for (const entry of allOf) {
|
|
61
|
+
const lookup = emitLookup(entry, ctx);
|
|
62
|
+
if (lookup !== null)
|
|
63
|
+
parts.push(call(lookup));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return parts.length === 0 ? null : parts.join(" ?? ");
|
|
21
67
|
};
|
|
22
|
-
const
|
|
23
|
-
const literal = getDefaultValue(
|
|
24
|
-
|
|
68
|
+
const emitFill = (node, self, id, ctx) => {
|
|
69
|
+
const literal = namesItsValue(node) ? getDefaultValue(node) : "{}";
|
|
70
|
+
const base = literal.startsWith("{") ? literal : "{}";
|
|
71
|
+
const steps = requiredKeys(node).map((key) => `fillKey(o, ${JSON.stringify(key)}, ${self}, depth)`);
|
|
72
|
+
const branches = conditionalBranches(node, ctx);
|
|
73
|
+
if (branches !== null) {
|
|
74
|
+
const { test, thenLookup, elseLookup } = branches;
|
|
75
|
+
const answer = (lookup) => lookup === null ? "null" : `${lookup}(NO_SEGMENTS, 0, o)`;
|
|
76
|
+
if (test === true && thenLookup !== null)
|
|
77
|
+
steps.push(`fillInto(o, ${answer(thenLookup)}, depth)`);
|
|
78
|
+
else if (test === false && elseLookup !== null)
|
|
79
|
+
steps.push(`fillInto(o, ${answer(elseLookup)}, depth)`);
|
|
80
|
+
else if (typeof test === "string") {
|
|
81
|
+
steps.push(`fillInto(o, ${test}(o) ? ${answer(thenLookup)} : ${answer(elseLookup)}, depth)`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const allOf = readKey(node, "allOf");
|
|
85
|
+
if (Array.isArray(allOf)) {
|
|
86
|
+
for (const entry of allOf) {
|
|
87
|
+
const lookup = emitLookup(entry, ctx);
|
|
88
|
+
if (lookup !== null)
|
|
89
|
+
steps.push(`fillInto(o, ${lookup}(NO_SEGMENTS, 0, o), depth)`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const name = `_repairFill${id}`;
|
|
93
|
+
const lines = steps.length === 0 ? [`const ${name}: RepairThunk = (_depth, into) => into ?? ${base}`] : [
|
|
94
|
+
`const ${name}: RepairThunk = (depth, into) => {`,
|
|
95
|
+
// The outermost fill starts the budget every level of it shares.
|
|
96
|
+
` if (into === undefined && depth === 0) fillBegin()`,
|
|
97
|
+
` const o: Record<string, unknown> = into ?? ${base}`,
|
|
98
|
+
` if (depth < MAX_FILL_DEPTH) {`,
|
|
99
|
+
...steps.map((step) => ` ${step}`),
|
|
100
|
+
` }`,
|
|
101
|
+
` return o`,
|
|
102
|
+
`}`
|
|
103
|
+
];
|
|
104
|
+
ctx.helpers.push(lines.join("\n"));
|
|
105
|
+
return name;
|
|
25
106
|
};
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
107
|
+
const ownAnswer = (node, self, id, ctx) => {
|
|
108
|
+
const marker = readKey(node, "x-repair");
|
|
109
|
+
if (marker === "drop")
|
|
110
|
+
return "REPAIR_DROP";
|
|
111
|
+
if (isObjectish(node))
|
|
112
|
+
return emitFill(node, self, id, ctx);
|
|
113
|
+
if (readKey(node, "type") === void 0 && !namesItsValue(node)) {
|
|
114
|
+
const conditional = conditionalAnswer(node, ctx);
|
|
115
|
+
if (conditional !== null)
|
|
116
|
+
return conditional;
|
|
117
|
+
}
|
|
118
|
+
const literal = getDefaultValue(node);
|
|
119
|
+
if (literal === "undefined")
|
|
120
|
+
return null;
|
|
121
|
+
const name = `_repairValue${id}`;
|
|
122
|
+
ctx.helpers.push(`const ${name}: RepairThunk = () => (${literal})`);
|
|
123
|
+
return name;
|
|
29
124
|
};
|
|
30
|
-
const
|
|
125
|
+
const emitLookup = (schema, ctx) => {
|
|
126
|
+
if (schema === false)
|
|
127
|
+
return dropLookup(ctx);
|
|
31
128
|
if (!isSchemaObject(schema))
|
|
32
129
|
return null;
|
|
130
|
+
if (ctx.lookups.has(schema))
|
|
131
|
+
return ctx.lookups.get(schema) ?? null;
|
|
33
132
|
const node = schema;
|
|
34
133
|
const ref = readKey(node, "$ref");
|
|
35
|
-
if (typeof ref === "string")
|
|
36
|
-
|
|
37
|
-
|
|
134
|
+
if (typeof ref === "string") {
|
|
135
|
+
const name2 = repairLookupNameFor(ref, ctx.typeSuffix);
|
|
136
|
+
ctx.lookups.set(schema, name2);
|
|
137
|
+
return name2;
|
|
138
|
+
}
|
|
139
|
+
const id = ctx.next();
|
|
140
|
+
const name = `_repairAt${id}`;
|
|
141
|
+
ctx.lookups.set(schema, name);
|
|
142
|
+
const own = ownAnswer(node, name, id, ctx);
|
|
143
|
+
const descent = emitDescent(node, id, ctx);
|
|
144
|
+
const conditional = conditionalAnswer(node, ctx);
|
|
145
|
+
if (own === null && descent === null && conditional === null) {
|
|
146
|
+
ctx.lookups.set(schema, null);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const deeper = descent !== null && conditional !== null ? `${conditional} ?? ${descent}(segments, i, value)` : descent !== null ? `${descent}(segments, i, value)` : conditional ?? "null";
|
|
150
|
+
const body = [` if (i === segments.length) return ${own ?? "null"}`, ` return ${deeper}`];
|
|
151
|
+
const parameter = body.some((line) => /\bvalue\b/.test(line)) ? "value" : "_value";
|
|
152
|
+
ctx.helpers.push([`const ${name}: RepairLookup = (segments, i, ${parameter}) => {`, ...body, `}`].join("\n"));
|
|
153
|
+
return name;
|
|
154
|
+
};
|
|
155
|
+
const emitDescent = (node, id, ctx) => {
|
|
38
156
|
const properties = schemaMap(node, "properties") ?? {};
|
|
39
|
-
const children = Object.entries(properties).map(([key,
|
|
157
|
+
const children = Object.entries(properties).map(([key, child2]) => [key, emitLookup(child2, ctx)]).filter((entry) => entry[1] !== null);
|
|
40
158
|
const patternProperties = schemaMap(node, "patternProperties") ?? {};
|
|
41
|
-
const patterns = Object.entries(patternProperties).map(([source,
|
|
159
|
+
const patterns = Object.entries(patternProperties).map(([source, child2]) => ({
|
|
42
160
|
source,
|
|
43
|
-
|
|
161
|
+
lookup: emitLookup(child2, ctx)
|
|
44
162
|
}));
|
|
45
|
-
const additional =
|
|
46
|
-
const
|
|
47
|
-
const mapped =
|
|
163
|
+
const additional = subschema(node, "additionalProperties");
|
|
164
|
+
const additionalLookup = additional === void 0 ? null : emitLookup(additional, ctx);
|
|
165
|
+
const mapped = additionalLookup !== null || patterns.some((pattern) => pattern.lookup !== null);
|
|
48
166
|
const prefixItems = readKey(node, "prefixItems");
|
|
49
167
|
const tuple = Array.isArray(prefixItems) ? prefixItems : [];
|
|
50
|
-
const
|
|
168
|
+
const tupleLookups = tuple.map((item) => emitLookup(item, ctx));
|
|
51
169
|
const items = readKey(node, "items");
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
170
|
+
const itemsSchema = typeof items === "boolean" || typeof items === "object" && items !== null && !Array.isArray(items);
|
|
171
|
+
const itemsLookup = itemsSchema ? emitLookup(items, ctx) : null;
|
|
172
|
+
const indexed = tupleLookups.some((lookup) => lookup !== null) || itemsLookup !== null;
|
|
173
|
+
if (children.length === 0 && !mapped && !indexed)
|
|
55
174
|
return null;
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const walkers = new Map(children);
|
|
77
|
-
lines.push(` switch (head) {`);
|
|
78
|
-
for (const key of declared) {
|
|
79
|
-
const walker = walkers.get(key);
|
|
80
|
-
lines.push(` case ${JSON.stringify(key)}: return ${walker === void 0 ? "null" : `${walker}(rest)`}`);
|
|
81
|
-
}
|
|
82
|
-
lines.push(` }`);
|
|
83
|
-
}
|
|
84
|
-
if (indexBlock) {
|
|
85
|
-
lines.push(` const index = Number(head)`);
|
|
86
|
-
lines.push(` if (Number.isInteger(index) && index >= 0) {`);
|
|
87
|
-
tupleWalkers.forEach((walker, position) => {
|
|
88
|
-
if (walker === null)
|
|
89
|
-
return;
|
|
90
|
-
lines.push(` if (index === ${position}) return ${walker}(rest)`);
|
|
91
|
-
});
|
|
92
|
-
if (itemsWalker !== null) {
|
|
93
|
-
lines.push(` if (index >= ${tuple.length}) return ${itemsWalker}(rest)`);
|
|
94
|
-
}
|
|
95
|
-
if (mapped && arrayKeywords)
|
|
96
|
-
lines.push(` return null`);
|
|
97
|
-
lines.push(` }`);
|
|
175
|
+
const name = `_repairChild${id}`;
|
|
176
|
+
const lines = [`const ${name}: RepairLookup = (segments, i, value) => {`];
|
|
177
|
+
lines.push(` const head = segments[i] as string`);
|
|
178
|
+
lines.push(` const next = i + 1`);
|
|
179
|
+
const declared = mapped ? Object.keys(properties) : children.map(([key]) => key);
|
|
180
|
+
const arrayKeywords = tuple.length > 0 || itemsSchema;
|
|
181
|
+
const indexBlock = indexed || mapped && arrayKeywords;
|
|
182
|
+
const nameSchema = readKey(node, "propertyNames");
|
|
183
|
+
const keyCheck = nameSchema === void 0 ? true : namedTest(nameSchema, ctx);
|
|
184
|
+
if (keyCheck === false)
|
|
185
|
+
lines.push(` if (next === segments.length) return null`);
|
|
186
|
+
if (typeof keyCheck === "string")
|
|
187
|
+
lines.push(` if (next === segments.length && !${keyCheck}(head)) return null`);
|
|
188
|
+
const child = `childAt(value, head)`;
|
|
189
|
+
if (declared.length > 0) {
|
|
190
|
+
const lookups = new Map(children);
|
|
191
|
+
lines.push(` switch (head) {`);
|
|
192
|
+
for (const key of declared) {
|
|
193
|
+
const lookup = lookups.get(key);
|
|
194
|
+
lines.push(` case ${JSON.stringify(key)}: return ${lookup === void 0 ? "null" : `${lookup}(segments, next, ${child})`}`);
|
|
98
195
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
196
|
+
lines.push(` }`);
|
|
197
|
+
}
|
|
198
|
+
if (indexBlock) {
|
|
199
|
+
lines.push(` const index = Number(head)`);
|
|
200
|
+
lines.push(` if (Number.isInteger(index) && index >= 0) {`);
|
|
201
|
+
tupleLookups.forEach((lookup, position) => {
|
|
202
|
+
if (lookup === null)
|
|
203
|
+
return;
|
|
204
|
+
lines.push(` if (index === ${position}) return ${lookup}(segments, next, ${child})`);
|
|
205
|
+
});
|
|
206
|
+
if (itemsLookup !== null)
|
|
207
|
+
lines.push(` if (index >= ${tuple.length}) return ${itemsLookup}(segments, next, ${child})`);
|
|
208
|
+
if (mapped && arrayKeywords)
|
|
209
|
+
lines.push(` return null`);
|
|
210
|
+
lines.push(` }`);
|
|
211
|
+
}
|
|
212
|
+
if (mapped && patterns.length > 0) {
|
|
213
|
+
const regexNames = patterns.map((pattern, index) => {
|
|
214
|
+
const regexName = `_repairPattern${id}_${index}`;
|
|
215
|
+
ctx.helpers.push(`const ${regexName} = new RegExp(${JSON.stringify(pattern.source)}, ${JSON.stringify(regexFlagsFor(pattern.source))})`);
|
|
216
|
+
return regexName;
|
|
217
|
+
});
|
|
218
|
+
const answer = (lookup) => lookup === null ? "null" : `${lookup}(segments, next, ${child})`;
|
|
219
|
+
const only = patterns[0];
|
|
220
|
+
if (patterns.length === 1 && only !== void 0) {
|
|
221
|
+
lines.push(` if (${regexNames[0]}.test(head)) return ${answer(only.lookup)}`);
|
|
222
|
+
} else {
|
|
223
|
+
lines.push(` const claims = [${regexNames.join(", ")}].filter((pattern) => pattern.test(head))`);
|
|
224
|
+
lines.push(` if (claims.length > 1) return null`);
|
|
225
|
+
patterns.forEach((pattern, index) => {
|
|
226
|
+
lines.push(` if (claims[0] === ${regexNames[index]}) return ${answer(pattern.lookup)}`);
|
|
104
227
|
});
|
|
105
|
-
const answer = (walker) => walker === null ? "null" : `${walker}(rest)`;
|
|
106
|
-
const only = patterns[0];
|
|
107
|
-
if (patterns.length === 1 && only !== void 0) {
|
|
108
|
-
lines.push(` if (${regexNames[0]}.test(head)) return ${answer(only.walker)}`);
|
|
109
|
-
} else {
|
|
110
|
-
lines.push(` const claims = [${regexNames.join(", ")}].filter((pattern) => pattern.test(head))`);
|
|
111
|
-
lines.push(` if (claims.length > 1) return null`);
|
|
112
|
-
patterns.forEach((pattern, index) => {
|
|
113
|
-
lines.push(` if (claims[0] === ${regexNames[index]}) return ${answer(pattern.walker)}`);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
228
|
}
|
|
117
229
|
}
|
|
118
|
-
lines.push(
|
|
119
|
-
|
|
230
|
+
lines.push(additionalLookup === null ? ` return null` : ` return ${additionalLookup}(segments, next, ${child})`, `}`);
|
|
231
|
+
const text = lines.join("\n");
|
|
232
|
+
ctx.helpers.push(/\bvalue\b/.test(text.slice(text.indexOf("\n"))) ? text : text.replace("i, value) => {", "i, _value) => {"));
|
|
120
233
|
return name;
|
|
121
234
|
};
|
|
122
235
|
const generateRepairFunction = (schema, typeName, typeSuffix = "", options = {}) => {
|
|
123
236
|
const helpers = [];
|
|
124
237
|
let counter = 0;
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const
|
|
238
|
+
const rootSchema = options.rootSchema ?? (isSchemaObject(schema) ? schema : void 0);
|
|
239
|
+
const matcher = createSubschemaMatcher(typeSuffix, rootSchema, options.formats ?? NO_FORMATS, "_repair");
|
|
240
|
+
const ctx = {
|
|
241
|
+
typeSuffix,
|
|
242
|
+
helpers,
|
|
243
|
+
next: () => counter++,
|
|
244
|
+
matcher,
|
|
245
|
+
lookups: /* @__PURE__ */ new Map(),
|
|
246
|
+
tests: /* @__PURE__ */ new Map(),
|
|
247
|
+
drop: null
|
|
248
|
+
};
|
|
249
|
+
const lookup = emitLookup(schema, ctx);
|
|
128
250
|
const lookupName = `repair${typeName}At`;
|
|
251
|
+
const walkName = repairWalkNameFor(typeName);
|
|
252
|
+
const walker = options.walk === true ? emitRepairWalk(schema, { repair: ctx, typeName, rootSchema, lookupFor: (node) => emitLookup(node, ctx) }) : null;
|
|
129
253
|
const declarations = [...matcher.declarations(helpers.join("\n")), ...helpers];
|
|
130
254
|
const parts = [
|
|
131
255
|
...declarations.length > 0 ? [declarations.join("\n\n"), ""] : [],
|
|
132
|
-
`export const ${lookupName}: RepairLookup = ${
|
|
256
|
+
`export const ${lookupName}: RepairLookup = ${lookup === null ? "() => null" : lookup}`,
|
|
133
257
|
"",
|
|
134
|
-
`export const
|
|
135
|
-
`
|
|
136
|
-
`
|
|
137
|
-
` const done = new Set<string>()`,
|
|
138
|
-
``,
|
|
139
|
-
` for (let pass = 0; pass < MAX_REPAIR_PASSES; pass++) {`,
|
|
140
|
-
` const result = validate${typeName}(value, _path)`,
|
|
141
|
-
` if (result === true) return { valid: true, value: value as ${typeName}, repairs }`,
|
|
142
|
-
``,
|
|
143
|
-
` const applied = applyRepairs(value, result.errors, ${lookupName}, done)`,
|
|
144
|
-
` // Nothing moved, so nothing on a further pass would either.`,
|
|
145
|
-
` if (applied.repaired.length === 0) return { valid: false, value, errors: result.errors, repairs }`,
|
|
146
|
-
` value = applied.value`,
|
|
147
|
-
` repairs.push(...applied.repaired)`,
|
|
148
|
-
` }`,
|
|
149
|
-
``,
|
|
150
|
-
` const final = validate${typeName}(value, _path)`,
|
|
151
|
-
` return final === true`,
|
|
152
|
-
` ? { valid: true, value: value as ${typeName}, repairs }`,
|
|
153
|
-
` : { valid: false, value, errors: final.errors, repairs }`,
|
|
154
|
-
`}`
|
|
258
|
+
...options.walk === true ? [`export const ${walkName}: RepairWalk = ${walker ?? "(value) => value"}`, ""] : [],
|
|
259
|
+
`export const repair${typeName} = (input: unknown, options?: RepairOptions | string): RepairResult<${typeName}> =>`,
|
|
260
|
+
options.walk === true ? ` runRepairWalk<${typeName}>(input, options, ${walkName})` : ` runRepairLoop<${typeName}>(input, options, validate${typeName}, ${lookupName}, coerce${typeName}Value)`
|
|
155
261
|
];
|
|
156
262
|
return { code: parts.join("\n") };
|
|
157
263
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
|
+
import { type RepairContext } from './repair-context.js';
|
|
3
|
+
/**
|
|
4
|
+
* The name of the walk a file exports for its definition. Not `repairXWalk`:
|
|
5
|
+
* that is the `repairX` of a definition named `XWalk`, and the barrel would
|
|
6
|
+
* export one name twice.
|
|
7
|
+
*/
|
|
8
|
+
export declare const repairWalkNameFor: (typeName: string) => string;
|
|
9
|
+
type WalkContext = {
|
|
10
|
+
/** The lookup's context: declarations, numbering and the shared tests. */
|
|
11
|
+
readonly repair: RepairContext;
|
|
12
|
+
/** The definition this file is for, which names the type of its root position. */
|
|
13
|
+
readonly typeName: string;
|
|
14
|
+
/** The whole document, to see through a `$ref` to a Reference schema. */
|
|
15
|
+
readonly rootSchema: Record<string, unknown> | undefined;
|
|
16
|
+
/** The lookup for a node — the one `repairXAt` reaches it through. */
|
|
17
|
+
readonly lookupFor: (schema: JSONSchema) => string | null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Emits the single-pass repair walk for a definition and answers the name of its
|
|
21
|
+
* root walker, or `null` when nothing in it is checked.
|
|
22
|
+
*/
|
|
23
|
+
export declare const emitRepairWalk: (schema: JSONSchema, ctx: WalkContext) => string | null;
|
|
24
|
+
export {};
|