@amritk/generate-validators 0.13.0 → 0.14.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 +3 -2
- package/README.md +42 -13
- package/dist/generators/assert-unevaluated-generatable.js +9 -8
- package/dist/generators/build-schema.d.ts +1 -1
- package/dist/generators/build-schema.js +135 -9
- package/dist/generators/collect-emitted-refs.js +12 -10
- package/dist/generators/collect-validator-imports.d.ts +22 -0
- package/dist/generators/collect-validator-imports.js +13 -3
- package/dist/generators/generate-files.js +12 -5
- package/dist/generators/generate-validator-function.js +194 -136
- package/dist/generators/tuple-shape.js +7 -5
- package/dist/generators/unevaluated-match.js +33 -28
- package/package.json +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { regexFlagsFor, regexLiteral } from "@amritk/helpers/escape-regex-pattern";
|
|
2
2
|
import { getMjstInstanceOf, getMjstPrimitive, MJST_EXTENSION_KEY } from "@amritk/helpers/mjst-extension";
|
|
3
3
|
import { multipleOfFailExpr, multipleOfPassExpr } from "@amritk/helpers/multiple-of-check";
|
|
4
|
+
import { declaresKey, readKey } from "@amritk/helpers/read-key";
|
|
4
5
|
import { refToName } from "@amritk/helpers/ref-to-name";
|
|
5
6
|
import { safeAccessor } from "@amritk/helpers/safe-accessor";
|
|
6
7
|
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasDependentRequired, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasPropertyNames, hasRef, hasRequired, hasStrictExclusiveMaximum, hasStrictExclusiveMinimum, hasType, hasUniqueItems, isObjectSchema, isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
@@ -49,7 +50,7 @@ const SCALAR_ITEM_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer"
|
|
|
49
50
|
const schemaIsScalarOnly = (schema) => {
|
|
50
51
|
if (!isSchemaObject(schema))
|
|
51
52
|
return false;
|
|
52
|
-
const t = schema
|
|
53
|
+
const t = readKey(schema, "type");
|
|
53
54
|
if (t === void 0)
|
|
54
55
|
return false;
|
|
55
56
|
const types = Array.isArray(t) ? t : [t];
|
|
@@ -89,31 +90,40 @@ const wrongTypeCondition = (accessor, type) => {
|
|
|
89
90
|
const declaresObjectType = (schema) => hasType(schema) && schema.type === "object";
|
|
90
91
|
const objectRootIsSelfContained = (schema) => !hasRef(schema) && !hasConst(schema) && !hasEnum(schema) && getMjstInstanceOf(schema) === void 0 && getMjstPrimitive(schema) === void 0;
|
|
91
92
|
const getTypeArray = (schema) => {
|
|
92
|
-
if (!isSchemaObject(schema) || !("type"
|
|
93
|
+
if (!isSchemaObject(schema) || !declaresKey(schema, "type") || !Array.isArray(schema.type))
|
|
93
94
|
return null;
|
|
94
95
|
return schema.type;
|
|
95
96
|
};
|
|
97
|
+
const ROOT_ERROR_SINK = "(errors ??= [])";
|
|
98
|
+
const MATCH_ERROR_SINK = "_m";
|
|
96
99
|
const createRootContext = (rootSchema) => ({
|
|
97
100
|
objVar: "obj",
|
|
98
101
|
pathPrefix: "${_path}",
|
|
99
102
|
depth: 0,
|
|
100
103
|
hoisted: [],
|
|
101
|
-
rootSchema
|
|
104
|
+
rootSchema,
|
|
105
|
+
sink: ROOT_ERROR_SINK
|
|
102
106
|
});
|
|
103
|
-
const pointerSegment = (key) =>
|
|
107
|
+
const pointerSegment = (key) => {
|
|
108
|
+
const pointer = key.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
109
|
+
return JSON.stringify(pointer).slice(1, -1).replace(/[`$]/g, "\\$&");
|
|
110
|
+
};
|
|
104
111
|
const patternPropertySources = (schema) => {
|
|
105
|
-
if (!isSchemaObject(schema) || !("patternProperties"
|
|
112
|
+
if (!isSchemaObject(schema) || !declaresKey(schema, "patternProperties"))
|
|
106
113
|
return [];
|
|
107
114
|
const patterns = schema.patternProperties;
|
|
108
115
|
if (typeof patterns !== "object" || patterns === null)
|
|
109
116
|
return [];
|
|
110
117
|
return Object.keys(patterns);
|
|
111
118
|
};
|
|
112
|
-
const
|
|
119
|
+
const readsBinding = (name, text) => new RegExp(`\\b${name}\\b`).test(text);
|
|
120
|
+
const readsObjBinding = (text) => readsBinding("obj", text);
|
|
113
121
|
const withHoisted = (hoisted, text) => {
|
|
114
122
|
const kept = hoisted.filter((entry) => text.includes(entry.reference)).map((entry) => entry.declaration);
|
|
115
|
-
const
|
|
116
|
-
const
|
|
123
|
+
const firstBreak = text.indexOf("\n");
|
|
124
|
+
const signature = firstBreak === -1 ? text : text.slice(0, firstBreak);
|
|
125
|
+
const rest = firstBreak === -1 ? "" : text.slice(firstBreak);
|
|
126
|
+
const body = /\binput\b/.test(rest) ? text : signature.replace("(input: unknown", "(_input: unknown") + rest;
|
|
117
127
|
return kept.length > 0 ? `${kept.join("\n")}
|
|
118
128
|
|
|
119
129
|
${body}` : body;
|
|
@@ -145,7 +155,7 @@ const generateStrictKeyChecks = (schema, ctx) => {
|
|
|
145
155
|
return [
|
|
146
156
|
` for (const _key${d} in ${ctx.objVar}) {`,
|
|
147
157
|
` if (${unknownTest}${patternGuard}) {`,
|
|
148
|
-
`
|
|
158
|
+
` ${ctx.sink}.push({ message: 'must NOT have additional properties', path: \`${ctx.pathPrefix}/\${escapePointer(_key${d})}\` })`,
|
|
149
159
|
` }`,
|
|
150
160
|
` }`
|
|
151
161
|
];
|
|
@@ -160,7 +170,7 @@ const generateMissingRequiredChecks = (schema, ctx) => {
|
|
|
160
170
|
if (Object.hasOwn(props, key))
|
|
161
171
|
continue;
|
|
162
172
|
lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
|
|
163
|
-
lines.push(`
|
|
173
|
+
lines.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
|
|
164
174
|
lines.push(` }`);
|
|
165
175
|
}
|
|
166
176
|
return lines;
|
|
@@ -172,15 +182,15 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
|
|
|
172
182
|
if (isRequired) {
|
|
173
183
|
return [
|
|
174
184
|
` if (${missingCheck(ctx.objVar, key)}) {`,
|
|
175
|
-
`
|
|
185
|
+
` ${ctx.sink}.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`,
|
|
176
186
|
` } else {`,
|
|
177
|
-
`
|
|
187
|
+
` ${ctx.sink}.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`,
|
|
178
188
|
` }`
|
|
179
189
|
];
|
|
180
190
|
}
|
|
181
191
|
return [
|
|
182
192
|
` if (${hasOwnCheck(ctx.objVar, key)}) {`,
|
|
183
|
-
`
|
|
193
|
+
` ${ctx.sink}.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`,
|
|
184
194
|
` }`
|
|
185
195
|
];
|
|
186
196
|
}
|
|
@@ -215,35 +225,35 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
|
|
|
215
225
|
const lines = [];
|
|
216
226
|
if (hasRef(schema)) {
|
|
217
227
|
const vName = validatorName(refToName(schema.$ref, suffix));
|
|
218
|
-
const delegate = [` const _r = ${vName}(${raw}, ${path})`, ` if (_r !== true)
|
|
228
|
+
const delegate = [` const _r = ${vName}(${raw}, ${path})`, ` if (_r !== true) ${ctx.sink}.push(..._r.errors)`];
|
|
219
229
|
if (presence === "")
|
|
220
|
-
lines.push(...delegate);
|
|
230
|
+
lines.push(` {`, ...delegate.map((line) => ` ${line}`), ` }`);
|
|
221
231
|
else
|
|
222
232
|
lines.push(` if (${raw} !== undefined) {`, ...delegate.map((line) => ` ${line}`), ` }`);
|
|
223
233
|
}
|
|
224
234
|
const instanceOf = getMjstInstanceOf(schema);
|
|
225
235
|
if (instanceOf) {
|
|
226
236
|
lines.push(` if (${presence}!(${raw} instanceof ${instanceOf})) {`);
|
|
227
|
-
lines.push(`
|
|
237
|
+
lines.push(` ${ctx.sink}.push({ message: 'must be ${instanceOf}', path: ${path} })`);
|
|
228
238
|
lines.push(` }`);
|
|
229
239
|
}
|
|
230
240
|
const primitive = getMjstPrimitive(schema);
|
|
231
241
|
if (primitive) {
|
|
232
242
|
lines.push(` if (${presence}typeof ${raw} !== "${primitive}") {`);
|
|
233
|
-
lines.push(`
|
|
243
|
+
lines.push(` ${ctx.sink}.push({ message: 'must be ${primitive}', path: ${path} })`);
|
|
234
244
|
lines.push(` }`);
|
|
235
245
|
}
|
|
236
246
|
if (hasConst(schema)) {
|
|
237
247
|
const mismatch = constMismatchCondition(raw, schema.const);
|
|
238
248
|
const msg = JSON.stringify(`must be ${JSON.stringify(schema.const)}`);
|
|
239
249
|
lines.push(` if (${presence}${mismatch}) {`);
|
|
240
|
-
lines.push(`
|
|
250
|
+
lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${path} })`);
|
|
241
251
|
lines.push(` }`);
|
|
242
252
|
}
|
|
243
253
|
if (hasEnum(schema)) {
|
|
244
254
|
const label = schema.enum.map((v) => JSON.stringify(v)).join(", ");
|
|
245
255
|
lines.push(` if (${presence}!${enumMembershipExpr(schema.enum, raw)}) {`);
|
|
246
|
-
lines.push(`
|
|
256
|
+
lines.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must be one of: ${label}`)}, path: ${path} })`);
|
|
247
257
|
lines.push(` }`);
|
|
248
258
|
}
|
|
249
259
|
if (instanceOf === void 0 && primitive === void 0) {
|
|
@@ -252,7 +262,7 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
|
|
|
252
262
|
const wrongType = wrongTypeCondition(raw, t);
|
|
253
263
|
if (wrongType) {
|
|
254
264
|
lines.push(` if (${presence === "" ? wrongType : `${presence}(${wrongType})`}) {`);
|
|
255
|
-
lines.push(`
|
|
265
|
+
lines.push(` ${ctx.sink}.push({ message: 'must be ${typeofString(t)}', path: ${path} })`);
|
|
256
266
|
lines.push(` }`);
|
|
257
267
|
}
|
|
258
268
|
}
|
|
@@ -262,7 +272,7 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
|
|
|
262
272
|
if (allWrong) {
|
|
263
273
|
const label = typeArray.map((t) => typeofString(t)).join(" or ");
|
|
264
274
|
lines.push(` if (${presence === "" ? allWrong : `${presence}(${allWrong})`}) {`);
|
|
265
|
-
lines.push(`
|
|
275
|
+
lines.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${path} })`);
|
|
266
276
|
lines.push(` }`);
|
|
267
277
|
}
|
|
268
278
|
}
|
|
@@ -281,7 +291,7 @@ const generatePropertyCheckLines = (key, propSchema, isRequired, suffix, ctx) =>
|
|
|
281
291
|
const parentPath = ctx.depth === 0 ? "_path" : `\`${ctx.pathPrefix}\``;
|
|
282
292
|
const missing = [
|
|
283
293
|
` if (${missingCheck(ctx.objVar, key)}) {`,
|
|
284
|
-
`
|
|
294
|
+
` ${ctx.sink}.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`
|
|
285
295
|
];
|
|
286
296
|
if (!isSchemaObject(propSchema)) {
|
|
287
297
|
if (isRequired && propSchema === true)
|
|
@@ -311,17 +321,17 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
|
|
|
311
321
|
const re = regexLiteral(propSchema.pattern);
|
|
312
322
|
const msg = JSON.stringify(`must match pattern ${propSchema.pattern}`);
|
|
313
323
|
lines.push(` if (typeof ${raw} === 'string' && !${re}.test(${raw})) {`);
|
|
314
|
-
lines.push(`
|
|
324
|
+
lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${path} })`);
|
|
315
325
|
lines.push(` }`);
|
|
316
326
|
}
|
|
317
327
|
if (hasMinLength(propSchema) && propSchema.minLength > 0) {
|
|
318
328
|
lines.push(` if (typeof ${raw} === 'string' && ${minLengthFailExpr(raw, propSchema.minLength)}) {`);
|
|
319
|
-
lines.push(`
|
|
329
|
+
lines.push(` ${ctx.sink}.push({ message: 'must have at least ${propSchema.minLength} characters', path: ${path} })`);
|
|
320
330
|
lines.push(` }`);
|
|
321
331
|
}
|
|
322
332
|
if (hasMaxLength(propSchema)) {
|
|
323
333
|
lines.push(` if (typeof ${raw} === 'string' && ${maxLengthFailExpr(raw, propSchema.maxLength)}) {`);
|
|
324
|
-
lines.push(`
|
|
334
|
+
lines.push(` ${ctx.sink}.push({ message: 'must have at most ${propSchema.maxLength} characters', path: ${path} })`);
|
|
325
335
|
lines.push(` }`);
|
|
326
336
|
}
|
|
327
337
|
}
|
|
@@ -330,29 +340,29 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
|
|
|
330
340
|
const strict = hasStrictExclusiveMinimum(propSchema);
|
|
331
341
|
const rel = strict ? ">" : ">=";
|
|
332
342
|
lines.push(` if (typeof ${raw} === 'number' && !(${raw} ${rel} ${propSchema.minimum})) {`);
|
|
333
|
-
lines.push(`
|
|
343
|
+
lines.push(` ${ctx.sink}.push({ message: 'must be ${rel} ${propSchema.minimum}', path: ${path} })`);
|
|
334
344
|
lines.push(` }`);
|
|
335
345
|
}
|
|
336
346
|
if (hasMaximum(propSchema)) {
|
|
337
347
|
const strict = hasStrictExclusiveMaximum(propSchema);
|
|
338
348
|
const rel = strict ? "<" : "<=";
|
|
339
349
|
lines.push(` if (typeof ${raw} === 'number' && !(${raw} ${rel} ${propSchema.maximum})) {`);
|
|
340
|
-
lines.push(`
|
|
350
|
+
lines.push(` ${ctx.sink}.push({ message: 'must be ${rel} ${propSchema.maximum}', path: ${path} })`);
|
|
341
351
|
lines.push(` }`);
|
|
342
352
|
}
|
|
343
353
|
if (hasExclusiveMinimum(propSchema)) {
|
|
344
354
|
lines.push(` if (typeof ${raw} === 'number' && !(${raw} > ${propSchema.exclusiveMinimum})) {`);
|
|
345
|
-
lines.push(`
|
|
355
|
+
lines.push(` ${ctx.sink}.push({ message: 'must be > ${propSchema.exclusiveMinimum}', path: ${path} })`);
|
|
346
356
|
lines.push(` }`);
|
|
347
357
|
}
|
|
348
358
|
if (hasExclusiveMaximum(propSchema)) {
|
|
349
359
|
lines.push(` if (typeof ${raw} === 'number' && !(${raw} < ${propSchema.exclusiveMaximum})) {`);
|
|
350
|
-
lines.push(`
|
|
360
|
+
lines.push(` ${ctx.sink}.push({ message: 'must be < ${propSchema.exclusiveMaximum}', path: ${path} })`);
|
|
351
361
|
lines.push(` }`);
|
|
352
362
|
}
|
|
353
363
|
if (hasMultipleOf(propSchema)) {
|
|
354
364
|
lines.push(` if (typeof ${raw} === 'number' && ${multipleOfFailExpr(raw, propSchema.multipleOf)}) {`);
|
|
355
|
-
lines.push(`
|
|
365
|
+
lines.push(` ${ctx.sink}.push({ message: 'must be a multiple of ${propSchema.multipleOf}', path: ${path} })`);
|
|
356
366
|
lines.push(` }`);
|
|
357
367
|
}
|
|
358
368
|
}
|
|
@@ -367,7 +377,7 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
|
|
|
367
377
|
lines.push(` if (Array.isArray(${raw})) {`);
|
|
368
378
|
lines.push(` for (let ${iv} = ${firstTailIndex}; ${iv} < ${raw}.length; ${iv}++) {`);
|
|
369
379
|
lines.push(` const _ir = ${vName}(${raw}[${iv}], ${itemPath})`);
|
|
370
|
-
lines.push(` if (_ir !== true)
|
|
380
|
+
lines.push(` if (_ir !== true) ${ctx.sink}.push(..._ir.errors)`);
|
|
371
381
|
lines.push(` }`);
|
|
372
382
|
lines.push(` }`);
|
|
373
383
|
} else if (isSchemaObject(itemSchema)) {
|
|
@@ -376,46 +386,72 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
|
|
|
376
386
|
if (detail.length > 0) {
|
|
377
387
|
lines.push(` if (Array.isArray(${raw})) {`);
|
|
378
388
|
lines.push(` for (let ${iv} = ${firstTailIndex}; ${iv} < ${raw}.length; ${iv}++) {`);
|
|
379
|
-
|
|
389
|
+
if (readsBinding(itemVar, detail.join("\n")))
|
|
390
|
+
lines.push(` const ${itemVar} = ${raw}[${iv}]`);
|
|
380
391
|
lines.push(...detail.map((l) => ` ${l}`));
|
|
381
392
|
lines.push(` }`);
|
|
382
393
|
lines.push(` }`);
|
|
383
394
|
}
|
|
384
395
|
}
|
|
385
396
|
}
|
|
386
|
-
if (hasMinItems(propSchema) || hasMaxItems(propSchema) || hasUniqueItems(propSchema) && propSchema.uniqueItems === true || "contains"
|
|
397
|
+
if (hasMinItems(propSchema) || hasMaxItems(propSchema) || hasUniqueItems(propSchema) && propSchema.uniqueItems === true || declaresKey(sp, "contains") || tuple !== void 0 || tail === false && tuple === void 0) {
|
|
387
398
|
if (tail === false && tuple === void 0) {
|
|
388
399
|
lines.push(` if (Array.isArray(${raw}) && ${raw}.length > 0) {`);
|
|
389
|
-
lines.push(`
|
|
400
|
+
lines.push(` ${ctx.sink}.push({ message: 'must NOT have more than 0 items', path: ${path} })`);
|
|
390
401
|
lines.push(` }`);
|
|
391
402
|
}
|
|
392
403
|
if (hasMinItems(propSchema)) {
|
|
393
404
|
lines.push(` if (Array.isArray(${raw}) && ${raw}.length < ${propSchema.minItems}) {`);
|
|
394
|
-
lines.push(`
|
|
405
|
+
lines.push(` ${ctx.sink}.push({ message: 'must have at least ${propSchema.minItems} items', path: ${path} })`);
|
|
395
406
|
lines.push(` }`);
|
|
396
407
|
}
|
|
397
408
|
if (hasMaxItems(propSchema)) {
|
|
398
409
|
lines.push(` if (Array.isArray(${raw}) && ${raw}.length > ${propSchema.maxItems}) {`);
|
|
399
|
-
lines.push(`
|
|
410
|
+
lines.push(` ${ctx.sink}.push({ message: 'must have at most ${propSchema.maxItems} items', path: ${path} })`);
|
|
400
411
|
lines.push(` }`);
|
|
401
412
|
}
|
|
402
413
|
if (hasUniqueItems(propSchema) && propSchema.uniqueItems === true) {
|
|
403
|
-
const dupCond = arrayItemsAreScalarOnly(sp) ? `new Set(
|
|
414
|
+
const dupCond = arrayItemsAreScalarOnly(sp) ? `new Set(${raw} as unknown[]).size !== ${raw}.length` : `!allUnique(${raw} as unknown[])`;
|
|
404
415
|
lines.push(` if (Array.isArray(${raw}) && ${dupCond}) {`);
|
|
405
|
-
lines.push(`
|
|
416
|
+
lines.push(` ${ctx.sink}.push({ message: 'must NOT have duplicate items', path: ${path} })`);
|
|
406
417
|
lines.push(` }`);
|
|
407
418
|
}
|
|
408
|
-
if ("contains"
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
+
if (declaresKey(sp, "contains")) {
|
|
420
|
+
const declaredMin = readKey(sp, "minContains");
|
|
421
|
+
const declaredMax = readKey(sp, "maxContains");
|
|
422
|
+
const min = typeof declaredMin === "number" ? declaredMin : 1;
|
|
423
|
+
const max = typeof declaredMax === "number" ? declaredMax : void 0;
|
|
424
|
+
if (min > 0 || max !== void 0) {
|
|
425
|
+
const matchExpr = generateMatchesExpr("_c", readKey(sp, "contains"), suffix, ctx, true);
|
|
426
|
+
const report = `${ctx.sink}.push({ message: 'array does not contain the required matching items', path: ${path} })`;
|
|
427
|
+
if (matchExpr === "true") {
|
|
428
|
+
const bound = max !== void 0 ? `${raw}.length < ${min} || ${raw}.length > ${max}` : `${raw}.length < ${min}`;
|
|
429
|
+
lines.push(` if (Array.isArray(${raw}) && (${bound})) {`);
|
|
430
|
+
lines.push(` ${report}`);
|
|
431
|
+
lines.push(` }`);
|
|
432
|
+
} else if (matchExpr === "false") {
|
|
433
|
+
if (0 < min || max !== void 0 && 0 > max) {
|
|
434
|
+
lines.push(` if (Array.isArray(${raw})) {`);
|
|
435
|
+
lines.push(` ${report}`);
|
|
436
|
+
lines.push(` }`);
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
const bound = max !== void 0 ? `_cn < ${min} || _cn > ${max}` : `_cn < ${min}`;
|
|
440
|
+
lines.push(` if (Array.isArray(${raw})) {`);
|
|
441
|
+
lines.push(` const _ca = ${raw} as unknown[]`);
|
|
442
|
+
lines.push(` let _cn = 0`);
|
|
443
|
+
lines.push(` for (let _ci = 0; _ci < _ca.length; _ci++) {`);
|
|
444
|
+
lines.push(` const _c = _ca[_ci]`);
|
|
445
|
+
lines.push(` if (${matchExpr}) _cn++`);
|
|
446
|
+
if (max === void 0)
|
|
447
|
+
lines.push(` if (_cn >= ${min}) break`);
|
|
448
|
+
lines.push(` }`);
|
|
449
|
+
lines.push(` if (${bound}) {`);
|
|
450
|
+
lines.push(` ${report}`);
|
|
451
|
+
lines.push(` }`);
|
|
452
|
+
lines.push(` }`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
419
455
|
}
|
|
420
456
|
if (tuple !== void 0) {
|
|
421
457
|
lines.push(` if (Array.isArray(${raw})) {`);
|
|
@@ -429,7 +465,7 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
|
|
|
429
465
|
}
|
|
430
466
|
if (tailIsClosed) {
|
|
431
467
|
lines.push(` if (${raw}.length > ${tuple.length}) {`);
|
|
432
|
-
lines.push(`
|
|
468
|
+
lines.push(` ${ctx.sink}.push({ message: 'must NOT have more than ${tuple.length} items', path: ${path} })`);
|
|
433
469
|
lines.push(` }`);
|
|
434
470
|
}
|
|
435
471
|
lines.push(` }`);
|
|
@@ -449,9 +485,9 @@ const generateValueChecks = (key, raw, path, propSchema, suffix, ctx, required =
|
|
|
449
485
|
const generateValueCheckLines = (_key, raw, path, propSchema, suffix, ctx, required = false) => {
|
|
450
486
|
const lines = [];
|
|
451
487
|
if (propSchema === false) {
|
|
452
|
-
const report =
|
|
488
|
+
const report = `${ctx.sink}.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`;
|
|
453
489
|
if (required)
|
|
454
|
-
return [` ${report}`];
|
|
490
|
+
return [` {`, ` ${report}`, ` }`];
|
|
455
491
|
return [` if (${raw} !== undefined) {`, ` ${report}`, ` }`];
|
|
456
492
|
}
|
|
457
493
|
if (!isSchemaObject(propSchema))
|
|
@@ -462,13 +498,14 @@ const generateValueCheckLines = (_key, raw, path, propSchema, suffix, ctx, requi
|
|
|
462
498
|
pathPrefix: path.slice(1, -1),
|
|
463
499
|
depth: ctx.depth + 1,
|
|
464
500
|
hoisted: ctx.hoisted,
|
|
465
|
-
rootSchema: ctx.rootSchema
|
|
501
|
+
rootSchema: ctx.rootSchema,
|
|
502
|
+
sink: ctx.sink
|
|
466
503
|
};
|
|
467
504
|
lines.push(...generateKeywordChecks("", raw, path, propSchema, suffix, valueCtx, presence));
|
|
468
505
|
return lines;
|
|
469
506
|
};
|
|
470
507
|
const NARROWABLE_REFERENCE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[A-Za-z_$][\w$]*\]|\[\d+\])*$/;
|
|
471
|
-
const generateMatchesExpr = (raw, sub, suffix, ctx) => {
|
|
508
|
+
const generateMatchesExpr = (raw, sub, suffix, ctx, required = false) => {
|
|
472
509
|
if (sub === true)
|
|
473
510
|
return "true";
|
|
474
511
|
if (sub === false)
|
|
@@ -476,22 +513,30 @@ const generateMatchesExpr = (raw, sub, suffix, ctx) => {
|
|
|
476
513
|
if (!isSchemaObject(sub))
|
|
477
514
|
return "true";
|
|
478
515
|
const value = NARROWABLE_REFERENCE.test(raw) ? raw : `_mv${ctx.depth}`;
|
|
479
|
-
const checks = generateValueChecks("", value, "`${_path}`", sub, suffix, ctx);
|
|
516
|
+
const checks = generateValueChecks("", value, "`${_path}`", sub, suffix, { ...ctx, sink: MATCH_ERROR_SINK }, required);
|
|
480
517
|
if (checks.length === 0)
|
|
481
518
|
return "true";
|
|
482
|
-
const body = checks.join("\n")
|
|
519
|
+
const body = checks.join("\n");
|
|
483
520
|
const binding = value === raw ? "" : `const ${value}: unknown = ${raw}
|
|
484
521
|
`;
|
|
485
522
|
return `((): boolean => { const _m: ValidationError[] = []
|
|
486
523
|
${binding}${body}
|
|
487
524
|
return _m.length === 0 })()`;
|
|
488
525
|
};
|
|
489
|
-
const unevaluatedMatcher = (suffix, ctx) => (accessor, schema, depth) =>
|
|
526
|
+
const unevaluatedMatcher = (suffix, ctx) => (accessor, schema, depth) => (
|
|
527
|
+
// Always `required`: every accessor handed to this matcher is a value the
|
|
528
|
+
// surrounding test has already established is there — the whole instance
|
|
529
|
+
// inside its type guard, or a key/index the sweep is walking. In optional
|
|
530
|
+
// mode a property whose value is `undefined` matched every branch, so
|
|
531
|
+
// `{ a: undefined }` satisfied an `unevaluatedProperties: { type: 'string' }`
|
|
532
|
+
// that Ajv and the interpreter both reject.
|
|
533
|
+
generateMatchesExpr(accessor, schema, suffix, { ...ctx, depth: ctx.depth + depth + 1 }, true)
|
|
534
|
+
);
|
|
490
535
|
const generateUnevaluatedChecks = (raw, path, schema, suffix, ctx) => {
|
|
491
536
|
if (!isSchemaObject(schema))
|
|
492
537
|
return [];
|
|
493
538
|
const s = schema;
|
|
494
|
-
if (!("unevaluatedProperties"
|
|
539
|
+
if (!declaresKey(s, "unevaluatedProperties") && !declaresKey(s, "unevaluatedItems"))
|
|
495
540
|
return [];
|
|
496
541
|
const match = unevaluatedMatcher(suffix, ctx);
|
|
497
542
|
const lines = [];
|
|
@@ -503,7 +548,7 @@ const generateUnevaluatedChecks = (raw, path, schema, suffix, ctx) => {
|
|
|
503
548
|
for (const statement of properties.setup)
|
|
504
549
|
lines.push(` ${statement}`);
|
|
505
550
|
lines.push(` if (!(${properties.expr})) {`);
|
|
506
|
-
lines.push(`
|
|
551
|
+
lines.push(` ${ctx.sink}.push({ message: 'must NOT have unevaluated properties', path: ${path} })`);
|
|
507
552
|
lines.push(` }`);
|
|
508
553
|
lines.push(` }`);
|
|
509
554
|
}
|
|
@@ -515,7 +560,7 @@ const generateUnevaluatedChecks = (raw, path, schema, suffix, ctx) => {
|
|
|
515
560
|
for (const statement of items.setup)
|
|
516
561
|
lines.push(` ${statement}`);
|
|
517
562
|
lines.push(` if (!(${items.expr})) {`);
|
|
518
|
-
lines.push(`
|
|
563
|
+
lines.push(` ${ctx.sink}.push({ message: 'must NOT have unevaluated items', path: ${path} })`);
|
|
519
564
|
lines.push(` }`);
|
|
520
565
|
lines.push(` }`);
|
|
521
566
|
}
|
|
@@ -527,39 +572,39 @@ const generateCombinatorChecks = (key, raw, path, schema, suffix, ctx) => {
|
|
|
527
572
|
const lines = [];
|
|
528
573
|
if (hasAllOf(schema)) {
|
|
529
574
|
for (const branch of schema.allOf)
|
|
530
|
-
lines.push(...generateValueChecks(key, raw, path, branch, suffix, ctx));
|
|
575
|
+
lines.push(...generateValueChecks(key, raw, path, branch, suffix, ctx, true));
|
|
531
576
|
}
|
|
532
577
|
if (hasAnyOf(schema) && schema.anyOf.length > 0) {
|
|
533
|
-
const conds = schema.anyOf.map((b) => generateMatchesExpr(raw, b, suffix, ctx));
|
|
578
|
+
const conds = schema.anyOf.map((b) => generateMatchesExpr(raw, b, suffix, ctx, true));
|
|
534
579
|
if (!conds.includes("true")) {
|
|
535
580
|
lines.push(` if (!(${conds.join(" || ")})) {`);
|
|
536
|
-
lines.push(`
|
|
581
|
+
lines.push(` ${ctx.sink}.push({ message: 'must match a schema in anyOf', path: ${path} })`);
|
|
537
582
|
lines.push(` }`);
|
|
538
583
|
}
|
|
539
584
|
}
|
|
540
585
|
if (hasOneOf(schema) && schema.oneOf.length > 0) {
|
|
541
|
-
const conds = schema.oneOf.map((b) => `(${generateMatchesExpr(raw, b, suffix, ctx)} ? 1 : 0)`);
|
|
586
|
+
const conds = schema.oneOf.map((b) => `(${generateMatchesExpr(raw, b, suffix, ctx, true)} ? 1 : 0)`);
|
|
542
587
|
lines.push(` if ((${conds.join(" + ")}) !== 1) {`);
|
|
543
|
-
lines.push(`
|
|
588
|
+
lines.push(` ${ctx.sink}.push({ message: 'must match exactly one schema in oneOf', path: ${path} })`);
|
|
544
589
|
lines.push(` }`);
|
|
545
590
|
}
|
|
546
|
-
const not = schema
|
|
591
|
+
const not = readKey(schema, "not");
|
|
547
592
|
if (not !== void 0 && (isSchemaObject(not) || typeof not === "boolean")) {
|
|
548
|
-
const cond = generateMatchesExpr(raw, not, suffix, ctx);
|
|
593
|
+
const cond = generateMatchesExpr(raw, not, suffix, ctx, true);
|
|
549
594
|
if (cond !== "false") {
|
|
550
595
|
lines.push(` if (${cond}) {`);
|
|
551
|
-
lines.push(`
|
|
596
|
+
lines.push(` ${ctx.sink}.push({ message: 'must NOT match the schema in not', path: ${path} })`);
|
|
552
597
|
lines.push(` }`);
|
|
553
598
|
}
|
|
554
599
|
}
|
|
555
|
-
const ifSchema = schema
|
|
600
|
+
const ifSchema = readKey(schema, "if");
|
|
556
601
|
if (ifSchema !== void 0 && (isSchemaObject(ifSchema) || typeof ifSchema === "boolean")) {
|
|
557
|
-
const thenSchema = schema
|
|
558
|
-
const elseSchema = schema
|
|
559
|
-
const thenLines = thenSchema !== void 0 ? generateValueChecks(key, raw, path, thenSchema, suffix, ctx) : [];
|
|
560
|
-
const elseLines = elseSchema !== void 0 ? generateValueChecks(key, raw, path, elseSchema, suffix, ctx) : [];
|
|
602
|
+
const thenSchema = readKey(schema, "then");
|
|
603
|
+
const elseSchema = readKey(schema, "else");
|
|
604
|
+
const thenLines = thenSchema !== void 0 ? generateValueChecks(key, raw, path, thenSchema, suffix, ctx, true) : [];
|
|
605
|
+
const elseLines = elseSchema !== void 0 ? generateValueChecks(key, raw, path, elseSchema, suffix, ctx, true) : [];
|
|
561
606
|
if (thenLines.length > 0 || elseLines.length > 0) {
|
|
562
|
-
const cond = generateMatchesExpr(raw, ifSchema, suffix, ctx);
|
|
607
|
+
const cond = generateMatchesExpr(raw, ifSchema, suffix, ctx, true);
|
|
563
608
|
if (cond === "true") {
|
|
564
609
|
lines.push(...thenLines);
|
|
565
610
|
} else if (cond === "false") {
|
|
@@ -580,38 +625,48 @@ const generatePatternAndAdditionalChecks = (schema, suffix, ctx) => {
|
|
|
580
625
|
return [];
|
|
581
626
|
const obj = ctx.objVar;
|
|
582
627
|
const d = ctx.depth;
|
|
583
|
-
const
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
628
|
+
const patternsRecord = declaresKey(schema, "patternProperties") && typeof schema.patternProperties === "object" && schema.patternProperties !== null ? schema.patternProperties : {};
|
|
629
|
+
const patterns = Object.keys(patternsRecord);
|
|
630
|
+
const kv = `_pk${d}`;
|
|
631
|
+
const valueChecksFor = (sub) => (
|
|
632
|
+
// `required` mode: the key came out of the loop over the object, so its value
|
|
633
|
+
// is there to be judged. Optional mode wrote `obj[_pk0] !== undefined &&` in
|
|
634
|
+
// front of every check, and a property whose value *is* `undefined` then
|
|
635
|
+
// satisfied all of them — `{ a: undefined }` passed a
|
|
636
|
+
// `patternProperties: { "^a": { type: "string" } }` that Ajv and the
|
|
637
|
+
// interpreter both reject.
|
|
638
|
+
generateValueChecks(`\${${kv}}`, `${obj}[${kv}]`, `\`${ctx.pathPrefix}/\${escapePointer(${kv})}\``, sub, suffix, ctx, true)
|
|
639
|
+
);
|
|
640
|
+
const body = [];
|
|
641
|
+
for (const [pattern, sub] of Object.entries(patternsRecord)) {
|
|
642
|
+
const valueChecks = valueChecksFor(sub);
|
|
590
643
|
if (valueChecks.length === 0)
|
|
591
644
|
continue;
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
lines.push(` }`);
|
|
596
|
-
lines.push(` }`);
|
|
645
|
+
body.push(` if (${regexLiteral(pattern)}.test(${kv})) {`);
|
|
646
|
+
body.push(...valueChecks.map((line) => ` ${line}`));
|
|
647
|
+
body.push(` }`);
|
|
597
648
|
}
|
|
598
649
|
if (hasAdditionalProperties(schema) && isSchemaObject(schema.additionalProperties)) {
|
|
599
|
-
const
|
|
600
|
-
const kv = `_ak${d}`;
|
|
601
|
-
const valueChecks = generateValueChecks(`\${${kv}}`, `${obj}[${kv}]`, `\`${ctx.pathPrefix}/\${escapePointer(${kv})}\``, additional, suffix, ctx);
|
|
650
|
+
const valueChecks = valueChecksFor(schema.additionalProperties);
|
|
602
651
|
if (valueChecks.length > 0) {
|
|
603
652
|
const known = Object.keys(hasProperties(schema) ? schema.properties : {});
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
653
|
+
if (known.length > 0) {
|
|
654
|
+
const check = unknownKeyCheck(known, `_knownKeys${ctx.hoisted.length}`);
|
|
655
|
+
const knownTest = check.isKnown(kv);
|
|
656
|
+
for (const declaration of check.declarations) {
|
|
657
|
+
ctx.hoisted.push({ declaration, reference: knownTest });
|
|
658
|
+
}
|
|
659
|
+
body.push(` if (${knownTest}) continue`);
|
|
609
660
|
}
|
|
610
|
-
|
|
611
|
-
|
|
661
|
+
for (const pattern of patterns) {
|
|
662
|
+
body.push(` if (${regexLiteral(pattern)}.test(${kv})) continue`);
|
|
663
|
+
}
|
|
664
|
+
body.push(...valueChecks.map((line) => ` ${line}`));
|
|
612
665
|
}
|
|
613
666
|
}
|
|
614
|
-
|
|
667
|
+
if (body.length === 0)
|
|
668
|
+
return [];
|
|
669
|
+
return [` for (const ${kv} in ${obj}) {`, ...body, ` }`];
|
|
615
670
|
};
|
|
616
671
|
const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
|
|
617
672
|
if (!isSchemaObject(propSchema))
|
|
@@ -624,7 +679,8 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
|
|
|
624
679
|
pathPrefix: key === "" ? ctx.pathPrefix : `${ctx.pathPrefix}/${pointerSegment(key)}`,
|
|
625
680
|
depth: ctx.depth + 1,
|
|
626
681
|
hoisted: ctx.hoisted,
|
|
627
|
-
rootSchema: ctx.rootSchema
|
|
682
|
+
rootSchema: ctx.rootSchema,
|
|
683
|
+
sink: ctx.sink
|
|
628
684
|
};
|
|
629
685
|
const required = new Set(hasRequired(propSchema) ? propSchema.required : []);
|
|
630
686
|
const properties = hasProperties(propSchema) ? propSchema.properties : {};
|
|
@@ -658,7 +714,8 @@ const generatePropertyNameChecks = (nameSchema, suffix, ctx) => {
|
|
|
658
714
|
pathPrefix: `${ctx.pathPrefix}/\${escapePointer(_name)}`,
|
|
659
715
|
depth: ctx.depth + 1,
|
|
660
716
|
hoisted: ctx.hoisted,
|
|
661
|
-
rootSchema: ctx.rootSchema
|
|
717
|
+
rootSchema: ctx.rootSchema,
|
|
718
|
+
sink: ctx.sink
|
|
662
719
|
};
|
|
663
720
|
const checks = generateValueChecks("", "_name", at, nameSchema, suffix, nameCtx, true);
|
|
664
721
|
if (checks.length === 0)
|
|
@@ -677,7 +734,7 @@ const generateDependentRequiredChecks = (schema, ctx) => {
|
|
|
677
734
|
for (const dep of deps) {
|
|
678
735
|
const msg = JSON.stringify(`must have property '${dep}' when '${trigger}' is present`);
|
|
679
736
|
lines.push(` if (${hasOwnCheck(obj, trigger)} && ${missingCheck(obj, dep)}) {`);
|
|
680
|
-
lines.push(`
|
|
737
|
+
lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
|
|
681
738
|
lines.push(` }`);
|
|
682
739
|
}
|
|
683
740
|
}
|
|
@@ -690,7 +747,7 @@ const dependentSelfBinding = (objVar, depth) => {
|
|
|
690
747
|
const generateDependentSchemasChecks = (schema, suffix, ctx) => {
|
|
691
748
|
if (!isSchemaObject(schema))
|
|
692
749
|
return [];
|
|
693
|
-
const dep = schema
|
|
750
|
+
const dep = readKey(schema, "dependentSchemas");
|
|
694
751
|
if (typeof dep !== "object" || dep === null || Array.isArray(dep))
|
|
695
752
|
return [];
|
|
696
753
|
const obj = ctx.objVar;
|
|
@@ -703,7 +760,7 @@ const generateDependentSchemasChecks = (schema, suffix, ctx) => {
|
|
|
703
760
|
if (sub === false) {
|
|
704
761
|
const msg = JSON.stringify(`must NOT have property '${trigger}'`);
|
|
705
762
|
lines.push(` if (${hasOwnCheck(obj, trigger)}) {`);
|
|
706
|
-
lines.push(`
|
|
763
|
+
lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
|
|
707
764
|
lines.push(` }`);
|
|
708
765
|
continue;
|
|
709
766
|
}
|
|
@@ -723,7 +780,7 @@ const generateDependentSchemasChecks = (schema, suffix, ctx) => {
|
|
|
723
780
|
const generateDependenciesChecks = (schema, suffix, ctx) => {
|
|
724
781
|
if (!isSchemaObject(schema))
|
|
725
782
|
return [];
|
|
726
|
-
const dep = schema
|
|
783
|
+
const dep = readKey(schema, "dependencies");
|
|
727
784
|
if (typeof dep !== "object" || dep === null || Array.isArray(dep))
|
|
728
785
|
return [];
|
|
729
786
|
const obj = ctx.objVar;
|
|
@@ -737,7 +794,7 @@ const generateDependenciesChecks = (schema, suffix, ctx) => {
|
|
|
737
794
|
continue;
|
|
738
795
|
const msg = JSON.stringify(`must have property '${key}' when '${trigger}' is present`);
|
|
739
796
|
lines.push(` if (${hasOwnCheck(obj, trigger)} && ${missingCheck(obj, key)}) {`);
|
|
740
|
-
lines.push(`
|
|
797
|
+
lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
|
|
741
798
|
lines.push(` }`);
|
|
742
799
|
}
|
|
743
800
|
continue;
|
|
@@ -747,7 +804,7 @@ const generateDependenciesChecks = (schema, suffix, ctx) => {
|
|
|
747
804
|
if (value === false) {
|
|
748
805
|
const msg = JSON.stringify(`must NOT have property '${trigger}'`);
|
|
749
806
|
lines.push(` if (${hasOwnCheck(obj, trigger)}) {`);
|
|
750
|
-
lines.push(`
|
|
807
|
+
lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
|
|
751
808
|
lines.push(` }`);
|
|
752
809
|
continue;
|
|
753
810
|
}
|
|
@@ -778,13 +835,13 @@ const generateMinMaxPropertiesChecks = (schema, ctx) => {
|
|
|
778
835
|
if (hasMin) {
|
|
779
836
|
const msg = JSON.stringify(`must have at least ${schema.minProperties} properties`);
|
|
780
837
|
lines.push(` if (${count} < ${schema.minProperties}) {`);
|
|
781
|
-
lines.push(`
|
|
838
|
+
lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
|
|
782
839
|
lines.push(` }`);
|
|
783
840
|
}
|
|
784
841
|
if (hasMax) {
|
|
785
842
|
const msg = JSON.stringify(`must have at most ${schema.maxProperties} properties`);
|
|
786
843
|
lines.push(` if (${count} > ${schema.maxProperties}) {`);
|
|
787
|
-
lines.push(`
|
|
844
|
+
lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
|
|
788
845
|
lines.push(` }`);
|
|
789
846
|
}
|
|
790
847
|
return lines;
|
|
@@ -792,19 +849,19 @@ const generateMinMaxPropertiesChecks = (schema, ctx) => {
|
|
|
792
849
|
const hasObjectLevelCombinator = (schema) => {
|
|
793
850
|
if (!isSchemaObject(schema))
|
|
794
851
|
return false;
|
|
795
|
-
return hasAllOf(schema) || hasAnyOf(schema) || hasOneOf(schema) || "not"
|
|
852
|
+
return hasAllOf(schema) || hasAnyOf(schema) || hasOneOf(schema) || declaresKey(schema, "not") || declaresKey(schema, "if");
|
|
796
853
|
};
|
|
797
854
|
const carriesUnevaluated = (schema) => {
|
|
798
855
|
if (!isSchemaObject(schema))
|
|
799
856
|
return false;
|
|
800
857
|
const s = schema;
|
|
801
|
-
return "unevaluatedProperties"
|
|
858
|
+
return declaresKey(s, "unevaluatedProperties") && s["unevaluatedProperties"] !== true || declaresKey(s, "unevaluatedItems") && s["unevaluatedItems"] !== true;
|
|
802
859
|
};
|
|
803
860
|
const guardPropConditions = (key, propSchema, objAcc) => {
|
|
804
861
|
if (!isSchemaObject(propSchema))
|
|
805
862
|
return null;
|
|
806
863
|
const raw = safeAccessor(objAcc, key);
|
|
807
|
-
if (hasRef(propSchema) || hasEnum(propSchema) || hasConst(propSchema) || hasOneOf(propSchema) || hasAnyOf(propSchema) || hasAllOf(propSchema) || "not"
|
|
864
|
+
if (hasRef(propSchema) || hasEnum(propSchema) || hasConst(propSchema) || hasOneOf(propSchema) || hasAnyOf(propSchema) || hasAllOf(propSchema) || declaresKey(propSchema, "not") || declaresKey(propSchema, "if") || getMjstInstanceOf(propSchema) !== void 0 || getMjstPrimitive(propSchema) !== void 0 || hasPattern(propSchema) || hasMinLength(propSchema) || hasMaxLength(propSchema) || hasMinimum(propSchema) || hasMaximum(propSchema) || hasExclusiveMinimum(propSchema) || hasExclusiveMaximum(propSchema) || hasMultipleOf(propSchema) || hasItems(propSchema) || carriesUnevaluated(propSchema)) {
|
|
808
865
|
return null;
|
|
809
866
|
}
|
|
810
867
|
if (!hasType(propSchema))
|
|
@@ -844,11 +901,11 @@ const arrayRejectedByRequiredProp = (keys, required, properties) => {
|
|
|
844
901
|
const guardObjectConditions = (schema, raw, objAcc) => {
|
|
845
902
|
if (!isObjectSchema(schema))
|
|
846
903
|
return null;
|
|
847
|
-
if (hasDependentRequired(schema) || hasPropertyNames(schema) || "dependentSchemas"
|
|
904
|
+
if (hasDependentRequired(schema) || hasPropertyNames(schema) || declaresKey(schema, "dependentSchemas"))
|
|
848
905
|
return null;
|
|
849
|
-
if (hasMinProperties(schema) || hasMaxProperties(schema) || "dependencies"
|
|
906
|
+
if (hasMinProperties(schema) || hasMaxProperties(schema) || declaresKey(schema, "dependencies"))
|
|
850
907
|
return null;
|
|
851
|
-
if (isSchemaObject(schema) && "patternProperties"
|
|
908
|
+
if (isSchemaObject(schema) && declaresKey(schema, "patternProperties"))
|
|
852
909
|
return null;
|
|
853
910
|
if (hasObjectLevelCombinator(schema))
|
|
854
911
|
return null;
|
|
@@ -909,10 +966,11 @@ const generateObjectValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
909
966
|
}
|
|
910
967
|
const objectCombinators = generateCombinatorChecks("", "_root", "`${_path}`", schema, suffix, ctx);
|
|
911
968
|
if (objectCombinators.length > 0) {
|
|
912
|
-
|
|
969
|
+
if (readsBinding("_root", objectCombinators.join("\n")))
|
|
970
|
+
propertyLines.push(` const _root: unknown = input`);
|
|
913
971
|
propertyLines.push(...objectCombinators);
|
|
914
972
|
}
|
|
915
|
-
const body =
|
|
973
|
+
const body = propertyLines.length > 0 ? "\n" + propertyLines.join("\n") + "\n" : "";
|
|
916
974
|
const guard = guardObjectConditions(schema, "input", "obj");
|
|
917
975
|
const objBinding = readsObjBinding(body) ? [` const obj = input as Record<string, unknown>`] : [];
|
|
918
976
|
const collectBody = (name, exported) => [
|
|
@@ -971,10 +1029,10 @@ const booleanLeafExpr = (schema, acc, narrowable = true) => {
|
|
|
971
1029
|
if (!isSchemaObject(schema))
|
|
972
1030
|
return null;
|
|
973
1031
|
const typed = (type) => narrowable ? acc : `(${acc} as ${type})`;
|
|
974
|
-
if (hasRef(schema) || hasConst(schema) || hasOneOf(schema) || "anyOf"
|
|
1032
|
+
if (hasRef(schema) || hasConst(schema) || hasOneOf(schema) || declaresKey(schema, "anyOf") || declaresKey(schema, "allOf") || declaresKey(schema, "not") || declaresKey(schema, "if") || declaresKey(schema, "contains") || declaresKey(schema, "prefixItems") || // A draft-07 tuple: the fixed positions live in an *array* `items` and the
|
|
975
1033
|
// tail in `additionalItems`. Neither is expressible flat, and reading past
|
|
976
1034
|
// them let the guard accept tuples `validateX` rejects.
|
|
977
|
-
Array.isArray(schema
|
|
1035
|
+
Array.isArray(readKey(schema, "items")) || declaresKey(schema, "additionalItems") || carriesUnevaluated(schema) || getMjstInstanceOf(schema) !== void 0 || getMjstPrimitive(schema) !== void 0) {
|
|
978
1036
|
return null;
|
|
979
1037
|
}
|
|
980
1038
|
if (!hasType(schema)) {
|
|
@@ -1045,10 +1103,10 @@ const booleanArrayExpr = (schema, acc, narrowable = true) => {
|
|
|
1045
1103
|
if (hasMaxItems(schema))
|
|
1046
1104
|
parts.push(`${arr}.length <= ${schema.maxItems}`);
|
|
1047
1105
|
if (hasUniqueItems(schema) && schema.uniqueItems === true) {
|
|
1048
|
-
parts.push(arrayItemsAreScalarOnly(schema) ? `new Set(
|
|
1106
|
+
parts.push(arrayItemsAreScalarOnly(schema) ? `new Set(${acc} as unknown[]).size === ${arr}.length` : `allUnique(${acc} as unknown[])`);
|
|
1049
1107
|
}
|
|
1050
1108
|
const base = parts.join(" && ");
|
|
1051
|
-
if (schema
|
|
1109
|
+
if (readKey(schema, "items") === false)
|
|
1052
1110
|
return `${base} && ${arr}.length === 0`;
|
|
1053
1111
|
if (!hasItems(schema))
|
|
1054
1112
|
return base;
|
|
@@ -1069,11 +1127,11 @@ const booleanObjectParts = (schema, raw, objAcc, narrowable = true) => {
|
|
|
1069
1127
|
return null;
|
|
1070
1128
|
if (getMjstInstanceOf(schema) !== void 0 || getMjstPrimitive(schema) !== void 0)
|
|
1071
1129
|
return null;
|
|
1072
|
-
if (hasDependentRequired(schema) || hasPropertyNames(schema) || "dependentSchemas"
|
|
1130
|
+
if (hasDependentRequired(schema) || hasPropertyNames(schema) || declaresKey(schema, "dependentSchemas"))
|
|
1073
1131
|
return null;
|
|
1074
|
-
if (hasMinProperties(schema) || hasMaxProperties(schema) || "dependencies"
|
|
1132
|
+
if (hasMinProperties(schema) || hasMaxProperties(schema) || declaresKey(schema, "dependencies"))
|
|
1075
1133
|
return null;
|
|
1076
|
-
if (isSchemaObject(schema) && "patternProperties"
|
|
1134
|
+
if (isSchemaObject(schema) && declaresKey(schema, "patternProperties"))
|
|
1077
1135
|
return null;
|
|
1078
1136
|
if (hasObjectLevelCombinator(schema))
|
|
1079
1137
|
return null;
|
|
@@ -1125,16 +1183,16 @@ const typeDescribesEveryAcceptedValue = (schema) => {
|
|
|
1125
1183
|
if (!isSchemaObject(schema))
|
|
1126
1184
|
return true;
|
|
1127
1185
|
const s = schema;
|
|
1128
|
-
if ("type"
|
|
1186
|
+
if (declaresKey(s, "type") || declaresKey(s, "enum") || declaresKey(s, "const") || declaresKey(s, "$ref"))
|
|
1129
1187
|
return true;
|
|
1130
1188
|
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
1131
|
-
const branches = s
|
|
1189
|
+
const branches = readKey(s, keyword);
|
|
1132
1190
|
if (!Array.isArray(branches))
|
|
1133
1191
|
continue;
|
|
1134
1192
|
if (!branches.every((branch) => typeDescribesEveryAcceptedValue(branch)))
|
|
1135
1193
|
return false;
|
|
1136
1194
|
}
|
|
1137
|
-
return !IMPLICIT_OBJECT_KEYWORDS.some((keyword) => keyword
|
|
1195
|
+
return !IMPLICIT_OBJECT_KEYWORDS.some((keyword) => declaresKey(s, keyword));
|
|
1138
1196
|
};
|
|
1139
1197
|
const generateBooleanGuard = (schema, typeName, _suffix = "") => {
|
|
1140
1198
|
const name = guardName(typeName);
|
|
@@ -1237,7 +1295,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
1237
1295
|
`}`
|
|
1238
1296
|
].join("\n");
|
|
1239
1297
|
}
|
|
1240
|
-
if (hasAllOf(schema) || hasAnyOf(schema) || hasOneOf(schema) || "not"
|
|
1298
|
+
if (hasAllOf(schema) || hasAnyOf(schema) || hasOneOf(schema) || declaresKey(schema, "not") || declaresKey(schema, "if")) {
|
|
1241
1299
|
const ctx = createRootContext(rootSchema);
|
|
1242
1300
|
const checks = [];
|
|
1243
1301
|
const rootPath = "`${_path}`";
|
|
@@ -1247,7 +1305,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
1247
1305
|
if (allWrong) {
|
|
1248
1306
|
const label = rootTypeArray2.map((t) => typeofString(t)).join(" or ");
|
|
1249
1307
|
checks.push(` if (${allWrong}) {`);
|
|
1250
|
-
checks.push(`
|
|
1308
|
+
checks.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${rootPath} })`);
|
|
1251
1309
|
checks.push(` }`);
|
|
1252
1310
|
}
|
|
1253
1311
|
} else if (hasType(schema)) {
|
|
@@ -1255,13 +1313,13 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
1255
1313
|
const wrongType = wrongTypeCondition("input", t);
|
|
1256
1314
|
if (wrongType) {
|
|
1257
1315
|
checks.push(` if (${wrongType}) {`);
|
|
1258
|
-
checks.push(`
|
|
1316
|
+
checks.push(` ${ctx.sink}.push({ message: 'must be ${typeofString(t)}', path: ${rootPath} })`);
|
|
1259
1317
|
checks.push(` }`);
|
|
1260
1318
|
}
|
|
1261
1319
|
}
|
|
1262
1320
|
checks.push(...generateConstraintChecks("", "input", rootPath, schema, suffix, ctx));
|
|
1263
1321
|
checks.push(...generateCombinatorChecks("", "input", rootPath, schema, suffix, ctx));
|
|
1264
|
-
const body = checks.join("\n")
|
|
1322
|
+
const body = checks.join("\n");
|
|
1265
1323
|
return withHoisted(ctx.hoisted, [
|
|
1266
1324
|
`export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
1267
1325
|
` let errors: ValidationError[] | undefined`,
|
|
@@ -1279,7 +1337,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
1279
1337
|
if (allWrong) {
|
|
1280
1338
|
const label = rootTypeArray.map((t) => typeofString(t)).join(" or ");
|
|
1281
1339
|
checks.push(` if (${allWrong}) {`);
|
|
1282
|
-
checks.push(`
|
|
1340
|
+
checks.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${rootPath} })`);
|
|
1283
1341
|
checks.push(` }`);
|
|
1284
1342
|
}
|
|
1285
1343
|
checks.push(...generateConstraintChecks("", "input", rootPath, schema, suffix, ctx));
|
|
@@ -1290,7 +1348,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
1290
1348
|
`}`
|
|
1291
1349
|
].join("\n");
|
|
1292
1350
|
}
|
|
1293
|
-
const body = checks.join("\n")
|
|
1351
|
+
const body = checks.join("\n");
|
|
1294
1352
|
return withHoisted(ctx.hoisted, [
|
|
1295
1353
|
`export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
1296
1354
|
` let errors: ValidationError[] | undefined`,
|
|
@@ -1327,9 +1385,9 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
1327
1385
|
` if (${wrongType}) {`,
|
|
1328
1386
|
` return { valid: false, errors: [{ message: 'must be ${typLabel}', path: _path }] }`,
|
|
1329
1387
|
` }`,
|
|
1330
|
-
` const _root: unknown = input
|
|
1388
|
+
...readsBinding("_root", constraintLines.join("\n")) ? [` const _root: unknown = input`] : [],
|
|
1331
1389
|
` let errors: ValidationError[] | undefined`,
|
|
1332
|
-
constraintLines.join("\n")
|
|
1390
|
+
constraintLines.join("\n"),
|
|
1333
1391
|
` return errors !== undefined ? { valid: false, errors } : true`,
|
|
1334
1392
|
`}`
|
|
1335
1393
|
].join("\n"));
|
|
@@ -1342,7 +1400,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
1342
1400
|
return withHoisted(typelessCtx.hoisted, [
|
|
1343
1401
|
`export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
1344
1402
|
` let errors: ValidationError[] | undefined`,
|
|
1345
|
-
typelessChecks.join("\n")
|
|
1403
|
+
typelessChecks.join("\n"),
|
|
1346
1404
|
` return errors !== undefined ? { valid: false, errors } : true`,
|
|
1347
1405
|
`}`
|
|
1348
1406
|
].join("\n"));
|
|
@@ -1350,7 +1408,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
|
|
|
1350
1408
|
const generateGeneralRootValidator = (schema, typeName, suffix, rootSchema) => {
|
|
1351
1409
|
const ctx = createRootContext(rootSchema);
|
|
1352
1410
|
const checks = generateValueChecks("", "input", "`${_path}`", schema, suffix, ctx, true);
|
|
1353
|
-
const body = checks.join("\n")
|
|
1411
|
+
const body = checks.join("\n");
|
|
1354
1412
|
return withHoisted(ctx.hoisted, [
|
|
1355
1413
|
`export const ${validatorName(typeName)} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
1356
1414
|
` let errors: ValidationError[] | undefined`,
|
|
@@ -1410,7 +1468,7 @@ const rewriteNullable = (node) => {
|
|
|
1410
1468
|
defineEntry(out, key, value);
|
|
1411
1469
|
}
|
|
1412
1470
|
}
|
|
1413
|
-
if (src
|
|
1471
|
+
if (readKey(src, "nullable") === true)
|
|
1414
1472
|
return { anyOf: [{ type: "null" }, out] };
|
|
1415
1473
|
return out;
|
|
1416
1474
|
};
|