zod-codegen 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.github/workflows/ci.yml +50 -48
  2. package/.github/workflows/release.yml +13 -3
  3. package/.husky/commit-msg +1 -1
  4. package/.husky/pre-commit +1 -1
  5. package/.lintstagedrc.json +5 -1
  6. package/.nvmrc +1 -1
  7. package/.prettierrc.json +12 -5
  8. package/CHANGELOG.md +15 -0
  9. package/CONTRIBUTING.md +12 -12
  10. package/EXAMPLES.md +135 -57
  11. package/PERFORMANCE.md +4 -4
  12. package/README.md +87 -64
  13. package/SECURITY.md +1 -1
  14. package/dist/src/cli.js +11 -18
  15. package/dist/src/generator.d.ts +2 -2
  16. package/dist/src/generator.d.ts.map +1 -1
  17. package/dist/src/generator.js +5 -3
  18. package/dist/src/interfaces/code-generator.d.ts.map +1 -1
  19. package/dist/src/services/code-generator.service.d.ts +24 -1
  20. package/dist/src/services/code-generator.service.d.ts.map +1 -1
  21. package/dist/src/services/code-generator.service.js +385 -216
  22. package/dist/src/services/file-reader.service.d.ts.map +1 -1
  23. package/dist/src/services/file-reader.service.js +1 -1
  24. package/dist/src/services/file-writer.service.d.ts.map +1 -1
  25. package/dist/src/services/file-writer.service.js +2 -2
  26. package/dist/src/services/import-builder.service.d.ts.map +1 -1
  27. package/dist/src/services/import-builder.service.js +3 -3
  28. package/dist/src/services/type-builder.service.d.ts.map +1 -1
  29. package/dist/src/types/generator-options.d.ts.map +1 -1
  30. package/dist/src/types/openapi.d.ts.map +1 -1
  31. package/dist/src/types/openapi.js +20 -20
  32. package/dist/src/utils/error-handler.d.ts.map +1 -1
  33. package/dist/src/utils/naming-convention.d.ts.map +1 -1
  34. package/dist/src/utils/naming-convention.js +6 -3
  35. package/dist/src/utils/signal-handler.d.ts.map +1 -1
  36. package/dist/tests/integration/cli-comprehensive.test.d.ts +2 -0
  37. package/dist/tests/integration/cli-comprehensive.test.d.ts.map +1 -0
  38. package/dist/tests/integration/cli-comprehensive.test.js +110 -0
  39. package/dist/tests/integration/cli.test.d.ts +2 -0
  40. package/dist/tests/integration/cli.test.d.ts.map +1 -0
  41. package/dist/tests/integration/cli.test.js +25 -0
  42. package/dist/tests/integration/error-scenarios.test.d.ts +2 -0
  43. package/dist/tests/integration/error-scenarios.test.d.ts.map +1 -0
  44. package/dist/tests/integration/error-scenarios.test.js +169 -0
  45. package/dist/tests/integration/snapshots.test.d.ts +2 -0
  46. package/dist/tests/integration/snapshots.test.d.ts.map +1 -0
  47. package/dist/tests/integration/snapshots.test.js +100 -0
  48. package/dist/tests/unit/code-generator-edge-cases.test.d.ts +2 -0
  49. package/dist/tests/unit/code-generator-edge-cases.test.d.ts.map +1 -0
  50. package/dist/tests/unit/code-generator-edge-cases.test.js +506 -0
  51. package/dist/tests/unit/code-generator.test.d.ts +2 -0
  52. package/dist/tests/unit/code-generator.test.d.ts.map +1 -0
  53. package/dist/tests/unit/code-generator.test.js +1364 -0
  54. package/dist/tests/unit/file-reader.test.d.ts +2 -0
  55. package/dist/tests/unit/file-reader.test.d.ts.map +1 -0
  56. package/dist/tests/unit/file-reader.test.js +125 -0
  57. package/dist/tests/unit/generator.test.d.ts +2 -0
  58. package/dist/tests/unit/generator.test.d.ts.map +1 -0
  59. package/dist/tests/unit/generator.test.js +119 -0
  60. package/dist/tests/unit/naming-convention.test.d.ts +2 -0
  61. package/dist/tests/unit/naming-convention.test.d.ts.map +1 -0
  62. package/dist/tests/unit/naming-convention.test.js +256 -0
  63. package/dist/tests/unit/reporter.test.d.ts +2 -0
  64. package/dist/tests/unit/reporter.test.d.ts.map +1 -0
  65. package/dist/tests/unit/reporter.test.js +44 -0
  66. package/dist/tests/unit/type-builder.test.d.ts +2 -0
  67. package/dist/tests/unit/type-builder.test.d.ts.map +1 -0
  68. package/dist/tests/unit/type-builder.test.js +108 -0
  69. package/dist/vitest.config.d.ts.map +1 -1
  70. package/dist/vitest.config.js +10 -20
  71. package/eslint.config.mjs +38 -28
  72. package/examples/.gitkeep +1 -1
  73. package/examples/README.md +4 -2
  74. package/examples/petstore/README.md +18 -17
  75. package/examples/petstore/{type.ts → api.ts} +158 -74
  76. package/examples/petstore/authenticated-usage.ts +6 -4
  77. package/examples/petstore/basic-usage.ts +4 -3
  78. package/examples/petstore/error-handling-usage.ts +84 -0
  79. package/examples/petstore/retry-handler-usage.ts +11 -18
  80. package/examples/petstore/server-variables-usage.ts +10 -10
  81. package/examples/pokeapi/README.md +8 -8
  82. package/examples/pokeapi/api.ts +218 -0
  83. package/examples/pokeapi/basic-usage.ts +3 -2
  84. package/examples/pokeapi/custom-client.ts +5 -4
  85. package/package.json +17 -21
  86. package/src/cli.ts +20 -25
  87. package/src/generator.ts +13 -11
  88. package/src/interfaces/code-generator.ts +1 -1
  89. package/src/services/code-generator.service.ts +989 -1099
  90. package/src/services/file-reader.service.ts +6 -5
  91. package/src/services/file-writer.service.ts +7 -7
  92. package/src/services/import-builder.service.ts +9 -13
  93. package/src/services/type-builder.service.ts +8 -19
  94. package/src/types/generator-options.ts +1 -1
  95. package/src/types/openapi.ts +22 -22
  96. package/src/utils/error-handler.ts +2 -2
  97. package/src/utils/naming-convention.ts +13 -10
  98. package/src/utils/reporter.ts +2 -2
  99. package/src/utils/signal-handler.ts +7 -8
  100. package/tests/integration/cli-comprehensive.test.ts +38 -32
  101. package/tests/integration/cli.test.ts +5 -5
  102. package/tests/integration/error-scenarios.test.ts +20 -26
  103. package/tests/integration/snapshots.test.ts +19 -23
  104. package/tests/unit/code-generator-edge-cases.test.ts +133 -133
  105. package/tests/unit/code-generator.test.ts +674 -268
  106. package/tests/unit/file-reader.test.ts +14 -14
  107. package/tests/unit/generator.test.ts +30 -18
  108. package/tests/unit/naming-convention.test.ts +27 -27
  109. package/tests/unit/type-builder.test.ts +2 -2
  110. package/tsconfig.json +5 -3
  111. package/vitest.config.ts +11 -21
  112. package/dist/scripts/update-manifest.d.ts +0 -14
  113. package/dist/scripts/update-manifest.d.ts.map +0 -1
  114. package/dist/scripts/update-manifest.js +0 -33
  115. package/dist/src/assets/manifest.json +0 -5
  116. package/examples/pokeapi/type.ts +0 -109
  117. package/generated/type.ts +0 -326
  118. package/scripts/update-manifest.ts +0 -49
  119. package/src/assets/manifest.json +0 -5
@@ -1,6 +1,6 @@
1
- import {beforeEach, describe, expect, it} from 'vitest';
2
- import {TypeScriptCodeGeneratorService} from '../../src/services/code-generator.service';
3
- import type {OpenApiSpecType} from '../../src/types/openapi';
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { TypeScriptCodeGeneratorService } from '../../src/services/code-generator.service';
3
+ import type { OpenApiSpecType } from '../../src/types/openapi';
4
4
 
5
5
  describe('TypeScriptCodeGeneratorService', () => {
6
6
  let generator: TypeScriptCodeGeneratorService;
@@ -15,7 +15,7 @@ describe('TypeScriptCodeGeneratorService', () => {
15
15
  openapi: '3.0.0',
16
16
  info: {
17
17
  title: 'Test API',
18
- version: '1.0.0',
18
+ version: '1.0.0'
19
19
  },
20
20
  paths: {
21
21
  '/users': {
@@ -26,18 +26,18 @@ describe('TypeScriptCodeGeneratorService', () => {
26
26
  description: 'Success',
27
27
  content: {
28
28
  'application/json': {
29
- schema: {type: 'string'},
30
- },
31
- },
32
- },
33
- },
34
- },
35
- },
36
- },
29
+ schema: { type: 'string' }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
37
  };
38
38
 
39
39
  const generatorWithConvention = new TypeScriptCodeGeneratorService({
40
- namingConvention: 'camelCase',
40
+ namingConvention: 'camelCase'
41
41
  });
42
42
  const code = generatorWithConvention.generate(spec);
43
43
  expect(code).toContain('async getUserById');
@@ -49,7 +49,7 @@ describe('TypeScriptCodeGeneratorService', () => {
49
49
  openapi: '3.0.0',
50
50
  info: {
51
51
  title: 'Test API',
52
- version: '1.0.0',
52
+ version: '1.0.0'
53
53
  },
54
54
  paths: {
55
55
  '/users': {
@@ -60,18 +60,18 @@ describe('TypeScriptCodeGeneratorService', () => {
60
60
  description: 'Success',
61
61
  content: {
62
62
  'application/json': {
63
- schema: {type: 'string'},
64
- },
65
- },
66
- },
67
- },
68
- },
69
- },
70
- },
63
+ schema: { type: 'string' }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
71
  };
72
72
 
73
73
  const generatorWithConvention = new TypeScriptCodeGeneratorService({
74
- namingConvention: 'PascalCase',
74
+ namingConvention: 'PascalCase'
75
75
  });
76
76
  const code = generatorWithConvention.generate(spec);
77
77
  expect(code).toContain('async GetUserById');
@@ -83,7 +83,7 @@ describe('TypeScriptCodeGeneratorService', () => {
83
83
  openapi: '3.0.0',
84
84
  info: {
85
85
  title: 'Test API',
86
- version: '1.0.0',
86
+ version: '1.0.0'
87
87
  },
88
88
  paths: {
89
89
  '/users': {
@@ -94,18 +94,18 @@ describe('TypeScriptCodeGeneratorService', () => {
94
94
  description: 'Success',
95
95
  content: {
96
96
  'application/json': {
97
- schema: {type: 'string'},
98
- },
99
- },
100
- },
101
- },
102
- },
103
- },
104
- },
97
+ schema: { type: 'string' }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
105
  };
106
106
 
107
107
  const generatorWithConvention = new TypeScriptCodeGeneratorService({
108
- namingConvention: 'snake_case',
108
+ namingConvention: 'snake_case'
109
109
  });
110
110
  const code = generatorWithConvention.generate(spec);
111
111
  expect(code).toContain('async get_user_by_id');
@@ -117,7 +117,7 @@ describe('TypeScriptCodeGeneratorService', () => {
117
117
  openapi: '3.0.0',
118
118
  info: {
119
119
  title: 'Test API',
120
- version: '1.0.0',
120
+ version: '1.0.0'
121
121
  },
122
122
  paths: {
123
123
  '/users/{id}': {
@@ -130,29 +130,22 @@ describe('TypeScriptCodeGeneratorService', () => {
130
130
  description: 'Success',
131
131
  content: {
132
132
  'application/json': {
133
- schema: {type: 'string'},
134
- },
135
- },
136
- },
137
- },
138
- },
139
- },
140
- },
133
+ schema: { type: 'string' }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
141
  };
142
142
 
143
- const customTransformer = (details: {
144
- operationId: string;
145
- method: string;
146
- path: string;
147
- tags?: string[];
148
- summary?: string;
149
- description?: string;
150
- }) => {
143
+ const customTransformer = (details: { operationId: string; method: string; path: string; tags?: string[]; summary?: string; description?: string }) => {
151
144
  return `${details.method.toUpperCase()}_${details.tags?.[0] || 'default'}_${details.operationId}`;
152
145
  };
153
146
 
154
147
  const generatorWithTransformer = new TypeScriptCodeGeneratorService({
155
- operationNameTransformer: customTransformer,
148
+ operationNameTransformer: customTransformer
156
149
  });
157
150
  const code = generatorWithTransformer.generate(spec);
158
151
  expect(code).toContain('async GET_users_getUserById');
@@ -163,7 +156,7 @@ describe('TypeScriptCodeGeneratorService', () => {
163
156
  openapi: '3.0.0',
164
157
  info: {
165
158
  title: 'Test API',
166
- version: '1.0.0',
159
+ version: '1.0.0'
167
160
  },
168
161
  paths: {
169
162
  '/users': {
@@ -174,21 +167,21 @@ describe('TypeScriptCodeGeneratorService', () => {
174
167
  description: 'Success',
175
168
  content: {
176
169
  'application/json': {
177
- schema: {type: 'string'},
178
- },
179
- },
180
- },
181
- },
182
- },
183
- },
184
- },
170
+ schema: { type: 'string' }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
185
178
  };
186
179
 
187
180
  const customTransformer = () => 'customName';
188
181
 
189
182
  const generatorWithBoth = new TypeScriptCodeGeneratorService({
190
183
  namingConvention: 'PascalCase',
191
- operationNameTransformer: customTransformer,
184
+ operationNameTransformer: customTransformer
192
185
  });
193
186
  const code = generatorWithBoth.generate(spec);
194
187
  expect(code).toContain('async customName');
@@ -203,9 +196,9 @@ describe('TypeScriptCodeGeneratorService', () => {
203
196
  openapi: '3.0.0',
204
197
  info: {
205
198
  title: 'Test API',
206
- version: '1.0.0',
199
+ version: '1.0.0'
207
200
  },
208
- paths: {},
201
+ paths: {}
209
202
  };
210
203
 
211
204
  const code = generator.generate(spec);
@@ -220,7 +213,7 @@ describe('TypeScriptCodeGeneratorService', () => {
220
213
  openapi: '3.0.0',
221
214
  info: {
222
215
  title: 'Test API',
223
- version: '1.0.0',
216
+ version: '1.0.0'
224
217
  },
225
218
  paths: {},
226
219
  components: {
@@ -228,13 +221,13 @@ describe('TypeScriptCodeGeneratorService', () => {
228
221
  User: {
229
222
  type: 'object',
230
223
  properties: {
231
- id: {type: 'integer'},
232
- name: {type: 'string'},
224
+ id: { type: 'integer' },
225
+ name: { type: 'string' }
233
226
  },
234
- required: ['id', 'name'],
235
- },
236
- },
237
- },
227
+ required: ['id', 'name']
228
+ }
229
+ }
230
+ }
238
231
  };
239
232
 
240
233
  const code = generator.generate(spec);
@@ -249,7 +242,7 @@ describe('TypeScriptCodeGeneratorService', () => {
249
242
  openapi: '3.0.0',
250
243
  info: {
251
244
  title: 'Test API',
252
- version: '1.0.0',
245
+ version: '1.0.0'
253
246
  },
254
247
  paths: {
255
248
  '/users': {
@@ -262,15 +255,15 @@ describe('TypeScriptCodeGeneratorService', () => {
262
255
  'application/json': {
263
256
  schema: {
264
257
  type: 'array',
265
- items: {type: 'string'},
266
- },
267
- },
268
- },
269
- },
270
- },
271
- },
272
- },
273
- },
258
+ items: { type: 'string' }
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
274
267
  };
275
268
 
276
269
  const code = generator.generate(spec);
@@ -283,9 +276,9 @@ describe('TypeScriptCodeGeneratorService', () => {
283
276
  openapi: '3.0.0',
284
277
  info: {
285
278
  title: 'Test API',
286
- version: '1.0.0',
279
+ version: '1.0.0'
287
280
  },
288
- paths: {},
281
+ paths: {}
289
282
  };
290
283
 
291
284
  const code = generator.generate(spec);
@@ -298,10 +291,10 @@ describe('TypeScriptCodeGeneratorService', () => {
298
291
  openapi: '3.0.0',
299
292
  info: {
300
293
  title: 'Test API',
301
- version: '1.0.0',
294
+ version: '1.0.0'
302
295
  },
303
- servers: [{url: 'https://api.example.com'}],
304
- paths: {},
296
+ servers: [{ url: 'https://api.example.com' }],
297
+ paths: {}
305
298
  };
306
299
 
307
300
  const code = generator.generate(spec);
@@ -314,7 +307,7 @@ describe('TypeScriptCodeGeneratorService', () => {
314
307
  openapi: '3.0.0',
315
308
  info: {
316
309
  title: 'Test API',
317
- version: '1.0.0',
310
+ version: '1.0.0'
318
311
  },
319
312
  paths: {},
320
313
  components: {
@@ -322,21 +315,21 @@ describe('TypeScriptCodeGeneratorService', () => {
322
315
  User: {
323
316
  type: 'object',
324
317
  properties: {
325
- id: {type: 'integer'},
326
- profile: {$ref: '#/components/schemas/Profile'},
318
+ id: { type: 'integer' },
319
+ profile: { $ref: '#/components/schemas/Profile' }
327
320
  },
328
- required: ['id'],
321
+ required: ['id']
329
322
  },
330
323
  Profile: {
331
324
  type: 'object',
332
325
  properties: {
333
- name: {type: 'string'},
334
- email: {type: 'string', format: 'email'},
326
+ name: { type: 'string' },
327
+ email: { type: 'string', format: 'email' }
335
328
  },
336
- required: ['name', 'email'],
337
- },
338
- },
339
- },
329
+ required: ['name', 'email']
330
+ }
331
+ }
332
+ }
340
333
  };
341
334
 
342
335
  const code = generator.generate(spec);
@@ -353,17 +346,17 @@ describe('TypeScriptCodeGeneratorService', () => {
353
346
  openapi: '3.0.0',
354
347
  info: {
355
348
  title: 'Test API',
356
- version: '1.0.0',
349
+ version: '1.0.0'
357
350
  },
358
351
  paths: {},
359
352
  components: {
360
353
  schemas: {
361
354
  Status: {
362
355
  type: 'string',
363
- enum: ['active', 'inactive', 'pending'],
364
- },
365
- },
366
- },
356
+ enum: ['active', 'inactive', 'pending']
357
+ }
358
+ }
359
+ }
367
360
  };
368
361
 
369
362
  const code = generator.generate(spec);
@@ -378,21 +371,21 @@ describe('TypeScriptCodeGeneratorService', () => {
378
371
  openapi: '3.0.0',
379
372
  info: {
380
373
  title: 'Test API',
381
- version: '1.0.0',
374
+ version: '1.0.0'
382
375
  },
383
376
  paths: {},
384
377
  components: {
385
378
  schemas: {
386
379
  Status: {
387
380
  type: 'integer',
388
- enum: [-99, 0, 1, 2],
381
+ enum: [-99, 0, 1, 2]
389
382
  },
390
383
  ExecutionMode: {
391
384
  type: 'integer',
392
- enum: [1, 2],
393
- },
394
- },
395
- },
385
+ enum: [1, 2]
386
+ }
387
+ }
388
+ }
396
389
  };
397
390
 
398
391
  const code = generator.generate(spec);
@@ -413,7 +406,7 @@ describe('TypeScriptCodeGeneratorService', () => {
413
406
  openapi: '3.0.0',
414
407
  info: {
415
408
  title: 'Test API',
416
- version: '1.0.0',
409
+ version: '1.0.0'
417
410
  },
418
411
  paths: {
419
412
  '/test': {
@@ -424,14 +417,14 @@ describe('TypeScriptCodeGeneratorService', () => {
424
417
  description: 'Success',
425
418
  content: {
426
419
  'application/json': {
427
- schema: {type: 'string'},
428
- },
429
- },
430
- },
431
- },
432
- },
433
- },
434
- },
420
+ schema: { type: 'string' }
421
+ }
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+ }
435
428
  };
436
429
 
437
430
  const code = generator.generate(spec);
@@ -456,17 +449,17 @@ describe('TypeScriptCodeGeneratorService', () => {
456
449
  openapi: '3.0.0',
457
450
  info: {
458
451
  title: 'Test API',
459
- version: '1.0.0',
452
+ version: '1.0.0'
460
453
  },
461
454
  paths: {},
462
455
  components: {
463
456
  schemas: {
464
457
  Tags: {
465
458
  type: 'array',
466
- items: {type: 'string'},
467
- },
468
- },
469
- },
459
+ items: { type: 'string' }
460
+ }
461
+ }
462
+ }
470
463
  };
471
464
 
472
465
  const code = generator.generate(spec);
@@ -475,33 +468,141 @@ describe('TypeScriptCodeGeneratorService', () => {
475
468
  });
476
469
  });
477
470
 
471
+ describe('ResponseValidationError', () => {
472
+ it('should generate ResponseValidationError class', () => {
473
+ const spec: OpenApiSpecType = {
474
+ openapi: '3.0.0',
475
+ info: {
476
+ title: 'Test API',
477
+ version: '1.0.0'
478
+ },
479
+ paths: {}
480
+ };
481
+
482
+ const code = generator.generate(spec);
483
+ expect(code).toContain('class ResponseValidationError<T> extends Error');
484
+ expect(code).toContain('readonly response: Response');
485
+ expect(code).toContain('readonly error: z.ZodError<T>');
486
+ expect(code).toContain("this.name = 'ResponseValidationError' as const");
487
+ expect(code).toContain('get data(): T');
488
+ });
489
+
490
+ it('should use safeParse with ResponseValidationError for methods with response schemas', () => {
491
+ const spec: OpenApiSpecType = {
492
+ openapi: '3.0.0',
493
+ info: {
494
+ title: 'Test API',
495
+ version: '1.0.0'
496
+ },
497
+ paths: {
498
+ '/pets': {
499
+ post: {
500
+ operationId: 'createPet',
501
+ requestBody: {
502
+ content: {
503
+ 'application/json': {
504
+ schema: { $ref: '#/components/schemas/Pet' }
505
+ }
506
+ }
507
+ },
508
+ responses: {
509
+ '200': {
510
+ description: 'Success',
511
+ content: {
512
+ 'application/json': {
513
+ schema: { $ref: '#/components/schemas/Pet' }
514
+ }
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+ },
521
+ components: {
522
+ schemas: {
523
+ Pet: {
524
+ type: 'object',
525
+ properties: {
526
+ id: { type: 'integer' },
527
+ name: { type: 'string' }
528
+ },
529
+ required: ['name']
530
+ }
531
+ }
532
+ }
533
+ };
534
+
535
+ const code = generator.generate(spec);
536
+ expect(code).toContain('Pet.safeParse(response)');
537
+ expect(code).toContain('parsedPet.success');
538
+ expect(code).toContain('new ResponseValidationError<Pet>');
539
+ expect(code).toContain('parsedPet.data');
540
+ expect(code).not.toContain('Pet.parse(');
541
+ });
542
+
543
+ it('should not use safeParse for methods without response schemas', () => {
544
+ const spec: OpenApiSpecType = {
545
+ openapi: '3.0.0',
546
+ info: {
547
+ title: 'Test API',
548
+ version: '1.0.0'
549
+ },
550
+ paths: {
551
+ '/pets/{id}': {
552
+ delete: {
553
+ operationId: 'deletePet',
554
+ parameters: [
555
+ {
556
+ name: 'id',
557
+ in: 'path',
558
+ required: true,
559
+ schema: { type: 'integer' }
560
+ }
561
+ ],
562
+ responses: {
563
+ '204': {
564
+ description: 'Deleted'
565
+ }
566
+ }
567
+ }
568
+ }
569
+ }
570
+ };
571
+
572
+ const code = generator.generate(spec);
573
+ expect(code).toContain('async deletePet');
574
+ expect(code).toContain('return await this.makeRequest');
575
+ expect(code).not.toContain('safeParse');
576
+ });
577
+ });
578
+
478
579
  describe('buildSchema', () => {
479
580
  it('should build schema for string type', () => {
480
- const schema = {type: 'string'};
581
+ const schema = { type: 'string' };
481
582
  const result = generator.buildSchema(schema, true);
482
583
  expect(result).toBeDefined();
483
584
  });
484
585
 
485
586
  it('should build schema for number type', () => {
486
- const schema = {type: 'number'};
587
+ const schema = { type: 'number' };
487
588
  const result = generator.buildSchema(schema, true);
488
589
  expect(result).toBeDefined();
489
590
  });
490
591
 
491
592
  it('should build schema for boolean type', () => {
492
- const schema = {type: 'boolean'};
593
+ const schema = { type: 'boolean' };
493
594
  const result = generator.buildSchema(schema, true);
494
595
  expect(result).toBeDefined();
495
596
  });
496
597
 
497
598
  it('should handle optional fields', () => {
498
- const schema = {type: 'string'};
599
+ const schema = { type: 'string' };
499
600
  const result = generator.buildSchema(schema, false);
500
601
  expect(result).toBeDefined();
501
602
  });
502
603
 
503
604
  it('should handle string formats', () => {
504
- const schema = {type: 'string', format: 'email'};
605
+ const schema = { type: 'string', format: 'email' };
505
606
  const result = generator.buildSchema(schema, true);
506
607
  expect(result).toBeDefined();
507
608
  });
@@ -513,7 +614,7 @@ describe('TypeScriptCodeGeneratorService', () => {
513
614
  openapi: '3.0.0',
514
615
  info: {
515
616
  title: 'Test API',
516
- version: '1.0.0',
617
+ version: '1.0.0'
517
618
  },
518
619
  paths: {},
519
620
  components: {
@@ -521,16 +622,16 @@ describe('TypeScriptCodeGeneratorService', () => {
521
622
  TreeNode: {
522
623
  type: 'object',
523
624
  properties: {
524
- value: {type: 'string'},
625
+ value: { type: 'string' },
525
626
  children: {
526
627
  type: 'array',
527
- items: {$ref: '#/components/schemas/TreeNode'},
528
- },
628
+ items: { $ref: '#/components/schemas/TreeNode' }
629
+ }
529
630
  },
530
- required: ['value'],
531
- },
532
- },
533
- },
631
+ required: ['value']
632
+ }
633
+ }
634
+ }
534
635
  };
535
636
 
536
637
  const code = generator.generate(spec);
@@ -542,7 +643,7 @@ describe('TypeScriptCodeGeneratorService', () => {
542
643
  openapi: '3.0.0',
543
644
  info: {
544
645
  title: 'Test API',
545
- version: '1.0.0',
646
+ version: '1.0.0'
546
647
  },
547
648
  paths: {},
548
649
  components: {
@@ -550,21 +651,21 @@ describe('TypeScriptCodeGeneratorService', () => {
550
651
  Person: {
551
652
  type: 'object',
552
653
  properties: {
553
- name: {type: 'string'},
554
- bestFriend: {$ref: '#/components/schemas/Friend'},
654
+ name: { type: 'string' },
655
+ bestFriend: { $ref: '#/components/schemas/Friend' }
555
656
  },
556
- required: ['name'],
657
+ required: ['name']
557
658
  },
558
659
  Friend: {
559
660
  type: 'object',
560
661
  properties: {
561
- nickname: {type: 'string'},
562
- person: {$ref: '#/components/schemas/Person'},
662
+ nickname: { type: 'string' },
663
+ person: { $ref: '#/components/schemas/Person' }
563
664
  },
564
- required: ['nickname'],
565
- },
566
- },
567
- },
665
+ required: ['nickname']
666
+ }
667
+ }
668
+ }
568
669
  };
569
670
 
570
671
  const code = generator.generate(spec);
@@ -578,7 +679,7 @@ describe('TypeScriptCodeGeneratorService', () => {
578
679
  openapi: '3.0.0',
579
680
  info: {
580
681
  title: 'Test API',
581
- version: '1.0.0',
682
+ version: '1.0.0'
582
683
  },
583
684
  paths: {},
584
685
  components: {
@@ -586,20 +687,20 @@ describe('TypeScriptCodeGeneratorService', () => {
586
687
  User: {
587
688
  type: 'object',
588
689
  properties: {
589
- id: {type: 'integer'},
590
- profile: {$ref: '#/components/schemas/Profile'},
690
+ id: { type: 'integer' },
691
+ profile: { $ref: '#/components/schemas/Profile' }
591
692
  },
592
- required: ['id'],
693
+ required: ['id']
593
694
  },
594
695
  Profile: {
595
696
  type: 'object',
596
697
  properties: {
597
- name: {type: 'string'},
698
+ name: { type: 'string' }
598
699
  },
599
- required: ['name'],
600
- },
601
- },
602
- },
700
+ required: ['name']
701
+ }
702
+ }
703
+ }
603
704
  };
604
705
 
605
706
  const code = generator.generate(spec);
@@ -613,7 +714,7 @@ describe('TypeScriptCodeGeneratorService', () => {
613
714
  openapi: '3.0.0',
614
715
  info: {
615
716
  title: 'Test API',
616
- version: '1.0.0',
717
+ version: '1.0.0'
617
718
  },
618
719
  paths: {},
619
720
  components: {
@@ -621,23 +722,23 @@ describe('TypeScriptCodeGeneratorService', () => {
621
722
  Alpha: {
622
723
  type: 'object',
623
724
  properties: {
624
- beta: {$ref: '#/components/schemas/Beta'},
625
- },
725
+ beta: { $ref: '#/components/schemas/Beta' }
726
+ }
626
727
  },
627
728
  Beta: {
628
729
  type: 'object',
629
730
  properties: {
630
- gamma: {$ref: '#/components/schemas/Gamma'},
631
- },
731
+ gamma: { $ref: '#/components/schemas/Gamma' }
732
+ }
632
733
  },
633
734
  Gamma: {
634
735
  type: 'object',
635
736
  properties: {
636
- alpha: {$ref: '#/components/schemas/Alpha'},
637
- },
638
- },
639
- },
640
- },
737
+ alpha: { $ref: '#/components/schemas/Alpha' }
738
+ }
739
+ }
740
+ }
741
+ }
641
742
  };
642
743
 
643
744
  const code = generator.generate(spec);
@@ -652,7 +753,7 @@ describe('TypeScriptCodeGeneratorService', () => {
652
753
  openapi: '3.0.0',
653
754
  info: {
654
755
  title: 'Test API',
655
- version: '1.0.0',
756
+ version: '1.0.0'
656
757
  },
657
758
  paths: {},
658
759
  components: {
@@ -660,16 +761,16 @@ describe('TypeScriptCodeGeneratorService', () => {
660
761
  Category: {
661
762
  type: 'object',
662
763
  properties: {
663
- name: {type: 'string'},
764
+ name: { type: 'string' },
664
765
  subcategories: {
665
766
  type: 'array',
666
- items: {$ref: '#/components/schemas/Category'},
667
- },
767
+ items: { $ref: '#/components/schemas/Category' }
768
+ }
668
769
  },
669
- required: ['name'],
670
- },
671
- },
672
- },
770
+ required: ['name']
771
+ }
772
+ }
773
+ }
673
774
  };
674
775
 
675
776
  const code = generator.generate(spec);
@@ -681,7 +782,7 @@ describe('TypeScriptCodeGeneratorService', () => {
681
782
  openapi: '3.0.0',
682
783
  info: {
683
784
  title: 'Test API',
684
- version: '1.0.0',
785
+ version: '1.0.0'
685
786
  },
686
787
  paths: {},
687
788
  components: {
@@ -690,12 +791,12 @@ describe('TypeScriptCodeGeneratorService', () => {
690
791
  type: 'object',
691
792
  properties: {
692
793
  value: {
693
- anyOf: [{type: 'string'}, {$ref: '#/components/schemas/Expression'}],
694
- },
695
- },
696
- },
697
- },
698
- },
794
+ anyOf: [{ type: 'string' }, { $ref: '#/components/schemas/Expression' }]
795
+ }
796
+ }
797
+ }
798
+ }
799
+ }
699
800
  };
700
801
 
701
802
  const code = generator.generate(spec);
@@ -709,16 +810,16 @@ describe('TypeScriptCodeGeneratorService', () => {
709
810
  openapi: '3.0.0',
710
811
  info: {
711
812
  title: 'Test API',
712
- version: '1.0.0',
813
+ version: '1.0.0'
713
814
  },
714
815
  paths: {},
715
816
  components: {
716
817
  schemas: {
717
818
  StringOrNumber: {
718
- anyOf: [{type: 'string'}, {type: 'number'}],
719
- },
720
- },
721
- },
819
+ anyOf: [{ type: 'string' }, { type: 'number' }]
820
+ }
821
+ }
822
+ }
722
823
  };
723
824
 
724
825
  const code = generator.generate(spec);
@@ -732,19 +833,19 @@ describe('TypeScriptCodeGeneratorService', () => {
732
833
  openapi: '3.0.0',
733
834
  info: {
734
835
  title: 'Test API',
735
- version: '1.0.0',
836
+ version: '1.0.0'
736
837
  },
737
838
  paths: {},
738
839
  components: {
739
840
  schemas: {
740
841
  Variant: {
741
842
  oneOf: [
742
- {type: 'object', properties: {name: {type: 'string'}}},
743
- {type: 'object', properties: {id: {type: 'number'}}},
744
- ],
745
- },
746
- },
747
- },
843
+ { type: 'object', properties: { name: { type: 'string' } } },
844
+ { type: 'object', properties: { id: { type: 'number' } } }
845
+ ]
846
+ }
847
+ }
848
+ }
748
849
  };
749
850
 
750
851
  const code = generator.generate(spec);
@@ -756,19 +857,19 @@ describe('TypeScriptCodeGeneratorService', () => {
756
857
  openapi: '3.0.0',
757
858
  info: {
758
859
  title: 'Test API',
759
- version: '1.0.0',
860
+ version: '1.0.0'
760
861
  },
761
862
  paths: {},
762
863
  components: {
763
864
  schemas: {
764
865
  Combined: {
765
866
  allOf: [
766
- {type: 'object', properties: {id: {type: 'number'}}},
767
- {type: 'object', properties: {name: {type: 'string'}}},
768
- ],
769
- },
770
- },
771
- },
867
+ { type: 'object', properties: { id: { type: 'number' } } },
868
+ { type: 'object', properties: { name: { type: 'string' } } }
869
+ ]
870
+ }
871
+ }
872
+ }
772
873
  };
773
874
 
774
875
  const code = generator.generate(spec);
@@ -780,16 +881,16 @@ describe('TypeScriptCodeGeneratorService', () => {
780
881
  openapi: '3.0.0',
781
882
  info: {
782
883
  title: 'Test API',
783
- version: '1.0.0',
884
+ version: '1.0.0'
784
885
  },
785
886
  paths: {},
786
887
  components: {
787
888
  schemas: {
788
889
  EmptyObject: {
789
- anyOf: [{type: 'object', properties: {}}],
790
- },
791
- },
792
- },
890
+ anyOf: [{ type: 'object', properties: {} }]
891
+ }
892
+ }
893
+ }
793
894
  };
794
895
 
795
896
  const code = generator.generate(spec);
@@ -802,16 +903,16 @@ describe('TypeScriptCodeGeneratorService', () => {
802
903
  openapi: '3.0.0',
803
904
  info: {
804
905
  title: 'Test API',
805
- version: '1.0.0',
906
+ version: '1.0.0'
806
907
  },
807
908
  paths: {},
808
909
  components: {
809
910
  schemas: {
810
911
  GenericArray: {
811
- anyOf: [{type: 'array'}],
812
- },
813
- },
814
- },
912
+ anyOf: [{ type: 'array' }]
913
+ }
914
+ }
915
+ }
815
916
  };
816
917
 
817
918
  const code = generator.generate(spec);
@@ -825,16 +926,16 @@ describe('TypeScriptCodeGeneratorService', () => {
825
926
  openapi: '3.0.0',
826
927
  info: {
827
928
  title: 'Test API',
828
- version: '1.0.0',
929
+ version: '1.0.0'
829
930
  },
830
931
  paths: {},
831
932
  components: {
832
933
  schemas: {
833
934
  UnknownType: {
834
- anyOf: [{type: 'unknown' as any}],
835
- },
836
- },
837
- },
935
+ anyOf: [{ type: 'unknown' as any }]
936
+ }
937
+ }
938
+ }
838
939
  };
839
940
 
840
941
  const code = generator.generate(spec);
@@ -846,16 +947,16 @@ describe('TypeScriptCodeGeneratorService', () => {
846
947
  openapi: '3.0.0',
847
948
  info: {
848
949
  title: 'Test API',
849
- version: '1.0.0',
950
+ version: '1.0.0'
850
951
  },
851
952
  paths: {},
852
953
  components: {
853
954
  schemas: {
854
955
  InvalidSchema: {
855
- anyOf: [null as any],
856
- },
857
- },
858
- },
956
+ anyOf: [null as any]
957
+ }
958
+ }
959
+ }
859
960
  };
860
961
 
861
962
  const code = generator.generate(spec);
@@ -868,16 +969,16 @@ describe('TypeScriptCodeGeneratorService', () => {
868
969
  openapi: '3.0.0',
869
970
  info: {
870
971
  title: 'Test API',
871
- version: '1.0.0',
972
+ version: '1.0.0'
872
973
  },
873
974
  paths: {},
874
975
  components: {
875
976
  schemas: {
876
977
  IntOrString: {
877
- anyOf: [{type: 'integer'}, {type: 'string'}],
878
- },
879
- },
880
- },
978
+ anyOf: [{ type: 'integer' }, { type: 'string' }]
979
+ }
980
+ }
981
+ }
881
982
  };
882
983
 
883
984
  const code = generator.generate(spec);
@@ -890,16 +991,16 @@ describe('TypeScriptCodeGeneratorService', () => {
890
991
  openapi: '3.0.0',
891
992
  info: {
892
993
  title: 'Test API',
893
- version: '1.0.0',
994
+ version: '1.0.0'
894
995
  },
895
996
  paths: {},
896
997
  components: {
897
998
  schemas: {
898
999
  BoolOrString: {
899
- anyOf: [{type: 'boolean'}, {type: 'string'}],
900
- },
901
- },
902
- },
1000
+ anyOf: [{ type: 'boolean' }, { type: 'string' }]
1001
+ }
1002
+ }
1003
+ }
903
1004
  };
904
1005
 
905
1006
  const code = generator.generate(spec);
@@ -912,7 +1013,7 @@ describe('TypeScriptCodeGeneratorService', () => {
912
1013
  openapi: '3.0.0',
913
1014
  info: {
914
1015
  title: 'Test API',
915
- version: '1.0.0',
1016
+ version: '1.0.0'
916
1017
  },
917
1018
  paths: {},
918
1019
  components: {
@@ -922,14 +1023,14 @@ describe('TypeScriptCodeGeneratorService', () => {
922
1023
  {
923
1024
  type: 'object',
924
1025
  properties: {
925
- name: {type: 'string'},
926
- age: {type: 'number'},
927
- },
928
- },
929
- ],
930
- },
931
- },
932
- },
1026
+ name: { type: 'string' },
1027
+ age: { type: 'number' }
1028
+ }
1029
+ }
1030
+ ]
1031
+ }
1032
+ }
1033
+ }
933
1034
  };
934
1035
 
935
1036
  const code = generator.generate(spec);
@@ -943,7 +1044,7 @@ describe('TypeScriptCodeGeneratorService', () => {
943
1044
  openapi: '3.0.0',
944
1045
  info: {
945
1046
  title: 'Test API',
946
- version: '1.0.0',
1047
+ version: '1.0.0'
947
1048
  },
948
1049
  paths: {},
949
1050
  components: {
@@ -952,12 +1053,12 @@ describe('TypeScriptCodeGeneratorService', () => {
952
1053
  anyOf: [
953
1054
  {
954
1055
  type: 'array',
955
- items: {type: 'string'},
956
- },
957
- ],
958
- },
959
- },
960
- },
1056
+ items: { type: 'string' }
1057
+ }
1058
+ ]
1059
+ }
1060
+ }
1061
+ }
961
1062
  };
962
1063
 
963
1064
  const code = generator.generate(spec);
@@ -971,16 +1072,16 @@ describe('TypeScriptCodeGeneratorService', () => {
971
1072
  openapi: '3.0.0',
972
1073
  info: {
973
1074
  title: 'Test API',
974
- version: '1.0.0',
1075
+ version: '1.0.0'
975
1076
  },
976
1077
  paths: {},
977
1078
  components: {
978
1079
  schemas: {
979
1080
  InvalidSchema: {
980
- anyOf: [{} as any],
981
- },
982
- },
983
- },
1081
+ anyOf: [{} as any]
1082
+ }
1083
+ }
1084
+ }
984
1085
  };
985
1086
 
986
1087
  const code = generator.generate(spec);
@@ -995,7 +1096,7 @@ describe('TypeScriptCodeGeneratorService', () => {
995
1096
  openapi: '3.0.0',
996
1097
  info: {
997
1098
  title: 'Test API',
998
- version: '1.0.0',
1099
+ version: '1.0.0'
999
1100
  },
1000
1101
  servers: [
1001
1102
  {
@@ -1003,18 +1104,18 @@ describe('TypeScriptCodeGeneratorService', () => {
1003
1104
  variables: {
1004
1105
  environment: {
1005
1106
  default: 'api',
1006
- enum: ['api', 'api.staging', 'api.prod'],
1107
+ enum: ['api', 'api.staging', 'api.prod']
1007
1108
  },
1008
1109
  port: {
1009
- default: '443',
1110
+ default: '443'
1010
1111
  },
1011
1112
  version: {
1012
- default: '1',
1013
- },
1014
- },
1015
- },
1113
+ default: '1'
1114
+ }
1115
+ }
1116
+ }
1016
1117
  ],
1017
- paths: {},
1118
+ paths: {}
1018
1119
  };
1019
1120
 
1020
1121
  const code = generator.generate(spec);
@@ -1031,7 +1132,7 @@ describe('TypeScriptCodeGeneratorService', () => {
1031
1132
  openapi: '3.0.0',
1032
1133
  info: {
1033
1134
  title: 'Test API',
1034
- version: '1.0.0',
1135
+ version: '1.0.0'
1035
1136
  },
1036
1137
  servers: [
1037
1138
  {
@@ -1040,12 +1141,12 @@ describe('TypeScriptCodeGeneratorService', () => {
1040
1141
  env: {
1041
1142
  default: 'prod',
1042
1143
  enum: ['dev', 'staging', 'prod'],
1043
- description: 'Environment',
1044
- },
1045
- },
1046
- },
1144
+ description: 'Environment'
1145
+ }
1146
+ }
1147
+ }
1047
1148
  ],
1048
- paths: {},
1149
+ paths: {}
1049
1150
  };
1050
1151
 
1051
1152
  const code = generator.generate(spec);
@@ -1060,22 +1161,22 @@ describe('TypeScriptCodeGeneratorService', () => {
1060
1161
  openapi: '3.0.0',
1061
1162
  info: {
1062
1163
  title: 'Test API',
1063
- version: '1.0.0',
1164
+ version: '1.0.0'
1064
1165
  },
1065
1166
  servers: [
1066
1167
  {
1067
- url: 'https://api.example.com',
1168
+ url: 'https://api.example.com'
1068
1169
  },
1069
1170
  {
1070
1171
  url: 'https://{env}.example.com',
1071
1172
  variables: {
1072
1173
  env: {
1073
- default: 'staging',
1074
- },
1075
- },
1076
- },
1174
+ default: 'staging'
1175
+ }
1176
+ }
1177
+ }
1077
1178
  ],
1078
- paths: {},
1179
+ paths: {}
1079
1180
  };
1080
1181
 
1081
1182
  const code = generator.generate(spec);
@@ -1085,4 +1186,309 @@ describe('TypeScriptCodeGeneratorService', () => {
1085
1186
  expect(code).toContain('https://{env}.example.com');
1086
1187
  });
1087
1188
  });
1189
+
1190
+ describe('explicit types', () => {
1191
+ it('should generate explicit interface for object schema', () => {
1192
+ const spec: OpenApiSpecType = {
1193
+ openapi: '3.0.0',
1194
+ info: {
1195
+ title: 'Test API',
1196
+ version: '1.0.0'
1197
+ },
1198
+ paths: {},
1199
+ components: {
1200
+ schemas: {
1201
+ Order: {
1202
+ type: 'object',
1203
+ properties: {
1204
+ id: { type: 'integer' },
1205
+ name: { type: 'string' }
1206
+ },
1207
+ required: ['id', 'name']
1208
+ }
1209
+ }
1210
+ }
1211
+ };
1212
+
1213
+ const code = generator.generate(spec);
1214
+
1215
+ // Should generate explicit interface
1216
+ expect(code).toContain('export interface Order');
1217
+ expect(code).toContain('id: number');
1218
+ expect(code).toContain('name: string');
1219
+
1220
+ // Should add type annotation to schema
1221
+ expect(code).toContain('export const Order: z.ZodType<Order>');
1222
+
1223
+ // Should NOT generate z.infer type export
1224
+ expect(code).not.toContain('z.infer<typeof Order>');
1225
+ });
1226
+
1227
+ it('should generate type alias for enum schema', () => {
1228
+ const spec: OpenApiSpecType = {
1229
+ openapi: '3.0.0',
1230
+ info: {
1231
+ title: 'Test API',
1232
+ version: '1.0.0'
1233
+ },
1234
+ paths: {},
1235
+ components: {
1236
+ schemas: {
1237
+ Status: {
1238
+ type: 'string',
1239
+ enum: ['active', 'inactive', 'pending']
1240
+ }
1241
+ }
1242
+ }
1243
+ };
1244
+
1245
+ const code = generator.generate(spec);
1246
+
1247
+ // Should generate type alias (not interface) for enum
1248
+ expect(code).toContain('export type Status');
1249
+ expect(code).toContain("'active'");
1250
+ expect(code).toContain("'inactive'");
1251
+ expect(code).toContain("'pending'");
1252
+
1253
+ // Should add type annotation to schema
1254
+ expect(code).toContain('export const Status: z.ZodType<Status>');
1255
+ });
1256
+
1257
+ it('should generate type alias for array schema', () => {
1258
+ const spec: OpenApiSpecType = {
1259
+ openapi: '3.0.0',
1260
+ info: {
1261
+ title: 'Test API',
1262
+ version: '1.0.0'
1263
+ },
1264
+ paths: {},
1265
+ components: {
1266
+ schemas: {
1267
+ Tags: {
1268
+ type: 'array',
1269
+ items: { type: 'string' }
1270
+ }
1271
+ }
1272
+ }
1273
+ };
1274
+
1275
+ const code = generator.generate(spec);
1276
+
1277
+ // Should generate type alias for array
1278
+ expect(code).toContain('export type Tags = string[]');
1279
+
1280
+ // Should add type annotation to schema
1281
+ expect(code).toContain('export const Tags: z.ZodType<Tags>');
1282
+ });
1283
+
1284
+ it('should handle nested objects with references', () => {
1285
+ const spec: OpenApiSpecType = {
1286
+ openapi: '3.0.0',
1287
+ info: {
1288
+ title: 'Test API',
1289
+ version: '1.0.0'
1290
+ },
1291
+ paths: {},
1292
+ components: {
1293
+ schemas: {
1294
+ User: {
1295
+ type: 'object',
1296
+ properties: {
1297
+ id: { type: 'integer' },
1298
+ profile: { $ref: '#/components/schemas/Profile' }
1299
+ },
1300
+ required: ['id']
1301
+ },
1302
+ Profile: {
1303
+ type: 'object',
1304
+ properties: {
1305
+ name: { type: 'string' }
1306
+ },
1307
+ required: ['name']
1308
+ }
1309
+ }
1310
+ }
1311
+ };
1312
+
1313
+ const code = generator.generate(spec);
1314
+
1315
+ // Should generate interfaces for both
1316
+ expect(code).toContain('export interface User');
1317
+ expect(code).toContain('export interface Profile');
1318
+
1319
+ // User should reference Profile type
1320
+ expect(code).toContain('profile?: Profile');
1321
+
1322
+ // Both should have type annotations
1323
+ expect(code).toContain('export const User: z.ZodType<User>');
1324
+ expect(code).toContain('export const Profile: z.ZodType<Profile>');
1325
+ });
1326
+
1327
+ it('should handle union types (anyOf)', () => {
1328
+ const spec: OpenApiSpecType = {
1329
+ openapi: '3.0.0',
1330
+ info: {
1331
+ title: 'Test API',
1332
+ version: '1.0.0'
1333
+ },
1334
+ paths: {},
1335
+ components: {
1336
+ schemas: {
1337
+ StringOrNumber: {
1338
+ anyOf: [{ type: 'string' }, { type: 'number' }]
1339
+ }
1340
+ }
1341
+ }
1342
+ };
1343
+
1344
+ const code = generator.generate(spec);
1345
+
1346
+ // Should generate type alias for union
1347
+ expect(code).toContain('export type StringOrNumber = string | number');
1348
+
1349
+ // Should add type annotation to schema
1350
+ expect(code).toContain('export const StringOrNumber: z.ZodType<StringOrNumber>');
1351
+ });
1352
+
1353
+ it('should handle intersection types (allOf)', () => {
1354
+ const spec: OpenApiSpecType = {
1355
+ openapi: '3.0.0',
1356
+ info: {
1357
+ title: 'Test API',
1358
+ version: '1.0.0'
1359
+ },
1360
+ paths: {},
1361
+ components: {
1362
+ schemas: {
1363
+ Base: {
1364
+ type: 'object',
1365
+ properties: {
1366
+ id: { type: 'integer' }
1367
+ },
1368
+ required: ['id']
1369
+ },
1370
+ Extended: {
1371
+ allOf: [
1372
+ { $ref: '#/components/schemas/Base' },
1373
+ {
1374
+ type: 'object',
1375
+ properties: {
1376
+ name: { type: 'string' }
1377
+ }
1378
+ }
1379
+ ]
1380
+ }
1381
+ }
1382
+ }
1383
+ };
1384
+
1385
+ const code = generator.generate(spec);
1386
+
1387
+ // Should generate interface for Base
1388
+ expect(code).toContain('export interface Base');
1389
+
1390
+ // Should generate type alias for Extended (intersection)
1391
+ expect(code).toContain('export type Extended = Base &');
1392
+
1393
+ // Should add type annotations to schemas
1394
+ expect(code).toContain('export const Base: z.ZodType<Base>');
1395
+ expect(code).toContain('export const Extended: z.ZodType<Extended>');
1396
+ });
1397
+
1398
+ it('should handle optional properties', () => {
1399
+ const spec: OpenApiSpecType = {
1400
+ openapi: '3.0.0',
1401
+ info: {
1402
+ title: 'Test API',
1403
+ version: '1.0.0'
1404
+ },
1405
+ paths: {},
1406
+ components: {
1407
+ schemas: {
1408
+ User: {
1409
+ type: 'object',
1410
+ properties: {
1411
+ id: { type: 'integer' },
1412
+ email: { type: 'string' }
1413
+ },
1414
+ required: ['id']
1415
+ }
1416
+ }
1417
+ }
1418
+ };
1419
+
1420
+ const code = generator.generate(spec);
1421
+
1422
+ // id should be required (no ?)
1423
+ expect(code).toContain('id: number');
1424
+ // email should be optional (with ?)
1425
+ expect(code).toContain('email?: string');
1426
+ });
1427
+
1428
+ it('should handle circular dependencies', () => {
1429
+ const spec: OpenApiSpecType = {
1430
+ openapi: '3.0.0',
1431
+ info: {
1432
+ title: 'Test API',
1433
+ version: '1.0.0'
1434
+ },
1435
+ paths: {},
1436
+ components: {
1437
+ schemas: {
1438
+ Node: {
1439
+ type: 'object',
1440
+ properties: {
1441
+ id: { type: 'integer' },
1442
+ children: {
1443
+ type: 'array',
1444
+ items: { $ref: '#/components/schemas/Node' }
1445
+ }
1446
+ },
1447
+ required: ['id']
1448
+ }
1449
+ }
1450
+ }
1451
+ };
1452
+
1453
+ const code = generator.generate(spec);
1454
+
1455
+ // Should generate interface with self-reference
1456
+ expect(code).toContain('export interface Node');
1457
+ expect(code).toContain('children?: Node[]');
1458
+
1459
+ // Should add type annotation to schema
1460
+ expect(code).toContain('export const Node: z.ZodType<Node>');
1461
+
1462
+ // Zod schema should use z.lazy for circular reference
1463
+ expect(code).toContain('z.lazy');
1464
+ });
1465
+
1466
+ it('should handle numeric enum types', () => {
1467
+ const spec: OpenApiSpecType = {
1468
+ openapi: '3.0.0',
1469
+ info: {
1470
+ title: 'Test API',
1471
+ version: '1.0.0'
1472
+ },
1473
+ paths: {},
1474
+ components: {
1475
+ schemas: {
1476
+ Priority: {
1477
+ type: 'integer',
1478
+ enum: [0, 1, 2]
1479
+ }
1480
+ }
1481
+ }
1482
+ };
1483
+
1484
+ const code = generator.generate(spec);
1485
+
1486
+ // Should generate type alias for numeric enum
1487
+ expect(code).toContain('export type Priority');
1488
+ expect(code).toMatch(/0\s*\|\s*1\s*\|\s*2/);
1489
+
1490
+ // Should add type annotation to schema
1491
+ expect(code).toContain('export const Priority: z.ZodType<Priority>');
1492
+ });
1493
+ });
1088
1494
  });