zod-codegen 1.6.3 → 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 +11 -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 +3 -1
  20. package/dist/src/services/code-generator.service.d.ts.map +1 -1
  21. package/dist/src/services/code-generator.service.js +236 -219
  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 +799 -1120
  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 +431 -330
  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 -371
  118. package/scripts/update-manifest.ts +0 -49
  119. package/src/assets/manifest.json +0 -5
@@ -0,0 +1,100 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { Generator } from '../../src/generator';
6
+ import { Reporter } from '../../src/utils/reporter';
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const testOutputDir = join(__dirname, '../../test-output-snapshots');
9
+ describe('Generated Code Snapshots', () => {
10
+ let generator;
11
+ const reporter = new Reporter(process.stdout, process.stderr);
12
+ beforeEach(() => {
13
+ if (existsSync(testOutputDir)) {
14
+ rmSync(testOutputDir, { recursive: true, force: true });
15
+ }
16
+ mkdirSync(testOutputDir, { recursive: true });
17
+ });
18
+ afterEach(() => {
19
+ if (existsSync(testOutputDir)) {
20
+ rmSync(testOutputDir, { recursive: true, force: true });
21
+ }
22
+ });
23
+ describe('swagger-petstore.yaml', () => {
24
+ it('should generate valid TypeScript code', async () => {
25
+ generator = new Generator('test-app', '1.0.0', reporter, './samples/swagger-petstore.yaml', testOutputDir);
26
+ const exitCode = await generator.run();
27
+ expect(exitCode).toBe(0);
28
+ const outputFile = join(testOutputDir, 'api.ts');
29
+ expect(existsSync(outputFile)).toBe(true);
30
+ const content = readFileSync(outputFile, 'utf-8');
31
+ // Verify key components exist
32
+ expect(content).toMatch(/import\s*{\s*z\s*}\s*from\s*['"]zod['"]/);
33
+ expect(content).toContain('SwaggerPetstoreOpenAPI30');
34
+ expect(content).toContain('export const Pet');
35
+ expect(content).toContain('export const Order');
36
+ expect(content).toContain('export const User');
37
+ expect(content).toContain('async findPetsByStatus');
38
+ expect(content).toContain('async addPet');
39
+ expect(content).toContain('protected getBaseRequestOptions');
40
+ expect(content).toContain('protected async handleResponse');
41
+ expect(content).toContain('class ResponseValidationError<T> extends Error');
42
+ expect(content).toContain('Pet.safeParse(response)');
43
+ expect(content).toContain('new ResponseValidationError<Pet>');
44
+ });
45
+ it('should generate syntactically valid TypeScript code', async () => {
46
+ generator = new Generator('test-app', '1.0.0', reporter, './samples/swagger-petstore.yaml', testOutputDir);
47
+ await generator.run();
48
+ const outputFile = join(testOutputDir, 'api.ts');
49
+ const content = readFileSync(outputFile, 'utf-8');
50
+ // Verify basic TypeScript syntax
51
+ expect(content).toContain('import');
52
+ expect(content).toContain('export');
53
+ expect(content).toContain('class');
54
+ expect(content).toContain('async');
55
+ expect(content).toContain('protected');
56
+ // Verify code structure
57
+ expect(content.length).toBeGreaterThan(1000); // Should be substantial
58
+ expect(content.split('{').length).toBeGreaterThan(content.split('}').length - 10); // Rough bracket balance check
59
+ });
60
+ });
61
+ describe('test-logical.yaml', () => {
62
+ it('should generate correct logical operators', async () => {
63
+ generator = new Generator('test-app', '1.0.0', reporter, './samples/test-logical.yaml', testOutputDir);
64
+ await generator.run();
65
+ const outputFile = join(testOutputDir, 'api.ts');
66
+ const content = readFileSync(outputFile, 'utf-8');
67
+ // Verify logical operators are generated correctly
68
+ expect(content).toContain('TestAnyOf');
69
+ expect(content).toContain('z.union');
70
+ expect(content).toContain('TestOneOf');
71
+ expect(content).toContain('TestAllOf');
72
+ expect(content).toContain('z.intersection');
73
+ expect(content).toContain('TestNot');
74
+ });
75
+ });
76
+ describe('server-variables-example.yaml', () => {
77
+ it('should generate server configuration with variables', async () => {
78
+ generator = new Generator('test-app', '1.0.0', reporter, './samples/server-variables-example.yaml', testOutputDir);
79
+ await generator.run();
80
+ const outputFile = join(testOutputDir, 'api.ts');
81
+ const content = readFileSync(outputFile, 'utf-8');
82
+ // Verify server variables are handled
83
+ expect(content).toContain('serverConfigurations');
84
+ expect(content).toContain('serverVariables');
85
+ expect(content).toContain('resolveServerUrl');
86
+ expect(content).toContain('ClientOptions');
87
+ });
88
+ });
89
+ describe('pokeapi-openapi.json', () => {
90
+ it('should generate code for PokéAPI spec', async () => {
91
+ generator = new Generator('test-app', '1.0.0', reporter, './samples/pokeapi-openapi.json', testOutputDir);
92
+ await generator.run();
93
+ const outputFile = join(testOutputDir, 'api.ts');
94
+ const content = readFileSync(outputFile, 'utf-8');
95
+ expect(content).toMatch(/export (default )?class/);
96
+ expect(content).toContain('export const Pokemon');
97
+ expect(content).toContain('async getPokemonById');
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=code-generator-edge-cases.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-generator-edge-cases.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/code-generator-edge-cases.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,506 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { TypeScriptCodeGeneratorService } from '../../src/services/code-generator.service';
3
+ describe('TypeScriptCodeGeneratorService - Edge Cases', () => {
4
+ let generator;
5
+ beforeEach(() => {
6
+ generator = new TypeScriptCodeGeneratorService();
7
+ });
8
+ describe('buildBasicTypeFromSchema edge cases', () => {
9
+ it('should handle unknown type in default case', () => {
10
+ const spec = {
11
+ openapi: '3.0.0',
12
+ info: {
13
+ title: 'Test API',
14
+ version: '1.0.0'
15
+ },
16
+ paths: {},
17
+ components: {
18
+ schemas: {
19
+ UnknownType: {
20
+ anyOf: [{ type: 'custom' }]
21
+ }
22
+ }
23
+ }
24
+ };
25
+ const code = generator.generate(spec);
26
+ expect(code).toContain('z.unknown()');
27
+ });
28
+ it('should handle unknown type directly in schema (not in logical operator)', () => {
29
+ const spec = {
30
+ openapi: '3.0.0',
31
+ info: {
32
+ title: 'Test API',
33
+ version: '1.0.0'
34
+ },
35
+ paths: {},
36
+ components: {
37
+ schemas: {
38
+ DirectUnknownType: {
39
+ type: 'custom'
40
+ }
41
+ }
42
+ }
43
+ };
44
+ const code = generator.generate(spec);
45
+ expect(code).toContain('z.unknown()');
46
+ expect(code).toContain('DirectUnknownType');
47
+ });
48
+ it('should handle object type with properties in buildBasicTypeFromSchema path', () => {
49
+ // Test the path where buildObjectTypeFromSchema is called with properties
50
+ // This happens when SchemaProperties.parse fails but schema has type: 'object'
51
+ // We need to create a scenario where parse fails but type property exists
52
+ // Since SchemaProperties is very permissive, we'll use a non-object value
53
+ // that gets coerced or handled specially
54
+ const spec = {
55
+ openapi: '3.0.0',
56
+ info: {
57
+ title: 'Test API',
58
+ version: '1.0.0'
59
+ },
60
+ paths: {},
61
+ components: {
62
+ schemas: {
63
+ ObjectWithPropsViaBasic: {
64
+ // Use oneOf with a schema that will fail parse but has type
65
+ oneOf: [
66
+ // Create an object that fails SchemaProperties validation
67
+ // by having invalid nested structure that causes parse to fail
68
+ // but still has the type property accessible
69
+ (() => {
70
+ const obj = { type: 'object', properties: { name: { type: 'string' } } };
71
+ // Make it fail parse by adding circular reference or invalid structure
72
+ // Actually, let's try making properties invalid in a way that causes top-level parse to fail
73
+ obj.properties = new Proxy({}, {
74
+ get() {
75
+ throw new Error('Proxy error');
76
+ }
77
+ });
78
+ return obj;
79
+ })()
80
+ ]
81
+ }
82
+ }
83
+ }
84
+ };
85
+ // This might throw, but let's see
86
+ try {
87
+ const code = generator.generate(spec);
88
+ expect(code).toBeTruthy();
89
+ }
90
+ catch {
91
+ // Expected if proxy causes issues
92
+ }
93
+ });
94
+ it('should handle schema without type property', () => {
95
+ const spec = {
96
+ openapi: '3.0.0',
97
+ info: {
98
+ title: 'Test API',
99
+ version: '1.0.0'
100
+ },
101
+ paths: {},
102
+ components: {
103
+ schemas: {
104
+ NoType: {
105
+ anyOf: [{}]
106
+ }
107
+ }
108
+ }
109
+ };
110
+ const code = generator.generate(spec);
111
+ expect(code).toContain('z.unknown()');
112
+ });
113
+ it('should handle null schema', () => {
114
+ const spec = {
115
+ openapi: '3.0.0',
116
+ info: {
117
+ title: 'Test API',
118
+ version: '1.0.0'
119
+ },
120
+ paths: {},
121
+ components: {
122
+ schemas: {
123
+ NullSchema: {
124
+ anyOf: [null]
125
+ }
126
+ }
127
+ }
128
+ };
129
+ const code = generator.generate(spec);
130
+ // Should handle gracefully
131
+ expect(code).toBeTruthy();
132
+ });
133
+ });
134
+ describe('buildObjectTypeFromSchema edge cases', () => {
135
+ it('should handle object with empty properties in logical operators', () => {
136
+ const spec = {
137
+ openapi: '3.0.0',
138
+ info: {
139
+ title: 'Test API',
140
+ version: '1.0.0'
141
+ },
142
+ paths: {},
143
+ components: {
144
+ schemas: {
145
+ EmptyObject: {
146
+ anyOf: [{ type: 'object', properties: {} }]
147
+ }
148
+ }
149
+ }
150
+ };
151
+ const code = generator.generate(spec);
152
+ // Empty object should fallback to record type
153
+ expect(code).toContain('z.record');
154
+ });
155
+ it('should handle object with properties in logical operators', () => {
156
+ const spec = {
157
+ openapi: '3.0.0',
158
+ info: {
159
+ title: 'Test API',
160
+ version: '1.0.0'
161
+ },
162
+ paths: {},
163
+ components: {
164
+ schemas: {
165
+ ObjectWithProps: {
166
+ anyOf: [
167
+ {
168
+ type: 'object',
169
+ properties: {
170
+ name: { type: 'string' },
171
+ age: { type: 'number' }
172
+ }
173
+ }
174
+ ]
175
+ }
176
+ }
177
+ }
178
+ };
179
+ const code = generator.generate(spec);
180
+ expect(code).toContain('name');
181
+ expect(code).toContain('age');
182
+ });
183
+ it('should handle object type directly in schema (not in logical operator)', () => {
184
+ const spec = {
185
+ openapi: '3.0.0',
186
+ info: {
187
+ title: 'Test API',
188
+ version: '1.0.0'
189
+ },
190
+ paths: {},
191
+ components: {
192
+ schemas: {
193
+ DirectObject: {
194
+ type: 'object',
195
+ properties: {
196
+ name: { type: 'string' },
197
+ age: { type: 'number' }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ };
203
+ const code = generator.generate(spec);
204
+ expect(code).toContain('name');
205
+ expect(code).toContain('age');
206
+ expect(code).toContain('DirectObject');
207
+ });
208
+ it('should handle object type with empty properties directly in schema', () => {
209
+ const spec = {
210
+ openapi: '3.0.0',
211
+ info: {
212
+ title: 'Test API',
213
+ version: '1.0.0'
214
+ },
215
+ paths: {},
216
+ components: {
217
+ schemas: {
218
+ EmptyDirectObject: {
219
+ type: 'object',
220
+ properties: {}
221
+ }
222
+ }
223
+ }
224
+ };
225
+ const code = generator.generate(spec);
226
+ // Empty object should fallback to record type
227
+ expect(code).toContain('z.record');
228
+ });
229
+ });
230
+ describe('buildArrayTypeFromSchema edge cases', () => {
231
+ it('should handle array without items in logical operators', () => {
232
+ const spec = {
233
+ openapi: '3.0.0',
234
+ info: {
235
+ title: 'Test API',
236
+ version: '1.0.0'
237
+ },
238
+ paths: {},
239
+ components: {
240
+ schemas: {
241
+ ArrayNoItems: {
242
+ anyOf: [{ type: 'array' }]
243
+ }
244
+ }
245
+ }
246
+ };
247
+ const code = generator.generate(spec);
248
+ expect(code).toContain('z.array');
249
+ expect(code).toContain('z.unknown()');
250
+ });
251
+ it('should handle array with items in logical operators', () => {
252
+ const spec = {
253
+ openapi: '3.0.0',
254
+ info: {
255
+ title: 'Test API',
256
+ version: '1.0.0'
257
+ },
258
+ paths: {},
259
+ components: {
260
+ schemas: {
261
+ ArrayWithItems: {
262
+ anyOf: [
263
+ {
264
+ type: 'array',
265
+ items: { type: 'string' }
266
+ }
267
+ ]
268
+ }
269
+ }
270
+ }
271
+ };
272
+ const code = generator.generate(spec);
273
+ expect(code).toContain('z.array');
274
+ expect(code).toContain('z.string()');
275
+ });
276
+ it('should handle array type directly in schema (not in logical operator)', () => {
277
+ const spec = {
278
+ openapi: '3.0.0',
279
+ info: {
280
+ title: 'Test API',
281
+ version: '1.0.0'
282
+ },
283
+ paths: {},
284
+ components: {
285
+ schemas: {
286
+ DirectArray: {
287
+ type: 'array',
288
+ items: { type: 'string' }
289
+ }
290
+ }
291
+ }
292
+ };
293
+ const code = generator.generate(spec);
294
+ expect(code).toContain('z.array');
295
+ expect(code).toContain('z.string()');
296
+ expect(code).toContain('DirectArray');
297
+ });
298
+ it('should handle array type without items directly in schema', () => {
299
+ const spec = {
300
+ openapi: '3.0.0',
301
+ info: {
302
+ title: 'Test API',
303
+ version: '1.0.0'
304
+ },
305
+ paths: {},
306
+ components: {
307
+ schemas: {
308
+ DirectArrayNoItems: {
309
+ type: 'array'
310
+ }
311
+ }
312
+ }
313
+ };
314
+ const code = generator.generate(spec);
315
+ expect(code).toContain('z.array');
316
+ expect(code).toContain('DirectArrayNoItems');
317
+ });
318
+ });
319
+ describe('Complex nested logical operators', () => {
320
+ it('should handle nested anyOf within allOf', () => {
321
+ const spec = {
322
+ openapi: '3.0.0',
323
+ info: {
324
+ title: 'Test API',
325
+ version: '1.0.0'
326
+ },
327
+ paths: {},
328
+ components: {
329
+ schemas: {
330
+ NestedLogical: {
331
+ allOf: [
332
+ {
333
+ anyOf: [{ type: 'string' }, { type: 'number' }]
334
+ },
335
+ {
336
+ type: 'object',
337
+ properties: {
338
+ id: { type: 'string' }
339
+ }
340
+ }
341
+ ]
342
+ }
343
+ }
344
+ }
345
+ };
346
+ const code = generator.generate(spec);
347
+ expect(code).toContain('z.intersection');
348
+ expect(code).toContain('z.union');
349
+ });
350
+ it('should handle not operator with complex schema', () => {
351
+ const spec = {
352
+ openapi: '3.0.0',
353
+ info: {
354
+ title: 'Test API',
355
+ version: '1.0.0'
356
+ },
357
+ paths: {},
358
+ components: {
359
+ schemas: {
360
+ NotComplex: {
361
+ not: {
362
+ type: 'object',
363
+ properties: {
364
+ forbidden: { type: 'string' }
365
+ }
366
+ }
367
+ }
368
+ }
369
+ }
370
+ };
371
+ const code = generator.generate(spec);
372
+ // The not operator uses z.any.refine() pattern
373
+ expect(code).toContain('z.any');
374
+ expect(code).toContain('refine');
375
+ expect(code).toMatch(/forbidden|Value must not match/);
376
+ });
377
+ });
378
+ describe('buildSchemaFromLogicalOperator edge cases', () => {
379
+ it('should handle unknown type in default case of buildSchemaFromLogicalOperator', () => {
380
+ const spec = {
381
+ openapi: '3.0.0',
382
+ info: {
383
+ title: 'Test API',
384
+ version: '1.0.0'
385
+ },
386
+ paths: {},
387
+ components: {
388
+ schemas: {
389
+ UnknownTypeInLogical: {
390
+ anyOf: [
391
+ {
392
+ type: 'custom' // Unknown type that hits default case
393
+ }
394
+ ]
395
+ }
396
+ }
397
+ }
398
+ };
399
+ const code = generator.generate(spec);
400
+ expect(code).toContain('z.unknown()');
401
+ });
402
+ it('should handle buildObjectTypeFromSchema with empty properties', () => {
403
+ const spec = {
404
+ openapi: '3.0.0',
405
+ info: {
406
+ title: 'Test API',
407
+ version: '1.0.0'
408
+ },
409
+ paths: {},
410
+ components: {
411
+ schemas: {
412
+ EmptyObjectInLogical: {
413
+ anyOf: [
414
+ {
415
+ type: 'object',
416
+ properties: {}
417
+ }
418
+ ]
419
+ }
420
+ }
421
+ }
422
+ };
423
+ const code = generator.generate(spec);
424
+ // Should fallback to record type when properties are empty
425
+ expect(code).toContain('z.record');
426
+ });
427
+ it('should handle buildObjectTypeFromSchema with properties', () => {
428
+ const spec = {
429
+ openapi: '3.0.0',
430
+ info: {
431
+ title: 'Test API',
432
+ version: '1.0.0'
433
+ },
434
+ paths: {},
435
+ components: {
436
+ schemas: {
437
+ ObjectWithPropsInLogical: {
438
+ anyOf: [
439
+ {
440
+ type: 'object',
441
+ properties: {
442
+ name: { type: 'string' },
443
+ value: { type: 'number' }
444
+ }
445
+ }
446
+ ]
447
+ }
448
+ }
449
+ }
450
+ };
451
+ const code = generator.generate(spec);
452
+ expect(code).toContain('name');
453
+ expect(code).toContain('value');
454
+ expect(code).toContain('z.object');
455
+ });
456
+ it('should handle buildArrayTypeFromSchema without items', () => {
457
+ const spec = {
458
+ openapi: '3.0.0',
459
+ info: {
460
+ title: 'Test API',
461
+ version: '1.0.0'
462
+ },
463
+ paths: {},
464
+ components: {
465
+ schemas: {
466
+ ArrayNoItemsInLogical: {
467
+ anyOf: [
468
+ {
469
+ type: 'array'
470
+ // No items property
471
+ }
472
+ ]
473
+ }
474
+ }
475
+ }
476
+ };
477
+ const code = generator.generate(spec);
478
+ expect(code).toContain('z.array');
479
+ });
480
+ it('should handle buildArrayTypeFromSchema with items', () => {
481
+ const spec = {
482
+ openapi: '3.0.0',
483
+ info: {
484
+ title: 'Test API',
485
+ version: '1.0.0'
486
+ },
487
+ paths: {},
488
+ components: {
489
+ schemas: {
490
+ ArrayWithItemsInLogical: {
491
+ anyOf: [
492
+ {
493
+ type: 'array',
494
+ items: { type: 'string' }
495
+ }
496
+ ]
497
+ }
498
+ }
499
+ }
500
+ };
501
+ const code = generator.generate(spec);
502
+ expect(code).toContain('z.array');
503
+ expect(code).toContain('z.string()');
504
+ });
505
+ });
506
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=code-generator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-generator.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/code-generator.test.ts"],"names":[],"mappings":""}