@alt-stack/zod-openapi 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/dist/index.cjs +404 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +401 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/interface-generator.spec.ts +595 -0
- package/src/interface-generator.ts +211 -0
- package/src/schema-dedup.ts +199 -0
- package/src/to-typescript.spec.ts +218 -0
- package/src/to-typescript.ts +253 -36
- package/src/to-zod.spec.ts +10 -6
package/src/to-typescript.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { topologicalSortSchemas } from "./dependencies";
|
|
2
|
+
import {
|
|
3
|
+
createSchemaRegistry,
|
|
4
|
+
findCommonSchemas,
|
|
5
|
+
getSchemaFingerprint,
|
|
6
|
+
preRegisterSchema,
|
|
7
|
+
registerSchema,
|
|
8
|
+
type SchemaRegistry,
|
|
9
|
+
} from "./schema-dedup";
|
|
2
10
|
import { convertSchemaToZodString } from "./to-zod";
|
|
3
11
|
import type { AnySchema } from "./types/types";
|
|
4
12
|
import {
|
|
@@ -6,6 +14,7 @@ import {
|
|
|
6
14
|
generateRouteSchemaNames,
|
|
7
15
|
type RouteInfo,
|
|
8
16
|
} from "./routes";
|
|
17
|
+
import { generateInterface, schemaToTypeString } from "./interface-generator";
|
|
9
18
|
|
|
10
19
|
const validIdentifierRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
11
20
|
|
|
@@ -39,12 +48,24 @@ function generateRouteSchemaName(
|
|
|
39
48
|
return parts.join("");
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Result of route schema generation including declarations and name mappings.
|
|
53
|
+
*/
|
|
54
|
+
interface RouteSchemaResult {
|
|
55
|
+
/** Schema declarations to be emitted */
|
|
56
|
+
declarations: string[];
|
|
57
|
+
/** Maps route-specific schema name to its canonical name (for deduplication) */
|
|
58
|
+
schemaNameToCanonical: Map<string, string>;
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
function generateRouteSchemas(
|
|
43
62
|
routes: RouteInfo[],
|
|
44
63
|
convertSchema: (schema: AnySchema) => string,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
64
|
+
registry: SchemaRegistry,
|
|
65
|
+
): RouteSchemaResult {
|
|
66
|
+
const declarations: string[] = [];
|
|
67
|
+
const schemaNameToCanonical = new Map<string, string>();
|
|
68
|
+
const generatedNames = new Set<string>();
|
|
48
69
|
|
|
49
70
|
for (const route of routes) {
|
|
50
71
|
const names = generateRouteSchemaNames(route);
|
|
@@ -52,27 +73,62 @@ function generateRouteSchemas(
|
|
|
52
73
|
const queryParams = route.parameters.filter((p) => p.in === "query");
|
|
53
74
|
const headerParams = route.parameters.filter((p) => p.in === "header");
|
|
54
75
|
|
|
76
|
+
// Generate params schema with deduplication
|
|
55
77
|
if (names.paramsSchemaName && pathParams.length > 0) {
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
const paramsSchema: AnySchema = {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: Object.fromEntries(
|
|
81
|
+
pathParams.map((p) => [p.name, p.schema]),
|
|
82
|
+
),
|
|
83
|
+
required: pathParams.filter((p) => p.required).map((p) => p.name),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const { isNew, canonicalName } = registerSchema(
|
|
87
|
+
registry,
|
|
88
|
+
names.paramsSchemaName,
|
|
89
|
+
paramsSchema,
|
|
90
|
+
);
|
|
91
|
+
schemaNameToCanonical.set(names.paramsSchemaName, canonicalName);
|
|
92
|
+
|
|
93
|
+
if (isNew && !generatedNames.has(names.paramsSchemaName)) {
|
|
94
|
+
generatedNames.add(names.paramsSchemaName);
|
|
58
95
|
const properties: string[] = [];
|
|
59
|
-
const required: string[] = [];
|
|
60
96
|
for (const param of pathParams) {
|
|
61
97
|
const zodExpr = convertSchema(param.schema);
|
|
62
98
|
properties.push(`${quotePropertyName(param.name)}: ${zodExpr}`);
|
|
63
|
-
if (param.required) {
|
|
64
|
-
required.push(param.name);
|
|
65
|
-
}
|
|
66
99
|
}
|
|
67
|
-
|
|
100
|
+
declarations.push(
|
|
68
101
|
`export const ${names.paramsSchemaName} = z.object({ ${properties.join(", ")} });`,
|
|
69
102
|
);
|
|
103
|
+
} else if (!isNew && names.paramsSchemaName !== canonicalName) {
|
|
104
|
+
if (!generatedNames.has(names.paramsSchemaName)) {
|
|
105
|
+
generatedNames.add(names.paramsSchemaName);
|
|
106
|
+
declarations.push(
|
|
107
|
+
`export const ${names.paramsSchemaName} = ${canonicalName};`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
70
110
|
}
|
|
71
111
|
}
|
|
72
112
|
|
|
113
|
+
// Generate query schema with deduplication
|
|
73
114
|
if (names.querySchemaName && queryParams.length > 0) {
|
|
74
|
-
|
|
75
|
-
|
|
115
|
+
const querySchema: AnySchema = {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: Object.fromEntries(
|
|
118
|
+
queryParams.map((p) => [p.name, p.schema]),
|
|
119
|
+
),
|
|
120
|
+
required: queryParams.filter((p) => p.required).map((p) => p.name),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const { isNew, canonicalName } = registerSchema(
|
|
124
|
+
registry,
|
|
125
|
+
names.querySchemaName,
|
|
126
|
+
querySchema,
|
|
127
|
+
);
|
|
128
|
+
schemaNameToCanonical.set(names.querySchemaName, canonicalName);
|
|
129
|
+
|
|
130
|
+
if (isNew && !generatedNames.has(names.querySchemaName)) {
|
|
131
|
+
generatedNames.add(names.querySchemaName);
|
|
76
132
|
const properties: string[] = [];
|
|
77
133
|
for (const param of queryParams) {
|
|
78
134
|
let zodExpr = convertSchema(param.schema);
|
|
@@ -81,15 +137,38 @@ function generateRouteSchemas(
|
|
|
81
137
|
}
|
|
82
138
|
properties.push(`${quotePropertyName(param.name)}: ${zodExpr}`);
|
|
83
139
|
}
|
|
84
|
-
|
|
140
|
+
declarations.push(
|
|
85
141
|
`export const ${names.querySchemaName} = z.object({ ${properties.join(", ")} });`,
|
|
86
142
|
);
|
|
143
|
+
} else if (!isNew && names.querySchemaName !== canonicalName) {
|
|
144
|
+
if (!generatedNames.has(names.querySchemaName)) {
|
|
145
|
+
generatedNames.add(names.querySchemaName);
|
|
146
|
+
declarations.push(
|
|
147
|
+
`export const ${names.querySchemaName} = ${canonicalName};`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
87
150
|
}
|
|
88
151
|
}
|
|
89
152
|
|
|
153
|
+
// Generate headers schema with deduplication
|
|
90
154
|
if (names.headersSchemaName && headerParams.length > 0) {
|
|
91
|
-
|
|
92
|
-
|
|
155
|
+
const headersSchema: AnySchema = {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: Object.fromEntries(
|
|
158
|
+
headerParams.map((p) => [p.name, p.schema]),
|
|
159
|
+
),
|
|
160
|
+
required: headerParams.filter((p) => p.required).map((p) => p.name),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const { isNew, canonicalName } = registerSchema(
|
|
164
|
+
registry,
|
|
165
|
+
names.headersSchemaName,
|
|
166
|
+
headersSchema,
|
|
167
|
+
);
|
|
168
|
+
schemaNameToCanonical.set(names.headersSchemaName, canonicalName);
|
|
169
|
+
|
|
170
|
+
if (isNew && !generatedNames.has(names.headersSchemaName)) {
|
|
171
|
+
generatedNames.add(names.headersSchemaName);
|
|
93
172
|
const properties: string[] = [];
|
|
94
173
|
for (const param of headerParams) {
|
|
95
174
|
let zodExpr = convertSchema(param.schema);
|
|
@@ -98,21 +177,43 @@ function generateRouteSchemas(
|
|
|
98
177
|
}
|
|
99
178
|
properties.push(`${quotePropertyName(param.name)}: ${zodExpr}`);
|
|
100
179
|
}
|
|
101
|
-
|
|
180
|
+
declarations.push(
|
|
102
181
|
`export const ${names.headersSchemaName} = z.object({ ${properties.join(", ")} });`,
|
|
103
182
|
);
|
|
183
|
+
} else if (!isNew && names.headersSchemaName !== canonicalName) {
|
|
184
|
+
if (!generatedNames.has(names.headersSchemaName)) {
|
|
185
|
+
generatedNames.add(names.headersSchemaName);
|
|
186
|
+
declarations.push(
|
|
187
|
+
`export const ${names.headersSchemaName} = ${canonicalName};`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
104
190
|
}
|
|
105
191
|
}
|
|
106
192
|
|
|
193
|
+
// Generate body schema with deduplication
|
|
107
194
|
if (names.bodySchemaName && route.requestBody) {
|
|
108
|
-
|
|
109
|
-
|
|
195
|
+
const { isNew, canonicalName } = registerSchema(
|
|
196
|
+
registry,
|
|
197
|
+
names.bodySchemaName,
|
|
198
|
+
route.requestBody,
|
|
199
|
+
);
|
|
200
|
+
schemaNameToCanonical.set(names.bodySchemaName, canonicalName);
|
|
201
|
+
|
|
202
|
+
if (isNew && !generatedNames.has(names.bodySchemaName)) {
|
|
203
|
+
generatedNames.add(names.bodySchemaName);
|
|
110
204
|
const zodExpr = convertSchema(route.requestBody);
|
|
111
|
-
|
|
205
|
+
declarations.push(`export const ${names.bodySchemaName} = ${zodExpr};`);
|
|
206
|
+
} else if (!isNew && names.bodySchemaName !== canonicalName) {
|
|
207
|
+
if (!generatedNames.has(names.bodySchemaName)) {
|
|
208
|
+
generatedNames.add(names.bodySchemaName);
|
|
209
|
+
declarations.push(
|
|
210
|
+
`export const ${names.bodySchemaName} = ${canonicalName};`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
112
213
|
}
|
|
113
214
|
}
|
|
114
215
|
|
|
115
|
-
// Generate schemas for ALL status codes
|
|
216
|
+
// Generate schemas for ALL status codes with deduplication
|
|
116
217
|
for (const [statusCode, responseSchema] of Object.entries(
|
|
117
218
|
route.responses,
|
|
118
219
|
)) {
|
|
@@ -128,18 +229,35 @@ function generateRouteSchemas(
|
|
|
128
229
|
suffix,
|
|
129
230
|
);
|
|
130
231
|
|
|
131
|
-
|
|
132
|
-
|
|
232
|
+
const { isNew, canonicalName } = registerSchema(
|
|
233
|
+
registry,
|
|
234
|
+
responseSchemaName,
|
|
235
|
+
responseSchema,
|
|
236
|
+
);
|
|
237
|
+
schemaNameToCanonical.set(responseSchemaName, canonicalName);
|
|
238
|
+
|
|
239
|
+
if (isNew && !generatedNames.has(responseSchemaName)) {
|
|
240
|
+
generatedNames.add(responseSchemaName);
|
|
133
241
|
const zodExpr = convertSchema(responseSchema);
|
|
134
|
-
|
|
242
|
+
declarations.push(`export const ${responseSchemaName} = ${zodExpr};`);
|
|
243
|
+
} else if (!isNew && responseSchemaName !== canonicalName) {
|
|
244
|
+
if (!generatedNames.has(responseSchemaName)) {
|
|
245
|
+
generatedNames.add(responseSchemaName);
|
|
246
|
+
declarations.push(
|
|
247
|
+
`export const ${responseSchemaName} = ${canonicalName};`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
135
250
|
}
|
|
136
251
|
}
|
|
137
252
|
}
|
|
138
253
|
|
|
139
|
-
return
|
|
254
|
+
return { declarations, schemaNameToCanonical };
|
|
140
255
|
}
|
|
141
256
|
|
|
142
|
-
function generateRequestResponseObjects(
|
|
257
|
+
function generateRequestResponseObjects(
|
|
258
|
+
routes: RouteInfo[],
|
|
259
|
+
schemaNameToCanonical: Map<string, string>,
|
|
260
|
+
): string[] {
|
|
143
261
|
const lines: string[] = [];
|
|
144
262
|
const requestPaths: Record<string, Record<string, string[]>> = {};
|
|
145
263
|
const responsePaths: Record<
|
|
@@ -147,6 +265,14 @@ function generateRequestResponseObjects(routes: RouteInfo[]): string[] {
|
|
|
147
265
|
Record<string, Record<string, string>>
|
|
148
266
|
> = {};
|
|
149
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Resolves a schema name to its canonical name if it exists,
|
|
270
|
+
* otherwise returns the original name.
|
|
271
|
+
*/
|
|
272
|
+
const resolveSchemaName = (name: string): string => {
|
|
273
|
+
return schemaNameToCanonical.get(name) ?? name;
|
|
274
|
+
};
|
|
275
|
+
|
|
150
276
|
for (const route of routes) {
|
|
151
277
|
const names = generateRouteSchemaNames(route);
|
|
152
278
|
const pathParams = route.parameters.filter((p) => p.in === "path");
|
|
@@ -163,16 +289,20 @@ function generateRequestResponseObjects(routes: RouteInfo[]): string[] {
|
|
|
163
289
|
|
|
164
290
|
const requestParts: string[] = [];
|
|
165
291
|
if (names.paramsSchemaName && pathParams.length > 0) {
|
|
166
|
-
requestParts.push(
|
|
292
|
+
requestParts.push(
|
|
293
|
+
`params: ${resolveSchemaName(names.paramsSchemaName)}`,
|
|
294
|
+
);
|
|
167
295
|
}
|
|
168
296
|
if (names.querySchemaName && queryParams.length > 0) {
|
|
169
|
-
requestParts.push(`query: ${names.querySchemaName}`);
|
|
297
|
+
requestParts.push(`query: ${resolveSchemaName(names.querySchemaName)}`);
|
|
170
298
|
}
|
|
171
299
|
if (names.headersSchemaName && headerParams.length > 0) {
|
|
172
|
-
requestParts.push(
|
|
300
|
+
requestParts.push(
|
|
301
|
+
`headers: ${resolveSchemaName(names.headersSchemaName)}`,
|
|
302
|
+
);
|
|
173
303
|
}
|
|
174
304
|
if (names.bodySchemaName && route.requestBody) {
|
|
175
|
-
requestParts.push(`body: ${names.bodySchemaName}`);
|
|
305
|
+
requestParts.push(`body: ${resolveSchemaName(names.bodySchemaName)}`);
|
|
176
306
|
}
|
|
177
307
|
|
|
178
308
|
if (requestParts.length > 0) {
|
|
@@ -202,7 +332,9 @@ function generateRequestResponseObjects(routes: RouteInfo[]): string[] {
|
|
|
202
332
|
route.method,
|
|
203
333
|
suffix,
|
|
204
334
|
);
|
|
205
|
-
|
|
335
|
+
// Use canonical name for the Response object
|
|
336
|
+
responseMethodObj[route.method]![statusCode] =
|
|
337
|
+
resolveSchemaName(responseSchemaName);
|
|
206
338
|
}
|
|
207
339
|
}
|
|
208
340
|
|
|
@@ -246,6 +378,37 @@ function generateRequestResponseObjects(routes: RouteInfo[]): string[] {
|
|
|
246
378
|
return lines;
|
|
247
379
|
}
|
|
248
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Collects all response schemas from routes for common schema detection.
|
|
383
|
+
*/
|
|
384
|
+
function collectRouteSchemas(
|
|
385
|
+
routes: RouteInfo[],
|
|
386
|
+
): Array<{ name: string; schema: AnySchema }> {
|
|
387
|
+
const collected: Array<{ name: string; schema: AnySchema }> = [];
|
|
388
|
+
|
|
389
|
+
for (const route of routes) {
|
|
390
|
+
for (const [statusCode, responseSchema] of Object.entries(
|
|
391
|
+
route.responses,
|
|
392
|
+
)) {
|
|
393
|
+
if (!responseSchema) continue;
|
|
394
|
+
|
|
395
|
+
const isSuccess = statusCode.startsWith("2");
|
|
396
|
+
const suffix = isSuccess
|
|
397
|
+
? `${statusCode}Response`
|
|
398
|
+
: `${statusCode}ErrorResponse`;
|
|
399
|
+
const responseSchemaName = generateRouteSchemaName(
|
|
400
|
+
route.path,
|
|
401
|
+
route.method,
|
|
402
|
+
suffix,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
collected.push({ name: responseSchemaName, schema: responseSchema });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return collected;
|
|
410
|
+
}
|
|
411
|
+
|
|
249
412
|
export const openApiToZodTsCode = (
|
|
250
413
|
openapi: Record<string, unknown>,
|
|
251
414
|
customImportLines?: string[],
|
|
@@ -267,31 +430,85 @@ export const openApiToZodTsCode = (
|
|
|
267
430
|
lines.push(...(customImportLines ?? []));
|
|
268
431
|
lines.push("");
|
|
269
432
|
|
|
433
|
+
// Type assertion helper for compile-time verification
|
|
434
|
+
lines.push("// Type assertion helper - verifies interface matches schema at compile time");
|
|
435
|
+
lines.push("type _AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : never) : never;");
|
|
436
|
+
lines.push("");
|
|
437
|
+
|
|
438
|
+
// Create registry for schema deduplication
|
|
439
|
+
const registry = createSchemaRegistry();
|
|
440
|
+
|
|
270
441
|
const sortedSchemaNames = topologicalSortSchemas(schemas);
|
|
271
442
|
|
|
443
|
+
// Collect all type assertions to emit after all schemas
|
|
444
|
+
const typeAssertions: string[] = [];
|
|
445
|
+
|
|
272
446
|
for (const name of sortedSchemaNames) {
|
|
273
447
|
const schema = schemas[name];
|
|
274
448
|
if (schema) {
|
|
275
449
|
const zodExpr = convertSchemaToZodString(schema);
|
|
276
450
|
const schemaName = `${name}Schema`;
|
|
277
451
|
const typeName = name;
|
|
278
|
-
|
|
279
|
-
|
|
452
|
+
|
|
453
|
+
// Generate interface (concrete type in .d.ts)
|
|
454
|
+
lines.push(generateInterface(typeName, schema));
|
|
455
|
+
|
|
456
|
+
// Generate schema with ZodType<T> annotation (simple type in .d.ts)
|
|
457
|
+
lines.push(`export const ${schemaName}: z.ZodType<${typeName}> = ${zodExpr};`);
|
|
280
458
|
lines.push("");
|
|
459
|
+
|
|
460
|
+
// Add type assertion to verify interface matches schema
|
|
461
|
+
typeAssertions.push(`type _Assert${typeName} = _AssertEqual<${typeName}, z.infer<typeof ${schemaName}>>;`);
|
|
462
|
+
|
|
463
|
+
// Register component schemas so they can be referenced by route schemas
|
|
464
|
+
const fingerprint = getSchemaFingerprint(schema);
|
|
465
|
+
preRegisterSchema(registry, schemaName, fingerprint);
|
|
281
466
|
}
|
|
282
467
|
}
|
|
283
468
|
|
|
469
|
+
// Emit all type assertions
|
|
470
|
+
if (typeAssertions.length > 0) {
|
|
471
|
+
lines.push("// Compile-time type assertions - ensure interfaces match schemas");
|
|
472
|
+
lines.push(typeAssertions.join("\n"));
|
|
473
|
+
lines.push("");
|
|
474
|
+
}
|
|
475
|
+
|
|
284
476
|
if (options?.includeRoutes) {
|
|
285
477
|
const routes = parseOpenApiPaths(openapi);
|
|
286
478
|
if (routes.length > 0) {
|
|
287
|
-
|
|
479
|
+
// Find common schemas that appear multiple times (for error responses, etc.)
|
|
480
|
+
const routeSchemaList = collectRouteSchemas(routes);
|
|
481
|
+
const commonSchemas = findCommonSchemas(routeSchemaList, 2);
|
|
482
|
+
|
|
483
|
+
// Generate common schemas first (e.g., UnauthorizedErrorSchema, NotFoundErrorSchema)
|
|
484
|
+
if (commonSchemas.length > 0) {
|
|
485
|
+
lines.push("// Common Error Schemas (deduplicated)");
|
|
486
|
+
for (const common of commonSchemas) {
|
|
487
|
+
const zodExpr = convertSchemaToZodString(common.schema);
|
|
488
|
+
lines.push(`export const ${common.name} = ${zodExpr};`);
|
|
489
|
+
// Pre-register so route schemas reference this instead of duplicating
|
|
490
|
+
preRegisterSchema(registry, common.name, common.fingerprint);
|
|
491
|
+
}
|
|
492
|
+
lines.push("");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Generate route schemas with deduplication
|
|
496
|
+
const { declarations, schemaNameToCanonical } = generateRouteSchemas(
|
|
288
497
|
routes,
|
|
289
498
|
convertSchemaToZodString,
|
|
499
|
+
registry,
|
|
290
500
|
);
|
|
291
|
-
|
|
292
|
-
|
|
501
|
+
|
|
502
|
+
if (declarations.length > 0) {
|
|
503
|
+
lines.push("// Route Schemas");
|
|
504
|
+
lines.push(...declarations);
|
|
293
505
|
lines.push("");
|
|
294
|
-
|
|
506
|
+
|
|
507
|
+
// Generate Request/Response objects using canonical names
|
|
508
|
+
const requestResponseObjs = generateRequestResponseObjects(
|
|
509
|
+
routes,
|
|
510
|
+
schemaNameToCanonical,
|
|
511
|
+
);
|
|
295
512
|
lines.push(...requestResponseObjs);
|
|
296
513
|
}
|
|
297
514
|
}
|
package/src/to-zod.spec.ts
CHANGED
|
@@ -289,9 +289,12 @@ describe("openApiToZodTsCode", () => {
|
|
|
289
289
|
|
|
290
290
|
const result = openApiToZodTsCode(openapi);
|
|
291
291
|
expect(result).toContain("import { z } from 'zod';");
|
|
292
|
-
|
|
292
|
+
// New format: interface + ZodType annotation
|
|
293
|
+
expect(result).toContain("export interface User {");
|
|
294
|
+
expect(result).toContain("export const UserSchema: z.ZodType<User> =");
|
|
293
295
|
expect(result).toContain("z.object({ name: z.string() })");
|
|
294
|
-
|
|
296
|
+
// Type assertion for compile-time verification
|
|
297
|
+
expect(result).toContain("type _AssertUser = _AssertEqual<User, z.infer<typeof UserSchema>>;");
|
|
295
298
|
});
|
|
296
299
|
|
|
297
300
|
it("should convert OpenAPI document with multiple schemas", () => {
|
|
@@ -317,10 +320,11 @@ describe("openApiToZodTsCode", () => {
|
|
|
317
320
|
};
|
|
318
321
|
|
|
319
322
|
const result = openApiToZodTsCode(openapi);
|
|
320
|
-
|
|
321
|
-
expect(result).toContain("export
|
|
322
|
-
expect(result).toContain("export
|
|
323
|
-
expect(result).toContain("export
|
|
323
|
+
// New format: interface + ZodType annotation
|
|
324
|
+
expect(result).toContain("export interface User {");
|
|
325
|
+
expect(result).toContain("export interface Product {");
|
|
326
|
+
expect(result).toContain("export const UserSchema: z.ZodType<User> =");
|
|
327
|
+
expect(result).toContain("export const ProductSchema: z.ZodType<Product> =");
|
|
324
328
|
});
|
|
325
329
|
|
|
326
330
|
it("should include file header comment", () => {
|