@amritk/generate-parsers 0.5.0 → 0.6.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/dist/generators/build-schema.d.ts +4 -1
- package/dist/generators/generate-files.d.ts +5 -0
- package/dist/generators/generate-parser-function.d.ts +6 -1
- package/dist/helpers/collect-imports.d.ts +6 -0
- package/dist/index.js +113 -80
- package/package.json +2 -2
- package/src/generators/build-schema.test.ts +17 -17
- package/src/generators/build-schema.ts +7 -1
- package/src/generators/generate-files.test.ts +65 -52
- package/src/generators/generate-files.ts +11 -4
- package/src/generators/generate-parser-function.test.ts +254 -258
- package/src/generators/generate-parser-function.ts +87 -58
- package/src/helpers/collect-imports.test.ts +44 -84
- package/src/helpers/collect-imports.ts +8 -1
|
@@ -53,6 +53,11 @@ type GenerateParserOptions = {
|
|
|
53
53
|
* with a path-aware Error. Unknown extra keys are still allowed.
|
|
54
54
|
*/
|
|
55
55
|
readonly strict?: boolean
|
|
56
|
+
/**
|
|
57
|
+
* Suffix appended to every type/parser name derived from a `$ref`. Must match
|
|
58
|
+
* the suffix used when generating the referenced files. Defaults to `''`.
|
|
59
|
+
*/
|
|
60
|
+
readonly typeSuffix?: string
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
/**
|
|
@@ -87,60 +92,60 @@ const isPropertyRequired = (key: string, schema: JSONSchema): boolean => {
|
|
|
87
92
|
|
|
88
93
|
/**
|
|
89
94
|
* Generates a parser call expression for a required $ref property.
|
|
90
|
-
* Example:
|
|
95
|
+
* Example: parseContact(input.contact)
|
|
91
96
|
*/
|
|
92
|
-
const generateRequiredRefCall = (key: string, ref: string): string => {
|
|
97
|
+
const generateRequiredRefCall = (key: string, ref: string, suffix: string): string => {
|
|
93
98
|
const acc = safeAccessor('input', key)
|
|
94
|
-
const parserName = generateParserName(refToName(ref))
|
|
99
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
95
100
|
return `${parserName}(${acc})`
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
/**
|
|
99
104
|
* Generates a parser call expression for an optional $ref property.
|
|
100
|
-
* Example: ...(input.contact && { contact:
|
|
105
|
+
* Example: ...(input.contact && { contact: parseContact(input.contact) })
|
|
101
106
|
*/
|
|
102
|
-
const generateOptionalRefCall = (key: string, ref: string): string => {
|
|
107
|
+
const generateOptionalRefCall = (key: string, ref: string, suffix: string): string => {
|
|
103
108
|
const acc = safeAccessor('input', key)
|
|
104
|
-
const parserName = generateParserName(refToName(ref))
|
|
109
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
105
110
|
return `...(${acc} && { ${safeKey(key)}: ${parserName}(${acc}) })`
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
/**
|
|
109
114
|
* Generates a validateArray call for required array properties with $ref items.
|
|
110
|
-
* Example: validateArray(input.contacts,
|
|
115
|
+
* Example: validateArray(input.contacts, parseContact)
|
|
111
116
|
*/
|
|
112
|
-
const generateRequiredArrayRefCall = (key: string, ref: string): string => {
|
|
113
|
-
const parserName = generateParserName(refToName(ref))
|
|
117
|
+
const generateRequiredArrayRefCall = (key: string, ref: string, suffix: string): string => {
|
|
118
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
114
119
|
return `validateArray(${safeAccessor('input', key)}, ${parserName})`
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
/**
|
|
118
123
|
* Generates a validateArray call for optional array properties with $ref items.
|
|
119
|
-
* Example: ...(input.contacts && { contacts: validateArray(input.contacts,
|
|
124
|
+
* Example: ...(input.contacts && { contacts: validateArray(input.contacts, parseContact) })
|
|
120
125
|
*/
|
|
121
|
-
const generateOptionalArrayRefCall = (key: string, ref: string): string => {
|
|
122
|
-
const parserName = generateParserName(refToName(ref))
|
|
126
|
+
const generateOptionalArrayRefCall = (key: string, ref: string, suffix: string): string => {
|
|
127
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
123
128
|
const acc = safeAccessor('input', key)
|
|
124
129
|
return `...(${acc} && { ${safeKey(key)}: validateArray(${acc}, ${parserName}) })`
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
/**
|
|
128
133
|
* Generates a validateRecord call for required object properties with additionalProperties $ref.
|
|
129
|
-
* Example: validateRecord(input.responses,
|
|
134
|
+
* Example: validateRecord(input.responses, parseResponse)
|
|
130
135
|
*/
|
|
131
|
-
const generateRequiredRecordRefCall = (key: string, ref: string): string => {
|
|
136
|
+
const generateRequiredRecordRefCall = (key: string, ref: string, suffix: string): string => {
|
|
132
137
|
const acc = safeAccessor('input', key)
|
|
133
|
-
const parserName = generateParserName(refToName(ref))
|
|
138
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
134
139
|
return `validateRecord(${acc}, ${parserName})`
|
|
135
140
|
}
|
|
136
141
|
|
|
137
142
|
/**
|
|
138
143
|
* Generates a validateRecord call for optional object properties with additionalProperties $ref.
|
|
139
|
-
* Example: ...(input.responses && { responses: validateRecord(input.responses,
|
|
144
|
+
* Example: ...(input.responses && { responses: validateRecord(input.responses, parseResponse) })
|
|
140
145
|
*/
|
|
141
|
-
const generateOptionalRecordRefCall = (key: string, ref: string): string => {
|
|
146
|
+
const generateOptionalRecordRefCall = (key: string, ref: string, suffix: string): string => {
|
|
142
147
|
const acc = safeAccessor('input', key)
|
|
143
|
-
const parserName = generateParserName(refToName(ref))
|
|
148
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
144
149
|
return `...(${acc} && { ${safeKey(key)}: validateRecord(${acc}, ${parserName}) })`
|
|
145
150
|
}
|
|
146
151
|
|
|
@@ -214,25 +219,28 @@ const generatePropertyValue = (
|
|
|
214
219
|
propSchema: JSONSchema,
|
|
215
220
|
isRequired: boolean,
|
|
216
221
|
useRefImports: boolean,
|
|
222
|
+
suffix: string,
|
|
217
223
|
): string => {
|
|
218
224
|
// Handle direct $ref properties
|
|
219
225
|
if (shouldUseRefImport(propSchema, useRefImports)) {
|
|
220
226
|
const ref = (propSchema as { $ref: string }).$ref
|
|
221
|
-
return isRequired ? generateRequiredRefCall(key, ref) : generateOptionalRefCall(key, ref)
|
|
227
|
+
return isRequired ? generateRequiredRefCall(key, ref, suffix) : generateOptionalRefCall(key, ref, suffix)
|
|
222
228
|
}
|
|
223
229
|
|
|
224
230
|
// Handle array properties with $ref items
|
|
225
231
|
if (shouldUseArrayRefImport(propSchema, useRefImports)) {
|
|
226
232
|
const items = (propSchema as { items: { $ref: string } }).items
|
|
227
233
|
const ref = items.$ref
|
|
228
|
-
return isRequired ? generateRequiredArrayRefCall(key, ref) : generateOptionalArrayRefCall(key, ref)
|
|
234
|
+
return isRequired ? generateRequiredArrayRefCall(key, ref, suffix) : generateOptionalArrayRefCall(key, ref, suffix)
|
|
229
235
|
}
|
|
230
236
|
|
|
231
237
|
// Handle object properties with additionalProperties $ref
|
|
232
238
|
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
233
239
|
const additionalProps = (propSchema as { additionalProperties: { $ref: string } }).additionalProperties
|
|
234
240
|
const ref = additionalProps.$ref
|
|
235
|
-
return isRequired
|
|
241
|
+
return isRequired
|
|
242
|
+
? generateRequiredRecordRefCall(key, ref, suffix)
|
|
243
|
+
: generateOptionalRecordRefCall(key, ref, suffix)
|
|
236
244
|
}
|
|
237
245
|
|
|
238
246
|
// Handle non-schema object properties (true/false)
|
|
@@ -249,7 +257,7 @@ const generatePropertyValue = (
|
|
|
249
257
|
/**
|
|
250
258
|
* Generates property entries for all properties in the schema.
|
|
251
259
|
*/
|
|
252
|
-
const generatePropertyEntries = (schema: JSONSchema, useRefImports: boolean): PropertyEntry[] => {
|
|
260
|
+
const generatePropertyEntries = (schema: JSONSchema, useRefImports: boolean, suffix: string): PropertyEntry[] => {
|
|
253
261
|
if (!hasProperties(schema)) {
|
|
254
262
|
return []
|
|
255
263
|
}
|
|
@@ -258,7 +266,7 @@ const generatePropertyEntries = (schema: JSONSchema, useRefImports: boolean): Pr
|
|
|
258
266
|
|
|
259
267
|
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
260
268
|
const isRequired = isPropertyRequired(key, schema)
|
|
261
|
-
const value = generatePropertyValue(key, propSchema, isRequired, useRefImports)
|
|
269
|
+
const value = generatePropertyValue(key, propSchema, isRequired, useRefImports, suffix)
|
|
262
270
|
|
|
263
271
|
// Determine if the property uses spread syntax (making it optional in the object literal)
|
|
264
272
|
const isOptional = value.startsWith('...')
|
|
@@ -274,11 +282,11 @@ const generatePropertyEntries = (schema: JSONSchema, useRefImports: boolean): Pr
|
|
|
274
282
|
* This returns simple type-based defaults without pattern inference,
|
|
275
283
|
* matching TypeBox's coercion behavior.
|
|
276
284
|
*/
|
|
277
|
-
const generateFallbackValue = (_: string, propSchema: JSONSchema, useRefImports: boolean): string => {
|
|
285
|
+
const generateFallbackValue = (_: string, propSchema: JSONSchema, useRefImports: boolean, suffix: string): string => {
|
|
278
286
|
// Handle direct $ref properties - call the parser with undefined
|
|
279
287
|
if (useRefImports && hasRef(propSchema)) {
|
|
280
288
|
const ref = (propSchema as { $ref: string }).$ref
|
|
281
|
-
const parserName = generateParserName(refToName(ref))
|
|
289
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
282
290
|
return `${parserName}(undefined)`
|
|
283
291
|
}
|
|
284
292
|
|
|
@@ -352,7 +360,12 @@ const generateFallbackValue = (_: string, propSchema: JSONSchema, useRefImports:
|
|
|
352
360
|
* Generates a fallback object with required properties filled with default values.
|
|
353
361
|
* This is used when input is not an object (undefined, null, etc.).
|
|
354
362
|
*/
|
|
355
|
-
const generateFallbackObject = (
|
|
363
|
+
const generateFallbackObject = (
|
|
364
|
+
schema: JSONSchema,
|
|
365
|
+
useRefImports: boolean,
|
|
366
|
+
typeName: string,
|
|
367
|
+
suffix: string,
|
|
368
|
+
): string => {
|
|
356
369
|
if (!hasProperties(schema)) {
|
|
357
370
|
return `{} as ${typeName}`
|
|
358
371
|
}
|
|
@@ -369,7 +382,7 @@ const generateFallbackObject = (schema: JSONSchema, useRefImports: boolean, type
|
|
|
369
382
|
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
370
383
|
const isRequired = isPropertyRequired(key, schema)
|
|
371
384
|
if (isRequired) {
|
|
372
|
-
const fallbackValue = generateFallbackValue(key, propSchema, useRefImports)
|
|
385
|
+
const fallbackValue = generateFallbackValue(key, propSchema, useRefImports, suffix)
|
|
373
386
|
if (fallbackValue === 'undefined') {
|
|
374
387
|
return `{} as ${typeName}`
|
|
375
388
|
}
|
|
@@ -467,12 +480,17 @@ const shapeValidatorName = (typeName: string): string => `validate${typeName}Sha
|
|
|
467
480
|
* checked by calling the imported shape predicate, allowing parent parsers
|
|
468
481
|
* to fast-path through nested ref types without recursing into their parsers.
|
|
469
482
|
*/
|
|
470
|
-
const generatePropertyTypeCheck = (
|
|
483
|
+
const generatePropertyTypeCheck = (
|
|
484
|
+
varName: string,
|
|
485
|
+
schema: JSONSchema,
|
|
486
|
+
useRefImports: boolean,
|
|
487
|
+
suffix: string,
|
|
488
|
+
): string | null => {
|
|
471
489
|
if (!isSchemaObject(schema)) return null
|
|
472
490
|
|
|
473
491
|
// $ref via shape predicate (deep fast-path through nested types).
|
|
474
492
|
if (useRefImports && hasRef(schema)) {
|
|
475
|
-
const refName = refToName((schema as { $ref: string }).$ref)
|
|
493
|
+
const refName = refToName((schema as { $ref: string }).$ref, suffix)
|
|
476
494
|
return `${shapeValidatorName(refName)}(${varName})`
|
|
477
495
|
}
|
|
478
496
|
|
|
@@ -498,7 +516,7 @@ const generatePropertyTypeCheck = (varName: string, schema: JSONSchema, useRefIm
|
|
|
498
516
|
isSchemaObject(schema.items) &&
|
|
499
517
|
hasRef(schema.items)
|
|
500
518
|
) {
|
|
501
|
-
const refName = refToName((schema.items as { $ref: string }).$ref)
|
|
519
|
+
const refName = refToName((schema.items as { $ref: string }).$ref, suffix)
|
|
502
520
|
return `Array.isArray(${varName}) && ${varName}.every(${shapeValidatorName(refName)})`
|
|
503
521
|
}
|
|
504
522
|
|
|
@@ -575,6 +593,7 @@ const generateObjectParser = (
|
|
|
575
593
|
schema: JSONSchema,
|
|
576
594
|
typeName: string,
|
|
577
595
|
useRefImports: boolean,
|
|
596
|
+
suffix: string,
|
|
578
597
|
logWarnings?: boolean,
|
|
579
598
|
strict?: boolean,
|
|
580
599
|
): string => {
|
|
@@ -587,7 +606,7 @@ const generateObjectParser = (
|
|
|
587
606
|
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? input as ${typeName} : {} as ${typeName};`
|
|
588
607
|
}
|
|
589
608
|
|
|
590
|
-
const fallbackObject = generateFallbackObject(schema, useRefImports, typeName)
|
|
609
|
+
const fallbackObject = generateFallbackObject(schema, useRefImports, typeName, suffix)
|
|
591
610
|
const properties = Object.entries((schema as { properties: Record<string, JSONSchema> }).properties)
|
|
592
611
|
|
|
593
612
|
const lines: string[] = []
|
|
@@ -631,7 +650,7 @@ const generateObjectParser = (
|
|
|
631
650
|
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
632
651
|
canFastPath = false
|
|
633
652
|
} else {
|
|
634
|
-
const check = generatePropertyTypeCheck(varName, propSchema, useRefImports)
|
|
653
|
+
const check = generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix)
|
|
635
654
|
if (check === null) {
|
|
636
655
|
canFastPath = false
|
|
637
656
|
} else {
|
|
@@ -681,7 +700,7 @@ const generateObjectParser = (
|
|
|
681
700
|
if (useRefImports && isSchemaObject(schema) && hasAllOf(schema)) {
|
|
682
701
|
for (const entry of schema.allOf) {
|
|
683
702
|
if (isSchemaObject(entry) && hasRef(entry)) {
|
|
684
|
-
const parserName = generateParserName(refToName(entry.$ref))
|
|
703
|
+
const parserName = generateParserName(refToName(entry.$ref, suffix))
|
|
685
704
|
objectLines.push(` ...(${parserName}(input) as Record<string, unknown>),`)
|
|
686
705
|
}
|
|
687
706
|
}
|
|
@@ -694,7 +713,7 @@ const generateObjectParser = (
|
|
|
694
713
|
// Handle direct $ref properties via imported parsers
|
|
695
714
|
if (shouldUseRefImport(propSchema, useRefImports)) {
|
|
696
715
|
const ref = (propSchema as { $ref: string }).$ref
|
|
697
|
-
const parserName = generateParserName(refToName(ref))
|
|
716
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
698
717
|
if (isRequired) {
|
|
699
718
|
objectLines.push(` ${safeKey(key)}: ${parserName}(${accessor}),`)
|
|
700
719
|
} else {
|
|
@@ -707,7 +726,7 @@ const generateObjectParser = (
|
|
|
707
726
|
if (shouldUseArrayRefImport(propSchema, useRefImports)) {
|
|
708
727
|
const items = (propSchema as { items: { $ref: string } }).items
|
|
709
728
|
const ref = items.$ref
|
|
710
|
-
const parserName = generateParserName(refToName(ref))
|
|
729
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
711
730
|
if (isRequired) {
|
|
712
731
|
objectLines.push(` ${safeKey(key)}: validateArray(${accessor}, ${parserName}),`)
|
|
713
732
|
} else {
|
|
@@ -722,7 +741,7 @@ const generateObjectParser = (
|
|
|
722
741
|
if (shouldUseRecordRefImport(propSchema, useRefImports)) {
|
|
723
742
|
const additionalProps = (propSchema as { additionalProperties: { $ref: string } }).additionalProperties
|
|
724
743
|
const ref = additionalProps.$ref
|
|
725
|
-
const parserName = generateParserName(refToName(ref))
|
|
744
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
726
745
|
if (isRequired) {
|
|
727
746
|
objectLines.push(` ${safeKey(key)}: validateRecord(${accessor}, ${parserName}),`)
|
|
728
747
|
} else {
|
|
@@ -790,11 +809,12 @@ const generateCombinedObjectParser = (
|
|
|
790
809
|
schema: JSONSchema,
|
|
791
810
|
typeName: string,
|
|
792
811
|
useRefImports: boolean,
|
|
812
|
+
suffix: string,
|
|
793
813
|
logWarnings?: boolean,
|
|
794
814
|
strict?: boolean,
|
|
795
815
|
): string => {
|
|
796
816
|
const functionName = generateParserName(typeName)
|
|
797
|
-
const entries = generatePropertyEntries(schema, useRefImports)
|
|
817
|
+
const entries = generatePropertyEntries(schema, useRefImports, suffix)
|
|
798
818
|
|
|
799
819
|
// Build the known property lines for the initial object
|
|
800
820
|
const propertyLines = entries.map((entry) => {
|
|
@@ -806,7 +826,7 @@ const generateCombinedObjectParser = (
|
|
|
806
826
|
|
|
807
827
|
// Find the first pattern with a $ref for parser delegation
|
|
808
828
|
if (!isSchemaObject(schema) || !('patternProperties' in schema)) {
|
|
809
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict)
|
|
829
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict)
|
|
810
830
|
}
|
|
811
831
|
|
|
812
832
|
const patternProps = schema.patternProperties as Record<string, JSONSchema>
|
|
@@ -814,12 +834,12 @@ const generateCombinedObjectParser = (
|
|
|
814
834
|
const refPattern = patterns.find(([, ps]) => isSchemaObject(ps) && hasRef(ps))
|
|
815
835
|
|
|
816
836
|
if (!refPattern || !useRefImports) {
|
|
817
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict)
|
|
837
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict)
|
|
818
838
|
}
|
|
819
839
|
|
|
820
840
|
const [pattern, patternSchema] = refPattern
|
|
821
841
|
const ref = (patternSchema as { $ref: string }).$ref
|
|
822
|
-
const parserName = generateParserName(refToName(ref))
|
|
842
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
823
843
|
const assignmentCode = `(result as Record<string, unknown>)[key] = ${parserName}(value);`
|
|
824
844
|
|
|
825
845
|
const escapedPattern = escapeRegexPattern(pattern)
|
|
@@ -860,6 +880,7 @@ const generateAdditionalPropertiesParser = (
|
|
|
860
880
|
schema: JSONSchema.Object,
|
|
861
881
|
typeName: string,
|
|
862
882
|
useRefImports: boolean,
|
|
883
|
+
suffix: string,
|
|
863
884
|
strict?: boolean,
|
|
864
885
|
): string => {
|
|
865
886
|
const functionName = generateParserName(typeName)
|
|
@@ -873,7 +894,7 @@ const generateAdditionalPropertiesParser = (
|
|
|
873
894
|
// If additionalProperties is a $ref and useRefImports is true, generate a loop
|
|
874
895
|
if (useRefImports && isSchemaObject(additionalProps) && hasRef(additionalProps)) {
|
|
875
896
|
const ref = additionalProps.$ref
|
|
876
|
-
const parserName = generateParserName(refToName(ref))
|
|
897
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
877
898
|
|
|
878
899
|
if (strict) {
|
|
879
900
|
return `export const ${functionName} = (input: unknown): ${typeName} => {\n if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);\n return validateRecord(input, ${parserName}) as ${typeName};\n};`
|
|
@@ -943,6 +964,7 @@ const generatePatternPropertiesParser = (
|
|
|
943
964
|
schema: JSONSchema.Object,
|
|
944
965
|
typeName: string,
|
|
945
966
|
useRefImports: boolean,
|
|
967
|
+
suffix: string,
|
|
946
968
|
strict?: boolean,
|
|
947
969
|
): string => {
|
|
948
970
|
const functionName = generateParserName(typeName)
|
|
@@ -965,7 +987,7 @@ const generatePatternPropertiesParser = (
|
|
|
965
987
|
// Use imported parser when pattern schema points to a $ref.
|
|
966
988
|
if (useRefImports && isSchemaObject(patternSchema) && hasRef(patternSchema)) {
|
|
967
989
|
const ref = patternSchema.$ref
|
|
968
|
-
const parserName = generateParserName(refToName(ref))
|
|
990
|
+
const parserName = generateParserName(refToName(ref, suffix))
|
|
969
991
|
patternAssignment = `(result as Record<string, unknown>)[key] = ${parserName}(value);`
|
|
970
992
|
} else if (patternSchema === false) {
|
|
971
993
|
// `false` means no values are allowed for matching keys.
|
|
@@ -1002,7 +1024,7 @@ ${notObjectBranch}
|
|
|
1002
1024
|
* This always generates parser calls regardless of useRefImports setting,
|
|
1003
1025
|
* because conditional logic requires delegating to the appropriate parser.
|
|
1004
1026
|
*/
|
|
1005
|
-
const generateConditionalParser = (schema: JSONSchema.Object, typeName: string): string => {
|
|
1027
|
+
const generateConditionalParser = (schema: JSONSchema.Object, typeName: string, suffix: string): string => {
|
|
1006
1028
|
const functionName = generateParserName(typeName)
|
|
1007
1029
|
|
|
1008
1030
|
// Extract the condition and branches
|
|
@@ -1020,7 +1042,7 @@ const generateConditionalParser = (schema: JSONSchema.Object, typeName: string):
|
|
|
1020
1042
|
// regular object parser from the merged property set.
|
|
1021
1043
|
const mergedSchema = getConditionalObjectSchema(schema)
|
|
1022
1044
|
if (mergedSchema) {
|
|
1023
|
-
return generateObjectParser(mergedSchema, typeName, false)
|
|
1045
|
+
return generateObjectParser(mergedSchema, typeName, false, suffix)
|
|
1024
1046
|
}
|
|
1025
1047
|
return generateEmptyObjectParser(typeName)
|
|
1026
1048
|
}
|
|
@@ -1037,7 +1059,7 @@ const generateConditionalParser = (schema: JSONSchema.Object, typeName: string):
|
|
|
1037
1059
|
// Condition is not a $ref check — flatten into an object parser
|
|
1038
1060
|
const mergedSchema = getConditionalObjectSchema(schema)
|
|
1039
1061
|
if (mergedSchema) {
|
|
1040
|
-
return generateObjectParser(mergedSchema, typeName, false)
|
|
1062
|
+
return generateObjectParser(mergedSchema, typeName, false, suffix)
|
|
1041
1063
|
}
|
|
1042
1064
|
return generateEmptyObjectParser(typeName)
|
|
1043
1065
|
}
|
|
@@ -1045,9 +1067,9 @@ const generateConditionalParser = (schema: JSONSchema.Object, typeName: string):
|
|
|
1045
1067
|
const thenRef = thenSchema.$ref
|
|
1046
1068
|
const elseRef = elseSchema.$ref
|
|
1047
1069
|
|
|
1048
|
-
const thenParserName = generateParserName(refToName(thenRef))
|
|
1049
|
-
const elseParserName = generateParserName(refToName(elseRef))
|
|
1050
|
-
const thenTypeName = refToName(thenRef)
|
|
1070
|
+
const thenParserName = generateParserName(refToName(thenRef, suffix))
|
|
1071
|
+
const elseParserName = generateParserName(refToName(elseRef, suffix))
|
|
1072
|
+
const thenTypeName = refToName(thenRef, suffix)
|
|
1051
1073
|
|
|
1052
1074
|
return `export const ${functionName} = (input: unknown): ${typeName} | ${thenTypeName} =>
|
|
1053
1075
|
hasRef(input) ? ${thenParserName}(input) : ${elseParserName}(input)
|
|
@@ -1149,9 +1171,11 @@ const selectParserStrategy = (schema: JSONSchema, typeName: string, options?: Ge
|
|
|
1149
1171
|
const useRefImports = options?.useRefImports ?? false
|
|
1150
1172
|
const logWarnings = options?.logWarnings ?? false
|
|
1151
1173
|
const strict = options?.strict ?? false
|
|
1174
|
+
const suffix = options?.typeSuffix ?? ''
|
|
1152
1175
|
|
|
1153
|
-
// Special case for
|
|
1154
|
-
|
|
1176
|
+
// Special case for the self-referential JSON Schema meta-schema type (e.g.
|
|
1177
|
+
// `Schema` / `SchemaObject`) - it can be any JSON Schema.
|
|
1178
|
+
if (typeName === `Schema${suffix}`) {
|
|
1155
1179
|
return generateSchemaObjectParser(typeName)
|
|
1156
1180
|
}
|
|
1157
1181
|
|
|
@@ -1168,7 +1192,7 @@ const selectParserStrategy = (schema: JSONSchema, typeName: string, options?: Ge
|
|
|
1168
1192
|
// This generates a parser that handles known properties and also iterates
|
|
1169
1193
|
// pattern-matched keys (e.g. responses with "default" + "200", "4XX").
|
|
1170
1194
|
if (hasProperties(schema) && isSchemaObject(schema) && 'patternProperties' in schema) {
|
|
1171
|
-
return generateCombinedObjectParser(schema, typeName, useRefImports, logWarnings, strict)
|
|
1195
|
+
return generateCombinedObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict)
|
|
1172
1196
|
}
|
|
1173
1197
|
|
|
1174
1198
|
// Handle schemas that have explicit properties — generate a full object parser.
|
|
@@ -1176,19 +1200,19 @@ const selectParserStrategy = (schema: JSONSchema, typeName: string, options?: Ge
|
|
|
1176
1200
|
// properties AND conditional keywords use all declared properties rather than
|
|
1177
1201
|
// only the if/then fragment.
|
|
1178
1202
|
if (hasProperties(schema)) {
|
|
1179
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict)
|
|
1203
|
+
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict)
|
|
1180
1204
|
}
|
|
1181
1205
|
|
|
1182
1206
|
// Handle conditional schemas (if/then/else) for schemas without explicit properties.
|
|
1183
1207
|
if (isSchemaObject(schema) && 'if' in schema && 'then' in schema && 'else' in schema) {
|
|
1184
|
-
return generateConditionalParser(schema as JSONSchema.Object, typeName)
|
|
1208
|
+
return generateConditionalParser(schema as JSONSchema.Object, typeName, suffix)
|
|
1185
1209
|
}
|
|
1186
1210
|
|
|
1187
1211
|
// Handle conditional schemas that only define if/then object fragments.
|
|
1188
1212
|
// We flatten the fragments into a regular object parser.
|
|
1189
1213
|
const conditionalObjectSchema = getConditionalObjectSchema(schema)
|
|
1190
1214
|
if (conditionalObjectSchema) {
|
|
1191
|
-
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, logWarnings, strict)
|
|
1215
|
+
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, suffix, logWarnings, strict)
|
|
1192
1216
|
}
|
|
1193
1217
|
|
|
1194
1218
|
// Handle non-object schemas with type-appropriate validation (no properties, no conditionals)
|
|
@@ -1198,7 +1222,7 @@ const selectParserStrategy = (schema: JSONSchema, typeName: string, options?: Ge
|
|
|
1198
1222
|
|
|
1199
1223
|
// Handle schemas with patternProperties (but no properties)
|
|
1200
1224
|
if ('patternProperties' in schema) {
|
|
1201
|
-
return generatePatternPropertiesParser(schema as JSONSchema.Object, typeName, useRefImports, strict)
|
|
1225
|
+
return generatePatternPropertiesParser(schema as JSONSchema.Object, typeName, useRefImports, suffix, strict)
|
|
1202
1226
|
}
|
|
1203
1227
|
|
|
1204
1228
|
// Handle schemas with additionalProperties as true or false (but no properties)
|
|
@@ -1211,7 +1235,7 @@ const selectParserStrategy = (schema: JSONSchema, typeName: string, options?: Ge
|
|
|
1211
1235
|
}
|
|
1212
1236
|
|
|
1213
1237
|
// Otherwise, handle as a schema (could be a $ref or object schema)
|
|
1214
|
-
return generateAdditionalPropertiesParser(schema as JSONSchema.Object, typeName, useRefImports, strict)
|
|
1238
|
+
return generateAdditionalPropertiesParser(schema as JSONSchema.Object, typeName, useRefImports, suffix, strict)
|
|
1215
1239
|
}
|
|
1216
1240
|
|
|
1217
1241
|
// Handle empty object schemas
|
|
@@ -1259,7 +1283,12 @@ export const shapeValidatorFunctionName = (typeName: string): string => shapeVal
|
|
|
1259
1283
|
* in additionalProperties). The stub is safe: parents calling it will fall
|
|
1260
1284
|
* through to the slow path, matching the pre-deep-fast-path behavior.
|
|
1261
1285
|
*/
|
|
1262
|
-
export const generateShapeValidator = (
|
|
1286
|
+
export const generateShapeValidator = (
|
|
1287
|
+
schema: JSONSchema,
|
|
1288
|
+
typeName: string,
|
|
1289
|
+
useRefImports: boolean,
|
|
1290
|
+
suffix = '',
|
|
1291
|
+
): string => {
|
|
1263
1292
|
const fnName = shapeValidatorName(typeName)
|
|
1264
1293
|
const stub = `export const ${fnName} = (_input: unknown): boolean => false;`
|
|
1265
1294
|
|
|
@@ -1291,7 +1320,7 @@ export const generateShapeValidator = (schema: JSONSchema, typeName: string, use
|
|
|
1291
1320
|
|
|
1292
1321
|
const accessor = safeAccessor('input', key)
|
|
1293
1322
|
const isRequired = isPropertyRequired(key, schema)
|
|
1294
|
-
const check = generatePropertyTypeCheck(accessor, propSchema, useRefImports)
|
|
1323
|
+
const check = generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix)
|
|
1295
1324
|
if (check === null) {
|
|
1296
1325
|
return stub
|
|
1297
1326
|
}
|