@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.
- package/AI.md +36 -0
- package/dist/generators/assert-supported-keywords.js +45 -72
- package/dist/generators/build-schema.js +56 -168
- package/dist/generators/generate-default-from-pattern.js +24 -31
- package/dist/generators/generate-discriminated-union-validation.js +27 -47
- package/dist/generators/generate-enum-check.js +27 -61
- package/dist/generators/generate-files.js +54 -83
- package/dist/generators/generate-non-discriminated-union-validation.js +24 -26
- package/dist/generators/generate-parser-function.d.ts +12 -0
- package/dist/generators/generate-parser-function.js +1132 -1711
- package/dist/generators/generate-schema-checks.js +216 -250
- package/dist/generators/generate-strict-assertion.js +311 -447
- package/dist/generators/generate-type-checks.js +287 -460
- package/dist/generators/generate-validation-expression.js +300 -439
- package/dist/generators/subschema-match.js +201 -266
- package/dist/helpers/apply-schema-extensions.js +18 -49
- package/dist/helpers/collect-helpers.js +58 -71
- package/dist/helpers/collect-imports.js +161 -237
- package/dist/helpers/find-discriminator.js +23 -29
- package/dist/helpers/get-default-value.js +76 -96
- package/dist/helpers/get-discriminator-value.js +16 -17
- package/dist/index.js +4 -1
- package/package.json +6 -5
|
@@ -1,1340 +1,909 @@
|
|
|
1
|
-
import { escapeRegexPattern } from
|
|
2
|
-
import { quoteJsString } from
|
|
3
|
-
import { refToName } from
|
|
4
|
-
import { resolveRef } from
|
|
5
|
-
import { safeAccessor, safeKey } from
|
|
6
|
-
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasEnum, hasItems, hasOneOf, hasProperties, hasRef, hasRequired, hasType, isObjectSchema, isSchemaObject
|
|
7
|
-
import { unknownKeyCheck } from
|
|
8
|
-
import { findDiscriminator } from
|
|
9
|
-
import { getDefaultValue } from
|
|
10
|
-
import { getDiscriminatorValue } from
|
|
11
|
-
import { assertNoUnsupportedKeywords } from
|
|
12
|
-
import { generateEnumCaseInsensitiveCoercion } from
|
|
13
|
-
import { generateContainsCheck, generateObjectKeywordChecks, generateObjectStrictAssertion, generateScalarStrictAssertion
|
|
14
|
-
import { canEnforceUnion, generatePropertyTypeCheck, generateUnionCheck, getUnionBranches, hasFastPathBlockingKeyword, isInlineObjectArrayProperty, isInlineObjectProperty, shapeValidatorName
|
|
15
|
-
import { generatePrefixItemsMap, generateValidationExpression, getPrefixItems, isCoercibleItemSchema, prefixItemsCapsLength
|
|
16
|
-
/**
|
|
17
|
-
* Generates the parser function name from a type name.
|
|
18
|
-
* Converts "UserObject" to "parseUserObject".
|
|
19
|
-
*/
|
|
1
|
+
import { escapeRegexPattern } from "@amritk/helpers/escape-regex-pattern";
|
|
2
|
+
import { quoteJsString } from "@amritk/helpers/quote-js-string";
|
|
3
|
+
import { refToName } from "@amritk/helpers/ref-to-name";
|
|
4
|
+
import { resolveRef } from "@amritk/helpers/resolve-ref";
|
|
5
|
+
import { safeAccessor, safeKey } from "@amritk/helpers/safe-accessor";
|
|
6
|
+
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasEnum, hasItems, hasOneOf, hasProperties, hasRef, hasRequired, hasType, isObjectSchema, isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
7
|
+
import { unknownKeyCheck } from "@amritk/helpers/unknown-key-check";
|
|
8
|
+
import { findDiscriminator } from "../helpers/find-discriminator.js";
|
|
9
|
+
import { getDefaultValue } from "../helpers/get-default-value.js";
|
|
10
|
+
import { getDiscriminatorValue } from "../helpers/get-discriminator-value.js";
|
|
11
|
+
import { assertNoUnsupportedKeywords } from "./assert-supported-keywords.js";
|
|
12
|
+
import { generateEnumCaseInsensitiveCoercion } from "./generate-enum-check.js";
|
|
13
|
+
import { generateContainsCheck, generateObjectKeywordChecks, generateObjectStrictAssertion, generateScalarStrictAssertion } from "./generate-strict-assertion.js";
|
|
14
|
+
import { canEnforceUnion, generatePropertyTypeCheck, generateUnionCheck, getUnionBranches, hasFastPathBlockingKeyword, isInlineObjectArrayProperty, isInlineObjectProperty, shapeValidatorName } from "./generate-type-checks.js";
|
|
15
|
+
import { generatePrefixItemsMap, generateValidationExpression, getPrefixItems, isCoercibleItemSchema, prefixItemsCapsLength } from "./generate-validation-expression.js";
|
|
20
16
|
const generateParserName = (typeName) => {
|
|
21
|
-
|
|
17
|
+
return `parse${typeName}`;
|
|
22
18
|
};
|
|
23
|
-
|
|
24
|
-
* Object-literal key form that is safe for the runtime-dangerous name
|
|
25
|
-
* `__proto__`. In an object literal `{ __proto__: v }` / `{ "__proto__": v }` is
|
|
26
|
-
* the special prototype-setter syntax — it never creates an own property — so a
|
|
27
|
-
* schema property literally named `__proto__` would silently vanish (and, for an
|
|
28
|
-
* object value, reassign the result's prototype). The computed form
|
|
29
|
-
* `{ ["__proto__"]: v }` creates a normal own property. Every other key keeps
|
|
30
|
-
* the plain `safeKey` output, so the common path is unchanged.
|
|
31
|
-
*/
|
|
32
|
-
const safeLiteralKey = (key) => (key === '__proto__' ? '["__proto__"]' : safeKey(key));
|
|
33
|
-
/**
|
|
34
|
-
* Checks if a property is required based on the schema's required array.
|
|
35
|
-
*/
|
|
19
|
+
const safeLiteralKey = (key) => key === "__proto__" ? '["__proto__"]' : safeKey(key);
|
|
36
20
|
const isPropertyRequired = (key, schema) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
21
|
+
if (!isSchemaObject(schema)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (!("required" in schema) || !Array.isArray(schema.required)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return schema.required.includes(key);
|
|
44
28
|
};
|
|
45
|
-
/**
|
|
46
|
-
* Generates a parser call expression for a required $ref property.
|
|
47
|
-
* Example: parseContact(input.contact)
|
|
48
|
-
*/
|
|
49
29
|
const generateRequiredRefCall = (key, ref, suffix) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
const acc = safeAccessor("input", key);
|
|
31
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
32
|
+
return `${parserName}(${acc})`;
|
|
53
33
|
};
|
|
54
|
-
/**
|
|
55
|
-
* Generates a parser call expression for an optional $ref property.
|
|
56
|
-
* Example: ...(input.contact && { contact: parseContact(input.contact) })
|
|
57
|
-
*/
|
|
58
34
|
const generateOptionalRefCall = (key, ref, suffix) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// is `false`/`0`/`""`/`null`, spreading it raw. `!== undefined` still omits an
|
|
63
|
-
// absent optional property but coerces every present one, matching the main path.
|
|
64
|
-
return `...(${acc} !== undefined && { ${safeLiteralKey(key)}: ${parserName}(${acc}) })`;
|
|
35
|
+
const acc = safeAccessor("input", key);
|
|
36
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
37
|
+
return `...(${acc} !== undefined && { ${safeLiteralKey(key)}: ${parserName}(${acc}) })`;
|
|
65
38
|
};
|
|
66
|
-
/**
|
|
67
|
-
* Generates a validateArray call for required array properties with $ref items.
|
|
68
|
-
* Example: validateArray(input.contacts, parseContact)
|
|
69
|
-
*/
|
|
70
39
|
const generateRequiredArrayRefCall = (key, ref, suffix) => {
|
|
71
|
-
|
|
72
|
-
|
|
40
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
41
|
+
return `validateArray(${safeAccessor("input", key)}, ${parserName})`;
|
|
73
42
|
};
|
|
74
|
-
/**
|
|
75
|
-
* Generates a validateArray call for optional array properties with $ref items.
|
|
76
|
-
* Example: ...(input.contacts && { contacts: validateArray(input.contacts, parseContact) })
|
|
77
|
-
*/
|
|
78
43
|
const generateOptionalArrayRefCall = (key, ref, suffix) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return `...(${acc} !== undefined && { ${safeLiteralKey(key)}: validateArray(${acc}, ${parserName}) })`;
|
|
44
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
45
|
+
const acc = safeAccessor("input", key);
|
|
46
|
+
return `...(${acc} !== undefined && { ${safeLiteralKey(key)}: validateArray(${acc}, ${parserName}) })`;
|
|
83
47
|
};
|
|
84
|
-
/**
|
|
85
|
-
* Generates a validateRecord call for required object properties with additionalProperties $ref.
|
|
86
|
-
* Example: validateRecord(input.responses, parseResponse)
|
|
87
|
-
*/
|
|
88
48
|
const generateRequiredRecordRefCall = (key, ref, suffix) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
49
|
+
const acc = safeAccessor("input", key);
|
|
50
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
51
|
+
return `validateRecord(${acc}, ${parserName})`;
|
|
92
52
|
};
|
|
93
|
-
/**
|
|
94
|
-
* Generates a validateRecord call for optional object properties with additionalProperties $ref.
|
|
95
|
-
* Example: ...(input.responses && { responses: validateRecord(input.responses, parseResponse) })
|
|
96
|
-
*/
|
|
97
53
|
const generateOptionalRecordRefCall = (key, ref, suffix) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return `...(${acc} !== undefined && { ${safeLiteralKey(key)}: validateRecord(${acc}, ${parserName}) })`;
|
|
54
|
+
const acc = safeAccessor("input", key);
|
|
55
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
56
|
+
return `...(${acc} !== undefined && { ${safeLiteralKey(key)}: validateRecord(${acc}, ${parserName}) })`;
|
|
102
57
|
};
|
|
103
|
-
/**
|
|
104
|
-
* Generates a spread entry for optional inline properties.
|
|
105
|
-
* The value expression is evaluated once and omitted when undefined.
|
|
106
|
-
*/
|
|
107
58
|
const generateOptionalInlineProperty = (key, valueExpression) => {
|
|
108
|
-
|
|
59
|
+
return `...((value => value === undefined ? {} : { ${safeLiteralKey(key)}: value })(${valueExpression}))`;
|
|
109
60
|
};
|
|
110
|
-
/**
|
|
111
|
-
* Determines if a property schema should use ref imports for validation.
|
|
112
|
-
*/
|
|
113
61
|
const shouldUseRefImport = (propSchema, useRefImports) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return true;
|
|
62
|
+
if (!useRefImports || !hasRef(propSchema)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const ref = propSchema.$ref;
|
|
66
|
+
const isUri = ref.startsWith("http://") || ref.startsWith("https://");
|
|
67
|
+
if (isUri && (ref.includes("#/properties/") || ref.includes("#/definitions/"))) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
124
71
|
};
|
|
125
|
-
/**
|
|
126
|
-
* Determines if an array property should use ref imports for its items.
|
|
127
|
-
*/
|
|
128
72
|
const shouldUseArrayRefImport = (propSchema, useRefImports) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
73
|
+
if (!useRefImports) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (!isSchemaObject(propSchema)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (!("type" in propSchema) || propSchema.type !== "array") {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return hasItems(propSchema) && hasRef(propSchema.items);
|
|
139
83
|
};
|
|
140
|
-
/**
|
|
141
|
-
* Determines if a property with additionalProperties should use ref imports.
|
|
142
|
-
*/
|
|
143
84
|
const shouldUseRecordRefImport = (propSchema, useRefImports) => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
85
|
+
if (!useRefImports) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (!isSchemaObject(propSchema)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (!("type" in propSchema) || propSchema.type !== "object") {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (!("additionalProperties" in propSchema)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const additionalProps = propSchema.additionalProperties;
|
|
98
|
+
return isSchemaObject(additionalProps) && hasRef(additionalProps);
|
|
158
99
|
};
|
|
159
|
-
/**
|
|
160
|
-
* Generates the value expression for a property based on its schema and options.
|
|
161
|
-
*/
|
|
162
100
|
const generatePropertyValue = (key, propSchema, isRequired, useRefImports, suffix, caseInsensitive = false) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (!isSchemaObject(propSchema)) {
|
|
184
|
-
return isRequired ? 'undefined' : generateOptionalInlineProperty(key, 'undefined');
|
|
185
|
-
}
|
|
186
|
-
// Generate standard validation expression
|
|
187
|
-
const defaultValue = getDefaultValue(propSchema);
|
|
188
|
-
const valueExpression = generateValidationExpression(key, propSchema, defaultValue, isRequired, undefined, undefined, undefined, undefined, caseInsensitive);
|
|
189
|
-
return isRequired ? valueExpression : generateOptionalInlineProperty(key, valueExpression);
|
|
101
|
+
if (shouldUseRefImport(propSchema, useRefImports)) {
|
|
102
|
+
const ref = propSchema.$ref;
|
|
103
|
+
return isRequired ? generateRequiredRefCall(key, ref, suffix) : generateOptionalRefCall(key, ref, suffix);
|
|
104
|
+
}
|
|
105
|
+
if (shouldUseArrayRefImport(propSchema, useRefImports)) {
|
|
106
|
+
const items = propSchema.items;
|
|
107
|
+
const ref = items.$ref;
|
|
108
|
+
return isRequired ? generateRequiredArrayRefCall(key, ref, suffix) : generateOptionalArrayRefCall(key, ref, suffix);
|
|
109
|
+
}
|
|
110
|
+
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
111
|
+
const additionalProps = propSchema.additionalProperties;
|
|
112
|
+
const ref = additionalProps.$ref;
|
|
113
|
+
return isRequired ? generateRequiredRecordRefCall(key, ref, suffix) : generateOptionalRecordRefCall(key, ref, suffix);
|
|
114
|
+
}
|
|
115
|
+
if (!isSchemaObject(propSchema)) {
|
|
116
|
+
return isRequired ? "undefined" : generateOptionalInlineProperty(key, "undefined");
|
|
117
|
+
}
|
|
118
|
+
const defaultValue = getDefaultValue(propSchema);
|
|
119
|
+
const valueExpression = generateValidationExpression(key, propSchema, defaultValue, isRequired, void 0, void 0, void 0, void 0, caseInsensitive);
|
|
120
|
+
return isRequired ? valueExpression : generateOptionalInlineProperty(key, valueExpression);
|
|
190
121
|
};
|
|
191
|
-
/**
|
|
192
|
-
* Generates property entries for all properties in the schema.
|
|
193
|
-
*/
|
|
194
122
|
const generatePropertyEntries = (schema, useRefImports, suffix, caseInsensitive = false) => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return entries;
|
|
123
|
+
if (!hasProperties(schema)) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const entries = [];
|
|
127
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
128
|
+
const isRequired = isPropertyRequired(key, schema);
|
|
129
|
+
const value = generatePropertyValue(key, propSchema, isRequired, useRefImports, suffix, caseInsensitive);
|
|
130
|
+
const isOptional = value.startsWith("...");
|
|
131
|
+
entries.push({ key, value, isOptional });
|
|
132
|
+
}
|
|
133
|
+
return entries;
|
|
207
134
|
};
|
|
208
|
-
/**
|
|
209
|
-
* Generates a fallback value for a required property.
|
|
210
|
-
* This returns simple type-based defaults without pattern inference,
|
|
211
|
-
* matching TypeBox's coercion behavior.
|
|
212
|
-
*/
|
|
213
135
|
const generateFallbackValue = (_, propSchema, useRefImports, suffix) => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return '[]';
|
|
227
|
-
}
|
|
228
|
-
// Handle object properties with additionalProperties $ref
|
|
229
|
-
if (useRefImports &&
|
|
230
|
-
isSchemaObject(propSchema) &&
|
|
231
|
-
propSchema.type === 'object' &&
|
|
232
|
-
'additionalProperties' in propSchema) {
|
|
233
|
-
const additionalProps = propSchema.additionalProperties;
|
|
234
|
-
if (isSchemaObject(additionalProps) && hasRef(additionalProps)) {
|
|
235
|
-
return '{}';
|
|
236
|
-
}
|
|
136
|
+
if (useRefImports && hasRef(propSchema)) {
|
|
137
|
+
const ref = propSchema.$ref;
|
|
138
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
139
|
+
return `${parserName}(undefined)`;
|
|
140
|
+
}
|
|
141
|
+
if (useRefImports && isSchemaObject(propSchema) && propSchema.type === "array" && hasItems(propSchema) && hasRef(propSchema.items)) {
|
|
142
|
+
return "[]";
|
|
143
|
+
}
|
|
144
|
+
if (useRefImports && isSchemaObject(propSchema) && propSchema.type === "object" && "additionalProperties" in propSchema) {
|
|
145
|
+
const additionalProps = propSchema.additionalProperties;
|
|
146
|
+
if (isSchemaObject(additionalProps) && hasRef(additionalProps)) {
|
|
147
|
+
return "{}";
|
|
237
148
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// object is itself a valid instance — `getDefaultValue` handles them all.
|
|
241
|
-
return getDefaultValue(propSchema);
|
|
149
|
+
}
|
|
150
|
+
return getDefaultValue(propSchema);
|
|
242
151
|
};
|
|
243
|
-
/**
|
|
244
|
-
* Generates a fallback object with required properties filled with default values.
|
|
245
|
-
* This is used when input is not an object (undefined, null, etc.).
|
|
246
|
-
*/
|
|
247
152
|
const generateFallbackObject = (schema, useRefImports, typeName, suffix, subTypeNames) => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// Inline nested objects delegate to their sub-parser so the fallback
|
|
262
|
-
// carries proper deep defaults instead of an empty object literal.
|
|
263
|
-
const subName = subTypeNames?.get(key);
|
|
264
|
-
const fallbackValue = subName
|
|
265
|
-
? `${generateParserName(subName)}(undefined)`
|
|
266
|
-
: generateFallbackValue(key, propSchema, useRefImports, suffix);
|
|
267
|
-
if (fallbackValue === 'undefined') {
|
|
268
|
-
return `{} as ${typeName}`;
|
|
269
|
-
}
|
|
270
|
-
requiredProps.push(` ${safeLiteralKey(key)}: ${fallbackValue},`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (requiredProps.length === 0) {
|
|
153
|
+
if (!hasProperties(schema)) {
|
|
154
|
+
return `{} as ${typeName}`;
|
|
155
|
+
}
|
|
156
|
+
if (isSchemaObject(schema) && ("if" in schema || "then" in schema || "else" in schema)) {
|
|
157
|
+
return `{} as ${typeName}`;
|
|
158
|
+
}
|
|
159
|
+
const requiredProps = [];
|
|
160
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
161
|
+
const isRequired = isPropertyRequired(key, schema);
|
|
162
|
+
if (isRequired) {
|
|
163
|
+
const subName = subTypeNames?.get(key);
|
|
164
|
+
const fallbackValue = subName ? `${generateParserName(subName)}(undefined)` : generateFallbackValue(key, propSchema, useRefImports, suffix);
|
|
165
|
+
if (fallbackValue === "undefined") {
|
|
274
166
|
return `{} as ${typeName}`;
|
|
167
|
+
}
|
|
168
|
+
requiredProps.push(` ${safeLiteralKey(key)}: ${fallbackValue},`);
|
|
275
169
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
170
|
+
}
|
|
171
|
+
if (requiredProps.length === 0) {
|
|
172
|
+
return `{} as ${typeName}`;
|
|
173
|
+
}
|
|
174
|
+
let result = "{\n";
|
|
175
|
+
for (let i = 0; i < requiredProps.length; i++) {
|
|
176
|
+
result += requiredProps[i];
|
|
177
|
+
if (i < requiredProps.length - 1) {
|
|
178
|
+
result += "\n";
|
|
282
179
|
}
|
|
283
|
-
|
|
284
|
-
|
|
180
|
+
}
|
|
181
|
+
result += "\n }";
|
|
182
|
+
return result;
|
|
285
183
|
};
|
|
286
|
-
/**
|
|
287
|
-
* Generates a parser for non-object schemas (string, number, boolean, array, etc.)
|
|
288
|
-
* that validates the input matches the expected primitive type before casting.
|
|
289
|
-
*/
|
|
290
|
-
/**
|
|
291
|
-
* A default value literal (as source text) for `schema`, used as the fallback a
|
|
292
|
-
* top-level union coerces an unmatched value to. Prefers a `const`/`enum` member,
|
|
293
|
-
* then a per-type empty value.
|
|
294
|
-
*/
|
|
295
184
|
const scalarDefaultLiteral = (schema) => {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
185
|
+
if (!isSchemaObject(schema))
|
|
186
|
+
return "{}";
|
|
187
|
+
if (hasConst(schema))
|
|
188
|
+
return JSON.stringify(schema.const);
|
|
189
|
+
if (hasEnum(schema) && schema.enum.length > 0)
|
|
190
|
+
return JSON.stringify(schema.enum[0]);
|
|
191
|
+
if (hasType(schema)) {
|
|
192
|
+
switch (schema.type) {
|
|
193
|
+
case "string":
|
|
194
|
+
return '""';
|
|
195
|
+
case "number":
|
|
196
|
+
case "integer":
|
|
197
|
+
return "0";
|
|
198
|
+
case "boolean":
|
|
199
|
+
return "false";
|
|
200
|
+
case "null":
|
|
201
|
+
return "null";
|
|
202
|
+
case "array":
|
|
203
|
+
return "[]";
|
|
204
|
+
case "object":
|
|
205
|
+
return "{}";
|
|
318
206
|
}
|
|
319
|
-
|
|
207
|
+
}
|
|
208
|
+
return "{}";
|
|
320
209
|
};
|
|
321
|
-
/**
|
|
322
|
-
* Emits a dispatcher for a top-level `oneOf`/`anyOf` whose branches are `$ref`s,
|
|
323
|
-
* when the branches share a discriminator (a `const`/`enum` tag such as `kind`).
|
|
324
|
-
* Each resolved branch's discriminant selects its imported parser, so a recursive
|
|
325
|
-
* discriminated union (e.g. `Expr = Lit | BinOp`, where `BinOp` has `Expr`
|
|
326
|
-
* children) is actually validated and dispatched instead of blindly cast. Returns
|
|
327
|
-
* `null` when it can't be done (no root schema, no discriminator, a non-ref
|
|
328
|
-
* branch, or ref imports disabled), so the caller keeps its passthrough fallback.
|
|
329
|
-
*/
|
|
330
210
|
const generateRefUnionDispatch = (functionName, typeName, branches, strict, ctx) => {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const fallback = strict
|
|
366
|
-
? `(() => { throw new Error(${quoteJsString(`[${typeName}] value does not match any union branch`)}); })()`
|
|
367
|
-
: `${cases[0]?.parser}(input)`;
|
|
368
|
-
let expr = fallback;
|
|
369
|
-
for (let i = cases.length - 1; i >= 0; i--) {
|
|
370
|
-
const c = cases[i];
|
|
371
|
-
if (c)
|
|
372
|
-
expr = `_disc === ${JSON.stringify(c.value)} ? ${c.parser}(input) : ${expr}`;
|
|
373
|
-
}
|
|
374
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {\n const _disc = ${discExpr};\n return ${expr};\n};`;
|
|
211
|
+
const rootSchema = ctx.rootSchema;
|
|
212
|
+
if (!ctx.useRefImports || rootSchema === void 0)
|
|
213
|
+
return null;
|
|
214
|
+
if (branches.length === 0 || !branches.every((b) => isSchemaObject(b) && hasRef(b)))
|
|
215
|
+
return null;
|
|
216
|
+
const resolved = branches.map((b) => {
|
|
217
|
+
const ref = b.$ref;
|
|
218
|
+
return { parser: generateParserName(refToName(ref, ctx.suffix)), schema: resolveRef(ref, rootSchema) };
|
|
219
|
+
});
|
|
220
|
+
if (resolved.some((r) => !isSchemaObject(r.schema)))
|
|
221
|
+
return null;
|
|
222
|
+
const discriminator = findDiscriminator(resolved.map((r) => r.schema));
|
|
223
|
+
if (discriminator === null)
|
|
224
|
+
return null;
|
|
225
|
+
const cases = [];
|
|
226
|
+
for (const r of resolved) {
|
|
227
|
+
const value = getDiscriminatorValue(r.schema, discriminator);
|
|
228
|
+
if (value === null)
|
|
229
|
+
return null;
|
|
230
|
+
cases.push({ value, parser: r.parser });
|
|
231
|
+
}
|
|
232
|
+
const access = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(discriminator) ? `.${discriminator}` : `[${JSON.stringify(discriminator)}]`;
|
|
233
|
+
const discExpr = `input == null ? undefined : (input as Record<string, unknown>)${access}`;
|
|
234
|
+
const fallback = strict ? `(() => { throw new Error(${quoteJsString(`[${typeName}] value does not match any union branch`)}); })()` : `${cases[0]?.parser}(input)`;
|
|
235
|
+
let expr = fallback;
|
|
236
|
+
for (let i = cases.length - 1; i >= 0; i--) {
|
|
237
|
+
const c = cases[i];
|
|
238
|
+
if (c)
|
|
239
|
+
expr = `_disc === ${JSON.stringify(c.value)} ? ${c.parser}(input) : ${expr}`;
|
|
240
|
+
}
|
|
241
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
242
|
+
const _disc = ${discExpr};
|
|
243
|
+
return ${expr};
|
|
244
|
+
};`;
|
|
375
245
|
};
|
|
376
246
|
const generateNonObjectParser = (typeName, schema, strict, unionCtx) => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (branches.length > 0 && branches.every((b) => isSchemaObject(b) && hasRef(b))) {
|
|
385
|
-
const dispatch = generateRefUnionDispatch(functionName, typeName, branches, strict, unionCtx);
|
|
386
|
-
if (dispatch !== null)
|
|
387
|
-
return dispatch;
|
|
388
|
-
}
|
|
247
|
+
const functionName = generateParserName(typeName);
|
|
248
|
+
if (unionCtx && isSchemaObject(schema) && (hasOneOf(schema) || hasAnyOf(schema))) {
|
|
249
|
+
const branches = hasOneOf(schema) ? schema.oneOf : hasAnyOf(schema) ? schema.anyOf : [];
|
|
250
|
+
if (branches.length > 0 && branches.every((b) => isSchemaObject(b) && hasRef(b))) {
|
|
251
|
+
const dispatch = generateRefUnionDispatch(functionName, typeName, branches, strict, unionCtx);
|
|
252
|
+
if (dispatch !== null)
|
|
253
|
+
return dispatch;
|
|
389
254
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const refName = refToName(schema.$ref, unionCtx.suffix);
|
|
396
|
-
if (refName !== typeName) {
|
|
397
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => ${generateParserName(refName)}(input) as ${typeName};`;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
// Strict union enforcement for inline (or mixed) branches: membership is the
|
|
401
|
-
// disjunction of per-branch checks, and a non-member throws. Only emitted
|
|
402
|
-
// when every branch check is false-sound (see canEnforceUnion) — otherwise a
|
|
403
|
-
// conservative stub validator somewhere in the graph could reject valid
|
|
404
|
-
// input — and never when stripping unknown keys (see UnionParserContext).
|
|
405
|
-
if (strict && unionCtx && isSchemaObject(schema)) {
|
|
406
|
-
const branches = getUnionBranches(schema);
|
|
407
|
-
if (branches && !unionCtx.stripUnknown && canEnforceUnion(branches, unionCtx.rootSchema)) {
|
|
408
|
-
const check = generateUnionCheck('input', branches, unionCtx.useRefImports, unionCtx.suffix);
|
|
409
|
-
if (check !== null) {
|
|
410
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {\n if (!(${check})) throw new Error(${quoteJsString(`[${typeName}] value does not match any union branch`)});\n return input as ${typeName};\n};`;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
// `const`/`enum` define the value space directly (the generated type is a
|
|
415
|
-
// literal / literal-union), so a non-member must coerce to a valid member —
|
|
416
|
-
// otherwise the returned value would not be of the declared type. This runs
|
|
417
|
-
// before the `type` switch so e.g. `{ type: 'string', enum: [...] }` is covered,
|
|
418
|
-
// and before the no-`type` bail so a bare `const`/`enum` schema is covered too.
|
|
419
|
-
if (!strict && isSchemaObject(schema)) {
|
|
420
|
-
if (hasConst(schema)) {
|
|
421
|
-
const literal = JSON.stringify(schema.const);
|
|
422
|
-
// The const is the only valid value, so the parser always yields it. For a
|
|
423
|
-
// primitive we can keep the caller's value when it already equals the const
|
|
424
|
-
// (`===` is a correct comparison); for an object/array, `===` would be a
|
|
425
|
-
// (always-false) reference comparison, so just return the const literal.
|
|
426
|
-
const isPrimitive = schema.const === null || typeof schema.const !== 'object';
|
|
427
|
-
return isPrimitive
|
|
428
|
-
? `export const ${functionName} = (input: unknown): ${typeName} => input === ${literal} ? input as ${typeName} : ${literal} as ${typeName};`
|
|
429
|
-
: `export const ${functionName} = (input: unknown): ${typeName} => ${literal} as ${typeName};`;
|
|
430
|
-
}
|
|
431
|
-
if (hasEnum(schema) && schema.enum.length > 0) {
|
|
432
|
-
const values = JSON.stringify(schema.enum);
|
|
433
|
-
const fallback = JSON.stringify(schema.enum[0]);
|
|
434
|
-
// Case-insensitive normalization sits on the non-member branch only, so an
|
|
435
|
-
// exact member still returns via the `includes` fast path untouched.
|
|
436
|
-
const ci = unionCtx?.caseInsensitive ? generateEnumCaseInsensitiveCoercion('input', schema.enum, fallback) : null;
|
|
437
|
-
const coerced = ci ? `(${ci})` : fallback;
|
|
438
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => ${values}.includes(input as never) ? input as ${typeName} : ${coerced} as ${typeName};`;
|
|
439
|
-
}
|
|
440
|
-
// A top-level union must validate membership: an unmatched value is not of
|
|
441
|
-
// the declared union type, so coerce it to a member-shaped default. Reuse the
|
|
442
|
-
// same union validation the property path uses.
|
|
443
|
-
if (hasOneOf(schema) || hasAnyOf(schema)) {
|
|
444
|
-
const branches = hasOneOf(schema) ? schema.oneOf : hasAnyOf(schema) ? schema.anyOf : [];
|
|
445
|
-
const hasRefBranch = branches.some((b) => isSchemaObject(b) && hasRef(b));
|
|
446
|
-
if (branches.length > 0 && !hasRefBranch) {
|
|
447
|
-
const fallback = scalarDefaultLiteral(branches[0]);
|
|
448
|
-
const expr = generateValidationExpression('', schema, fallback, true, undefined, undefined, 'input', true);
|
|
449
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => (${expr}) as ${typeName};`;
|
|
450
|
-
}
|
|
451
|
-
// Ref-branch union: a *discriminated* one was already dispatched above (both
|
|
452
|
-
// modes). Anything reaching here is non-discriminated, which can't be validated
|
|
453
|
-
// inline without risking dropping valid input — fall back to a passthrough cast.
|
|
454
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
// Strict counterpart of the block above for a *type-less* `const`/`enum` root
|
|
458
|
-
// (`{ enum: [...] }` / `{ const: ... }` with no `type`): the no-`type` bail
|
|
459
|
-
// below would otherwise return a bare cast, so strict mode has to assert
|
|
460
|
-
// membership here. A typed `const`/`enum` (`{ type: 'string', enum: [...] }`)
|
|
461
|
-
// keeps flowing to the strict scalar path, which asserts it via
|
|
462
|
-
// generateScalarStrictAssertion.
|
|
463
|
-
if (strict && isSchemaObject(schema) && !hasType(schema) && (hasConst(schema) || hasEnum(schema))) {
|
|
464
|
-
const assertion = generateScalarStrictAssertion(schema, typeName, unionCtx?.rootSchema);
|
|
465
|
-
if (assertion !== null) {
|
|
466
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {\n${assertion}\n return input as ${typeName};\n};`;
|
|
467
|
-
}
|
|
255
|
+
}
|
|
256
|
+
if (unionCtx?.useRefImports && isSchemaObject(schema) && hasRef(schema) && !hasProperties(schema)) {
|
|
257
|
+
const refName = refToName(schema.$ref, unionCtx.suffix);
|
|
258
|
+
if (refName !== typeName) {
|
|
259
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => ${generateParserName(refName)}(input) as ${typeName};`;
|
|
468
260
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const items = schema.items;
|
|
481
|
-
const notArrayThrow = `if (!Array.isArray(input)) throw new Error(\`[${typeName}] expected array, got \${input === null ? "null" : typeof input}\`);`;
|
|
482
|
-
// This delegating path bypasses generateScalarStrictAssertion, so enforce the
|
|
483
|
-
// array-level `contains` constraint here too — otherwise a root array of
|
|
484
|
-
// objects/$refs would silently ignore it in strict mode.
|
|
485
|
-
const strictArrayChecks = strict ? generateContainsCheck('input', schema, `[${typeName}]`).join('\n') : '';
|
|
486
|
-
// validateArray identity-returns the input array when every element parses
|
|
487
|
-
// to itself; this parser is EXPORTED, and exported parsers never alias the
|
|
488
|
-
// value the caller passed in (matching the scalar root-array path's
|
|
489
|
-
// `[...input]` copy), so materialize a copy exactly when that happens —
|
|
490
|
-
// element references are still shared, like every other fast path.
|
|
491
|
-
const delegatedBody = (itemParserName) => ` const _parsed = validateArray(input, ${itemParserName});\n return (_parsed === input ? [..._parsed] : _parsed) as ${typeName};`;
|
|
492
|
-
const delegated = (itemParserName) => strict
|
|
493
|
-
? `export const ${functionName} = (input: unknown): ${typeName} => {\n ${notArrayThrow}${strictArrayChecks ? `\n${strictArrayChecks}` : ''}\n${delegatedBody(itemParserName)}\n};`
|
|
494
|
-
: `export const ${functionName} = (input: unknown): ${typeName} => {\n${delegatedBody(itemParserName)}\n};`;
|
|
495
|
-
if (unionCtx?.useRefImports && isSchemaObject(items) && hasRef(items)) {
|
|
496
|
-
return delegated(generateParserName(refToName(items.$ref, unionCtx.suffix)));
|
|
497
|
-
}
|
|
498
|
-
if (isInlineObjectProperty(items)) {
|
|
499
|
-
const reserved = unionCtx?.reservedNames ?? NO_RESERVED_NAMES;
|
|
500
|
-
// Dedup against imported identifiers: a root `List` whose items carry a
|
|
501
|
-
// $ref to `#/$defs/listItem` imports parseListItem — a bare `ListItem`
|
|
502
|
-
// here would shadow it (TS2440) and self-recurse instead of delegating.
|
|
503
|
-
let itemName = `${typeName}_Item`;
|
|
504
|
-
while (reserved.has(itemName))
|
|
505
|
-
itemName = `${itemName}_`;
|
|
506
|
-
const useRefImports = unionCtx?.useRefImports ?? false;
|
|
507
|
-
const suffix = unionCtx?.suffix ?? '';
|
|
508
|
-
const stripUnknown = unionCtx?.stripUnknown ?? false;
|
|
509
|
-
const preamble = [
|
|
510
|
-
`type ${itemName} = ${typeName}[number];`,
|
|
511
|
-
generateShapeValidator(items, itemName, useRefImports, suffix, false, stripUnknown, reserved),
|
|
512
|
-
generateObjectParser(items, itemName, useRefImports, suffix, unionCtx?.logWarnings ?? false, strict, false, stripUnknown, unionCtx?.rootSchema, reserved, unionCtx?.caseInsensitive ?? false),
|
|
513
|
-
].join('\n\n');
|
|
514
|
-
return `${preamble}\n\n${delegated(generateParserName(itemName))}`;
|
|
515
|
-
}
|
|
261
|
+
}
|
|
262
|
+
if (strict && unionCtx && isSchemaObject(schema)) {
|
|
263
|
+
const branches = getUnionBranches(schema);
|
|
264
|
+
if (branches && !unionCtx.stripUnknown && canEnforceUnion(branches, unionCtx.rootSchema)) {
|
|
265
|
+
const check = generateUnionCheck("input", branches, unionCtx.useRefImports, unionCtx.suffix);
|
|
266
|
+
if (check !== null) {
|
|
267
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
268
|
+
if (!(${check})) throw new Error(${quoteJsString(`[${typeName}] value does not match any union branch`)});
|
|
269
|
+
return input as ${typeName};
|
|
270
|
+
};`;
|
|
271
|
+
}
|
|
516
272
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
273
|
+
}
|
|
274
|
+
if (!strict && isSchemaObject(schema)) {
|
|
275
|
+
if (hasConst(schema)) {
|
|
276
|
+
const literal = JSON.stringify(schema.const);
|
|
277
|
+
const isPrimitive = schema.const === null || typeof schema.const !== "object";
|
|
278
|
+
return isPrimitive ? `export const ${functionName} = (input: unknown): ${typeName} => input === ${literal} ? input as ${typeName} : ${literal} as ${typeName};` : `export const ${functionName} = (input: unknown): ${typeName} => ${literal} as ${typeName};`;
|
|
279
|
+
}
|
|
280
|
+
if (hasEnum(schema) && schema.enum.length > 0) {
|
|
281
|
+
const values = JSON.stringify(schema.enum);
|
|
282
|
+
const fallback = JSON.stringify(schema.enum[0]);
|
|
283
|
+
const ci = unionCtx?.caseInsensitive ? generateEnumCaseInsensitiveCoercion("input", schema.enum, fallback) : null;
|
|
284
|
+
const coerced = ci ? `(${ci})` : fallback;
|
|
285
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => ${values}.includes(input as never) ? input as ${typeName} : ${coerced} as ${typeName};`;
|
|
286
|
+
}
|
|
287
|
+
if (hasOneOf(schema) || hasAnyOf(schema)) {
|
|
288
|
+
const branches = hasOneOf(schema) ? schema.oneOf : hasAnyOf(schema) ? schema.anyOf : [];
|
|
289
|
+
const hasRefBranch = branches.some((b) => isSchemaObject(b) && hasRef(b));
|
|
290
|
+
if (branches.length > 0 && !hasRefBranch) {
|
|
291
|
+
const fallback = scalarDefaultLiteral(branches[0]);
|
|
292
|
+
const expr = generateValidationExpression("", schema, fallback, true, void 0, void 0, "input", true);
|
|
293
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => (${expr}) as ${typeName};`;
|
|
294
|
+
}
|
|
295
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
520
296
|
}
|
|
521
|
-
|
|
522
|
-
|
|
297
|
+
}
|
|
298
|
+
if (strict && isSchemaObject(schema) && !hasType(schema) && (hasConst(schema) || hasEnum(schema))) {
|
|
299
|
+
const assertion = generateScalarStrictAssertion(schema, typeName, unionCtx?.rootSchema);
|
|
300
|
+
if (assertion !== null) {
|
|
301
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
302
|
+
${assertion}
|
|
303
|
+
return input as ${typeName};
|
|
304
|
+
};`;
|
|
523
305
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
306
|
+
}
|
|
307
|
+
if (isSchemaObject(schema) && hasType(schema) && schema.type === "array" && hasItems(schema) && !Array.isArray(schema.items)) {
|
|
308
|
+
const items = schema.items;
|
|
309
|
+
const notArrayThrow = `if (!Array.isArray(input)) throw new Error(\`[${typeName}] expected array, got \${input === null ? "null" : typeof input}\`);`;
|
|
310
|
+
const strictArrayChecks = strict ? generateContainsCheck("input", schema, `[${typeName}]`).join("\n") : "";
|
|
311
|
+
const delegatedBody = (itemParserName) => ` const _parsed = validateArray(input, ${itemParserName});
|
|
312
|
+
return (_parsed === input ? [..._parsed] : _parsed) as ${typeName};`;
|
|
313
|
+
const delegated = (itemParserName) => strict ? `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
314
|
+
${notArrayThrow}${strictArrayChecks ? `
|
|
315
|
+
${strictArrayChecks}` : ""}
|
|
316
|
+
${delegatedBody(itemParserName)}
|
|
317
|
+
};` : `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
318
|
+
${delegatedBody(itemParserName)}
|
|
319
|
+
};`;
|
|
320
|
+
if (unionCtx?.useRefImports && isSchemaObject(items) && hasRef(items)) {
|
|
321
|
+
return delegated(generateParserName(refToName(items.$ref, unionCtx.suffix)));
|
|
322
|
+
}
|
|
323
|
+
if (isInlineObjectProperty(items)) {
|
|
324
|
+
const reserved = unionCtx?.reservedNames ?? NO_RESERVED_NAMES;
|
|
325
|
+
let itemName = `${typeName}_Item`;
|
|
326
|
+
while (reserved.has(itemName))
|
|
327
|
+
itemName = `${itemName}_`;
|
|
328
|
+
const useRefImports = unionCtx?.useRefImports ?? false;
|
|
329
|
+
const suffix = unionCtx?.suffix ?? "";
|
|
330
|
+
const stripUnknown = unionCtx?.stripUnknown ?? false;
|
|
331
|
+
const itemShapeValidator = generateShapeValidator(items, itemName, useRefImports, suffix, false, stripUnknown, reserved);
|
|
332
|
+
const preamble = [
|
|
333
|
+
`type ${itemName} = ${typeName}[number];`,
|
|
334
|
+
itemShapeValidator,
|
|
335
|
+
generateObjectParser(items, itemName, useRefImports, suffix, unionCtx?.logWarnings ?? false, strict, false, stripUnknown, unionCtx?.rootSchema, reserved, unionCtx?.caseInsensitive ?? false, itemShapeValidator)
|
|
336
|
+
].join("\n\n");
|
|
337
|
+
return `${preamble}
|
|
338
|
+
|
|
339
|
+
${delegated(generateParserName(itemName))}`;
|
|
531
340
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
// Tuple `prefixItems`: coerce each declared position and, under items:false,
|
|
544
|
-
// drop elements past the tuple length. A non-array coerces to an empty array.
|
|
545
|
-
const prefixItems = getPrefixItems(schema);
|
|
546
|
-
if (prefixItems) {
|
|
547
|
-
const mapped = generatePrefixItemsMap('input', prefixItems, prefixItemsCapsLength(schema), unionCtx?.rootSchema, undefined, unionCtx?.caseInsensitive);
|
|
548
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => Array.isArray(input) ? ${mapped} as ${typeName} : [] as ${typeName};`;
|
|
549
|
-
}
|
|
550
|
-
// Coerce each element when the item schema is a single scalar type or an enum.
|
|
551
|
-
if (hasItems(schema) && !Array.isArray(schema.items) && isCoercibleItemSchema(schema.items)) {
|
|
552
|
-
const item = schema.items;
|
|
553
|
-
const itemExpr = generateValidationExpression('', item, getDefaultValue(item), true, undefined, undefined, '_it', true, unionCtx?.caseInsensitive);
|
|
554
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => Array.isArray(input) ? (input as unknown[]).map((_it) => ${itemExpr}) as ${typeName} : [] as ${typeName};`;
|
|
555
|
-
}
|
|
556
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => Array.isArray(input) ? [...input] as ${typeName} : [] as ${typeName};`;
|
|
557
|
-
}
|
|
558
|
-
default:
|
|
559
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
341
|
+
}
|
|
342
|
+
if (!isSchemaObject(schema) || !hasType(schema)) {
|
|
343
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
344
|
+
}
|
|
345
|
+
if (!strict && schema.type === "null") {
|
|
346
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => null as ${typeName};`;
|
|
347
|
+
}
|
|
348
|
+
if (strict) {
|
|
349
|
+
const assertion = generateScalarStrictAssertion(schema, typeName, unionCtx?.rootSchema);
|
|
350
|
+
if (assertion === null) {
|
|
351
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
560
352
|
}
|
|
353
|
+
const returnExpr = schema.type === "array" ? `[...(input as readonly unknown[])] as ${typeName}` : `input as ${typeName}`;
|
|
354
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
355
|
+
${assertion}
|
|
356
|
+
return ${returnExpr};
|
|
357
|
+
};`;
|
|
358
|
+
}
|
|
359
|
+
switch (schema.type) {
|
|
360
|
+
case "string":
|
|
361
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => typeof input === "string" ? input as ${typeName} : "" as ${typeName};`;
|
|
362
|
+
case "number":
|
|
363
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => typeof input === "number" ? input as ${typeName} : 0 as ${typeName};`;
|
|
364
|
+
case "integer":
|
|
365
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => typeof input === "number" && Number.isInteger(input) ? input as ${typeName} : 0 as ${typeName};`;
|
|
366
|
+
case "boolean":
|
|
367
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => typeof input === "boolean" ? input as ${typeName} : false as ${typeName};`;
|
|
368
|
+
case "array": {
|
|
369
|
+
const prefixItems = getPrefixItems(schema);
|
|
370
|
+
if (prefixItems) {
|
|
371
|
+
const mapped = generatePrefixItemsMap("input", prefixItems, prefixItemsCapsLength(schema), unionCtx?.rootSchema, void 0, unionCtx?.caseInsensitive);
|
|
372
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => Array.isArray(input) ? ${mapped} as ${typeName} : [] as ${typeName};`;
|
|
373
|
+
}
|
|
374
|
+
if (hasItems(schema) && !Array.isArray(schema.items) && isCoercibleItemSchema(schema.items)) {
|
|
375
|
+
const item = schema.items;
|
|
376
|
+
const itemExpr = generateValidationExpression("", item, getDefaultValue(item), true, void 0, void 0, "_it", true, unionCtx?.caseInsensitive);
|
|
377
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => Array.isArray(input) ? (input as unknown[]).map((_it) => ${itemExpr}) as ${typeName} : [] as ${typeName};`;
|
|
378
|
+
}
|
|
379
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => Array.isArray(input) ? [...input] as ${typeName} : [] as ${typeName};`;
|
|
380
|
+
}
|
|
381
|
+
default:
|
|
382
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
383
|
+
}
|
|
561
384
|
};
|
|
562
|
-
/**
|
|
563
|
-
* Object-level keyword checks (`propertyNames` / `dependentSchemas` /
|
|
564
|
-
* `dependentRequired`) rendered as a newline-prefixed prelude for the
|
|
565
|
-
* specialized strict object parsers (empty-object, pattern-properties,
|
|
566
|
-
* additionalProperties, combined) that build their result outside
|
|
567
|
-
* generateObjectStrictAssertion. Each of those emits its own
|
|
568
|
-
* `if (!isObject(input)) throw` first, so the checks run against a proven object
|
|
569
|
-
* (`guard = false`). Returns `''` when the schema carries none of the keywords,
|
|
570
|
-
* so existing output for every other schema is byte-for-byte unchanged.
|
|
571
|
-
*/
|
|
572
385
|
const strictObjectKeywordPrelude = (schema, typeName) => {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
386
|
+
if (!isSchemaObject(schema))
|
|
387
|
+
return "";
|
|
388
|
+
const lines = generateObjectKeywordChecks("input", schema, `[${typeName}]`, false);
|
|
389
|
+
return lines.length > 0 ? `
|
|
390
|
+
${lines.join("\n")}` : "";
|
|
577
391
|
};
|
|
578
|
-
/**
|
|
579
|
-
* Generates a parser for empty object schemas or schemas with only additionalProperties.
|
|
580
|
-
* Validates the input is an object before casting, falling back to an empty object.
|
|
581
|
-
* Returns a shallow copy to avoid mutating the original input. `schema`, when
|
|
582
|
-
* given, supplies any object-level keyword checks the strict parser must enforce.
|
|
583
|
-
*/
|
|
584
392
|
const generateEmptyObjectParser = (typeName, strict, schema) => {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
393
|
+
const functionName = generateParserName(typeName);
|
|
394
|
+
if (strict) {
|
|
395
|
+
const prelude = schema !== void 0 ? strictObjectKeywordPrelude(schema, typeName) : "";
|
|
396
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
397
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${prelude}
|
|
398
|
+
return { ...input } as ${typeName};
|
|
399
|
+
};`;
|
|
400
|
+
}
|
|
401
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? { ...input } as ${typeName} : {} as ${typeName};`;
|
|
591
402
|
};
|
|
592
|
-
/**
|
|
593
|
-
* Converts a property key to a safe local variable name for caching.
|
|
594
|
-
* Replaces non-identifier characters with underscores.
|
|
595
|
-
*/
|
|
596
403
|
const toVarName = (key) => {
|
|
597
|
-
|
|
598
|
-
|
|
404
|
+
const safe = key.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
405
|
+
return `_${safe}`;
|
|
599
406
|
};
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
.split('_')
|
|
604
|
-
.filter((part) => part.length > 0)
|
|
605
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
606
|
-
.join('');
|
|
607
|
-
/** Shared empty reserved-name set for callers with no import context. */
|
|
608
|
-
const NO_RESERVED_NAMES = new Set();
|
|
609
|
-
/**
|
|
610
|
-
* Shared empty result for the common no-inline-sub-types case, so the per-node
|
|
611
|
-
* calls (parser and validator, for every object node, on every generation)
|
|
612
|
-
* allocate nothing. Callers only read from the maps.
|
|
613
|
-
*/
|
|
614
|
-
const EMPTY_SUB_TYPES = { objects: new Map(), arrayItems: new Map() };
|
|
407
|
+
const pascalCaseKey = (key) => key.replace(/[^a-zA-Z0-9_$]/g, "_").split("_").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
408
|
+
const NO_RESERVED_NAMES = /* @__PURE__ */ new Set();
|
|
409
|
+
const EMPTY_SUB_TYPES = { objects: /* @__PURE__ */ new Map(), arrayItems: /* @__PURE__ */ new Map() };
|
|
615
410
|
const collectInlineSubTypes = (schema, typeName, reservedNames = NO_RESERVED_NAMES) => {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
let
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
objects ??= new Map();
|
|
639
|
-
objects.set(key, claim(`${typeName}_${pascalCaseKey(key) || 'Value'}`));
|
|
640
|
-
}
|
|
641
|
-
else if (isInlineObjectArrayProperty(propSchema)) {
|
|
642
|
-
arrayItems ??= new Map();
|
|
643
|
-
arrayItems.set(key, claim(`${typeName}_${pascalCaseKey(key) || 'Value'}Item`));
|
|
644
|
-
}
|
|
411
|
+
if (!hasProperties(schema))
|
|
412
|
+
return EMPTY_SUB_TYPES;
|
|
413
|
+
const props = schema.properties;
|
|
414
|
+
let objects = null;
|
|
415
|
+
let arrayItems = null;
|
|
416
|
+
let used = null;
|
|
417
|
+
const claim = (base) => {
|
|
418
|
+
used ??= /* @__PURE__ */ new Set();
|
|
419
|
+
let subName = base;
|
|
420
|
+
while (used.has(subName) || reservedNames.has(subName))
|
|
421
|
+
subName = `${subName}_`;
|
|
422
|
+
used.add(subName);
|
|
423
|
+
return subName;
|
|
424
|
+
};
|
|
425
|
+
for (const key in props) {
|
|
426
|
+
const propSchema = props[key];
|
|
427
|
+
if (isInlineObjectProperty(propSchema)) {
|
|
428
|
+
objects ??= /* @__PURE__ */ new Map();
|
|
429
|
+
objects.set(key, claim(`${typeName}_${pascalCaseKey(key) || "Value"}`));
|
|
430
|
+
} else if (isInlineObjectArrayProperty(propSchema)) {
|
|
431
|
+
arrayItems ??= /* @__PURE__ */ new Map();
|
|
432
|
+
arrayItems.set(key, claim(`${typeName}_${pascalCaseKey(key) || "Value"}Item`));
|
|
645
433
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return
|
|
434
|
+
}
|
|
435
|
+
if (objects === null && arrayItems === null)
|
|
436
|
+
return EMPTY_SUB_TYPES;
|
|
437
|
+
return { objects: objects ?? EMPTY_SUB_TYPES.objects, arrayItems: arrayItems ?? EMPTY_SUB_TYPES.arrayItems };
|
|
649
438
|
};
|
|
650
|
-
/**
|
|
651
|
-
* True when the parser must reject (strict mode) or strip (coerce mode) every
|
|
652
|
-
* undeclared key: the schema sets `additionalProperties: false` and nothing
|
|
653
|
-
* else (key patterns, composition) can legitimately introduce extra keys.
|
|
654
|
-
*/
|
|
655
439
|
const hasStrictKeys = (schema) => {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
440
|
+
if (!isSchemaObject(schema) || !hasProperties(schema))
|
|
441
|
+
return false;
|
|
442
|
+
if (!hasAdditionalProperties(schema) || schema.additionalProperties !== false)
|
|
443
|
+
return false;
|
|
444
|
+
if ("patternProperties" in schema)
|
|
445
|
+
return false;
|
|
446
|
+
if (hasAllOf(schema) || hasOneOf(schema) || hasAnyOf(schema))
|
|
447
|
+
return false;
|
|
448
|
+
return true;
|
|
665
449
|
};
|
|
666
|
-
/**
|
|
667
|
-
* When every declared property is required (and every required key is
|
|
668
|
-
* declared, with a real schema), the fast-path known-keys test can be an
|
|
669
|
-
* own-key *count* comparison instead of a per-key `for..in` walk: the typed
|
|
670
|
-
* property checks already prove all N declared keys are present, so
|
|
671
|
-
* `Object.keys(input).length === N` proves there are no undeclared extras —
|
|
672
|
-
* measurably cheaper on the hot path. Returns the declared-key count, or null
|
|
673
|
-
* when the cheaper form would be unsound (an optional or non-schema property
|
|
674
|
-
* breaks the presence proof).
|
|
675
|
-
*/
|
|
676
450
|
const exactKeyCountOf = (schema) => {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
// literal emits no check at all.
|
|
693
|
-
const isRequired = requiredLookup !== null ? requiredLookup.has(key) : required.includes(key);
|
|
694
|
-
if (!isRequired || !isSchemaObject(props[key]))
|
|
695
|
-
return null;
|
|
696
|
-
}
|
|
697
|
-
// Every declared key is required (above) and the counts match, so the
|
|
698
|
-
// required list is exactly the declared keys (a duplicated required entry
|
|
699
|
-
// would leave some declared key uncovered and fail the loop).
|
|
700
|
-
if (declaredCount === 0 || required.length !== declaredCount)
|
|
701
|
-
return null;
|
|
702
|
-
return declaredCount;
|
|
451
|
+
if (!isSchemaObject(schema) || !hasProperties(schema) || !hasRequired(schema))
|
|
452
|
+
return null;
|
|
453
|
+
const props = schema.properties;
|
|
454
|
+
const required = schema.required;
|
|
455
|
+
const requiredLookup = required.length > 16 ? new Set(required) : null;
|
|
456
|
+
let declaredCount = 0;
|
|
457
|
+
for (const key in props) {
|
|
458
|
+
declaredCount++;
|
|
459
|
+
const isRequired = requiredLookup !== null ? requiredLookup.has(key) : required.includes(key);
|
|
460
|
+
if (!isRequired || !isSchemaObject(props[key]))
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
if (declaredCount === 0 || required.length !== declaredCount)
|
|
464
|
+
return null;
|
|
465
|
+
return declaredCount;
|
|
703
466
|
};
|
|
704
|
-
/**
|
|
705
|
-
* Determines if a property needs a local variable or can be inlined.
|
|
706
|
-
* Schema-object properties are always cached: the slow-path ternary chain
|
|
707
|
-
* reads each value 3-4x (typeof check, valid branch, undefined check, coerce),
|
|
708
|
-
* so a single hoisted load is strictly cheaper than the inlined optional chain.
|
|
709
|
-
*/
|
|
710
467
|
const shouldCacheVariable = (propSchema, _canFastPath, _useRefImports) => {
|
|
711
|
-
|
|
712
|
-
// `undefined` with no value access, so caching would be wasted.
|
|
713
|
-
return isSchemaObject(propSchema);
|
|
468
|
+
return isSchemaObject(propSchema);
|
|
714
469
|
};
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, exported = true, stripUnknown = false, rootSchema, reservedNames = NO_RESERVED_NAMES, caseInsensitive = false) => {
|
|
725
|
-
const functionName = generateParserName(typeName);
|
|
726
|
-
const exportPrefix = exported ? 'export ' : '';
|
|
727
|
-
if (!hasProperties(schema)) {
|
|
728
|
-
if (strict) {
|
|
729
|
-
return `${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {\n if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${strictObjectKeywordPrelude(schema, typeName)}\n return input as ${typeName};\n};`;
|
|
730
|
-
}
|
|
731
|
-
return `${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? input as ${typeName} : {} as ${typeName};`;
|
|
732
|
-
}
|
|
733
|
-
const schemaProps = schema.properties;
|
|
734
|
-
// Keys once, values via lookup — Object.entries allocates a tuple per
|
|
735
|
-
// property and this runs for every object node on every generation.
|
|
736
|
-
const propertyKeys = Object.keys(schemaProps);
|
|
737
|
-
// Inline nested object properties get a private sub-parser (plus shape
|
|
738
|
-
// predicate and type alias) in the same file, so their fields are parsed for
|
|
739
|
-
// real instead of only passing an isObject check. The sub-parser recurses
|
|
740
|
-
// through this same generator, so nesting works to any depth. Array
|
|
741
|
-
// properties with inline object items get the same treatment for their
|
|
742
|
-
// element type, so nested enums and $refs *inside array items* are validated
|
|
743
|
-
// too (each element runs through the item sub-parser via validateArray).
|
|
744
|
-
const { objects: subTypeNames, arrayItems: subItemNames } = collectInlineSubTypes(schema, typeName, reservedNames);
|
|
745
|
-
const preamble = [];
|
|
746
|
-
for (const [key, subName] of subTypeNames) {
|
|
747
|
-
const propSchema = schemaProps[key];
|
|
748
|
-
preamble.push(`type ${subName} = ${typeName}[${JSON.stringify(key)}];`);
|
|
749
|
-
preamble.push(generateShapeValidator(propSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames));
|
|
750
|
-
preamble.push(generateObjectParser(propSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive));
|
|
751
|
-
}
|
|
752
|
-
for (const [key, subName] of subItemNames) {
|
|
753
|
-
const propSchema = schemaProps[key];
|
|
754
|
-
const itemSchema = propSchema.items;
|
|
755
|
-
// NonNullable strips the `| undefined` an optional array property carries.
|
|
756
|
-
preamble.push(`type ${subName} = NonNullable<${typeName}[${JSON.stringify(key)}]>[number];`);
|
|
757
|
-
preamble.push(generateShapeValidator(itemSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames));
|
|
758
|
-
// Hand-rolled loop instead of Array.prototype.every on the guard path — the
|
|
759
|
-
// callback protocol costs a few percent on element-heavy hot paths.
|
|
760
|
-
preamble.push(`const _every${subName} = (arr: readonly unknown[]): boolean => {\n for (let i = 0; i < arr.length; i++) if (!${shapeValidatorName(subName)}(arr[i])) return false;\n return true;\n};`);
|
|
761
|
-
preamble.push(generateObjectParser(itemSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive));
|
|
470
|
+
const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, exported = true, stripUnknown = false, rootSchema, reservedNames = NO_RESERVED_NAMES, caseInsensitive = false, shapeValidatorSource) => {
|
|
471
|
+
const functionName = generateParserName(typeName);
|
|
472
|
+
const exportPrefix = exported ? "export " : "";
|
|
473
|
+
if (!hasProperties(schema)) {
|
|
474
|
+
if (strict) {
|
|
475
|
+
return `${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {
|
|
476
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${strictObjectKeywordPrelude(schema, typeName)}
|
|
477
|
+
return input as ${typeName};
|
|
478
|
+
};`;
|
|
762
479
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
480
|
+
return `${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? input as ${typeName} : {} as ${typeName};`;
|
|
481
|
+
}
|
|
482
|
+
const schemaProps = schema.properties;
|
|
483
|
+
const propertyKeys = Object.keys(schemaProps);
|
|
484
|
+
const { objects: subTypeNames, arrayItems: subItemNames } = collectInlineSubTypes(schema, typeName, reservedNames);
|
|
485
|
+
const preamble = [];
|
|
486
|
+
for (const [key, subName] of subTypeNames) {
|
|
487
|
+
const propSchema = schemaProps[key];
|
|
488
|
+
preamble.push(`type ${subName} = ${typeName}[${JSON.stringify(key)}];`);
|
|
489
|
+
const subShapeValidator = generateShapeValidator(propSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames);
|
|
490
|
+
preamble.push(subShapeValidator);
|
|
491
|
+
preamble.push(generateObjectParser(propSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, subShapeValidator));
|
|
492
|
+
}
|
|
493
|
+
for (const [key, subName] of subItemNames) {
|
|
494
|
+
const propSchema = schemaProps[key];
|
|
495
|
+
const itemSchema = propSchema.items;
|
|
496
|
+
preamble.push(`type ${subName} = NonNullable<${typeName}[${JSON.stringify(key)}]>[number];`);
|
|
497
|
+
const itemShapeValidator = generateShapeValidator(itemSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames);
|
|
498
|
+
preamble.push(itemShapeValidator);
|
|
499
|
+
preamble.push(`const _every${subName} = (arr: readonly unknown[]): boolean => {
|
|
500
|
+
for (let i = 0; i < arr.length; i++) if (!${shapeValidatorName(subName)}(arr[i])) return false;
|
|
501
|
+
return true;
|
|
502
|
+
};`);
|
|
503
|
+
preamble.push(generateObjectParser(itemSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, itemShapeValidator));
|
|
504
|
+
}
|
|
505
|
+
const strictKeys = hasStrictKeys(schema);
|
|
506
|
+
const stripKeys = strictKeys || stripUnknown;
|
|
507
|
+
const exactKeyCount = stripKeys ? exactKeyCountOf(schema) : null;
|
|
508
|
+
const strictKeyCheck = unknownKeyCheck(propertyKeys, `_knownKeys${typeName}`);
|
|
509
|
+
if (stripKeys && exactKeyCount === null) {
|
|
510
|
+
for (const declaration of strictKeyCheck.declarations) {
|
|
511
|
+
preamble.push(`${declaration};`);
|
|
512
|
+
}
|
|
513
|
+
preamble.push(`const _hasOnlyKnownKeys${typeName} = (input: Record<string, unknown>): boolean => {
|
|
514
|
+
for (const _k in input) if (${strictKeyCheck.isUnknown("_k")}) return false;
|
|
515
|
+
return true;
|
|
516
|
+
};`);
|
|
517
|
+
} else if (strict && strictKeys) {
|
|
518
|
+
for (const declaration of strictKeyCheck.declarations) {
|
|
519
|
+
preamble.push(`${declaration};`);
|
|
785
520
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
521
|
+
}
|
|
522
|
+
const fallbackObject = generateFallbackObject(schema, useRefImports, typeName, suffix, subTypeNames);
|
|
523
|
+
const propInfo = [];
|
|
524
|
+
const hasAllOfRefParsers = useRefImports && isSchemaObject(schema) && hasAllOf(schema) && schema.allOf.some((entry) => isSchemaObject(entry) && hasRef(entry));
|
|
525
|
+
let canFastPath = !hasAllOfRefParsers && !hasFastPathBlockingKeyword(schema);
|
|
526
|
+
const fastPathChecks = [];
|
|
527
|
+
const fastPathAccessorChecks = [];
|
|
528
|
+
const usedVarNames = /* @__PURE__ */ new Set();
|
|
529
|
+
for (const key of propertyKeys) {
|
|
530
|
+
const propSchema = schemaProps[key];
|
|
531
|
+
const isRequired = isPropertyRequired(key, schema);
|
|
532
|
+
let varName = toVarName(key);
|
|
533
|
+
while (usedVarNames.has(varName))
|
|
534
|
+
varName = `${varName}_`;
|
|
535
|
+
usedVarNames.add(varName);
|
|
536
|
+
propInfo.push({ key, varName, isRequired, propSchema });
|
|
537
|
+
if (!canFastPath)
|
|
538
|
+
continue;
|
|
539
|
+
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
540
|
+
canFastPath = false;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const subName = subTypeNames.get(key);
|
|
544
|
+
let check = subName ? `${shapeValidatorName(subName)}(${varName})` : generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
|
|
545
|
+
const itemSubName = subItemNames.get(key);
|
|
546
|
+
if (check !== null && itemSubName) {
|
|
547
|
+
check = `${check} && _every${itemSubName}(${varName})`;
|
|
548
|
+
}
|
|
549
|
+
if (check === null) {
|
|
550
|
+
canFastPath = false;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
const accessor = safeAccessor("input", key);
|
|
554
|
+
let accessorCheck = subName ? `${shapeValidatorName(subName)}(${accessor})` : generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
|
|
555
|
+
if (itemSubName) {
|
|
556
|
+
accessorCheck = `${accessorCheck} && _every${itemSubName}(${accessor})`;
|
|
557
|
+
}
|
|
558
|
+
if (isRequired) {
|
|
559
|
+
fastPathChecks.push(check);
|
|
560
|
+
fastPathAccessorChecks.push(accessorCheck);
|
|
561
|
+
} else {
|
|
562
|
+
fastPathChecks.push(`(${varName} === undefined || ${check})`);
|
|
563
|
+
fastPathAccessorChecks.push(`(${accessor} === undefined || ${accessorCheck})`);
|
|
792
564
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
for (const key of propertyKeys) {
|
|
818
|
-
const propSchema = schemaProps[key];
|
|
819
|
-
const isRequired = isPropertyRequired(key, schema);
|
|
820
|
-
let varName = toVarName(key);
|
|
821
|
-
while (usedVarNames.has(varName))
|
|
822
|
-
varName = `${varName}_`;
|
|
823
|
-
usedVarNames.add(varName);
|
|
824
|
-
propInfo.push({ key, varName, isRequired, propSchema });
|
|
825
|
-
if (!canFastPath)
|
|
826
|
-
continue;
|
|
827
|
-
// Records of refs require iterating every value to check shape — too
|
|
828
|
-
// expensive to inline into the parser's fast path. Disable.
|
|
829
|
-
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
830
|
-
canFastPath = false;
|
|
831
|
-
continue;
|
|
832
|
-
}
|
|
833
|
-
const subName = subTypeNames.get(key);
|
|
834
|
-
// Inline nested objects fast-path through their private shape predicate, the
|
|
835
|
-
// same way $ref properties use the imported one. Arrays of inline objects
|
|
836
|
-
// additionally prove every element via the private item predicate.
|
|
837
|
-
let check = subName
|
|
838
|
-
? `${shapeValidatorName(subName)}(${varName})`
|
|
839
|
-
: generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
|
|
840
|
-
const itemSubName = subItemNames.get(key);
|
|
841
|
-
if (check !== null && itemSubName) {
|
|
842
|
-
check = `${check} && _every${itemSubName}(${varName})`;
|
|
843
|
-
}
|
|
844
|
-
if (check === null) {
|
|
845
|
-
canFastPath = false;
|
|
846
|
-
}
|
|
847
|
-
else if (isRequired) {
|
|
848
|
-
fastPathChecks.push(check);
|
|
849
|
-
}
|
|
850
|
-
else {
|
|
851
|
-
fastPathChecks.push(`(${varName} === undefined || ${check})`);
|
|
852
|
-
}
|
|
565
|
+
}
|
|
566
|
+
if (stripKeys) {
|
|
567
|
+
fastPathChecks.push(exactKeyCount !== null ? `Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${exactKeyCount}` : `_hasOnlyKnownKeys${typeName}(input)`);
|
|
568
|
+
}
|
|
569
|
+
const deepGuard = canFastPath && fastPathChecks.length > 0 ? fastPathChecks.join(" && ") : null;
|
|
570
|
+
const literalReturnUsesVars = stripKeys && !(isSchemaObject(schema) && hasAllOf(schema)) && exported;
|
|
571
|
+
let deepGuardExpr = deepGuard;
|
|
572
|
+
if (deepGuard !== null && shapeValidatorSource !== void 0 && !literalReturnUsesVars) {
|
|
573
|
+
const shapeFnName = shapeValidatorName(typeName);
|
|
574
|
+
const shapeStrictKeysGuard = stripKeys && exactKeyCount === null ? `
|
|
575
|
+
if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
|
|
576
|
+
const shapeChecks = [...fastPathAccessorChecks];
|
|
577
|
+
if (stripKeys && exactKeyCount !== null) {
|
|
578
|
+
shapeChecks.push(`Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${exactKeyCount}`);
|
|
579
|
+
}
|
|
580
|
+
const expectedShapeValidator = shapeChecks.length === 0 ? shapeStrictKeysGuard ? `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => {
|
|
581
|
+
if (!isObject(input)) return false;${shapeStrictKeysGuard}
|
|
582
|
+
return true;
|
|
583
|
+
};` : `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => isObject(input);` : `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => {
|
|
584
|
+
if (!isObject(input)) return false;${shapeStrictKeysGuard}
|
|
585
|
+
return ${shapeChecks.join("\n && ")};
|
|
586
|
+
};`;
|
|
587
|
+
if (shapeValidatorSource === expectedShapeValidator) {
|
|
588
|
+
deepGuardExpr = `${shapeFnName}(input)`;
|
|
853
589
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
590
|
+
}
|
|
591
|
+
const deepGuardCallsShape = deepGuardExpr !== deepGuard;
|
|
592
|
+
let canShallowGuard = strict && stripUnknown && !strictKeys && !hasFastPathBlockingKeyword(schema);
|
|
593
|
+
const shallowChecks = [];
|
|
594
|
+
if (canShallowGuard) {
|
|
595
|
+
for (const { key, varName, isRequired, propSchema } of propInfo) {
|
|
596
|
+
const subName = subTypeNames.get(key);
|
|
597
|
+
let check;
|
|
598
|
+
if (subName) {
|
|
599
|
+
check = `isObject(${varName})`;
|
|
600
|
+
} else if (isSchemaObject(propSchema) && !hasEnum(propSchema) && !hasRef(propSchema) && !hasOneOf(propSchema) && !hasAnyOf(propSchema) && !hasAllOf(propSchema) && !("not" in propSchema) && !shouldUseRefImport(propSchema, useRefImports) && !shouldUseArrayRefImport(propSchema, useRefImports) && !shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
601
|
+
check = generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
|
|
602
|
+
} else {
|
|
603
|
+
check = null;
|
|
604
|
+
}
|
|
605
|
+
if (check === null) {
|
|
606
|
+
canShallowGuard = false;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
shallowChecks.push(isRequired ? check : `(${varName} === undefined || ${check})`);
|
|
868
610
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
// nor requires the absence of extras. That lets it fire on the common
|
|
876
|
-
// stripUnknown input — which carries extras the build removes, and nested
|
|
877
|
-
// extras each sub-parser removes — while the cold path's per-property
|
|
878
|
-
// assertions still produce the precise error on a genuine mismatch.
|
|
879
|
-
let canShallowGuard = strict && stripUnknown && !strictKeys && !hasFastPathBlockingKeyword(schema);
|
|
880
|
-
const shallowChecks = [];
|
|
881
|
-
if (canShallowGuard) {
|
|
882
|
-
for (const { key, varName, isRequired, propSchema } of propInfo) {
|
|
883
|
-
const subName = subTypeNames.get(key);
|
|
884
|
-
let check;
|
|
885
|
-
if (subName) {
|
|
886
|
-
// Nested inline object: a shallow `isObject` is enough — the sub-parser
|
|
887
|
-
// validates and strips it.
|
|
888
|
-
check = `isObject(${varName})`;
|
|
889
|
-
}
|
|
890
|
-
else if (isSchemaObject(propSchema) &&
|
|
891
|
-
!hasEnum(propSchema) &&
|
|
892
|
-
!hasRef(propSchema) &&
|
|
893
|
-
!hasOneOf(propSchema) &&
|
|
894
|
-
!hasAnyOf(propSchema) &&
|
|
895
|
-
!hasAllOf(propSchema) &&
|
|
896
|
-
!('not' in propSchema) &&
|
|
897
|
-
!shouldUseRefImport(propSchema, useRefImports) &&
|
|
898
|
-
!shouldUseArrayRefImport(propSchema, useRefImports) &&
|
|
899
|
-
!shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
900
|
-
// Plain scalar / array / object: its own type check is already shallow
|
|
901
|
-
// (arrays are not walked, objects only `isObject`-checked).
|
|
902
|
-
check = generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
|
|
903
|
-
}
|
|
904
|
-
else {
|
|
905
|
-
check = null;
|
|
906
|
-
}
|
|
907
|
-
if (check === null) {
|
|
908
|
-
canShallowGuard = false;
|
|
909
|
-
break;
|
|
910
|
-
}
|
|
911
|
-
shallowChecks.push(isRequired ? check : `(${varName} === undefined || ${check})`);
|
|
912
|
-
}
|
|
611
|
+
}
|
|
612
|
+
const shallowGuard = canShallowGuard && shallowChecks.length > 0 ? shallowChecks.join(" && ") : null;
|
|
613
|
+
const varDeclLines = [];
|
|
614
|
+
for (const { key, varName, propSchema } of propInfo) {
|
|
615
|
+
if (shouldCacheVariable(propSchema, canFastPath, useRefImports)) {
|
|
616
|
+
varDeclLines.push(` const ${varName} = ${safeAccessor("input", key)};`);
|
|
913
617
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
const
|
|
918
|
-
for (const
|
|
919
|
-
|
|
920
|
-
|
|
618
|
+
}
|
|
619
|
+
const warnLines = [];
|
|
620
|
+
if (logWarnings && propInfo.length > 0) {
|
|
621
|
+
const warnKeyCheck = unknownKeyCheck(propInfo.map(({ key }) => key), "_knownKeys");
|
|
622
|
+
for (const declaration of warnKeyCheck.declarations) {
|
|
623
|
+
warnLines.push(` ${declaration};`);
|
|
624
|
+
}
|
|
625
|
+
warnLines.push(` for (const _k in input) {`);
|
|
626
|
+
warnLines.push(` if (${warnKeyCheck.isUnknown("_k")}) {`);
|
|
627
|
+
warnLines.push(` console.warn(\`[${typeName}] Unknown property "\${_k}"\`);`);
|
|
628
|
+
warnLines.push(` }`);
|
|
629
|
+
warnLines.push(` }`);
|
|
630
|
+
}
|
|
631
|
+
const buildObjectLines = (directAssign) => {
|
|
632
|
+
const objectLines = [];
|
|
633
|
+
if (!stripKeys) {
|
|
634
|
+
objectLines.push(" ...input,");
|
|
635
|
+
}
|
|
636
|
+
if (useRefImports && isSchemaObject(schema) && hasAllOf(schema)) {
|
|
637
|
+
for (const entry of schema.allOf) {
|
|
638
|
+
if (isSchemaObject(entry) && hasRef(entry)) {
|
|
639
|
+
const parserName = generateParserName(refToName(entry.$ref, suffix));
|
|
640
|
+
objectLines.push(` ...(${parserName}(input) as Record<string, unknown>),`);
|
|
921
641
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
for (const { key, varName, isRequired, propSchema } of propInfo) {
|
|
645
|
+
const shouldCache = shouldCacheVariable(propSchema, canFastPath, useRefImports);
|
|
646
|
+
const accessor = shouldCache ? varName : safeAccessor("input", key);
|
|
647
|
+
const subName = subTypeNames.get(key);
|
|
648
|
+
if (subName) {
|
|
649
|
+
const subParserName = generateParserName(subName);
|
|
650
|
+
if (isRequired) {
|
|
651
|
+
objectLines.push(` ${safeLiteralKey(key)}: ${subParserName}(${accessor}),`);
|
|
652
|
+
} else {
|
|
653
|
+
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${subParserName}(${accessor}) }),`);
|
|
929
654
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
// Coerce mode keeps the type-checking-and-coercing expression. Refs, arrays of
|
|
940
|
-
// refs, records of refs and inline nested objects always delegate to their
|
|
941
|
-
// parser in both modes; the input spread is dropped whenever stripping.
|
|
942
|
-
const buildObjectLines = (directAssign) => {
|
|
943
|
-
const objectLines = [];
|
|
944
|
-
if (!stripKeys) {
|
|
945
|
-
objectLines.push(' ...input,');
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
const itemSubName = subItemNames.get(key);
|
|
658
|
+
if (itemSubName) {
|
|
659
|
+
const itemParserName = generateParserName(itemSubName);
|
|
660
|
+
if (isRequired) {
|
|
661
|
+
objectLines.push(` ${safeLiteralKey(key)}: validateArray(${accessor}, ${itemParserName}),`);
|
|
662
|
+
} else {
|
|
663
|
+
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateArray(${accessor}, ${itemParserName}) }),`);
|
|
946
664
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (shouldUseRefImport(propSchema, useRefImports)) {
|
|
668
|
+
const ref = propSchema.$ref;
|
|
669
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
670
|
+
if (isRequired) {
|
|
671
|
+
objectLines.push(` ${safeLiteralKey(key)}: ${parserName}(${accessor}),`);
|
|
672
|
+
} else {
|
|
673
|
+
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${parserName}(${accessor}) }),`);
|
|
956
674
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
else {
|
|
969
|
-
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${subParserName}(${accessor}) }),`);
|
|
970
|
-
}
|
|
971
|
-
continue;
|
|
972
|
-
}
|
|
973
|
-
// Arrays of inline objects run every element through the private item
|
|
974
|
-
// sub-parser, the same way arrays of $refs delegate via validateArray.
|
|
975
|
-
// In strict mode the item parser throws on a bad element; in coerce mode
|
|
976
|
-
// it repairs the element to a valid instance.
|
|
977
|
-
const itemSubName = subItemNames.get(key);
|
|
978
|
-
if (itemSubName) {
|
|
979
|
-
const itemParserName = generateParserName(itemSubName);
|
|
980
|
-
if (isRequired) {
|
|
981
|
-
objectLines.push(` ${safeLiteralKey(key)}: validateArray(${accessor}, ${itemParserName}),`);
|
|
982
|
-
}
|
|
983
|
-
else {
|
|
984
|
-
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateArray(${accessor}, ${itemParserName}) }),`);
|
|
985
|
-
}
|
|
986
|
-
continue;
|
|
987
|
-
}
|
|
988
|
-
// Handle direct $ref properties via imported parsers
|
|
989
|
-
if (shouldUseRefImport(propSchema, useRefImports)) {
|
|
990
|
-
const ref = propSchema.$ref;
|
|
991
|
-
const parserName = generateParserName(refToName(ref, suffix));
|
|
992
|
-
if (isRequired) {
|
|
993
|
-
objectLines.push(` ${safeLiteralKey(key)}: ${parserName}(${accessor}),`);
|
|
994
|
-
}
|
|
995
|
-
else {
|
|
996
|
-
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${parserName}(${accessor}) }),`);
|
|
997
|
-
}
|
|
998
|
-
continue;
|
|
999
|
-
}
|
|
1000
|
-
// Handle array properties with $ref items
|
|
1001
|
-
if (shouldUseArrayRefImport(propSchema, useRefImports)) {
|
|
1002
|
-
const items = propSchema.items;
|
|
1003
|
-
const ref = items.$ref;
|
|
1004
|
-
const parserName = generateParserName(refToName(ref, suffix));
|
|
1005
|
-
if (isRequired) {
|
|
1006
|
-
objectLines.push(` ${safeLiteralKey(key)}: validateArray(${accessor}, ${parserName}),`);
|
|
1007
|
-
}
|
|
1008
|
-
else {
|
|
1009
|
-
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateArray(${accessor}, ${parserName}) }),`);
|
|
1010
|
-
}
|
|
1011
|
-
continue;
|
|
1012
|
-
}
|
|
1013
|
-
// Handle object properties with additionalProperties $ref
|
|
1014
|
-
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
1015
|
-
const additionalProps = propSchema.additionalProperties;
|
|
1016
|
-
const ref = additionalProps.$ref;
|
|
1017
|
-
const parserName = generateParserName(refToName(ref, suffix));
|
|
1018
|
-
if (isRequired) {
|
|
1019
|
-
objectLines.push(` ${safeLiteralKey(key)}: validateRecord(${accessor}, ${parserName}),`);
|
|
1020
|
-
}
|
|
1021
|
-
else {
|
|
1022
|
-
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateRecord(${accessor}, ${parserName}) }),`);
|
|
1023
|
-
}
|
|
1024
|
-
continue;
|
|
1025
|
-
}
|
|
1026
|
-
// Handle non-schema-object properties
|
|
1027
|
-
if (!isSchemaObject(propSchema)) {
|
|
1028
|
-
if (isRequired) {
|
|
1029
|
-
objectLines.push(` ${safeLiteralKey(key)}: undefined,`);
|
|
1030
|
-
}
|
|
1031
|
-
continue;
|
|
1032
|
-
}
|
|
1033
|
-
// Strict mode has already proven the value's type (by guard or assertion),
|
|
1034
|
-
// so the field is assigned straight from the cached read with no coercion.
|
|
1035
|
-
let valueExpr;
|
|
1036
|
-
if (directAssign) {
|
|
1037
|
-
valueExpr = accessor;
|
|
1038
|
-
}
|
|
1039
|
-
else {
|
|
1040
|
-
const defaultValue = getDefaultValue(propSchema);
|
|
1041
|
-
// For optional properties we know the value is not undefined because
|
|
1042
|
-
// we're inside the `...(accessor !== undefined && { ... })` check.
|
|
1043
|
-
const knownNotUndefined = !isRequired;
|
|
1044
|
-
valueExpr = generateValidationExpression(key, propSchema, defaultValue, true, undefined, undefined, shouldCache ? varName : undefined, knownNotUndefined, caseInsensitive);
|
|
1045
|
-
}
|
|
1046
|
-
if (isRequired) {
|
|
1047
|
-
objectLines.push(` ${safeLiteralKey(key)}: ${valueExpr},`);
|
|
1048
|
-
}
|
|
1049
|
-
else {
|
|
1050
|
-
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${valueExpr} }),`);
|
|
1051
|
-
}
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (shouldUseArrayRefImport(propSchema, useRefImports)) {
|
|
678
|
+
const items = propSchema.items;
|
|
679
|
+
const ref = items.$ref;
|
|
680
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
681
|
+
if (isRequired) {
|
|
682
|
+
objectLines.push(` ${safeLiteralKey(key)}: validateArray(${accessor}, ${parserName}),`);
|
|
683
|
+
} else {
|
|
684
|
+
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateArray(${accessor}, ${parserName}) }),`);
|
|
1052
685
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
// the input's keys are exactly the declared properties: an explicit literal of
|
|
1064
|
-
// those keys is then equivalent to the spread, and faster — a fixed-shape
|
|
1065
|
-
// literal beats a generic spread, yields a stable hidden class, and produces
|
|
1066
|
-
// the same declared key order as the slow path (the spread used input order).
|
|
1067
|
-
// The literal shares each value by reference, exactly like the spread did, so
|
|
1068
|
-
// it never re-parses an already-validated nested object. allOf merges in
|
|
1069
|
-
// properties this `propInfo` list doesn't carry, so it keeps the spread.
|
|
1070
|
-
const emitDeepGuardReturn = (lines) => {
|
|
1071
|
-
const canLiteral = stripKeys && !(isSchemaObject(schema) && hasAllOf(schema));
|
|
1072
|
-
if (!canLiteral) {
|
|
1073
|
-
lines.push(` if (${deepGuard}) return { ...input } as ${typeName};`);
|
|
1074
|
-
return;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
689
|
+
const additionalProps = propSchema.additionalProperties;
|
|
690
|
+
const ref = additionalProps.$ref;
|
|
691
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
692
|
+
if (isRequired) {
|
|
693
|
+
objectLines.push(` ${safeLiteralKey(key)}: validateRecord(${accessor}, ${parserName}),`);
|
|
694
|
+
} else {
|
|
695
|
+
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateRecord(${accessor}, ${parserName}) }),`);
|
|
1075
696
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
// _items`), and it is what keeps clean array elements allocation-free.
|
|
1082
|
-
// Exported root parsers keep returning a fresh object so callers never
|
|
1083
|
-
// alias the value they passed in.
|
|
1084
|
-
if (!exported) {
|
|
1085
|
-
lines.push(` if (${deepGuard}) return input as ${typeName};`);
|
|
1086
|
-
return;
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (!isSchemaObject(propSchema)) {
|
|
700
|
+
if (isRequired) {
|
|
701
|
+
objectLines.push(` ${safeLiteralKey(key)}: undefined,`);
|
|
1087
702
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
let valueExpr;
|
|
706
|
+
if (directAssign) {
|
|
707
|
+
valueExpr = accessor;
|
|
708
|
+
} else {
|
|
709
|
+
const defaultValue = getDefaultValue(propSchema);
|
|
710
|
+
const knownNotUndefined = !isRequired;
|
|
711
|
+
valueExpr = generateValidationExpression(key, propSchema, defaultValue, true, void 0, void 0, shouldCache ? varName : void 0, knownNotUndefined, caseInsensitive);
|
|
712
|
+
}
|
|
713
|
+
if (isRequired) {
|
|
714
|
+
objectLines.push(` ${safeLiteralKey(key)}: ${valueExpr},`);
|
|
715
|
+
} else {
|
|
716
|
+
objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${valueExpr} }),`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return objectLines;
|
|
720
|
+
};
|
|
721
|
+
const emitReturn = (lines2, objectLines) => {
|
|
722
|
+
lines2.push(` return {`);
|
|
723
|
+
lines2.push(objectLines.join("\n"));
|
|
724
|
+
lines2.push(` } as unknown as ${typeName};`);
|
|
725
|
+
};
|
|
726
|
+
const emitDeepGuardReturn = (lines2) => {
|
|
727
|
+
const canLiteral = stripKeys && !(isSchemaObject(schema) && hasAllOf(schema));
|
|
728
|
+
if (!canLiteral) {
|
|
729
|
+
lines2.push(` if (${deepGuardExpr}) return { ...input } as ${typeName};`);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (!exported) {
|
|
733
|
+
lines2.push(` if (${deepGuardExpr}) return input as ${typeName};`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const fields = [];
|
|
737
|
+
for (const { key, varName, isRequired, propSchema } of propInfo) {
|
|
738
|
+
if (!isSchemaObject(propSchema)) {
|
|
739
|
+
if (isRequired)
|
|
740
|
+
fields.push(` ${safeLiteralKey(key)}: undefined,`);
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
const accessor = shouldCacheVariable(propSchema, canFastPath, useRefImports) ? varName : safeAccessor("input", key);
|
|
744
|
+
fields.push(isRequired ? ` ${safeLiteralKey(key)}: ${accessor},` : ` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${accessor} }),`);
|
|
745
|
+
}
|
|
746
|
+
lines2.push(` if (${deepGuardExpr}) return {`);
|
|
747
|
+
lines2.push(fields.join("\n"));
|
|
748
|
+
lines2.push(` } as ${typeName};`);
|
|
749
|
+
};
|
|
750
|
+
const lines = [];
|
|
751
|
+
lines.push(`${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {`);
|
|
752
|
+
const varsAfterGuard = deepGuardCallsShape && shallowGuard === null;
|
|
753
|
+
if (strict) {
|
|
754
|
+
lines.push(` if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);`);
|
|
755
|
+
if (!varsAfterGuard)
|
|
756
|
+
lines.push(...varDeclLines);
|
|
757
|
+
lines.push(...warnLines);
|
|
758
|
+
const assertionLines = generateObjectStrictAssertion(schema, typeName, {
|
|
759
|
+
useRefImports,
|
|
760
|
+
suffix,
|
|
761
|
+
stripUnknown,
|
|
762
|
+
...rootSchema !== void 0 ? { rootSchema } : {}
|
|
763
|
+
}).slice(1);
|
|
764
|
+
if (shallowGuard) {
|
|
765
|
+
if (!exported && deepGuard) {
|
|
766
|
+
const residual = [];
|
|
767
|
+
for (let i = 0; i < fastPathChecks.length; i++) {
|
|
768
|
+
const deep = fastPathChecks[i];
|
|
769
|
+
const shallow = shallowChecks[i];
|
|
770
|
+
if (deep === shallow)
|
|
771
|
+
continue;
|
|
772
|
+
residual.push(shallow !== void 0 && deep.startsWith(`${shallow} && `) ? deep.slice(shallow.length + 4) : deep);
|
|
1101
773
|
}
|
|
1102
|
-
lines.push(` if (${
|
|
1103
|
-
lines.push(
|
|
1104
|
-
lines.push(` }
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
lines.push(`${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {`);
|
|
1108
|
-
if (strict) {
|
|
1109
|
-
// Guard first: a non-object throws straight away; a clean, well-typed input
|
|
1110
|
-
// then short-circuits past the per-property assertions, which only run to
|
|
1111
|
-
// pinpoint the failure when the guard rejects the input.
|
|
1112
|
-
lines.push(` if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);`);
|
|
1113
|
-
lines.push(...varDeclLines);
|
|
1114
|
-
lines.push(...warnLines);
|
|
1115
|
-
// The first assertion line is the `isObject` check, already done above.
|
|
1116
|
-
const assertionLines = generateObjectStrictAssertion(schema, typeName, {
|
|
1117
|
-
useRefImports,
|
|
1118
|
-
suffix,
|
|
1119
|
-
stripUnknown,
|
|
1120
|
-
...(rootSchema !== undefined ? { rootSchema } : {}),
|
|
1121
|
-
}).slice(1);
|
|
1122
|
-
if (shallowGuard) {
|
|
1123
|
-
// A private sub-parser hands input that is already exactly the declared
|
|
1124
|
-
// shape (no extras at any level) back by reference, so a clean array
|
|
1125
|
-
// element or nested object costs no allocation. The deep guard is
|
|
1126
|
-
// evaluated as the shallow guard plus only its *residual* terms (deeper
|
|
1127
|
-
// per-property checks and the no-extras term), so a carries-extras input
|
|
1128
|
-
// never runs the same typed checks twice before taking the strip build.
|
|
1129
|
-
if (!exported && deepGuard) {
|
|
1130
|
-
const residual = [];
|
|
1131
|
-
for (let i = 0; i < fastPathChecks.length; i++) {
|
|
1132
|
-
const deep = fastPathChecks[i];
|
|
1133
|
-
const shallow = shallowChecks[i];
|
|
1134
|
-
if (deep === shallow)
|
|
1135
|
-
continue;
|
|
1136
|
-
// When the deep check extends the shallow one by conjunction (e.g.
|
|
1137
|
-
// `Array.isArray(_x) && _everyItem(_x)`), the shallow guard already
|
|
1138
|
-
// proved the prefix — re-evaluating it per clean parse was pure
|
|
1139
|
-
// waste on the hot path, so only the extension joins the residual.
|
|
1140
|
-
residual.push(shallow !== undefined && deep.startsWith(`${shallow} && `) ? deep.slice(shallow.length + 4) : deep);
|
|
1141
|
-
}
|
|
1142
|
-
lines.push(` if (${shallowGuard}) {`);
|
|
1143
|
-
lines.push(residual.length > 0
|
|
1144
|
-
? ` if (${residual.join(' && ')}) return input as ${typeName};`
|
|
1145
|
-
: ` return input as ${typeName};`);
|
|
1146
|
-
lines.push(` } else {`);
|
|
1147
|
-
for (const assertionLine of assertionLines) {
|
|
1148
|
-
lines.push(` ${assertionLine}`);
|
|
1149
|
-
}
|
|
1150
|
-
lines.push(` }`);
|
|
1151
|
-
}
|
|
1152
|
-
else {
|
|
1153
|
-
// stripUnknown: a well-typed input skips the assertions and goes
|
|
1154
|
-
// straight to the strip build (which removes extras and recurses into
|
|
1155
|
-
// sub-parsers).
|
|
1156
|
-
lines.push(` if (!(${shallowGuard})) {`);
|
|
1157
|
-
for (const assertionLine of assertionLines) {
|
|
1158
|
-
lines.push(` ${assertionLine}`);
|
|
1159
|
-
}
|
|
1160
|
-
lines.push(` }`);
|
|
1161
|
-
}
|
|
1162
|
-
emitReturn(lines, buildObjectLines(true));
|
|
774
|
+
lines.push(` if (${shallowGuard}) {`);
|
|
775
|
+
lines.push(residual.length > 0 ? ` if (${residual.join(" && ")}) return input as ${typeName};` : ` return input as ${typeName};`);
|
|
776
|
+
lines.push(` } else {`);
|
|
777
|
+
for (const assertionLine of assertionLines) {
|
|
778
|
+
lines.push(` ${assertionLine}`);
|
|
1163
779
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
lines.push(assertionLine);
|
|
1170
|
-
}
|
|
1171
|
-
if (strictKeys) {
|
|
1172
|
-
// for..in (not Object.keys) deliberately: this loop is cold — the fast
|
|
1173
|
-
// path already proved the key set — but swapping in the keys-array
|
|
1174
|
-
// iterator here once regressed the *hot* path several percent on CI:
|
|
1175
|
-
// the extra dead-path bytecode changed the engine's inlining of the
|
|
1176
|
-
// whole parser. The fast-path no-extras test uses own-key semantics;
|
|
1177
|
-
// an inherited-key mismatch merely lands here and keeps the historical
|
|
1178
|
-
// for..in rejection.
|
|
1179
|
-
lines.push(` for (const _k in input) {`);
|
|
1180
|
-
lines.push(` if (${strictKeyCheck.isUnknown('_k')}) throw new Error(\`[${typeName}] unknown property "\${_k}"\`);`);
|
|
1181
|
-
lines.push(` }`);
|
|
1182
|
-
}
|
|
1183
|
-
emitReturn(lines, buildObjectLines(true));
|
|
780
|
+
lines.push(` }`);
|
|
781
|
+
} else {
|
|
782
|
+
lines.push(` if (!(${shallowGuard})) {`);
|
|
783
|
+
for (const assertionLine of assertionLines) {
|
|
784
|
+
lines.push(` ${assertionLine}`);
|
|
1184
785
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
786
|
+
lines.push(` }`);
|
|
787
|
+
}
|
|
788
|
+
emitReturn(lines, buildObjectLines(true));
|
|
789
|
+
} else {
|
|
790
|
+
if (deepGuard) {
|
|
791
|
+
emitDeepGuardReturn(lines);
|
|
792
|
+
}
|
|
793
|
+
if (varsAfterGuard)
|
|
1188
794
|
lines.push(...varDeclLines);
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
795
|
+
for (const assertionLine of assertionLines) {
|
|
796
|
+
lines.push(assertionLine);
|
|
797
|
+
}
|
|
798
|
+
if (strictKeys) {
|
|
799
|
+
lines.push(` for (const _k in input) {`);
|
|
800
|
+
lines.push(` if (${strictKeyCheck.isUnknown("_k")}) throw new Error(\`[${typeName}] unknown property "\${_k}"\`);`);
|
|
801
|
+
lines.push(` }`);
|
|
802
|
+
}
|
|
803
|
+
emitReturn(lines, buildObjectLines(true));
|
|
804
|
+
}
|
|
805
|
+
} else {
|
|
806
|
+
lines.push(` if (!isObject(input)) return ${fallbackObject};`);
|
|
807
|
+
if (!varsAfterGuard)
|
|
808
|
+
lines.push(...varDeclLines);
|
|
809
|
+
lines.push(...warnLines);
|
|
810
|
+
if (deepGuard) {
|
|
811
|
+
emitDeepGuardReturn(lines);
|
|
812
|
+
}
|
|
813
|
+
if (varsAfterGuard)
|
|
814
|
+
lines.push(...varDeclLines);
|
|
815
|
+
emitReturn(lines, buildObjectLines(false));
|
|
816
|
+
}
|
|
817
|
+
lines.push(`}`);
|
|
818
|
+
const fn = lines.join("\n");
|
|
819
|
+
if (preamble.length === 0)
|
|
820
|
+
return fn;
|
|
821
|
+
return `${preamble.join("\n\n")}
|
|
822
|
+
|
|
823
|
+
${fn}`;
|
|
1200
824
|
};
|
|
1201
|
-
/**
|
|
1202
|
-
* Combined-parser variant for `properties` + `patternProperties` schemas that
|
|
1203
|
-
* set `additionalProperties: false`. Declared properties are coerced as usual,
|
|
1204
|
-
* keys matching any pattern are kept (the first `$ref` pattern is coerced via
|
|
1205
|
-
* its imported parser when ref imports are on), and every other key is rejected
|
|
1206
|
-
* in strict mode or stripped in coerce mode — matching the interpreter.
|
|
1207
|
-
*/
|
|
1208
|
-
/**
|
|
1209
|
-
* Emits a prototype-safe `result[key] = value` for the dynamic `for..in` copy
|
|
1210
|
-
* loops in the pattern-property parsers. A bare `result[key] = v` is a `[[Set]]`
|
|
1211
|
-
* — and for the key `"__proto__"` (own-enumerable when the input came from
|
|
1212
|
-
* `JSON.parse`) it invokes the inherited `Object.prototype` setter and reassigns
|
|
1213
|
-
* `result`'s prototype to attacker-controlled data, the same vector
|
|
1214
|
-
* `validateRecord` guards against. `"__proto__"` is the only own-enumerable key
|
|
1215
|
-
* whose name resolves to an accessor on `Object.prototype` (`constructor` et al.
|
|
1216
|
-
* are writable data properties, so a `[[Set]]` there just creates an own key),
|
|
1217
|
-
* so a single-keyed guard is sufficient. Each loop already runs a `.test(key)`
|
|
1218
|
-
* regex per iteration, which dominates the cost, so the extra `===` on the
|
|
1219
|
-
* common path is immeasurable. Assumes the target variable is named `result`,
|
|
1220
|
-
* as it is in every parser that calls this.
|
|
1221
|
-
*/
|
|
1222
825
|
const safeResultAssign = (keyVar, valueExpr) => `if (${keyVar} === "__proto__") { Object.defineProperty(result, ${keyVar}, { value: ${valueExpr}, writable: true, enumerable: true, configurable: true }); } else { (result as Record<string, unknown>)[${keyVar}] = ${valueExpr}; }`;
|
|
1223
826
|
const generateStrictCombinedParser = (schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, strict) => {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
: ` return {} as unknown as ${typeName};`;
|
|
1255
|
-
const resultBody = propertyLines.length > 0 ? `{\n${propertyLines.join('\n')}\n }` : '{}';
|
|
1256
|
-
// Only the Set form needs a declaration; the inline === chain is stateless.
|
|
1257
|
-
const knownKeysDeclaration = knownKeyCheck.declarations.map((decl) => ` ${decl};\n`).join('');
|
|
1258
|
-
// In strict mode assert the declared properties (type, required, enum,
|
|
1259
|
-
// constraints) — the `result` below is built from the *coercing* property
|
|
1260
|
-
// lines, which silently repair a wrong type and default a missing required key
|
|
1261
|
-
// instead of throwing. `generateObjectStrictAssertion` throws first; the
|
|
1262
|
-
// coercing build then runs only on already-valid input (a no-op there). It also
|
|
1263
|
-
// covers the object-level keyword checks, so it replaces the keyword-only
|
|
1264
|
-
// prelude. `.slice(1)` drops its `isObject` check (emitted just below). Coerce
|
|
1265
|
-
// mode (stripUnknown) keeps its lighter prelude.
|
|
1266
|
-
const assertionPrelude = strict
|
|
1267
|
-
? generateObjectStrictAssertion(schema, typeName, { useRefImports, suffix }).slice(1)
|
|
1268
|
-
: [];
|
|
1269
|
-
const keywordPrelude = assertionPrelude.length > 0 ? `\n${assertionPrelude.join('\n')}` : '';
|
|
1270
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
827
|
+
const declaredKeys = hasProperties(schema) ? Object.keys(schema.properties) : [];
|
|
828
|
+
const knownKeyCheck = unknownKeyCheck(declaredKeys, "_knownKeys");
|
|
829
|
+
const refPattern = useRefImports ? patterns.find(([, ps]) => isSchemaObject(ps) && hasRef(ps)) : void 0;
|
|
830
|
+
const loopLines = [` if (${knownKeyCheck.isKnown("key")}) continue;`];
|
|
831
|
+
if (refPattern) {
|
|
832
|
+
const parserName = generateParserName(refToName(refPattern[1].$ref, suffix));
|
|
833
|
+
loopLines.push(` if (/${escapeRegexPattern(refPattern[0])}/.test(key)) {`);
|
|
834
|
+
loopLines.push(` ${safeResultAssign("key", `${parserName}(input[key])`)}`);
|
|
835
|
+
loopLines.push(` continue;`);
|
|
836
|
+
loopLines.push(` }`);
|
|
837
|
+
}
|
|
838
|
+
const keepConditions = patterns.filter(([p]) => !(refPattern && p === refPattern[0])).map(([p]) => `/${escapeRegexPattern(p)}/.test(key)`);
|
|
839
|
+
if (keepConditions.length > 0) {
|
|
840
|
+
loopLines.push(` if (${keepConditions.join(" || ")}) {`);
|
|
841
|
+
loopLines.push(` ${safeResultAssign("key", "input[key]")}`);
|
|
842
|
+
loopLines.push(` continue;`);
|
|
843
|
+
loopLines.push(` }`);
|
|
844
|
+
}
|
|
845
|
+
if (strict)
|
|
846
|
+
loopLines.push(` throw new Error(\`[${typeName}] unknown property "\${key}"\`);`);
|
|
847
|
+
const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
|
|
848
|
+
const resultBody = propertyLines.length > 0 ? `{
|
|
849
|
+
${propertyLines.join("\n")}
|
|
850
|
+
}` : "{}";
|
|
851
|
+
const knownKeysDeclaration = knownKeyCheck.declarations.map((decl) => ` ${decl};
|
|
852
|
+
`).join("");
|
|
853
|
+
const assertionPrelude = strict ? generateObjectStrictAssertion(schema, typeName, { useRefImports, suffix }).slice(1) : [];
|
|
854
|
+
const keywordPrelude = assertionPrelude.length > 0 ? `
|
|
855
|
+
${assertionPrelude.join("\n")}` : "";
|
|
856
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1271
857
|
if (!isObject(input)) {
|
|
1272
858
|
${notObjectBranch}
|
|
1273
859
|
}${keywordPrelude}
|
|
1274
860
|
${knownKeysDeclaration} const result = ${resultBody} as unknown as ${typeName};
|
|
1275
861
|
for (const key in input) {
|
|
1276
|
-
${loopLines.join(
|
|
862
|
+
${loopLines.join("\n")}
|
|
1277
863
|
}
|
|
1278
864
|
return result;
|
|
1279
865
|
};`;
|
|
1280
866
|
};
|
|
1281
|
-
/**
|
|
1282
|
-
* Generates a parser for schemas that have both properties AND patternProperties.
|
|
1283
|
-
* Parses the known properties first, then iterates remaining keys to match patterns.
|
|
1284
|
-
*/
|
|
1285
867
|
const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, stripUnknown = false, caseInsensitive = false) => {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
}
|
|
1321
|
-
const [pattern, patternSchema] = refPattern;
|
|
1322
|
-
const ref = patternSchema.$ref;
|
|
1323
|
-
const parserName = generateParserName(refToName(ref, suffix));
|
|
1324
|
-
const assignmentCode = `(result as Record<string, unknown>)[key] = ${parserName}(value);`;
|
|
1325
|
-
const escapedPattern = escapeRegexPattern(pattern);
|
|
1326
|
-
const inputSpread = ' ...input,';
|
|
1327
|
-
let objectProperties = inputSpread;
|
|
1328
|
-
if (propertyLines.length > 0) {
|
|
1329
|
-
for (const line of propertyLines) {
|
|
1330
|
-
objectProperties += '\n' + line;
|
|
1331
|
-
}
|
|
868
|
+
const functionName = generateParserName(typeName);
|
|
869
|
+
const entries = generatePropertyEntries(schema, useRefImports, suffix, caseInsensitive);
|
|
870
|
+
const propertyLines = entries.map((entry) => {
|
|
871
|
+
if (entry.isOptional) {
|
|
872
|
+
return ` ${entry.value},`;
|
|
873
|
+
}
|
|
874
|
+
return ` ${safeLiteralKey(entry.key)}: ${entry.value},`;
|
|
875
|
+
});
|
|
876
|
+
if (!isSchemaObject(schema) || !("patternProperties" in schema)) {
|
|
877
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
|
|
878
|
+
}
|
|
879
|
+
const patternProps = schema.patternProperties;
|
|
880
|
+
const patterns = Object.entries(patternProps);
|
|
881
|
+
const refPattern = patterns.find(([, ps]) => isSchemaObject(ps) && hasRef(ps));
|
|
882
|
+
const strictKeys = hasAdditionalProperties(schema) && schema.additionalProperties === false;
|
|
883
|
+
if (strictKeys) {
|
|
884
|
+
return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, strict);
|
|
885
|
+
}
|
|
886
|
+
if (stripUnknown && !strict) {
|
|
887
|
+
return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, false);
|
|
888
|
+
}
|
|
889
|
+
if (!refPattern || !useRefImports) {
|
|
890
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
|
|
891
|
+
}
|
|
892
|
+
const [pattern, patternSchema] = refPattern;
|
|
893
|
+
const ref = patternSchema.$ref;
|
|
894
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
895
|
+
const assignmentCode = `(result as Record<string, unknown>)[key] = ${parserName}(value);`;
|
|
896
|
+
const escapedPattern = escapeRegexPattern(pattern);
|
|
897
|
+
const inputSpread = " ...input,";
|
|
898
|
+
let objectProperties = inputSpread;
|
|
899
|
+
if (propertyLines.length > 0) {
|
|
900
|
+
for (const line of propertyLines) {
|
|
901
|
+
objectProperties += "\n" + line;
|
|
1332
902
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
903
|
+
}
|
|
904
|
+
const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
|
|
905
|
+
const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName) : "";
|
|
906
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1338
907
|
if (!isObject(input)) {
|
|
1339
908
|
${notObjectBranch}
|
|
1340
909
|
}${keywordPrelude}
|
|
@@ -1350,162 +919,135 @@ ${objectProperties}
|
|
|
1350
919
|
return result;
|
|
1351
920
|
};`;
|
|
1352
921
|
};
|
|
1353
|
-
/**
|
|
1354
|
-
* Generates a parser for schemas with additionalProperties.
|
|
1355
|
-
*/
|
|
1356
922
|
const generateAdditionalPropertiesParser = (schema, typeName, useRefImports, suffix, strict) => {
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
const ref = additionalProps.$ref;
|
|
1367
|
-
const parserName = generateParserName(refToName(ref, suffix));
|
|
1368
|
-
if (strict) {
|
|
1369
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {\n if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${keywordPrelude}\n return validateRecord(input, ${parserName}) as ${typeName};\n};`;
|
|
1370
|
-
}
|
|
1371
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => validateRecord(input, ${parserName}) as ${typeName};`;
|
|
1372
|
-
}
|
|
1373
|
-
// Handle additionalProperties with a known type by generating inline validation
|
|
1374
|
-
if (isSchemaObject(additionalProps) && hasType(additionalProps)) {
|
|
1375
|
-
const inlineParser = generateInlineValueParser(additionalProps, strict, `[${typeName}]`);
|
|
1376
|
-
if (inlineParser) {
|
|
1377
|
-
if (strict) {
|
|
1378
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {\n if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${keywordPrelude}\n return validateRecord(input, ${inlineParser}) as ${typeName};\n};`;
|
|
1379
|
-
}
|
|
1380
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => validateRecord(input, ${inlineParser}) as ${typeName};`;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
// Otherwise, just validate the input type and shallow copy
|
|
923
|
+
const functionName = generateParserName(typeName);
|
|
924
|
+
const additionalProps = schema.additionalProperties;
|
|
925
|
+
if (!additionalProps) {
|
|
926
|
+
return generateEmptyObjectParser(typeName, strict, schema);
|
|
927
|
+
}
|
|
928
|
+
const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName) : "";
|
|
929
|
+
if (useRefImports && isSchemaObject(additionalProps) && hasRef(additionalProps)) {
|
|
930
|
+
const ref = additionalProps.$ref;
|
|
931
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
1384
932
|
if (strict) {
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
}
|
|
1389
|
-
/**
|
|
1390
|
-
* Generates an inline arrow function that validates a single value based on
|
|
1391
|
-
* the additionalProperties schema type. Returns null if the type is not supported.
|
|
1392
|
-
*
|
|
1393
|
-
* Used to generate parsers for Record-like types where values have a known
|
|
1394
|
-
* primitive or array type (e.g. Record<string, string> or Record<string, string[]>).
|
|
1395
|
-
*/
|
|
1396
|
-
const generateInlineValueParser = (schema, strict = false, label = '') => {
|
|
1397
|
-
if (!isSchemaObject(schema) || !hasType(schema)) {
|
|
1398
|
-
return null;
|
|
933
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
934
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${keywordPrelude}
|
|
935
|
+
return validateRecord(input, ${parserName}) as ${typeName};
|
|
936
|
+
};`;
|
|
1399
937
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
case 'boolean':
|
|
1413
|
-
return `(value: unknown) => { if (typeof value !== "boolean") ${err('boolean')}; return value; }`;
|
|
1414
|
-
case 'array':
|
|
1415
|
-
return `(value: unknown) => { if (!Array.isArray(value)) ${err('array')}; return value; }`;
|
|
1416
|
-
default:
|
|
1417
|
-
return null;
|
|
1418
|
-
}
|
|
938
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => validateRecord(input, ${parserName}) as ${typeName};`;
|
|
939
|
+
}
|
|
940
|
+
if (isSchemaObject(additionalProps) && hasType(additionalProps)) {
|
|
941
|
+
const inlineParser = generateInlineValueParser(additionalProps, strict, `[${typeName}]`);
|
|
942
|
+
if (inlineParser) {
|
|
943
|
+
if (strict) {
|
|
944
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
945
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${keywordPrelude}
|
|
946
|
+
return validateRecord(input, ${inlineParser}) as ${typeName};
|
|
947
|
+
};`;
|
|
948
|
+
}
|
|
949
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => validateRecord(input, ${inlineParser}) as ${typeName};`;
|
|
1419
950
|
}
|
|
951
|
+
}
|
|
952
|
+
if (strict) {
|
|
953
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
954
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${keywordPrelude}
|
|
955
|
+
return { ...input } as ${typeName};
|
|
956
|
+
};`;
|
|
957
|
+
}
|
|
958
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? { ...input } as ${typeName} : {};`;
|
|
959
|
+
};
|
|
960
|
+
const generateInlineValueParser = (schema, strict = false, label = "") => {
|
|
961
|
+
if (!isSchemaObject(schema) || !hasType(schema)) {
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
if (strict) {
|
|
965
|
+
const err = (t) => `throw new Error(${JSON.stringify(`${label} record value must be ${t}`)})`;
|
|
1420
966
|
switch (schema.type) {
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
default:
|
|
1434
|
-
return null;
|
|
967
|
+
case "string":
|
|
968
|
+
return `(value: unknown) => { if (typeof value !== "string") ${err("string")}; return value; }`;
|
|
969
|
+
case "number":
|
|
970
|
+
return `(value: unknown) => { if (typeof value !== "number") ${err("number")}; return value; }`;
|
|
971
|
+
case "integer":
|
|
972
|
+
return `(value: unknown) => { if (typeof value !== "number" || !Number.isInteger(value)) ${err("integer")}; return value; }`;
|
|
973
|
+
case "boolean":
|
|
974
|
+
return `(value: unknown) => { if (typeof value !== "boolean") ${err("boolean")}; return value; }`;
|
|
975
|
+
case "array":
|
|
976
|
+
return `(value: unknown) => { if (!Array.isArray(value)) ${err("array")}; return value; }`;
|
|
977
|
+
default:
|
|
978
|
+
return null;
|
|
1435
979
|
}
|
|
980
|
+
}
|
|
981
|
+
switch (schema.type) {
|
|
982
|
+
case "string":
|
|
983
|
+
return '(value: unknown) => typeof value === "string" ? value : ""';
|
|
984
|
+
case "number":
|
|
985
|
+
return '(value: unknown) => typeof value === "number" ? value : 0';
|
|
986
|
+
case "integer":
|
|
987
|
+
return '(value: unknown) => typeof value === "number" && Number.isInteger(value) ? value : 0';
|
|
988
|
+
case "boolean":
|
|
989
|
+
return '(value: unknown) => typeof value === "boolean" ? value : false';
|
|
990
|
+
case "array":
|
|
991
|
+
return "(value: unknown) => Array.isArray(value) ? value : []";
|
|
992
|
+
default:
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
1436
995
|
};
|
|
1437
|
-
/**
|
|
1438
|
-
* Generates a parser for schemas with patternProperties.
|
|
1439
|
-
* Handles both patternProperties and specification-extensions (x- prefix).
|
|
1440
|
-
*/
|
|
1441
996
|
const generatePatternPropertiesParser = (schema, typeName, useRefImports, suffix, strict) => {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
997
|
+
const functionName = generateParserName(typeName);
|
|
998
|
+
if (!("patternProperties" in schema) || typeof schema.patternProperties !== "object") {
|
|
999
|
+
return generateEmptyObjectParser(typeName, strict, schema);
|
|
1000
|
+
}
|
|
1001
|
+
const patternProps = schema.patternProperties;
|
|
1002
|
+
const patterns = Object.entries(patternProps);
|
|
1003
|
+
if (patterns.length === 0) {
|
|
1004
|
+
return generateEmptyObjectParser(typeName, strict, schema);
|
|
1005
|
+
}
|
|
1006
|
+
const [pattern, patternSchema] = patterns[0];
|
|
1007
|
+
let patternAssignment = "(result as Record<string, unknown>)[key] = value;";
|
|
1008
|
+
let patternValueExpr = "value";
|
|
1009
|
+
if (useRefImports && isSchemaObject(patternSchema) && hasRef(patternSchema)) {
|
|
1010
|
+
const ref = patternSchema.$ref;
|
|
1011
|
+
const parserName = generateParserName(refToName(ref, suffix));
|
|
1012
|
+
patternAssignment = `(result as Record<string, unknown>)[key] = ${parserName}(value);`;
|
|
1013
|
+
patternValueExpr = `${parserName}(value)`;
|
|
1014
|
+
} else if (patternSchema === false) {
|
|
1015
|
+
patternAssignment = "";
|
|
1016
|
+
patternValueExpr = null;
|
|
1017
|
+
}
|
|
1018
|
+
const escapedPattern = escapeRegexPattern(pattern);
|
|
1019
|
+
const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
|
|
1020
|
+
const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName) : "";
|
|
1021
|
+
const strictKeys = hasAdditionalProperties(schema) && schema.additionalProperties === false;
|
|
1022
|
+
if (strictKeys) {
|
|
1023
|
+
const loopLines = [` if (/${escapedPattern}/.test(key)) {`];
|
|
1024
|
+
if (patternValueExpr) {
|
|
1025
|
+
loopLines.push(` const value = input[key];`);
|
|
1026
|
+
loopLines.push(` ${safeResultAssign("key", patternValueExpr)}`);
|
|
1027
|
+
}
|
|
1028
|
+
loopLines.push(` continue;`);
|
|
1029
|
+
loopLines.push(` }`);
|
|
1030
|
+
const keepConditions = patterns.slice(1).map(([p]) => `/${escapeRegexPattern(p)}/.test(key)`);
|
|
1031
|
+
if (keepConditions.length > 0) {
|
|
1032
|
+
loopLines.push(` if (${keepConditions.join(" || ")}) {`);
|
|
1033
|
+
loopLines.push(` ${safeResultAssign("key", "input[key]")}`);
|
|
1034
|
+
loopLines.push(` continue;`);
|
|
1035
|
+
loopLines.push(` }`);
|
|
1468
1036
|
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
const
|
|
1472
|
-
? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);`
|
|
1473
|
-
: ` return {} as unknown as ${typeName};`;
|
|
1474
|
-
const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName) : '';
|
|
1475
|
-
// With `additionalProperties: false`, only keys matching a pattern survive:
|
|
1476
|
-
// others are rejected (strict) or stripped (coerce). Start from an empty
|
|
1477
|
-
// object rather than spreading every input key.
|
|
1478
|
-
const strictKeys = hasAdditionalProperties(schema) && schema.additionalProperties === false;
|
|
1479
|
-
if (strictKeys) {
|
|
1480
|
-
const loopLines = [` if (/${escapedPattern}/.test(key)) {`];
|
|
1481
|
-
if (patternValueExpr) {
|
|
1482
|
-
loopLines.push(` const value = input[key];`);
|
|
1483
|
-
loopLines.push(` ${safeResultAssign('key', patternValueExpr)}`);
|
|
1484
|
-
}
|
|
1485
|
-
loopLines.push(` continue;`);
|
|
1486
|
-
loopLines.push(` }`);
|
|
1487
|
-
const keepConditions = patterns.slice(1).map(([p]) => `/${escapeRegexPattern(p)}/.test(key)`);
|
|
1488
|
-
if (keepConditions.length > 0) {
|
|
1489
|
-
loopLines.push(` if (${keepConditions.join(' || ')}) {`);
|
|
1490
|
-
loopLines.push(` ${safeResultAssign('key', 'input[key]')}`);
|
|
1491
|
-
loopLines.push(` continue;`);
|
|
1492
|
-
loopLines.push(` }`);
|
|
1493
|
-
}
|
|
1494
|
-
if (strict)
|
|
1495
|
-
loopLines.push(` throw new Error(\`[${typeName}] unknown property "\${key}"\`);`);
|
|
1496
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1037
|
+
if (strict)
|
|
1038
|
+
loopLines.push(` throw new Error(\`[${typeName}] unknown property "\${key}"\`);`);
|
|
1039
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1497
1040
|
if (!isObject(input)) {
|
|
1498
1041
|
${notObjectBranch}
|
|
1499
1042
|
}${keywordPrelude}
|
|
1500
1043
|
const result = {} as unknown as ${typeName};
|
|
1501
1044
|
for (const key in input) {
|
|
1502
|
-
${loopLines.join(
|
|
1045
|
+
${loopLines.join("\n")}
|
|
1503
1046
|
}
|
|
1504
1047
|
return result;
|
|
1505
1048
|
};`;
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1049
|
+
}
|
|
1050
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1509
1051
|
if (!isObject(input)) {
|
|
1510
1052
|
${notObjectBranch}
|
|
1511
1053
|
}${keywordPrelude}
|
|
@@ -1521,117 +1063,91 @@ ${notObjectBranch}
|
|
|
1521
1063
|
return result;
|
|
1522
1064
|
};`;
|
|
1523
1065
|
};
|
|
1524
|
-
/**
|
|
1525
|
-
* Generates a parser for conditional schemas with if/then/else.
|
|
1526
|
-
* This always generates parser calls regardless of useRefImports setting,
|
|
1527
|
-
* because conditional logic requires delegating to the appropriate parser.
|
|
1528
|
-
*/
|
|
1529
1066
|
const generateConditionalParser = (schema, typeName, suffix, stripUnknown = false, caseInsensitive = false) => {
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
const
|
|
1538
|
-
if (
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
// Check if the condition is checking for $ref property
|
|
1549
|
-
const isRefCondition = ifSchema &&
|
|
1550
|
-
isSchemaObject(ifSchema) &&
|
|
1551
|
-
'required' in ifSchema &&
|
|
1552
|
-
Array.isArray(ifSchema.required) &&
|
|
1553
|
-
ifSchema.required.includes('$ref');
|
|
1554
|
-
if (!isRefCondition) {
|
|
1555
|
-
// Condition is not a $ref check — flatten into an object parser
|
|
1556
|
-
const mergedSchema = getConditionalObjectSchema(schema);
|
|
1557
|
-
if (mergedSchema) {
|
|
1558
|
-
return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, undefined, NO_RESERVED_NAMES, caseInsensitive);
|
|
1559
|
-
}
|
|
1560
|
-
return generateEmptyObjectParser(typeName);
|
|
1067
|
+
const functionName = generateParserName(typeName);
|
|
1068
|
+
const ifSchema = schema.if;
|
|
1069
|
+
const thenSchema = schema.then;
|
|
1070
|
+
const elseSchema = schema.else;
|
|
1071
|
+
const thenHasRef = thenSchema && isSchemaObject(thenSchema) && hasRef(thenSchema);
|
|
1072
|
+
const elseHasRef = elseSchema && isSchemaObject(elseSchema) && hasRef(elseSchema);
|
|
1073
|
+
if (!thenHasRef || !elseHasRef) {
|
|
1074
|
+
const mergedSchema = getConditionalObjectSchema(schema);
|
|
1075
|
+
if (mergedSchema) {
|
|
1076
|
+
return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
|
|
1077
|
+
}
|
|
1078
|
+
return generateEmptyObjectParser(typeName);
|
|
1079
|
+
}
|
|
1080
|
+
const isRefCondition = ifSchema && isSchemaObject(ifSchema) && "required" in ifSchema && Array.isArray(ifSchema.required) && ifSchema.required.includes("$ref");
|
|
1081
|
+
if (!isRefCondition) {
|
|
1082
|
+
const mergedSchema = getConditionalObjectSchema(schema);
|
|
1083
|
+
if (mergedSchema) {
|
|
1084
|
+
return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
|
|
1561
1085
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1086
|
+
return generateEmptyObjectParser(typeName);
|
|
1087
|
+
}
|
|
1088
|
+
const thenRef = thenSchema.$ref;
|
|
1089
|
+
const elseRef = elseSchema.$ref;
|
|
1090
|
+
const thenParserName = generateParserName(refToName(thenRef, suffix));
|
|
1091
|
+
const elseParserName = generateParserName(refToName(elseRef, suffix));
|
|
1092
|
+
const thenTypeName = refToName(thenRef, suffix);
|
|
1093
|
+
return `export const ${functionName} = (input: unknown): ${typeName} | ${thenTypeName} =>
|
|
1568
1094
|
hasRef(input) ? ${thenParserName}(input) : ${elseParserName}(input)
|
|
1569
1095
|
`;
|
|
1570
1096
|
};
|
|
1571
|
-
/**
|
|
1572
|
-
* Builds an object schema from conditional if/then keywords when the schema does not
|
|
1573
|
-
* declare type: "object". Flattens if/then/else property sets into a single object schema.
|
|
1574
|
-
*/
|
|
1575
1097
|
const getConditionalObjectSchema = (schema) => {
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
}
|
|
1098
|
+
if (!isSchemaObject(schema)) {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
if (!("if" in schema) || !("then" in schema)) {
|
|
1102
|
+
return null;
|
|
1103
|
+
}
|
|
1104
|
+
const ifSchema = schema.if;
|
|
1105
|
+
const thenSchema = schema.then;
|
|
1106
|
+
const elseSchema = "else" in schema ? schema.else : void 0;
|
|
1107
|
+
if (!isSchemaObject(ifSchema) || !isSchemaObject(thenSchema)) {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
const ifProperties = ifSchema.properties;
|
|
1111
|
+
const thenProperties = thenSchema.properties;
|
|
1112
|
+
const elseProperties = elseSchema && isSchemaObject(elseSchema) ? elseSchema.properties : void 0;
|
|
1113
|
+
const hasIfProperties = ifProperties && typeof ifProperties === "object";
|
|
1114
|
+
const hasThenProperties = thenProperties && typeof thenProperties === "object";
|
|
1115
|
+
const hasElseProperties = elseProperties && typeof elseProperties === "object";
|
|
1116
|
+
if (!hasIfProperties && !hasThenProperties && !hasElseProperties) {
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
const required = /* @__PURE__ */ new Set();
|
|
1120
|
+
if (Array.isArray(ifSchema.required)) {
|
|
1121
|
+
for (const key of ifSchema.required) {
|
|
1122
|
+
required.add(key);
|
|
1602
1123
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1124
|
+
}
|
|
1125
|
+
if (hasIfProperties && ifProperties && typeof ifProperties === "object") {
|
|
1126
|
+
for (const [key, propSchema] of Object.entries(ifProperties)) {
|
|
1127
|
+
if (isSchemaObject(propSchema) && hasConst(propSchema)) {
|
|
1128
|
+
required.add(key);
|
|
1129
|
+
}
|
|
1611
1130
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1131
|
+
}
|
|
1132
|
+
if (Array.isArray(thenSchema.required)) {
|
|
1133
|
+
for (const key of thenSchema.required) {
|
|
1134
|
+
required.add(key);
|
|
1616
1135
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
}
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
type: "object",
|
|
1139
|
+
properties: {
|
|
1140
|
+
// else properties first so then properties take precedence on overlap
|
|
1141
|
+
...hasElseProperties ? elseProperties : {},
|
|
1142
|
+
...hasIfProperties ? ifProperties : {},
|
|
1143
|
+
...hasThenProperties ? thenProperties : {}
|
|
1144
|
+
},
|
|
1145
|
+
...required.size > 0 ? { required: Array.from(required) } : {}
|
|
1146
|
+
};
|
|
1627
1147
|
};
|
|
1628
|
-
/**
|
|
1629
|
-
* Generates a parser for SchemaObject that validates all JSON Schema 2020-12 properties.
|
|
1630
|
-
* This handles the special case where a schema can be any valid JSON Schema.
|
|
1631
|
-
*/
|
|
1632
1148
|
const generateSchemaObjectParser = (typeName) => {
|
|
1633
|
-
|
|
1634
|
-
|
|
1149
|
+
const functionName = generateParserName(typeName);
|
|
1150
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1635
1151
|
if (typeof input === 'boolean') {
|
|
1636
1152
|
return input as ${typeName};
|
|
1637
1153
|
}
|
|
@@ -1643,251 +1159,156 @@ const generateSchemaObjectParser = (typeName) => {
|
|
|
1643
1159
|
return input as ${typeName};
|
|
1644
1160
|
};`;
|
|
1645
1161
|
};
|
|
1646
|
-
/**
|
|
1647
|
-
* Determines the appropriate parser generation strategy for a schema.
|
|
1648
|
-
*/
|
|
1649
1162
|
const selectParserStrategy = (schema, typeName, options) => {
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
// Handle non-object schemas with type-appropriate validation (no properties, no conditionals)
|
|
1703
|
-
if (!isObjectLikeSchema) {
|
|
1704
|
-
return generateNonObjectParser(typeName, schema, strict, {
|
|
1705
|
-
useRefImports,
|
|
1706
|
-
suffix,
|
|
1707
|
-
stripUnknown,
|
|
1708
|
-
caseInsensitive,
|
|
1709
|
-
logWarnings,
|
|
1710
|
-
...(options?.rootSchema !== undefined ? { rootSchema: options.rootSchema } : {}),
|
|
1711
|
-
...(options?.reservedNames !== undefined ? { reservedNames: options.reservedNames } : {}),
|
|
1712
|
-
});
|
|
1713
|
-
}
|
|
1714
|
-
// Handle schemas with patternProperties (but no properties)
|
|
1715
|
-
if ('patternProperties' in schema) {
|
|
1716
|
-
return generatePatternPropertiesParser(schema, typeName, useRefImports, suffix, strict);
|
|
1717
|
-
}
|
|
1718
|
-
// Handle schemas with additionalProperties as true or false (but no properties)
|
|
1719
|
-
if ('additionalProperties' in schema && !hasProperties(schema)) {
|
|
1720
|
-
const additionalProps = schema.additionalProperties;
|
|
1721
|
-
// If additionalProperties is true or false, validate it is an object
|
|
1722
|
-
if (additionalProps === true || additionalProps === false) {
|
|
1723
|
-
return generateEmptyObjectParser(typeName, strict, schema);
|
|
1724
|
-
}
|
|
1725
|
-
// Otherwise, handle as a schema (could be a $ref or object schema)
|
|
1726
|
-
return generateAdditionalPropertiesParser(schema, typeName, useRefImports, suffix, strict);
|
|
1727
|
-
}
|
|
1728
|
-
// Handle empty object schemas
|
|
1729
|
-
if ('type' in schema && schema.type === 'object' && !hasProperties(schema)) {
|
|
1730
|
-
return generateEmptyObjectParser(typeName, strict, schema);
|
|
1163
|
+
const useRefImports = options?.useRefImports ?? false;
|
|
1164
|
+
const logWarnings = options?.logWarnings ?? false;
|
|
1165
|
+
const strict = options?.strict ?? false;
|
|
1166
|
+
const stripUnknown = options?.stripUnknown ?? false;
|
|
1167
|
+
const caseInsensitive = options?.caseInsensitive ?? false;
|
|
1168
|
+
const suffix = options?.typeSuffix ?? "";
|
|
1169
|
+
if (!options?.isRoot && typeName === `Schema${suffix}`) {
|
|
1170
|
+
return generateSchemaObjectParser(typeName);
|
|
1171
|
+
}
|
|
1172
|
+
const isObjectLikeSchema = isObjectSchema(schema) || isSchemaObject(schema) && ("patternProperties" in schema || "additionalProperties" in schema);
|
|
1173
|
+
if (!isObjectLikeSchema && !isSchemaObject(schema)) {
|
|
1174
|
+
return generateNonObjectParser(typeName, schema, strict, {
|
|
1175
|
+
useRefImports,
|
|
1176
|
+
suffix,
|
|
1177
|
+
stripUnknown,
|
|
1178
|
+
caseInsensitive,
|
|
1179
|
+
logWarnings,
|
|
1180
|
+
...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
|
|
1181
|
+
...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
if (hasProperties(schema) && isSchemaObject(schema) && "patternProperties" in schema) {
|
|
1185
|
+
return generateCombinedObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, stripUnknown, caseInsensitive);
|
|
1186
|
+
}
|
|
1187
|
+
if (hasProperties(schema)) {
|
|
1188
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, options?.rootSchema, options?.reservedNames, caseInsensitive, options?.shapeValidatorSource);
|
|
1189
|
+
}
|
|
1190
|
+
if (isSchemaObject(schema) && "if" in schema && "then" in schema && "else" in schema) {
|
|
1191
|
+
return generateConditionalParser(schema, typeName, suffix, stripUnknown, caseInsensitive);
|
|
1192
|
+
}
|
|
1193
|
+
const conditionalObjectSchema = getConditionalObjectSchema(schema);
|
|
1194
|
+
if (conditionalObjectSchema) {
|
|
1195
|
+
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
|
|
1196
|
+
}
|
|
1197
|
+
if (!isObjectLikeSchema) {
|
|
1198
|
+
return generateNonObjectParser(typeName, schema, strict, {
|
|
1199
|
+
useRefImports,
|
|
1200
|
+
suffix,
|
|
1201
|
+
stripUnknown,
|
|
1202
|
+
caseInsensitive,
|
|
1203
|
+
logWarnings,
|
|
1204
|
+
...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
|
|
1205
|
+
...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
if ("patternProperties" in schema) {
|
|
1209
|
+
return generatePatternPropertiesParser(schema, typeName, useRefImports, suffix, strict);
|
|
1210
|
+
}
|
|
1211
|
+
if ("additionalProperties" in schema && !hasProperties(schema)) {
|
|
1212
|
+
const additionalProps = schema.additionalProperties;
|
|
1213
|
+
if (additionalProps === true || additionalProps === false) {
|
|
1214
|
+
return generateEmptyObjectParser(typeName, strict, schema);
|
|
1731
1215
|
}
|
|
1732
|
-
|
|
1216
|
+
return generateAdditionalPropertiesParser(schema, typeName, useRefImports, suffix, strict);
|
|
1217
|
+
}
|
|
1218
|
+
if ("type" in schema && schema.type === "object" && !hasProperties(schema)) {
|
|
1733
1219
|
return generateEmptyObjectParser(typeName, strict, schema);
|
|
1220
|
+
}
|
|
1221
|
+
return generateEmptyObjectParser(typeName, strict, schema);
|
|
1734
1222
|
};
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
* When useRefImports is enabled, properties with $ref will call the imported
|
|
1740
|
-
* parser function (e.g., parseContact(input?.contact)) instead of inlining
|
|
1741
|
-
* the resolved schema's validation logic. Array properties whose items are a
|
|
1742
|
-
* $ref will map each element through the imported parser.
|
|
1743
|
-
*/
|
|
1744
|
-
export const generateParserFunction = (schema, typeName, options) => {
|
|
1745
|
-
// Strict parsers promise to throw on violations, so any keyword we cannot
|
|
1746
|
-
// enforce must fail loudly at generation rather than emit a permissive parser.
|
|
1747
|
-
// Coercing parsers are documented to repair rather than reject, so the guard
|
|
1748
|
-
// does not apply to them.
|
|
1749
|
-
if (options?.strict)
|
|
1750
|
-
assertNoUnsupportedKeywords(schema, typeName);
|
|
1751
|
-
return selectParserStrategy(schema, typeName, options);
|
|
1223
|
+
const generateParserFunction = (schema, typeName, options) => {
|
|
1224
|
+
if (options?.strict)
|
|
1225
|
+
assertNoUnsupportedKeywords(schema, typeName);
|
|
1226
|
+
return selectParserStrategy(schema, typeName, options);
|
|
1752
1227
|
};
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
// stripUnknown: the branch checks carry no known-keys terms, so a `true`
|
|
1797
|
-
// could not guarantee the parser's strip build would be a no-op.
|
|
1798
|
-
if (!hasProperties(schema) && !('patternProperties' in schema) && !('if' in schema) && !stripUnknown) {
|
|
1799
|
-
const branches = getUnionBranches(schema);
|
|
1800
|
-
if (branches) {
|
|
1801
|
-
const check = generateUnionCheck('input', branches, useRefImports, suffix);
|
|
1802
|
-
if (check !== null) {
|
|
1803
|
-
return `${exportPrefix}const ${fnName} = (input: unknown): boolean => ${check};`;
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
return stub;
|
|
1228
|
+
const shapeValidatorFunctionName = (typeName) => shapeValidatorName(typeName);
|
|
1229
|
+
const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", exported = true, stripUnknown = false, reservedNames = NO_RESERVED_NAMES) => {
|
|
1230
|
+
const fnName = shapeValidatorName(typeName);
|
|
1231
|
+
const exportPrefix = exported ? "export " : "";
|
|
1232
|
+
const stub = `${exportPrefix}const ${fnName} = (_input: unknown): boolean => false;`;
|
|
1233
|
+
if (!isSchemaObject(schema))
|
|
1234
|
+
return stub;
|
|
1235
|
+
if (hasFastPathBlockingKeyword(schema))
|
|
1236
|
+
return stub;
|
|
1237
|
+
if (useRefImports && hasRef(schema) && !hasProperties(schema)) {
|
|
1238
|
+
const refName = refToName(schema.$ref, suffix);
|
|
1239
|
+
if (refName !== typeName) {
|
|
1240
|
+
return `${exportPrefix}const ${fnName} = (input: unknown): boolean => ${shapeValidatorName(refName)}(input);`;
|
|
1241
|
+
}
|
|
1242
|
+
return stub;
|
|
1243
|
+
}
|
|
1244
|
+
if (!hasProperties(schema) && !("patternProperties" in schema) && !("if" in schema) && !stripUnknown) {
|
|
1245
|
+
const branches = getUnionBranches(schema);
|
|
1246
|
+
if (branches) {
|
|
1247
|
+
const check = generateUnionCheck("input", branches, useRefImports, suffix);
|
|
1248
|
+
if (check !== null) {
|
|
1249
|
+
return `${exportPrefix}const ${fnName} = (input: unknown): boolean => ${check};`;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
return stub;
|
|
1253
|
+
}
|
|
1254
|
+
if (!hasProperties(schema))
|
|
1255
|
+
return stub;
|
|
1256
|
+
if (hasOneOf(schema) || hasAnyOf(schema) || hasAllOf(schema) || "not" in schema || "patternProperties" in schema || "if" in schema || "then" in schema || "else" in schema) {
|
|
1257
|
+
return stub;
|
|
1258
|
+
}
|
|
1259
|
+
const schemaProps = schema.properties;
|
|
1260
|
+
const propertyKeys = Object.keys(schemaProps);
|
|
1261
|
+
const { objects: subTypeNames, arrayItems: subItemNames } = collectInlineSubTypes(schema, typeName, reservedNames);
|
|
1262
|
+
const validatorStripKeys = hasStrictKeys(schema) || stripUnknown;
|
|
1263
|
+
const validatorKeyCount = validatorStripKeys ? exactKeyCountOf(schema) : null;
|
|
1264
|
+
const strictKeysGuard = validatorStripKeys && validatorKeyCount === null ? `
|
|
1265
|
+
if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
|
|
1266
|
+
const checks = [];
|
|
1267
|
+
for (const key of propertyKeys) {
|
|
1268
|
+
const propSchema = schemaProps[key];
|
|
1269
|
+
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
1270
|
+
return stub;
|
|
1807
1271
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
'patternProperties' in schema ||
|
|
1816
|
-
'if' in schema ||
|
|
1817
|
-
'then' in schema ||
|
|
1818
|
-
'else' in schema) {
|
|
1819
|
-
return stub;
|
|
1272
|
+
const accessor = safeAccessor("input", key);
|
|
1273
|
+
const isRequired = isPropertyRequired(key, schema);
|
|
1274
|
+
const subName = subTypeNames.get(key);
|
|
1275
|
+
let check = subName ? `${shapeValidatorName(subName)}(${accessor})` : generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
|
|
1276
|
+
const itemSubName = subItemNames.get(key);
|
|
1277
|
+
if (check !== null && itemSubName) {
|
|
1278
|
+
check = `${check} && _every${itemSubName}(${accessor})`;
|
|
1820
1279
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
// Same deterministic naming as the parser's sub-parser generation, so the
|
|
1824
|
-
// referenced private shape predicates exist in the same file.
|
|
1825
|
-
const { objects: subTypeNames, arrayItems: subItemNames } = collectInlineSubTypes(schema, typeName, reservedNames);
|
|
1826
|
-
// When the parser strips extras (additionalProperties: false or stripUnknown),
|
|
1827
|
-
// an input carrying an undeclared key is *not* fast-path eligible — the parser
|
|
1828
|
-
// would strip it rather than return `{ ...input }` — so the shape only matches
|
|
1829
|
-
// when every key is declared. With every declared property required, the
|
|
1830
|
-
// no-extras test is the cheaper own-key count appended after the typed checks
|
|
1831
|
-
// (which prove all N keys present); otherwise it is the `_hasOnlyKnownKeys`
|
|
1832
|
-
// walk the parser emits under the same `stripKeys` condition.
|
|
1833
|
-
const validatorStripKeys = hasStrictKeys(schema) || stripUnknown;
|
|
1834
|
-
const validatorKeyCount = validatorStripKeys ? exactKeyCountOf(schema) : null;
|
|
1835
|
-
const strictKeysGuard = validatorStripKeys && validatorKeyCount === null
|
|
1836
|
-
? `\n if (!_hasOnlyKnownKeys${typeName}(input)) return false;`
|
|
1837
|
-
: '';
|
|
1838
|
-
const checks = [];
|
|
1839
|
-
for (const key of propertyKeys) {
|
|
1840
|
-
const propSchema = schemaProps[key];
|
|
1841
|
-
// Records of refs require iterating every value — too expensive for the fast path.
|
|
1842
|
-
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
1843
|
-
return stub;
|
|
1844
|
-
}
|
|
1845
|
-
const accessor = safeAccessor('input', key);
|
|
1846
|
-
const isRequired = isPropertyRequired(key, schema);
|
|
1847
|
-
const subName = subTypeNames.get(key);
|
|
1848
|
-
let check = subName
|
|
1849
|
-
? `${shapeValidatorName(subName)}(${accessor})`
|
|
1850
|
-
: generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
|
|
1851
|
-
// Arrays of inline objects prove every element via the private item loop
|
|
1852
|
-
// helper (emitted alongside the item sub-parser), matching the parser's
|
|
1853
|
-
// fast-path guard.
|
|
1854
|
-
const itemSubName = subItemNames.get(key);
|
|
1855
|
-
if (check !== null && itemSubName) {
|
|
1856
|
-
check = `${check} && _every${itemSubName}(${accessor})`;
|
|
1857
|
-
}
|
|
1858
|
-
if (check === null) {
|
|
1859
|
-
return stub;
|
|
1860
|
-
}
|
|
1861
|
-
if (isRequired) {
|
|
1862
|
-
checks.push(check);
|
|
1863
|
-
}
|
|
1864
|
-
else {
|
|
1865
|
-
checks.push(`(${accessor} === undefined || ${check})`);
|
|
1866
|
-
}
|
|
1280
|
+
if (check === null) {
|
|
1281
|
+
return stub;
|
|
1867
1282
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
// parser's own fast path.
|
|
1873
|
-
if (validatorKeyCount !== null) {
|
|
1874
|
-
checks.push(`Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${validatorKeyCount}`);
|
|
1283
|
+
if (isRequired) {
|
|
1284
|
+
checks.push(check);
|
|
1285
|
+
} else {
|
|
1286
|
+
checks.push(`(${accessor} === undefined || ${check})`);
|
|
1875
1287
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1288
|
+
}
|
|
1289
|
+
if (validatorKeyCount !== null) {
|
|
1290
|
+
checks.push(`Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${validatorKeyCount}`);
|
|
1291
|
+
}
|
|
1292
|
+
if (checks.length === 0) {
|
|
1293
|
+
if (strictKeysGuard) {
|
|
1294
|
+
return `${exportPrefix}const ${fnName} = (input: unknown): boolean => {
|
|
1879
1295
|
if (!isObject(input)) return false;${strictKeysGuard}
|
|
1880
1296
|
return true;
|
|
1881
1297
|
};`;
|
|
1882
|
-
}
|
|
1883
|
-
return `${exportPrefix}const ${fnName} = (input: unknown): boolean => isObject(input);`;
|
|
1884
|
-
}
|
|
1885
|
-
let body = checks[0];
|
|
1886
|
-
for (let i = 1; i < checks.length; i++) {
|
|
1887
|
-
body += '\n && ' + checks[i];
|
|
1888
1298
|
}
|
|
1889
|
-
return `${exportPrefix}const ${fnName} = (input: unknown): boolean =>
|
|
1299
|
+
return `${exportPrefix}const ${fnName} = (input: unknown): boolean => isObject(input);`;
|
|
1300
|
+
}
|
|
1301
|
+
let body = checks[0];
|
|
1302
|
+
for (let i = 1; i < checks.length; i++) {
|
|
1303
|
+
body += "\n && " + checks[i];
|
|
1304
|
+
}
|
|
1305
|
+
return `${exportPrefix}const ${fnName} = (input: unknown): boolean => {
|
|
1890
1306
|
if (!isObject(input)) return false;${strictKeysGuard}
|
|
1891
1307
|
return ${body};
|
|
1892
1308
|
};`;
|
|
1893
1309
|
};
|
|
1310
|
+
export {
|
|
1311
|
+
generateParserFunction,
|
|
1312
|
+
generateShapeValidator,
|
|
1313
|
+
shapeValidatorFunctionName
|
|
1314
|
+
};
|