zod-codegen 1.1.0 → 1.1.2
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/CHANGELOG.md +11 -0
- package/EXAMPLES.md +33 -7
- package/README.md +2 -0
- package/dist/src/cli.js +1 -0
- package/dist/src/services/code-generator.service.js +38 -19
- package/dist/tests/unit/code-generator.test.js +71 -0
- package/package.json +1 -1
- package/src/cli.ts +1 -0
- package/src/services/code-generator.service.ts +66 -31
- package/tests/unit/code-generator.test.ts +78 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## <small>1.1.2 (2025-11-13)</small>
|
|
2
|
+
|
|
3
|
+
- Merge pull request #30 from julienandreu/fix/cli-strict-mode ([d098ac7](https://github.com/julienandreu/zod-codegen/commit/d098ac7)), closes [#30](https://github.com/julienandreu/zod-codegen/issues/30)
|
|
4
|
+
- fix: add strict mode to CLI to catch typos in arguments ([b5ad8aa](https://github.com/julienandreu/zod-codegen/commit/b5ad8aa))
|
|
5
|
+
|
|
6
|
+
## <small>1.1.1 (2025-11-13)</small>
|
|
7
|
+
|
|
8
|
+
- Merge pull request #29 from julienandreu/feat/server-configuration-and-examples ([c598b5a](https://github.com/julienandreu/zod-codegen/commit/c598b5a)), closes [#29](https://github.com/julienandreu/zod-codegen/issues/29)
|
|
9
|
+
- fix: clarify getBaseRequestOptions merging behavior ([13379fd](https://github.com/julienandreu/zod-codegen/commit/13379fd))
|
|
10
|
+
- fix: use z.union with z.literal for numeric enums instead of z.enum ([5e0c7ea](https://github.com/julienandreu/zod-codegen/commit/5e0c7ea))
|
|
11
|
+
|
|
1
12
|
## 1.1.0 (2025-11-13)
|
|
2
13
|
|
|
3
14
|
- Merge pull request #27 from julienandreu/docs/update-readme-reflect-changes ([3f6745e](https://github.com/julienandreu/zod-codegen/commit/3f6745e)), closes [#27](https://github.com/julienandreu/zod-codegen/issues/27)
|
package/EXAMPLES.md
CHANGED
|
@@ -405,10 +405,17 @@ The generated client uses a layered approach to request configuration:
|
|
|
405
405
|
|
|
406
406
|
When a request is made, options are merged in this order (later values override earlier ones):
|
|
407
407
|
|
|
408
|
-
1. **Base Options** from `getBaseRequestOptions()` (headers, signal, credentials, etc.)
|
|
409
|
-
2. **Content-Type Header**
|
|
410
|
-
3. **Request-Specific Headers**
|
|
411
|
-
4. **Method and Body**
|
|
408
|
+
1. **Base Options** from `getBaseRequestOptions()` - All RequestInit options (headers, signal, credentials, mode, cache, etc.)
|
|
409
|
+
2. **Content-Type Header** - Automatically set based on request body (`application/json` or `application/x-www-form-urlencoded`)
|
|
410
|
+
3. **Request-Specific Headers** - From `options.headers` parameter (if provided)
|
|
411
|
+
4. **Method and Body** - Always set by generated code (cannot be overridden)
|
|
412
|
+
|
|
413
|
+
**Important**: `getBaseRequestOptions()` returns **base options that are merged with**, not replaced by, request-specific options. This means:
|
|
414
|
+
|
|
415
|
+
- ✅ Base options like `mode`, `credentials`, `signal` are preserved
|
|
416
|
+
- ✅ Headers are merged (base headers + Content-Type + request headers)
|
|
417
|
+
- ✅ Request-specific headers override base headers
|
|
418
|
+
- ✅ Method and body always come from the request (not from baseOptions)
|
|
412
419
|
|
|
413
420
|
### Type Safety
|
|
414
421
|
|
|
@@ -419,20 +426,39 @@ The `getBaseRequestOptions()` method returns `Partial<Omit<RequestInit, 'method'
|
|
|
419
426
|
|
|
420
427
|
This ensures type safety while preventing accidental overrides of critical request properties.
|
|
421
428
|
|
|
422
|
-
###
|
|
429
|
+
### Complete Options Merging Details
|
|
423
430
|
|
|
424
|
-
|
|
431
|
+
The final fetch request uses `Object.assign()` to merge options:
|
|
425
432
|
|
|
426
433
|
```typescript
|
|
434
|
+
// Headers are merged first:
|
|
427
435
|
const finalHeaders = Object.assign(
|
|
428
436
|
{}, // Start with empty object
|
|
429
437
|
baseOptions.headers || {}, // 1. Base headers from getBaseRequestOptions()
|
|
430
438
|
{'Content-Type': contentType}, // 2. Content-Type (may override base)
|
|
431
439
|
options.headers || {}, // 3. Request-specific headers (highest priority)
|
|
432
440
|
);
|
|
441
|
+
|
|
442
|
+
// Then all options are merged:
|
|
443
|
+
const finalOptions = Object.assign(
|
|
444
|
+
{}, // Start with empty object
|
|
445
|
+
baseOptions, // 1. All base options (mode, credentials, signal, cache, etc.)
|
|
446
|
+
{
|
|
447
|
+
// 2. Request-specific options (override base)
|
|
448
|
+
method, // Always from endpoint
|
|
449
|
+
headers: finalHeaders, // Merged headers
|
|
450
|
+
body, // Always from request data
|
|
451
|
+
},
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
fetch(url, finalOptions);
|
|
433
455
|
```
|
|
434
456
|
|
|
435
|
-
**Important**:
|
|
457
|
+
**Important**:
|
|
458
|
+
|
|
459
|
+
- Always return `Record<string, string>` for headers in `getBaseRequestOptions()` for predictable merging behavior
|
|
460
|
+
- Base options (like `mode`, `credentials`, `signal`) are preserved unless explicitly overridden
|
|
461
|
+
- Headers are merged, not replaced - base headers + Content-Type + request headers
|
|
436
462
|
|
|
437
463
|
### Request Flow
|
|
438
464
|
|
package/README.md
CHANGED
|
@@ -253,6 +253,8 @@ The generated client includes a protected `getBaseRequestOptions()` method that
|
|
|
253
253
|
- **CORS**: `mode`, `credentials` for cross-origin requests
|
|
254
254
|
- **Request Options**: `signal` (AbortController), `cache`, `redirect`, `referrer`, etc.
|
|
255
255
|
|
|
256
|
+
**Important**: Options from `getBaseRequestOptions()` are **merged with** (not replaced by) request-specific options. Base options like `mode`, `credentials`, and `signal` are preserved, while headers are merged (base headers + Content-Type + request headers). See [EXAMPLES.md](EXAMPLES.md) for detailed merging behavior.
|
|
257
|
+
|
|
256
258
|
#### Basic Authentication Example
|
|
257
259
|
|
|
258
260
|
```typescript
|
package/dist/src/cli.js
CHANGED
|
@@ -708,26 +708,45 @@ export class TypeScriptCodeGeneratorService {
|
|
|
708
708
|
}
|
|
709
709
|
// Handle enum
|
|
710
710
|
if (prop['enum'] && Array.isArray(prop['enum']) && prop['enum'].length > 0) {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
711
|
+
// Check if all enum values are strings (z.enum only works with strings)
|
|
712
|
+
const allStrings = prop['enum'].every((val) => typeof val === 'string');
|
|
713
|
+
if (allStrings) {
|
|
714
|
+
// Use z.enum() for string enums
|
|
715
|
+
const enumValues = prop['enum'].map((val) => ts.factory.createStringLiteral(val, true));
|
|
716
|
+
const enumExpression = ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('enum')), undefined, [ts.factory.createArrayLiteralExpression(enumValues, false)]);
|
|
717
|
+
return required
|
|
718
|
+
? enumExpression
|
|
719
|
+
: ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(enumExpression, ts.factory.createIdentifier('optional')), undefined, []);
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
// Use z.union([z.literal(...), ...]) for numeric/boolean/mixed enums
|
|
723
|
+
const literalSchemas = prop['enum'].map((val) => {
|
|
724
|
+
let literalValue;
|
|
725
|
+
if (typeof val === 'string') {
|
|
726
|
+
literalValue = ts.factory.createStringLiteral(val, true);
|
|
719
727
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
728
|
+
else if (typeof val === 'number') {
|
|
729
|
+
// Handle negative numbers correctly
|
|
730
|
+
if (val < 0) {
|
|
731
|
+
literalValue = ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(String(Math.abs(val))));
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
literalValue = ts.factory.createNumericLiteral(String(val));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
else if (typeof val === 'boolean') {
|
|
738
|
+
literalValue = val ? ts.factory.createTrue() : ts.factory.createFalse();
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
literalValue = ts.factory.createStringLiteral(String(val), true);
|
|
742
|
+
}
|
|
743
|
+
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('literal')), undefined, [literalValue]);
|
|
744
|
+
});
|
|
745
|
+
const unionExpression = ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('union')), undefined, [ts.factory.createArrayLiteralExpression(literalSchemas, false)]);
|
|
746
|
+
return required
|
|
747
|
+
? unionExpression
|
|
748
|
+
: ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(unionExpression, ts.factory.createIdentifier('optional')), undefined, []);
|
|
749
|
+
}
|
|
731
750
|
}
|
|
732
751
|
switch (prop['type']) {
|
|
733
752
|
case 'array': {
|
|
@@ -167,6 +167,77 @@ describe('TypeScriptCodeGeneratorService', () => {
|
|
|
167
167
|
expect(code).toContain('inactive');
|
|
168
168
|
expect(code).toContain('pending');
|
|
169
169
|
});
|
|
170
|
+
it('should handle numeric enum types with z.union and z.literal', () => {
|
|
171
|
+
const spec = {
|
|
172
|
+
openapi: '3.0.0',
|
|
173
|
+
info: {
|
|
174
|
+
title: 'Test API',
|
|
175
|
+
version: '1.0.0',
|
|
176
|
+
},
|
|
177
|
+
paths: {},
|
|
178
|
+
components: {
|
|
179
|
+
schemas: {
|
|
180
|
+
Status: {
|
|
181
|
+
type: 'integer',
|
|
182
|
+
enum: [-99, 0, 1, 2],
|
|
183
|
+
},
|
|
184
|
+
ExecutionMode: {
|
|
185
|
+
type: 'integer',
|
|
186
|
+
enum: [1, 2],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
const code = generator.generate(spec);
|
|
192
|
+
// Numeric enums should use z.union([z.literal(...), ...])
|
|
193
|
+
expect(code).toContain('z.union');
|
|
194
|
+
expect(code).toContain('z.literal');
|
|
195
|
+
expect(code).toContain('-99');
|
|
196
|
+
expect(code).toContain('0');
|
|
197
|
+
expect(code).toContain('1');
|
|
198
|
+
expect(code).toContain('2');
|
|
199
|
+
// Should not use z.enum for numeric enums
|
|
200
|
+
expect(code).not.toContain('Status: z.enum');
|
|
201
|
+
expect(code).not.toContain('ExecutionMode: z.enum');
|
|
202
|
+
});
|
|
203
|
+
it('should merge baseOptions with request-specific options in #makeRequest', () => {
|
|
204
|
+
const spec = {
|
|
205
|
+
openapi: '3.0.0',
|
|
206
|
+
info: {
|
|
207
|
+
title: 'Test API',
|
|
208
|
+
version: '1.0.0',
|
|
209
|
+
},
|
|
210
|
+
paths: {
|
|
211
|
+
'/test': {
|
|
212
|
+
get: {
|
|
213
|
+
operationId: 'testEndpoint',
|
|
214
|
+
responses: {
|
|
215
|
+
'200': {
|
|
216
|
+
description: 'Success',
|
|
217
|
+
content: {
|
|
218
|
+
'application/json': {
|
|
219
|
+
schema: { type: 'string' },
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
const code = generator.generate(spec);
|
|
229
|
+
// Should call getBaseRequestOptions()
|
|
230
|
+
expect(code).toContain('getBaseRequestOptions()');
|
|
231
|
+
// Should merge headers: baseHeaders + Content-Type + request headers
|
|
232
|
+
expect(code).toContain('Object.assign');
|
|
233
|
+
expect(code).toContain('baseHeaders');
|
|
234
|
+
expect(code).toContain('Content-Type');
|
|
235
|
+
// Should merge all options: baseOptions + {method, headers, body}
|
|
236
|
+
expect(code).toMatch(/Object\.assign\s*\(\s*\{\s*\}\s*,\s*baseOptions/);
|
|
237
|
+
expect(code).toContain('method');
|
|
238
|
+
expect(code).toContain('headers');
|
|
239
|
+
expect(code).toContain('body');
|
|
240
|
+
});
|
|
170
241
|
it('should handle array types', () => {
|
|
171
242
|
const spec = {
|
|
172
243
|
openapi: '3.0.0',
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1750,42 +1750,77 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
|
|
|
1750
1750
|
|
|
1751
1751
|
// Handle enum
|
|
1752
1752
|
if (prop['enum'] && Array.isArray(prop['enum']) && prop['enum'].length > 0) {
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1753
|
+
// Check if all enum values are strings (z.enum only works with strings)
|
|
1754
|
+
const allStrings = prop['enum'].every((val) => typeof val === 'string');
|
|
1755
|
+
|
|
1756
|
+
if (allStrings) {
|
|
1757
|
+
// Use z.enum() for string enums
|
|
1758
|
+
const enumValues = prop['enum'].map((val) => ts.factory.createStringLiteral(val as string, true));
|
|
1759
|
+
const enumExpression = ts.factory.createCallExpression(
|
|
1760
|
+
ts.factory.createPropertyAccessExpression(
|
|
1761
|
+
ts.factory.createIdentifier('z'),
|
|
1762
|
+
ts.factory.createIdentifier('enum'),
|
|
1763
|
+
),
|
|
1764
|
+
undefined,
|
|
1765
|
+
[ts.factory.createArrayLiteralExpression(enumValues, false)],
|
|
1766
|
+
);
|
|
1767
|
+
|
|
1768
|
+
return required
|
|
1769
|
+
? enumExpression
|
|
1770
|
+
: ts.factory.createCallExpression(
|
|
1771
|
+
ts.factory.createPropertyAccessExpression(enumExpression, ts.factory.createIdentifier('optional')),
|
|
1772
|
+
undefined,
|
|
1773
|
+
[],
|
|
1763
1774
|
);
|
|
1775
|
+
} else {
|
|
1776
|
+
// Use z.union([z.literal(...), ...]) for numeric/boolean/mixed enums
|
|
1777
|
+
const literalSchemas = prop['enum'].map((val) => {
|
|
1778
|
+
let literalValue: ts.Expression;
|
|
1779
|
+
if (typeof val === 'string') {
|
|
1780
|
+
literalValue = ts.factory.createStringLiteral(val, true);
|
|
1781
|
+
} else if (typeof val === 'number') {
|
|
1782
|
+
// Handle negative numbers correctly
|
|
1783
|
+
if (val < 0) {
|
|
1784
|
+
literalValue = ts.factory.createPrefixUnaryExpression(
|
|
1785
|
+
ts.SyntaxKind.MinusToken,
|
|
1786
|
+
ts.factory.createNumericLiteral(String(Math.abs(val))),
|
|
1787
|
+
);
|
|
1788
|
+
} else {
|
|
1789
|
+
literalValue = ts.factory.createNumericLiteral(String(val));
|
|
1790
|
+
}
|
|
1791
|
+
} else if (typeof val === 'boolean') {
|
|
1792
|
+
literalValue = val ? ts.factory.createTrue() : ts.factory.createFalse();
|
|
1793
|
+
} else {
|
|
1794
|
+
literalValue = ts.factory.createStringLiteral(String(val), true);
|
|
1764
1795
|
}
|
|
1765
|
-
return ts.factory.createNumericLiteral(String(val));
|
|
1766
|
-
}
|
|
1767
|
-
if (typeof val === 'boolean') {
|
|
1768
|
-
return val ? ts.factory.createTrue() : ts.factory.createFalse();
|
|
1769
|
-
}
|
|
1770
|
-
return ts.factory.createStringLiteral(String(val), true);
|
|
1771
|
-
});
|
|
1772
1796
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
undefined,
|
|
1779
|
-
[ts.factory.createArrayLiteralExpression(enumValues, false)],
|
|
1780
|
-
);
|
|
1781
|
-
|
|
1782
|
-
return required
|
|
1783
|
-
? enumExpression
|
|
1784
|
-
: ts.factory.createCallExpression(
|
|
1785
|
-
ts.factory.createPropertyAccessExpression(enumExpression, ts.factory.createIdentifier('optional')),
|
|
1797
|
+
return ts.factory.createCallExpression(
|
|
1798
|
+
ts.factory.createPropertyAccessExpression(
|
|
1799
|
+
ts.factory.createIdentifier('z'),
|
|
1800
|
+
ts.factory.createIdentifier('literal'),
|
|
1801
|
+
),
|
|
1786
1802
|
undefined,
|
|
1787
|
-
[],
|
|
1803
|
+
[literalValue],
|
|
1788
1804
|
);
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
const unionExpression = ts.factory.createCallExpression(
|
|
1808
|
+
ts.factory.createPropertyAccessExpression(
|
|
1809
|
+
ts.factory.createIdentifier('z'),
|
|
1810
|
+
ts.factory.createIdentifier('union'),
|
|
1811
|
+
),
|
|
1812
|
+
undefined,
|
|
1813
|
+
[ts.factory.createArrayLiteralExpression(literalSchemas, false)],
|
|
1814
|
+
);
|
|
1815
|
+
|
|
1816
|
+
return required
|
|
1817
|
+
? unionExpression
|
|
1818
|
+
: ts.factory.createCallExpression(
|
|
1819
|
+
ts.factory.createPropertyAccessExpression(unionExpression, ts.factory.createIdentifier('optional')),
|
|
1820
|
+
undefined,
|
|
1821
|
+
[],
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1789
1824
|
}
|
|
1790
1825
|
|
|
1791
1826
|
switch (prop['type']) {
|
|
@@ -185,6 +185,84 @@ describe('TypeScriptCodeGeneratorService', () => {
|
|
|
185
185
|
expect(code).toContain('pending');
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
it('should handle numeric enum types with z.union and z.literal', () => {
|
|
189
|
+
const spec: OpenApiSpecType = {
|
|
190
|
+
openapi: '3.0.0',
|
|
191
|
+
info: {
|
|
192
|
+
title: 'Test API',
|
|
193
|
+
version: '1.0.0',
|
|
194
|
+
},
|
|
195
|
+
paths: {},
|
|
196
|
+
components: {
|
|
197
|
+
schemas: {
|
|
198
|
+
Status: {
|
|
199
|
+
type: 'integer',
|
|
200
|
+
enum: [-99, 0, 1, 2],
|
|
201
|
+
},
|
|
202
|
+
ExecutionMode: {
|
|
203
|
+
type: 'integer',
|
|
204
|
+
enum: [1, 2],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const code = generator.generate(spec);
|
|
211
|
+
// Numeric enums should use z.union([z.literal(...), ...])
|
|
212
|
+
expect(code).toContain('z.union');
|
|
213
|
+
expect(code).toContain('z.literal');
|
|
214
|
+
expect(code).toContain('-99');
|
|
215
|
+
expect(code).toContain('0');
|
|
216
|
+
expect(code).toContain('1');
|
|
217
|
+
expect(code).toContain('2');
|
|
218
|
+
// Should not use z.enum for numeric enums
|
|
219
|
+
expect(code).not.toContain('Status: z.enum');
|
|
220
|
+
expect(code).not.toContain('ExecutionMode: z.enum');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should merge baseOptions with request-specific options in #makeRequest', () => {
|
|
224
|
+
const spec: OpenApiSpecType = {
|
|
225
|
+
openapi: '3.0.0',
|
|
226
|
+
info: {
|
|
227
|
+
title: 'Test API',
|
|
228
|
+
version: '1.0.0',
|
|
229
|
+
},
|
|
230
|
+
paths: {
|
|
231
|
+
'/test': {
|
|
232
|
+
get: {
|
|
233
|
+
operationId: 'testEndpoint',
|
|
234
|
+
responses: {
|
|
235
|
+
'200': {
|
|
236
|
+
description: 'Success',
|
|
237
|
+
content: {
|
|
238
|
+
'application/json': {
|
|
239
|
+
schema: {type: 'string'},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const code = generator.generate(spec);
|
|
250
|
+
|
|
251
|
+
// Should call getBaseRequestOptions()
|
|
252
|
+
expect(code).toContain('getBaseRequestOptions()');
|
|
253
|
+
|
|
254
|
+
// Should merge headers: baseHeaders + Content-Type + request headers
|
|
255
|
+
expect(code).toContain('Object.assign');
|
|
256
|
+
expect(code).toContain('baseHeaders');
|
|
257
|
+
expect(code).toContain('Content-Type');
|
|
258
|
+
|
|
259
|
+
// Should merge all options: baseOptions + {method, headers, body}
|
|
260
|
+
expect(code).toMatch(/Object\.assign\s*\(\s*\{\s*\}\s*,\s*baseOptions/);
|
|
261
|
+
expect(code).toContain('method');
|
|
262
|
+
expect(code).toContain('headers');
|
|
263
|
+
expect(code).toContain('body');
|
|
264
|
+
});
|
|
265
|
+
|
|
188
266
|
it('should handle array types', () => {
|
|
189
267
|
const spec: OpenApiSpecType = {
|
|
190
268
|
openapi: '3.0.0',
|