@amritk/generate-parsers 0.16.3 → 0.17.1

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.
@@ -1,265 +1,231 @@
1
- import { escapeRegexPattern } from '@amritk/helpers/escape-regex-pattern';
2
- import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension';
3
- import { multipleOfPassExpr } from '@amritk/helpers/multiple-of-check';
4
- import { hasAdditionalProperties, hasConst, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasPattern, hasProperties, hasRequired, hasType, hasUniqueItems, isSchemaObject, } from '@amritk/helpers/schema-guards';
5
- import { generateEnumCheck } from './generate-enum-check.js';
6
- // Keywords that only make sense on a given type. A branch that carries any of
7
- // them but no explicit `type` is treated as that type for discrimination.
1
+ import { escapeRegexPattern } from "@amritk/helpers/escape-regex-pattern";
2
+ import { getMjstInstanceOf, getMjstPrimitive } from "@amritk/helpers/mjst-extension";
3
+ import { multipleOfPassExpr } from "@amritk/helpers/multiple-of-check";
4
+ import { hasAdditionalProperties, hasConst, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasPattern, hasProperties, hasRequired, hasType, hasUniqueItems, isSchemaObject } from "@amritk/helpers/schema-guards";
5
+ import { generateEnumCheck } from "./generate-enum-check.js";
8
6
  const OBJECT_KEYWORDS = [
9
- 'properties',
10
- 'required',
11
- 'minProperties',
12
- 'maxProperties',
13
- 'additionalProperties',
14
- 'patternProperties',
15
- 'dependentRequired',
16
- 'propertyNames',
7
+ "properties",
8
+ "required",
9
+ "minProperties",
10
+ "maxProperties",
11
+ "additionalProperties",
12
+ "patternProperties",
13
+ "dependentRequired",
14
+ "propertyNames"
17
15
  ];
18
- const ARRAY_KEYWORDS = ['items', 'prefixItems', 'minItems', 'maxItems', 'uniqueItems', 'contains'];
19
- const STRING_KEYWORDS = ['minLength', 'maxLength', 'pattern', 'format'];
20
- const NUMBER_KEYWORDS = ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'];
21
- const countKeywords = (schema, keywords) => keywords.reduce((total, keyword) => (keyword in schema ? total + 1 : total), 0);
22
- /** Resolves the JSON Schema type of a single literal (a `const` value). */
16
+ const ARRAY_KEYWORDS = ["items", "prefixItems", "minItems", "maxItems", "uniqueItems", "contains"];
17
+ const STRING_KEYWORDS = ["minLength", "maxLength", "pattern", "format"];
18
+ const NUMBER_KEYWORDS = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
19
+ const countKeywords = (schema, keywords) => keywords.reduce((total, keyword) => keyword in schema ? total + 1 : total, 0);
23
20
  const typeOfLiteral = (value) => {
24
- if (value === null)
25
- return 'null';
26
- if (Array.isArray(value))
27
- return 'array';
28
- const type = typeof value;
29
- if (type === 'boolean')
30
- return 'boolean';
31
- if (type === 'number' || type === 'bigint')
32
- return 'number';
33
- if (type === 'string')
34
- return 'string';
35
- return 'object';
21
+ if (value === null)
22
+ return "null";
23
+ if (Array.isArray(value))
24
+ return "array";
25
+ const type = typeof value;
26
+ if (type === "boolean")
27
+ return "boolean";
28
+ if (type === "number" || type === "bigint")
29
+ return "number";
30
+ if (type === "string")
31
+ return "string";
32
+ return "object";
36
33
  };
37
- /**
38
- * Infers the type of a schema branch that omits an explicit `type`.
39
- *
40
- * A `const` pins the value to one literal, so its type is definitive. An enum
41
- * of only `null`s can only be the null type. Otherwise each candidate type is
42
- * scored by how many of its characteristic keywords the schema carries and the
43
- * highest wins, with ties resolved in `object > array > string > number` order
44
- * (so `{ minProperties, minItems }` is an object and `{ minItems, maxItems,
45
- * minLength }` is an array). Returns undefined when nothing points at a type —
46
- * e.g. a bare `{ description }` — so the caller emits no checks.
47
- */
48
34
  const inferSchemaType = (schema) => {
49
- if (!isSchemaObject(schema))
50
- return undefined;
51
- if (hasConst(schema)) {
52
- return typeOfLiteral(schema.const);
35
+ if (!isSchemaObject(schema))
36
+ return void 0;
37
+ if (hasConst(schema)) {
38
+ return typeOfLiteral(schema.const);
39
+ }
40
+ if (hasEnum(schema) && schema.enum.length > 0 && schema.enum.every((value) => value === null)) {
41
+ return "null";
42
+ }
43
+ const scores = {
44
+ object: countKeywords(schema, OBJECT_KEYWORDS),
45
+ array: countKeywords(schema, ARRAY_KEYWORDS),
46
+ string: countKeywords(schema, STRING_KEYWORDS),
47
+ number: countKeywords(schema, NUMBER_KEYWORDS)
48
+ };
49
+ let best;
50
+ for (const type of ["object", "array", "string", "number"]) {
51
+ if (scores[type] > 0 && (best === void 0 || scores[type] > scores[best])) {
52
+ best = type;
53
53
  }
54
- if (hasEnum(schema) && schema.enum.length > 0 && schema.enum.every((value) => value === null)) {
55
- return 'null';
56
- }
57
- const scores = {
58
- object: countKeywords(schema, OBJECT_KEYWORDS),
59
- array: countKeywords(schema, ARRAY_KEYWORDS),
60
- string: countKeywords(schema, STRING_KEYWORDS),
61
- number: countKeywords(schema, NUMBER_KEYWORDS),
62
- };
63
- // Iterating in priority order and only replacing on a strictly-greater score
64
- // means the earliest (highest-priority) type wins any tie.
65
- let best;
66
- for (const type of ['object', 'array', 'string', 'number']) {
67
- if (scores[type] > 0 && (best === undefined || scores[type] > scores[best])) {
68
- best = type;
69
- }
70
- }
71
- return best;
54
+ }
55
+ return best;
72
56
  };
73
- /**
74
- * Emits the checks for a branch whose type was inferred (not explicit). It
75
- * mirrors the explicit-type switch below, with two deliberate differences: the
76
- * enum membership check uses the `.includes` form (appended by the caller) and
77
- * `multipleOf` uses the plain `% === 0` form, matching the inference spec.
78
- */
79
57
  const generateInferredChecks = (accessor, schema, type) => {
80
- const checks = [];
81
- switch (type) {
82
- case 'object':
83
- checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`);
84
- if (hasRequired(schema) && schema.required.length > 0) {
85
- for (const requiredKey of schema.required) {
86
- checks.push(`${JSON.stringify(requiredKey)} in ${accessor}`);
87
- }
88
- }
89
- if (hasMinProperties(schema)) {
90
- checks.push(`Object.keys(${accessor}).length >= ${schema.minProperties}`);
91
- }
92
- if (hasMaxProperties(schema)) {
93
- checks.push(`Object.keys(${accessor}).length <= ${schema.maxProperties}`);
94
- }
95
- if (hasAdditionalProperties(schema) && schema.additionalProperties === false && hasProperties(schema)) {
96
- const allowedKeysStr = JSON.stringify(Object.keys(schema.properties));
97
- checks.push(`Object.keys(${accessor}).every(k => ${allowedKeysStr}.includes(k))`);
98
- }
99
- break;
100
- case 'array':
101
- checks.push(`Array.isArray(${accessor})`);
102
- if (hasMinItems(schema)) {
103
- checks.push(`${accessor}.length >= ${schema.minItems}`);
104
- }
105
- if (hasMaxItems(schema)) {
106
- checks.push(`${accessor}.length <= ${schema.maxItems}`);
107
- }
108
- if (hasUniqueItems(schema) && schema.uniqueItems === true) {
109
- checks.push(`new Set(${accessor}).size === ${accessor}.length`);
110
- }
111
- break;
112
- case 'string':
113
- checks.push(`typeof ${accessor} === "string"`);
114
- if (hasPattern(schema)) {
115
- checks.push(`/${escapeRegexPattern(schema.pattern)}/.test(${accessor})`);
116
- }
117
- if (hasMinLength(schema)) {
118
- checks.push(`${accessor}.length >= ${schema.minLength}`);
119
- }
120
- if (hasMaxLength(schema)) {
121
- checks.push(`${accessor}.length <= ${schema.maxLength}`);
122
- }
123
- break;
124
- case 'number':
125
- checks.push(`typeof ${accessor} === "number"`);
126
- if (hasMinimum(schema)) {
127
- checks.push(`${accessor} >= ${schema.minimum}`);
128
- }
129
- if (hasMaximum(schema)) {
130
- checks.push(`${accessor} <= ${schema.maximum}`);
131
- }
132
- if (hasExclusiveMinimum(schema)) {
133
- checks.push(`${accessor} > ${schema.exclusiveMinimum}`);
134
- }
135
- if (hasExclusiveMaximum(schema)) {
136
- checks.push(`${accessor} < ${schema.exclusiveMaximum}`);
137
- }
138
- if (hasMultipleOf(schema)) {
139
- checks.push(`${accessor} % ${schema.multipleOf} === 0`);
140
- }
141
- break;
142
- case 'boolean':
143
- checks.push(`typeof ${accessor} === "boolean"`);
144
- break;
145
- case 'null':
146
- // A `null` inferred from an all-null enum is covered by the membership
147
- // check the caller appends; only a `const: null` needs its own identity
148
- // check here.
149
- if (hasConst(schema)) {
150
- checks.push(`${accessor} === null`);
151
- }
152
- break;
153
- }
154
- return checks;
58
+ const checks = [];
59
+ switch (type) {
60
+ case "object":
61
+ checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`);
62
+ if (hasRequired(schema) && schema.required.length > 0) {
63
+ for (const requiredKey of schema.required) {
64
+ checks.push(`${JSON.stringify(requiredKey)} in ${accessor}`);
65
+ }
66
+ }
67
+ if (hasMinProperties(schema)) {
68
+ checks.push(`Object.keys(${accessor}).length >= ${schema.minProperties}`);
69
+ }
70
+ if (hasMaxProperties(schema)) {
71
+ checks.push(`Object.keys(${accessor}).length <= ${schema.maxProperties}`);
72
+ }
73
+ if (hasAdditionalProperties(schema) && schema.additionalProperties === false && hasProperties(schema)) {
74
+ const allowedKeysStr = JSON.stringify(Object.keys(schema.properties));
75
+ checks.push(`Object.keys(${accessor}).every(k => ${allowedKeysStr}.includes(k))`);
76
+ }
77
+ break;
78
+ case "array":
79
+ checks.push(`Array.isArray(${accessor})`);
80
+ if (hasMinItems(schema)) {
81
+ checks.push(`${accessor}.length >= ${schema.minItems}`);
82
+ }
83
+ if (hasMaxItems(schema)) {
84
+ checks.push(`${accessor}.length <= ${schema.maxItems}`);
85
+ }
86
+ if (hasUniqueItems(schema) && schema.uniqueItems === true) {
87
+ checks.push(`new Set(${accessor}).size === ${accessor}.length`);
88
+ }
89
+ break;
90
+ case "string":
91
+ checks.push(`typeof ${accessor} === "string"`);
92
+ if (hasPattern(schema)) {
93
+ checks.push(`/${escapeRegexPattern(schema.pattern)}/.test(${accessor})`);
94
+ }
95
+ if (hasMinLength(schema)) {
96
+ checks.push(`${accessor}.length >= ${schema.minLength}`);
97
+ }
98
+ if (hasMaxLength(schema)) {
99
+ checks.push(`${accessor}.length <= ${schema.maxLength}`);
100
+ }
101
+ break;
102
+ case "number":
103
+ checks.push(`typeof ${accessor} === "number"`);
104
+ if (hasMinimum(schema)) {
105
+ checks.push(`${accessor} >= ${schema.minimum}`);
106
+ }
107
+ if (hasMaximum(schema)) {
108
+ checks.push(`${accessor} <= ${schema.maximum}`);
109
+ }
110
+ if (hasExclusiveMinimum(schema)) {
111
+ checks.push(`${accessor} > ${schema.exclusiveMinimum}`);
112
+ }
113
+ if (hasExclusiveMaximum(schema)) {
114
+ checks.push(`${accessor} < ${schema.exclusiveMaximum}`);
115
+ }
116
+ if (hasMultipleOf(schema)) {
117
+ checks.push(`${accessor} % ${schema.multipleOf} === 0`);
118
+ }
119
+ break;
120
+ case "boolean":
121
+ checks.push(`typeof ${accessor} === "boolean"`);
122
+ break;
123
+ case "null":
124
+ if (hasConst(schema)) {
125
+ checks.push(`${accessor} === null`);
126
+ }
127
+ break;
128
+ }
129
+ return checks;
155
130
  };
156
- /**
157
- * Generates validation checks for a single schema (used in union validation).
158
- */
159
- export const generateSchemaChecks = (accessor, schema) => {
160
- const checks = [];
161
- // x-mjst instanceOf nodes validate purely by instance check (no native type).
162
- const instanceOf = getMjstInstanceOf(schema);
163
- if (instanceOf) {
164
- return [`${accessor} instanceof ${instanceOf}`];
131
+ const generateSchemaChecks = (accessor, schema) => {
132
+ const checks = [];
133
+ const instanceOf = getMjstInstanceOf(schema);
134
+ if (instanceOf) {
135
+ return [`${accessor} instanceof ${instanceOf}`];
136
+ }
137
+ const primitive = getMjstPrimitive(schema);
138
+ if (primitive) {
139
+ return [`typeof ${accessor} === "${primitive}"`];
140
+ }
141
+ if (!hasType(schema)) {
142
+ const inferred = inferSchemaType(schema);
143
+ if (!inferred) {
144
+ return checks;
165
145
  }
166
- // x-mjst primitive nodes (e.g. bigint) validate purely by typeof check.
167
- const primitive = getMjstPrimitive(schema);
168
- if (primitive) {
169
- return [`typeof ${accessor} === "${primitive}"`];
146
+ checks.push(...generateInferredChecks(accessor, schema, inferred));
147
+ if (hasEnum(schema) && schema.enum.length > 0) {
148
+ checks.push(`${JSON.stringify(schema.enum)}.includes(${accessor} as never)`);
170
149
  }
171
- // Without an explicit `type`, a branch's keywords still imply one. Inferring
172
- // it lets union discrimination reject the branch instead of matching anything.
173
- if (!hasType(schema)) {
174
- const inferred = inferSchemaType(schema);
175
- if (!inferred) {
176
- return checks;
177
- }
178
- checks.push(...generateInferredChecks(accessor, schema, inferred));
179
- if (hasEnum(schema) && schema.enum.length > 0) {
180
- checks.push(`${JSON.stringify(schema.enum)}.includes(${accessor} as never)`);
181
- }
182
- return checks;
150
+ return checks;
151
+ }
152
+ switch (schema.type) {
153
+ case "string": {
154
+ checks.push(`typeof ${accessor} === "string"`);
155
+ if (hasPattern(schema)) {
156
+ checks.push(`/${escapeRegexPattern(schema.pattern)}/.test(${accessor})`);
157
+ }
158
+ if (hasMinLength(schema)) {
159
+ checks.push(`${accessor}.length >= ${schema.minLength}`);
160
+ }
161
+ if (hasMaxLength(schema)) {
162
+ checks.push(`${accessor}.length <= ${schema.maxLength}`);
163
+ }
164
+ break;
183
165
  }
184
- switch (schema.type) {
185
- case 'string': {
186
- checks.push(`typeof ${accessor} === "string"`);
187
- if (hasPattern(schema)) {
188
- checks.push(`/${escapeRegexPattern(schema.pattern)}/.test(${accessor})`);
189
- }
190
- if (hasMinLength(schema)) {
191
- checks.push(`${accessor}.length >= ${schema.minLength}`);
192
- }
193
- if (hasMaxLength(schema)) {
194
- checks.push(`${accessor}.length <= ${schema.maxLength}`);
195
- }
196
- break;
197
- }
198
- case 'number':
199
- case 'integer':
200
- checks.push(`typeof ${accessor} === "number"`);
201
- // `integer` must reject non-integral numbers (a bare typeof accepts `1.5`).
202
- if (schema.type === 'integer') {
203
- checks.push(`Number.isInteger(${accessor})`);
204
- }
205
- if (hasMinimum(schema)) {
206
- checks.push(`${accessor} >= ${schema.minimum}`);
207
- }
208
- if (hasMaximum(schema)) {
209
- checks.push(`${accessor} <= ${schema.maximum}`);
210
- }
211
- if (hasExclusiveMinimum(schema)) {
212
- checks.push(`${accessor} > ${schema.exclusiveMinimum}`);
213
- }
214
- if (hasExclusiveMaximum(schema)) {
215
- checks.push(`${accessor} < ${schema.exclusiveMaximum}`);
216
- }
217
- if (hasMultipleOf(schema)) {
218
- checks.push(multipleOfPassExpr(accessor, schema.multipleOf));
219
- }
220
- break;
221
- case 'boolean':
222
- checks.push(`typeof ${accessor} === "boolean"`);
223
- break;
224
- case 'array':
225
- checks.push(`Array.isArray(${accessor})`);
226
- if (hasMinItems(schema)) {
227
- checks.push(`${accessor}.length >= ${schema.minItems}`);
228
- }
229
- if (hasMaxItems(schema)) {
230
- checks.push(`${accessor}.length <= ${schema.maxItems}`);
231
- }
232
- if (hasUniqueItems(schema) && schema.uniqueItems === true) {
233
- checks.push(`new Set(${accessor}).size === ${accessor}.length`);
234
- }
235
- break;
236
- case 'object': {
237
- checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`);
238
- // Check required properties exist
239
- if (hasRequired(schema) && schema.required.length > 0) {
240
- for (const requiredKey of schema.required) {
241
- checks.push(`${JSON.stringify(requiredKey)} in ${accessor}`);
242
- }
243
- }
244
- // Check minProperties
245
- if (hasMinProperties(schema)) {
246
- checks.push(`Object.keys(${accessor}).length >= ${schema.minProperties}`);
247
- }
248
- // Check maxProperties
249
- if (hasMaxProperties(schema)) {
250
- checks.push(`Object.keys(${accessor}).length <= ${schema.maxProperties}`);
251
- }
252
- // Check additionalProperties
253
- if (hasAdditionalProperties(schema) && schema.additionalProperties === false && hasProperties(schema)) {
254
- const allowedKeys = Object.keys(schema.properties);
255
- const allowedKeysStr = JSON.stringify(allowedKeys);
256
- checks.push(`Object.keys(${accessor}).every(k => ${allowedKeysStr}.includes(k))`);
257
- }
258
- break;
166
+ case "number":
167
+ case "integer":
168
+ checks.push(`typeof ${accessor} === "number"`);
169
+ if (schema.type === "integer") {
170
+ checks.push(`Number.isInteger(${accessor})`);
171
+ }
172
+ if (hasMinimum(schema)) {
173
+ checks.push(`${accessor} >= ${schema.minimum}`);
174
+ }
175
+ if (hasMaximum(schema)) {
176
+ checks.push(`${accessor} <= ${schema.maximum}`);
177
+ }
178
+ if (hasExclusiveMinimum(schema)) {
179
+ checks.push(`${accessor} > ${schema.exclusiveMinimum}`);
180
+ }
181
+ if (hasExclusiveMaximum(schema)) {
182
+ checks.push(`${accessor} < ${schema.exclusiveMaximum}`);
183
+ }
184
+ if (hasMultipleOf(schema)) {
185
+ checks.push(multipleOfPassExpr(accessor, schema.multipleOf));
186
+ }
187
+ break;
188
+ case "boolean":
189
+ checks.push(`typeof ${accessor} === "boolean"`);
190
+ break;
191
+ case "array":
192
+ checks.push(`Array.isArray(${accessor})`);
193
+ if (hasMinItems(schema)) {
194
+ checks.push(`${accessor}.length >= ${schema.minItems}`);
195
+ }
196
+ if (hasMaxItems(schema)) {
197
+ checks.push(`${accessor}.length <= ${schema.maxItems}`);
198
+ }
199
+ if (hasUniqueItems(schema) && schema.uniqueItems === true) {
200
+ checks.push(`new Set(${accessor}).size === ${accessor}.length`);
201
+ }
202
+ break;
203
+ case "object": {
204
+ checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`);
205
+ if (hasRequired(schema) && schema.required.length > 0) {
206
+ for (const requiredKey of schema.required) {
207
+ checks.push(`${JSON.stringify(requiredKey)} in ${accessor}`);
259
208
  }
209
+ }
210
+ if (hasMinProperties(schema)) {
211
+ checks.push(`Object.keys(${accessor}).length >= ${schema.minProperties}`);
212
+ }
213
+ if (hasMaxProperties(schema)) {
214
+ checks.push(`Object.keys(${accessor}).length <= ${schema.maxProperties}`);
215
+ }
216
+ if (hasAdditionalProperties(schema) && schema.additionalProperties === false && hasProperties(schema)) {
217
+ const allowedKeys = Object.keys(schema.properties);
218
+ const allowedKeysStr = JSON.stringify(allowedKeys);
219
+ checks.push(`Object.keys(${accessor}).every(k => ${allowedKeysStr}.includes(k))`);
220
+ }
221
+ break;
260
222
  }
261
- if (hasEnum(schema) && schema.enum.length > 0) {
262
- checks.push(generateEnumCheck(accessor, schema.enum));
263
- }
264
- return checks;
223
+ }
224
+ if (hasEnum(schema) && schema.enum.length > 0) {
225
+ checks.push(generateEnumCheck(accessor, schema.enum));
226
+ }
227
+ return checks;
228
+ };
229
+ export {
230
+ generateSchemaChecks
265
231
  };