@amritk/generate-parsers 0.24.0 → 0.25.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.
Files changed (51) hide show
  1. package/README.md +37 -463
  2. package/dist/index.d.ts +22 -2
  3. package/dist/index.js +18 -1
  4. package/package.json +15 -40
  5. package/AI.md +0 -52
  6. package/dist/generators/assert-supported-keywords.d.ts +0 -30
  7. package/dist/generators/assert-supported-keywords.js +0 -87
  8. package/dist/generators/build-schema.d.ts +0 -111
  9. package/dist/generators/build-schema.js +0 -74
  10. package/dist/generators/generate-deep-equal-check.d.ts +0 -24
  11. package/dist/generators/generate-deep-equal-check.js +0 -35
  12. package/dist/generators/generate-default-from-pattern.d.ts +0 -26
  13. package/dist/generators/generate-default-from-pattern.js +0 -223
  14. package/dist/generators/generate-discriminated-union-validation.d.ts +0 -15
  15. package/dist/generators/generate-discriminated-union-validation.js +0 -28
  16. package/dist/generators/generate-enum-check.d.ts +0 -35
  17. package/dist/generators/generate-enum-check.js +0 -31
  18. package/dist/generators/generate-files.d.ts +0 -138
  19. package/dist/generators/generate-files.js +0 -89
  20. package/dist/generators/generate-non-discriminated-union-validation.d.ts +0 -6
  21. package/dist/generators/generate-non-discriminated-union-validation.js +0 -26
  22. package/dist/generators/generate-parser-function.d.ts +0 -120
  23. package/dist/generators/generate-parser-function.js +0 -1973
  24. package/dist/generators/generate-schema-checks.d.ts +0 -5
  25. package/dist/generators/generate-schema-checks.js +0 -260
  26. package/dist/generators/generate-strict-assertion.d.ts +0 -155
  27. package/dist/generators/generate-strict-assertion.js +0 -759
  28. package/dist/generators/generate-type-checks.d.ts +0 -148
  29. package/dist/generators/generate-type-checks.js +0 -454
  30. package/dist/generators/generate-unique-items-check.d.ts +0 -16
  31. package/dist/generators/generate-unique-items-check.js +0 -32
  32. package/dist/generators/generate-validation-expression.d.ts +0 -79
  33. package/dist/generators/generate-validation-expression.js +0 -416
  34. package/dist/generators/subschema-match.d.ts +0 -48
  35. package/dist/generators/subschema-match.js +0 -485
  36. package/dist/generators/unevaluated-match.d.ts +0 -19
  37. package/dist/generators/unevaluated-match.js +0 -206
  38. package/dist/helpers/apply-schema-extensions.d.ts +0 -35
  39. package/dist/helpers/apply-schema-extensions.js +0 -20
  40. package/dist/helpers/collect-helpers.d.ts +0 -15
  41. package/dist/helpers/collect-helpers.js +0 -64
  42. package/dist/helpers/collect-imports.d.ts +0 -104
  43. package/dist/helpers/collect-imports.js +0 -227
  44. package/dist/helpers/find-discriminator.d.ts +0 -6
  45. package/dist/helpers/find-discriminator.js +0 -27
  46. package/dist/helpers/get-default-value.d.ts +0 -7
  47. package/dist/helpers/get-default-value.js +0 -155
  48. package/dist/helpers/get-discriminator-value.d.ts +0 -6
  49. package/dist/helpers/get-discriminator-value.js +0 -18
  50. package/dist/types/schema-extensions.d.ts +0 -34
  51. package/dist/types/schema-extensions.js +0 -0
@@ -1,759 +0,0 @@
1
- import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
2
- import { getMjstInstanceOf, getMjstPrimitive } from "@amritk/helpers/mjst-extension";
3
- import { multipleOfFailExpr } from "@amritk/helpers/multiple-of-check";
4
- import { boundFailExpr } from "@amritk/helpers/numeric-bound-check";
5
- import { quoteJsString } from "@amritk/helpers/quote-js-string";
6
- import { resolveRef } from "@amritk/helpers/resolve-ref";
7
- import { hasOwnCheck, missingCheck, safeAccessor } from "@amritk/helpers/safe-accessor";
8
- import { hasConst, hasDependentRequired, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasPattern, hasProperties, hasPropertyNames, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject } from "@amritk/helpers/schema-guards";
9
- import { maxLengthFailExpr, minLengthFailExpr } from "@amritk/helpers/string-length-check";
10
- import { generateDeepEqualCheck } from "./generate-deep-equal-check.js";
11
- import { generateEnumCheck } from "./generate-enum-check.js";
12
- import { canEnforceUnion, generateUnionCheck, getUnionBranches, isExclusiveUnion, isInlineObjectProperty, multiTypeCheck } from "./generate-type-checks.js";
13
- import { generateUniqueItemsCheck } from "./generate-unique-items-check.js";
14
- import { everyTailItem, getPrefixItems, plainScalarItemCheck, prefixItemsCapsLength } from "./generate-validation-expression.js";
15
- import { hasConstrainingRefSibling, subschemaMatchExpr } from "./subschema-match.js";
16
- const shapeIsDelegated = (schema, context) => (context.subParsers ?? true) && isInlineObjectProperty(schema);
17
- const matchContext = (context) => context.rootSchema ? { rootSchema: context.rootSchema } : {};
18
- const TYPELESS_CONSTRAINT_KEYWORDS = [
19
- "minimum",
20
- "maximum",
21
- "exclusiveMinimum",
22
- "exclusiveMaximum",
23
- "multipleOf",
24
- "minLength",
25
- "maxLength",
26
- "pattern",
27
- "minItems",
28
- "maxItems",
29
- "uniqueItems",
30
- "items",
31
- "prefixItems",
32
- "contains"
33
- ];
34
- const TYPELESS_OBJECT_KEYWORDS = [
35
- "required",
36
- "minProperties",
37
- "maxProperties",
38
- "propertyNames",
39
- "patternProperties",
40
- "additionalProperties",
41
- "dependentRequired",
42
- "dependentSchemas"
43
- ];
44
- const backstopReason = (schema, context) => {
45
- if (!isSchemaObject(schema))
46
- return null;
47
- const record = schema;
48
- if ("unevaluatedProperties" in record && record["unevaluatedProperties"] !== true)
49
- return "unevaluatedProperties";
50
- if ("unevaluatedItems" in record && record["unevaluatedItems"] !== true)
51
- return "unevaluatedItems";
52
- if (hasRef(schema) && (!context.useRefImports || hasConstrainingRefSibling(schema)))
53
- return "$ref";
54
- if (record["items"] === false && !Array.isArray(record["prefixItems"]))
55
- return "items";
56
- if (!("type" in record)) {
57
- if (TYPELESS_CONSTRAINT_KEYWORDS.some((keyword) => keyword in record))
58
- return "constraints";
59
- if (!("properties" in record) && TYPELESS_OBJECT_KEYWORDS.some((keyword) => keyword in record))
60
- return "constraints";
61
- return null;
62
- }
63
- if (Array.isArray(record["type"])) {
64
- const nonNull = record["type"].filter((type) => type !== "null");
65
- const constrains = TYPELESS_CONSTRAINT_KEYWORDS.some((keyword) => keyword in record) || TYPELESS_OBJECT_KEYWORDS.some((keyword) => keyword in record);
66
- if (nonNull.length > 1 && constrains)
67
- return "constraints";
68
- }
69
- return null;
70
- };
71
- const backstopSchema = (schema, reason) => {
72
- if (reason !== "constraints" && reason !== "items")
73
- return schema;
74
- const record = schema;
75
- const residual = {};
76
- for (const keyword of [
77
- "type",
78
- ...TYPELESS_CONSTRAINT_KEYWORDS,
79
- ...TYPELESS_OBJECT_KEYWORDS,
80
- "minContains",
81
- "maxContains"
82
- ]) {
83
- if (keyword in record)
84
- residual[keyword] = record[keyword];
85
- }
86
- if ("additionalProperties" in residual && "properties" in record) {
87
- const properties = record["properties"];
88
- if (typeof properties === "object" && properties !== null && !Array.isArray(properties)) {
89
- residual["properties"] = Object.fromEntries(Object.keys(properties).map((k) => [k, true]));
90
- }
91
- }
92
- return residual;
93
- };
94
- const backstopMessage = (reason, label) => {
95
- switch (reason) {
96
- case "unevaluatedProperties":
97
- return `${label} must NOT have unevaluated properties`;
98
- case "unevaluatedItems":
99
- return `${label} must NOT have unevaluated items`;
100
- case "$ref":
101
- return `${label} does not satisfy the referenced schema`;
102
- default:
103
- return `${label} does not satisfy the schema`;
104
- }
105
- };
106
- const generateBackstopAssertion = (acc, schema, label, context, optional = false) => {
107
- const reason = backstopReason(schema, context);
108
- if (reason === null)
109
- return [];
110
- const match = subschemaMatchExpr(acc, backstopSchema(schema, reason), matchContext(context));
111
- if (match === null || match === "true")
112
- return [];
113
- const failure = throwError(backstopMessage(reason, label));
114
- return [optional ? ` if (${acc} !== undefined && !(${match})) ${failure};` : ` if (!(${match})) ${failure};`];
115
- };
116
- const wrongTypeCondition = (accessor, type) => {
117
- switch (type) {
118
- case "string":
119
- return `typeof ${accessor} !== "string"`;
120
- case "number":
121
- return `typeof ${accessor} !== "number"`;
122
- case "integer":
123
- return `(typeof ${accessor} !== "number" || !Number.isInteger(${accessor}))`;
124
- case "boolean":
125
- return `typeof ${accessor} !== "boolean"`;
126
- case "null":
127
- return `${accessor} !== null`;
128
- case "array":
129
- return `!Array.isArray(${accessor})`;
130
- case "object":
131
- return `!isObject(${accessor})`;
132
- default:
133
- return null;
134
- }
135
- };
136
- const typeLabel = (type) => type === "integer" ? "number" : type;
137
- const throwError = (message, suffixExpr) => {
138
- const literal = quoteJsString(message);
139
- return suffixExpr ? `throw new Error(${literal} + (${suffixExpr}))` : `throw new Error(${literal})`;
140
- };
141
- const generateConstraintChecks = (acc, propSchema, field, context = {}) => {
142
- if (!isSchemaObject(propSchema) || !hasType(propSchema))
143
- return [];
144
- const t = propSchema.type;
145
- const lines = [];
146
- if (t === "string") {
147
- if (hasPattern(propSchema)) {
148
- const patternLiteral = regexLiteral(propSchema.pattern);
149
- lines.push(` if (typeof ${acc} === "string" && !${patternLiteral}.test(${acc})) ${throwError(`${field} must match pattern ${propSchema.pattern}`)};`);
150
- }
151
- if (hasMinLength(propSchema)) {
152
- lines.push(` if (typeof ${acc} === "string" && ${minLengthFailExpr(acc, propSchema.minLength)}) ${throwError(`${field} must have at least ${propSchema.minLength} characters`)};`);
153
- }
154
- if (hasMaxLength(propSchema)) {
155
- lines.push(` if (typeof ${acc} === "string" && ${maxLengthFailExpr(acc, propSchema.maxLength)}) ${throwError(`${field} must have at most ${propSchema.maxLength} characters`)};`);
156
- }
157
- }
158
- if (t === "number" || t === "integer") {
159
- if (hasMinimum(propSchema)) {
160
- lines.push(` if (typeof ${acc} === "number" && ${boundFailExpr(acc, "minimum", propSchema.minimum)}) ${throwError(`${field} must be >= ${propSchema.minimum}`)};`);
161
- }
162
- if (hasMaximum(propSchema)) {
163
- lines.push(` if (typeof ${acc} === "number" && ${boundFailExpr(acc, "maximum", propSchema.maximum)}) ${throwError(`${field} must be <= ${propSchema.maximum}`)};`);
164
- }
165
- if (hasExclusiveMinimum(propSchema)) {
166
- lines.push(` if (typeof ${acc} === "number" && ${boundFailExpr(acc, "minimum", propSchema.exclusiveMinimum, true)}) ${throwError(`${field} must be > ${propSchema.exclusiveMinimum}`)};`);
167
- }
168
- if (hasExclusiveMaximum(propSchema)) {
169
- lines.push(` if (typeof ${acc} === "number" && ${boundFailExpr(acc, "maximum", propSchema.exclusiveMaximum, true)}) ${throwError(`${field} must be < ${propSchema.exclusiveMaximum}`)};`);
170
- }
171
- if (hasMultipleOf(propSchema)) {
172
- lines.push(` if (typeof ${acc} === "number" && ${multipleOfFailExpr(acc, propSchema.multipleOf)}) ${throwError(`${field} must be a multiple of ${propSchema.multipleOf}`)};`);
173
- }
174
- }
175
- if (t === "array") {
176
- if (hasMinItems(propSchema)) {
177
- lines.push(` if (Array.isArray(${acc}) && ${acc}.length < ${propSchema.minItems}) ${throwError(`${field} must have at least ${propSchema.minItems} items`)};`);
178
- }
179
- if (hasMaxItems(propSchema)) {
180
- lines.push(` if (Array.isArray(${acc}) && ${acc}.length > ${propSchema.maxItems}) ${throwError(`${field} must have at most ${propSchema.maxItems} items`)};`);
181
- }
182
- if (hasUniqueItems(propSchema) && propSchema.uniqueItems === true) {
183
- lines.push(` if (Array.isArray(${acc}) && !(${generateUniqueItemsCheck(acc, propSchema)})) ${throwError(`${field} must NOT have duplicate items`)};`);
184
- }
185
- const itemCheck = generateItemCheck(propSchema, context);
186
- if (itemCheck) {
187
- lines.push(` if (Array.isArray(${acc}) && !${everyTailItem(acc, itemCheck.check, propSchema)}) ${throwError(`${field} ${itemCheck.message}`)};`);
188
- }
189
- lines.push(...generatePrefixItemsAssertion(acc, field, propSchema, context));
190
- }
191
- if (t === "object") {
192
- lines.push(...generateObjectSizeChecks(acc, propSchema, field, true));
193
- }
194
- return lines;
195
- };
196
- const generateObjectSizeChecks = (acc, schema, label, guard) => {
197
- if (!isSchemaObject(schema))
198
- return [];
199
- const lines = [];
200
- const prefix = guard ? `isObject(${acc}) && ` : "";
201
- if (hasMinProperties(schema)) {
202
- lines.push(` if (${prefix}Object.keys(${acc}).length < ${schema.minProperties}) ${throwError(`${label} must have at least ${schema.minProperties} properties`)};`);
203
- }
204
- if (hasMaxProperties(schema)) {
205
- lines.push(` if (${prefix}Object.keys(${acc}).length > ${schema.maxProperties}) ${throwError(`${label} must have at most ${schema.maxProperties} properties`)};`);
206
- }
207
- return lines;
208
- };
209
- const generateItemCheck = (schema, context) => {
210
- if (!isSchemaObject(schema) || !hasItems(schema) || Array.isArray(schema.items))
211
- return null;
212
- const items = schema.items;
213
- if (isSchemaObject(items) && hasEnum(items) && items.enum.length > 0) {
214
- const label = items.enum.map((v) => JSON.stringify(v)).join(", ");
215
- return { check: generateEnumCheck("_it", items.enum), message: `items must be one of: ${label}` };
216
- }
217
- const scalarCheck = plainScalarItemCheck(items, "_it");
218
- if (scalarCheck !== null && isSchemaObject(items) && hasType(items)) {
219
- return { check: scalarCheck, message: `items expected ${typeLabel(items.type)}` };
220
- }
221
- if (hasRef(items) && context.useRefImports === true || shapeIsDelegated(items, context))
222
- return null;
223
- const match = subschemaMatchExpr("_it", items, matchContext(context));
224
- if (match !== null)
225
- return match === "true" ? null : { check: match, message: "items do not match the item schema" };
226
- const branches = getUnionBranches(items);
227
- if (branches === null || context.stripUnknown === true)
228
- return null;
229
- if (!canEnforceUnion(branches, context.rootSchema))
230
- return null;
231
- const union = generateUnionCheck("_it", branches, context.useRefImports ?? false, context.suffix ?? "", isExclusiveUnion(items));
232
- return union === null ? null : { check: union, message: "items do not match the item schema" };
233
- };
234
- const nullableObjectShapeChecks = (acc, schema, type, label, optional, context) => {
235
- if (type !== "object")
236
- return [];
237
- const match = subschemaMatchExpr(acc, { ...schema, type: "object" }, matchContext(context));
238
- if (match === null || match === "true")
239
- return [];
240
- const presence = optional ? `${acc} !== undefined && ${acc} !== null` : `${acc} !== null`;
241
- return [` if (${presence} && !(${match})) ${throwError(`${label} does not match the declared object shape`)};`];
242
- };
243
- const objectShapeResidual = (schema, context) => {
244
- if (!isSchemaObject(schema) || !hasType(schema) || schema.type !== "object")
245
- return null;
246
- if (shapeIsDelegated(schema, context))
247
- return null;
248
- const record = schema;
249
- const residual = { type: "object" };
250
- if (hasProperties(schema))
251
- residual["properties"] = record["properties"];
252
- if (hasRequired(schema))
253
- residual["required"] = record["required"];
254
- if (record["additionalProperties"] === false) {
255
- residual["additionalProperties"] = false;
256
- if ("patternProperties" in record)
257
- residual["patternProperties"] = record["patternProperties"];
258
- }
259
- return "properties" in residual || "required" in residual || "additionalProperties" in residual ? residual : null;
260
- };
261
- const declaredObjectShapeChecks = (acc, schema, label, optional, context) => {
262
- const residual = objectShapeResidual(schema, context);
263
- if (residual === null)
264
- return [];
265
- const match = subschemaMatchExpr(acc, residual, matchContext(context));
266
- if (match === null || match === "true")
267
- return [];
268
- const presence = optional ? `${acc} !== undefined && ` : "";
269
- return [` if (${presence}!(${match})) ${throwError(`${label} does not match the declared object shape`)};`];
270
- };
271
- const generateNotCheck = (acc, schema, label, optional, context) => {
272
- if (!isSchemaObject(schema) || !("not" in schema))
273
- return [];
274
- const match = subschemaMatchExpr(acc, schema["not"], matchContext(context));
275
- if (match === null || match === "false")
276
- return [];
277
- const failure = throwError(`${label} must NOT match the excluded schema`);
278
- return [optional ? ` if (${acc} !== undefined && (${match})) ${failure};` : ` if (${match}) ${failure};`];
279
- };
280
- const generateAllOfChecks = (acc, schema, label, optional, context, skipInlineMembers = false) => {
281
- if (!isSchemaObject(schema) || !Array.isArray(schema.allOf))
282
- return [];
283
- const handledInline = new Set(skipInlineMembers ? inlineAllOfMembers(schema) : []);
284
- const lines = [];
285
- for (const member of schema.allOf) {
286
- if (handledInline.has(member))
287
- continue;
288
- const delegated = context.useRefImports === true && hasProperties(schema) && !hasConstrainingRefSibling(member);
289
- if (isSchemaObject(member) && hasRef(member) && delegated)
290
- continue;
291
- const match = subschemaMatchExpr(acc, member, matchContext(context));
292
- if (match === null || match === "true")
293
- continue;
294
- const failure = throwError(`${label} does not satisfy all of the declared schemas`);
295
- lines.push(optional ? ` if (${acc} !== undefined && !(${match})) ${failure};` : ` if (!(${match})) ${failure};`);
296
- }
297
- return lines;
298
- };
299
- const flatUnionCheck = (acc, schema, context) => {
300
- const branches = getUnionBranches(schema);
301
- if (branches === null || context.stripUnknown === true)
302
- return null;
303
- if (!canEnforceUnion(branches, context.rootSchema))
304
- return null;
305
- return generateUnionCheck(acc, branches, context.useRefImports ?? false, context.suffix ?? "", isExclusiveUnion(schema));
306
- };
307
- const generateUnionMatchChecks = (acc, schema, label, optional, context) => {
308
- if (!isSchemaObject(schema))
309
- return [];
310
- const record = schema;
311
- const covered = flatUnionCheck(acc, schema, context) === null ? void 0 : isExclusiveUnion(schema) ? "oneOf" : "anyOf";
312
- const lines = [];
313
- for (const keyword of ["anyOf", "oneOf"]) {
314
- if (keyword === covered)
315
- continue;
316
- const members = record[keyword];
317
- if (!Array.isArray(members) || members.length === 0)
318
- continue;
319
- const matches = [];
320
- for (const member of members) {
321
- const match = subschemaMatchExpr(acc, member, matchContext(context));
322
- if (match === null)
323
- break;
324
- matches.push(match);
325
- }
326
- if (matches.length !== members.length)
327
- continue;
328
- const check = unionMatchExpr(keyword, matches);
329
- if (check === null || check === "true")
330
- continue;
331
- const failure = throwError(`${label} does not match ${keyword === "anyOf" ? "any of" : "exactly one of"} the declared schemas`);
332
- lines.push(optional ? ` if (${acc} !== undefined && !(${check})) ${failure};` : ` if (!(${check})) ${failure};`);
333
- }
334
- return lines;
335
- };
336
- const unionMatchExpr = (keyword, matches) => {
337
- if (keyword === "anyOf") {
338
- if (matches.includes("true"))
339
- return null;
340
- return matches.length === 1 ? matches[0] : `(${matches.join(" || ")})`;
341
- }
342
- if (matches.length === 1)
343
- return matches[0];
344
- return `((${matches.map((match) => `(${match} ? 1 : 0)`).join(" + ")}) === 1)`;
345
- };
346
- const generateConditionalChecks = (acc, schema, label, context) => {
347
- if (!isSchemaObject(schema))
348
- return [];
349
- const record = schema;
350
- if (!("if" in record) || !("then" in record) && !("else" in record))
351
- return [];
352
- const ifMatch = subschemaMatchExpr(acc, record["if"], matchContext(context));
353
- if (ifMatch === null)
354
- return [];
355
- const lines = [];
356
- if ("then" in record) {
357
- const thenMatch = subschemaMatchExpr(acc, record["then"], matchContext(context));
358
- if (thenMatch === null)
359
- return [];
360
- if (thenMatch !== "true") {
361
- lines.push(` if ((${ifMatch}) && !(${thenMatch})) ${throwError(`${label} does not satisfy the 'then' schema`)};`);
362
- }
363
- }
364
- if ("else" in record) {
365
- const elseMatch = subschemaMatchExpr(acc, record["else"], matchContext(context));
366
- if (elseMatch === null)
367
- return [];
368
- if (elseMatch !== "true") {
369
- lines.push(` if (!(${ifMatch}) && !(${elseMatch})) ${throwError(`${label} does not satisfy the 'else' schema`)};`);
370
- }
371
- }
372
- return lines;
373
- };
374
- const generateKeyedValueChecks = (obj, schema, label, context = {}) => {
375
- if (!isSchemaObject(schema))
376
- return [];
377
- const record = schema;
378
- const patternEntries = [];
379
- const patternSource = record["patternProperties"];
380
- if (typeof patternSource === "object" && patternSource !== null && !Array.isArray(patternSource)) {
381
- for (const [pattern, sub] of Object.entries(patternSource)) {
382
- const match = subschemaMatchExpr("_v", sub, matchContext(context));
383
- if (match === null || match === "true")
384
- continue;
385
- patternEntries.push([pattern, match]);
386
- }
387
- }
388
- const allPatterns = typeof patternSource === "object" && patternSource !== null && !Array.isArray(patternSource) ? Object.keys(patternSource) : [];
389
- const additional = record["additionalProperties"];
390
- const additionalMatch = additional !== void 0 && typeof additional !== "boolean" ? subschemaMatchExpr("_v", additional, matchContext(context)) : null;
391
- const lines = [];
392
- for (const [pattern, match] of patternEntries) {
393
- lines.push(` if (${regexLiteral(pattern)}.test(_k) && !(${match})) ${throwError(`${label} invalid value for property `, "_k")};`);
394
- }
395
- if (additionalMatch !== null && additionalMatch !== "true") {
396
- const declared = hasProperties(schema) ? Object.keys(schema.properties) : [];
397
- const guards = [];
398
- if (declared.length > 0)
399
- guards.push(`!${JSON.stringify(declared)}.includes(_k)`);
400
- for (const pattern of allPatterns)
401
- guards.push(`!${regexLiteral(pattern)}.test(_k)`);
402
- const prefix = guards.length > 0 ? `${guards.join(" && ")} && ` : "";
403
- lines.push(` if (${prefix}!(${additionalMatch})) ${throwError(`${label} invalid value for property `, "_k")};`);
404
- }
405
- if (lines.length === 0)
406
- return [];
407
- return [
408
- ` for (const _k of Object.keys(${obj})) {`,
409
- ` const _v = (${obj} as Record<string, unknown>)[_k];`,
410
- ...lines,
411
- ` }`
412
- ];
413
- };
414
- const generateContainsCheck = (acc, schema, label, context = {}) => {
415
- if (!isSchemaObject(schema))
416
- return [];
417
- const sp = schema;
418
- if (!("contains" in sp))
419
- return [];
420
- const match = subschemaMatchExpr("_c", sp["contains"], matchContext(context));
421
- if (match === null)
422
- return [];
423
- const min = typeof sp["minContains"] === "number" ? sp["minContains"] : 1;
424
- const max = typeof sp["maxContains"] === "number" ? sp["maxContains"] : void 0;
425
- const bound = max !== void 0 ? `_cn < ${min} || _cn > ${max}` : `_cn < ${min}`;
426
- return [
427
- ` if (Array.isArray(${acc})) {`,
428
- ` const _cn = (${acc} as unknown[]).filter((_c) => ${match}).length;`,
429
- ` if (${bound}) ${throwError(`${label} array does not contain the required matching items`)};`,
430
- ` }`
431
- ];
432
- };
433
- const generateDependentRequiredChecks = (obj, schema, label) => {
434
- if (!hasDependentRequired(schema))
435
- return [];
436
- const lines = [];
437
- for (const [trigger, deps] of Object.entries(schema.dependentRequired)) {
438
- if (!Array.isArray(deps))
439
- continue;
440
- for (const dep of deps) {
441
- lines.push(` if (${hasOwnCheck(obj, trigger)} && ${missingCheck(obj, dep)}) ${throwError(`${label} must have property '${dep}' when '${trigger}' is present`)};`);
442
- }
443
- }
444
- return lines;
445
- };
446
- const generateDependentSchemasChecks = (obj, schema, label, context) => {
447
- const dep = schema["dependentSchemas"];
448
- if (typeof dep !== "object" || dep === null || Array.isArray(dep))
449
- return [];
450
- const lines = [];
451
- for (const [trigger, sub] of Object.entries(dep)) {
452
- if (sub === true)
453
- continue;
454
- if (sub === false) {
455
- lines.push(` if (${hasOwnCheck(obj, trigger)}) ${throwError(`${label} must NOT have property '${trigger}'`)};`);
456
- continue;
457
- }
458
- const match = subschemaMatchExpr(obj, sub, matchContext(context));
459
- if (match === null || match === "true")
460
- continue;
461
- lines.push(` if (${hasOwnCheck(obj, trigger)} && !(${match})) ${throwError(`${label} does not satisfy the schema required when '${trigger}' is present`)};`);
462
- }
463
- return lines;
464
- };
465
- const resolvePositionSchema = (pos, rootSchema) => {
466
- if (isSchemaObject(pos) && hasRef(pos) && rootSchema) {
467
- const resolved = resolveRef(pos.$ref, rootSchema);
468
- if (resolved)
469
- return resolved;
470
- }
471
- return pos;
472
- };
473
- const generatePrefixItemsAssertion = (acc, field, schema, context) => {
474
- const rootSchema = context.rootSchema;
475
- const prefix = getPrefixItems(schema);
476
- if (!prefix)
477
- return [];
478
- const lines = [];
479
- for (let i = 0; i < prefix.length; i++) {
480
- const pos = resolvePositionSchema(prefix[i], rootSchema);
481
- const el = `${acc}[${i}]`;
482
- const present = `Array.isArray(${acc}) && ${acc}.length > ${i}`;
483
- if (isSchemaObject(pos) && hasEnum(pos) && pos.enum.length > 0) {
484
- const label = pos.enum.map((v) => JSON.stringify(v)).join(", ");
485
- lines.push(` if (${present} && !${generateEnumCheck(el, pos.enum)}) ${throwError(`${field}[${i}] must be one of: ${label}`)};`);
486
- continue;
487
- }
488
- if (isSchemaObject(pos) && hasType(pos)) {
489
- const pt = pos.type;
490
- const wrong = wrongTypeCondition(el, pt);
491
- if (wrong) {
492
- lines.push(` if (${present} && (${wrong})) ${throwError(`${field}[${i}] expected ${typeLabel(pt)}, got `, `typeof ${el}`)};`);
493
- }
494
- }
495
- const match = subschemaMatchExpr(el, prefix[i], matchContext(context));
496
- if (match !== null && match !== "true") {
497
- lines.push(` if (${present} && !(${match})) ${throwError(`${field}[${i}] does not match the tuple schema`)};`);
498
- }
499
- }
500
- if (prefixItemsCapsLength(schema)) {
501
- lines.push(` if (Array.isArray(${acc}) && ${acc}.length > ${prefix.length}) ${throwError(`${field} must NOT have more than ${prefix.length} items`)};`);
502
- }
503
- return lines;
504
- };
505
- const generatePropertyNameChecks = (obj, schema, label, context) => {
506
- if (!hasPropertyNames(schema) || !isSchemaObject(schema.propertyNames))
507
- return [];
508
- const match = subschemaMatchExpr("_name", schema.propertyNames, matchContext(context));
509
- if (match === null || match === "true")
510
- return [];
511
- return [
512
- ` for (const _name of Object.keys(${obj})) {`,
513
- ` if (!(${match})) ${throwError(`${label} invalid property name: `, "_name")};`,
514
- ` }`
515
- ];
516
- };
517
- const generateCompositionChecks = (acc, schema, label, context = {}) => [
518
- ...generateNotCheck(acc, schema, label, false, context),
519
- ...generateAllOfChecks(acc, schema, label, false, context),
520
- ...generateUnionMatchChecks(acc, schema, label, false, context),
521
- ...generateConditionalChecks(acc, schema, label, context)
522
- ];
523
- const generateObjectKeywordChecks = (obj, schema, label, guard, context = {}) => {
524
- if (!isSchemaObject(schema))
525
- return [];
526
- const inner = [
527
- ...generateDependentRequiredChecks(obj, schema, label),
528
- ...generateDependentSchemasChecks(obj, schema, label, context),
529
- ...generatePropertyNameChecks(obj, schema, label, context)
530
- ];
531
- if (inner.length === 0)
532
- return [];
533
- if (!guard)
534
- return inner;
535
- return [` if (isObject(${obj})) {`, ...inner.map((line) => ` ${line}`), ` }`];
536
- };
537
- const generatePropertyAssertion = (key, propSchema, isRequired, typeName, context = {}) => {
538
- const acc = safeAccessor("input", key);
539
- const field = `[${typeName}] field '${key}'`;
540
- const lines = [];
541
- if (isRequired) {
542
- lines.push(` if (${missingCheck("input", key)}) ${throwError(`[${typeName}] missing required property '${key}'`)};`);
543
- }
544
- if (propSchema === false) {
545
- lines.push(` if (${hasOwnCheck("input", key)}) ${throwError(`[${typeName}] must NOT have property '${key}'`)};`);
546
- return lines;
547
- }
548
- if (!isSchemaObject(propSchema))
549
- return lines;
550
- lines.push(...generateBackstopAssertion(acc, propSchema, field, context, !isRequired));
551
- if (hasRef(propSchema))
552
- return lines;
553
- lines.push(...generateNotCheck(acc, propSchema, field, !isRequired, context));
554
- lines.push(...generateAllOfChecks(acc, propSchema, field, !isRequired, context));
555
- lines.push(...generateUnionMatchChecks(acc, propSchema, field, !isRequired, context));
556
- lines.push(...generateConditionalChecks(acc, propSchema, field, context));
557
- lines.push(...declaredObjectShapeChecks(acc, propSchema, field, !isRequired, context));
558
- const unionBranches = getUnionBranches(propSchema);
559
- if (unionBranches) {
560
- if (!context.stripUnknown && canEnforceUnion(unionBranches, context.rootSchema)) {
561
- const check = generateUnionCheck(acc, unionBranches, context.useRefImports ?? false, context.suffix ?? "", isExclusiveUnion(propSchema));
562
- if (check !== null) {
563
- const failure = throwError(`${field} does not match any allowed variant`);
564
- lines.push(isRequired ? ` if (!(${check})) ${failure};` : ` if (${acc} !== undefined && !(${check})) ${failure};`);
565
- }
566
- }
567
- return lines;
568
- }
569
- const instanceOf = getMjstInstanceOf(propSchema);
570
- if (instanceOf) {
571
- if (isRequired) {
572
- lines.push(` if (!(${acc} instanceof ${instanceOf})) ${throwError(`${field} must be ${instanceOf}`)};`);
573
- } else {
574
- lines.push(` if (${acc} !== undefined && !(${acc} instanceof ${instanceOf})) ${throwError(`${field} must be ${instanceOf}`)};`);
575
- }
576
- return lines;
577
- }
578
- const primitive = getMjstPrimitive(propSchema);
579
- if (primitive) {
580
- if (isRequired) {
581
- lines.push(` if (typeof ${acc} !== "${primitive}") ${throwError(`${field} must be ${primitive}`)};`);
582
- } else {
583
- lines.push(` if (${acc} !== undefined && typeof ${acc} !== "${primitive}") ${throwError(`${field} must be ${primitive}`)};`);
584
- }
585
- return lines;
586
- }
587
- if (hasConst(propSchema)) {
588
- const equal = generateDeepEqualCheck(acc, propSchema.const);
589
- const failure = throwError(`${field} must be ${JSON.stringify(propSchema.const)}`);
590
- lines.push(isRequired ? ` if (!(${equal})) ${failure};` : ` if (${acc} !== undefined && !(${equal})) ${failure};`);
591
- return lines;
592
- }
593
- if (hasEnum(propSchema)) {
594
- const member = generateEnumCheck(acc, propSchema.enum);
595
- const label = propSchema.enum.map((v) => JSON.stringify(v)).join(", ");
596
- if (isRequired) {
597
- lines.push(` if (!${member}) ${throwError(`${field} must be one of: ${label}`)};`);
598
- } else {
599
- lines.push(` if (${acc} !== undefined && !${member}) ${throwError(`${field} must be one of: ${label}`)};`);
600
- }
601
- return lines;
602
- }
603
- const multiType = multiTypeCheck(acc, propSchema, { ignoreConstraints: true });
604
- if (multiType !== void 0) {
605
- const types = propSchema.type;
606
- if (multiType !== null) {
607
- const expected = throwError(`${field} expected ${types.map(typeLabel).join(" | ")}, got `, `typeof ${acc}`);
608
- lines.push(isRequired ? ` if (!${multiType}) ${expected};` : ` if (${acc} !== undefined && !${multiType}) ${expected};`);
609
- }
610
- const nonNull = types.filter((type) => type !== "null");
611
- if (nonNull.length === 1) {
612
- lines.push(...generateConstraintChecks(acc, { ...propSchema, type: nonNull[0] }, field, context));
613
- lines.push(...nullableObjectShapeChecks(acc, propSchema, nonNull[0], field, !isRequired, context));
614
- }
615
- return lines;
616
- }
617
- if (hasType(propSchema)) {
618
- const t = propSchema.type;
619
- const wrongType = wrongTypeCondition(acc, t);
620
- if (wrongType) {
621
- const expected = throwError(`${field} expected ${typeLabel(t)}, got `, `typeof ${acc}`);
622
- if (isRequired) {
623
- lines.push(` if (${wrongType}) ${expected};`);
624
- } else {
625
- lines.push(` if (${acc} !== undefined && (${wrongType})) ${expected};`);
626
- }
627
- }
628
- lines.push(...generateConstraintChecks(acc, propSchema, field, context));
629
- }
630
- if (isSchemaObject(propSchema)) {
631
- const r = propSchema;
632
- if ("contains" in r)
633
- lines.push(...generateContainsCheck(acc, propSchema, field, context));
634
- if (("dependentRequired" in r || "dependentSchemas" in r || "propertyNames" in r) && !isInlineObjectProperty(propSchema)) {
635
- lines.push(...generateObjectKeywordChecks(acc, propSchema, field, true, context));
636
- }
637
- if ("patternProperties" in r || isSchemaObject(propSchema) && typeof r["additionalProperties"] === "object") {
638
- const keyed = generateKeyedValueChecks(acc, propSchema, field, context);
639
- if (keyed.length > 0) {
640
- lines.push(` if (isObject(${acc})) {`, ...keyed.map((line) => ` ${line}`), ` }`);
641
- }
642
- }
643
- }
644
- return lines;
645
- };
646
- const inlineAllOfMembers = (schema) => {
647
- if (!isSchemaObject(schema) || !Array.isArray(schema.allOf))
648
- return [];
649
- return schema.allOf.filter((member) => {
650
- if (!isSchemaObject(member) || hasRef(member))
651
- return false;
652
- const record = member;
653
- if ("allOf" in record || "oneOf" in record || "anyOf" in record || "not" in record)
654
- return false;
655
- if ("if" in record || "then" in record || "else" in record)
656
- return false;
657
- return hasProperties(member) || hasRequired(member);
658
- });
659
- };
660
- const generateObjectStrictAssertion = (schema, typeName, context = {}) => {
661
- const lines = [];
662
- lines.push(` if (!isObject(input)) ${throwError(`[${typeName}] expected object, got `, 'input === null ? "null" : typeof input')};`);
663
- if (!isSchemaObject(schema))
664
- return lines;
665
- const required = new Set(hasRequired(schema) ? schema.required : []);
666
- if (hasProperties(schema)) {
667
- const props = schema.properties;
668
- for (const key in props) {
669
- lines.push(...generatePropertyAssertion(key, props[key], required.has(key), typeName, context));
670
- }
671
- for (const key in props)
672
- required.delete(key);
673
- }
674
- for (const key of required) {
675
- lines.push(` if (${missingCheck("input", key)}) ${throwError(`[${typeName}] missing required property '${key}'`)};`);
676
- }
677
- for (const member of inlineAllOfMembers(schema)) {
678
- lines.push(...generateObjectStrictAssertion(member, typeName, context).slice(1));
679
- }
680
- const sr = schema;
681
- if ("dependentRequired" in sr || "dependentSchemas" in sr || "propertyNames" in sr) {
682
- lines.push(...generateObjectKeywordChecks("input", schema, `[${typeName}]`, false, context));
683
- }
684
- lines.push(...generateObjectSizeChecks("input", schema, `[${typeName}]`, false));
685
- lines.push(...generateNotCheck("input", schema, `[${typeName}]`, false, context));
686
- lines.push(...generateAllOfChecks("input", schema, `[${typeName}]`, false, context, true));
687
- lines.push(...generateUnionMatchChecks("input", schema, `[${typeName}]`, false, context));
688
- lines.push(...generateConditionalChecks("input", schema, `[${typeName}]`, context));
689
- lines.push(...generateKeyedValueChecks("input", schema, `[${typeName}]`, context));
690
- lines.push(...generateBackstopAssertion("input", schema, `[${typeName}]`, context));
691
- return lines;
692
- };
693
- const generateScalarStrictAssertion = (schema, typeName, context = {}) => {
694
- if (schema === false)
695
- return ` ${throwError(`[${typeName}] schema is false: no value is valid`)};`;
696
- const got = 'input === null ? "null" : typeof input';
697
- const instanceOf = getMjstInstanceOf(schema);
698
- if (instanceOf) {
699
- return ` if (!(input instanceof ${instanceOf})) ${throwError(`[${typeName}] expected ${instanceOf}, got `, got)};`;
700
- }
701
- const primitive = getMjstPrimitive(schema);
702
- if (primitive) {
703
- return ` if (typeof input !== "${primitive}") ${throwError(`[${typeName}] expected ${primitive}, got `, got)};`;
704
- }
705
- if (!isSchemaObject(schema))
706
- return null;
707
- const label = `[${typeName}]`;
708
- const lines = [];
709
- if (hasConst(schema)) {
710
- lines.push(` if (!(${generateDeepEqualCheck("input", schema.const)})) ${throwError(`${label} must be ${JSON.stringify(schema.const)}`)};`);
711
- } else if (hasEnum(schema)) {
712
- const enumLabel = schema.enum.map((v) => JSON.stringify(v)).join(", ");
713
- lines.push(` if (!${generateEnumCheck("input", schema.enum)}) ${throwError(`${label} must be one of: ${enumLabel}`)};`);
714
- }
715
- lines.push(...generateNotCheck("input", schema, label, false, context));
716
- lines.push(...generateAllOfChecks("input", schema, label, false, context));
717
- lines.push(...generateUnionMatchChecks("input", schema, label, false, context));
718
- lines.push(...generateConditionalChecks("input", schema, label, context));
719
- const rootMultiType = multiTypeCheck("input", schema, { ignoreConstraints: true });
720
- if (rootMultiType !== void 0) {
721
- const types = schema.type;
722
- if (rootMultiType !== null) {
723
- lines.push(` if (!${rootMultiType}) ${throwError(`${label} expected ${types.map(typeLabel).join(" | ")}, got `, got)};`);
724
- }
725
- const nonNull = types.filter((type) => type !== "null");
726
- if (nonNull.length === 1) {
727
- lines.push(...generateConstraintChecks("input", { ...schema, type: nonNull[0] }, label, context));
728
- lines.push(...nullableObjectShapeChecks("input", schema, nonNull[0], label, false, context));
729
- }
730
- lines.push(...generateBackstopAssertion("input", schema, label, context));
731
- return lines.length > 0 ? lines.join("\n") : null;
732
- }
733
- if (hasType(schema)) {
734
- const t = schema.type;
735
- const wrongType = wrongTypeCondition("input", t);
736
- if (wrongType) {
737
- lines.push(` if (${wrongType}) ${throwError(`${label} expected ${typeLabel(t)}, got `, got)};`);
738
- }
739
- if (t === "array") {
740
- lines.push(...generateContainsCheck("input", schema, label, context));
741
- }
742
- lines.push(...generateConstraintChecks("input", schema, label, context));
743
- }
744
- lines.push(...generateBackstopAssertion("input", schema, label, context));
745
- return lines.length > 0 ? lines.join("\n") : null;
746
- };
747
- export {
748
- backstopReason,
749
- backstopSchema,
750
- generateBackstopAssertion,
751
- generateCompositionChecks,
752
- generateContainsCheck,
753
- generateKeyedValueChecks,
754
- generateObjectKeywordChecks,
755
- generateObjectSizeChecks,
756
- generateObjectStrictAssertion,
757
- generateScalarStrictAssertion,
758
- inlineAllOfMembers
759
- };