@alt-stack/zod-openapi 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +331 -0
- package/bin/zod-openapi.js +28 -0
- package/dist/index.d.mts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +860 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +825 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +35 -0
- package/src/cli.ts +105 -0
- package/src/dependencies.spec.ts +440 -0
- package/src/dependencies.ts +176 -0
- package/src/index.ts +27 -0
- package/src/registry.spec.ts +308 -0
- package/src/registry.ts +227 -0
- package/src/routes.ts +231 -0
- package/src/to-typescript.spec.ts +474 -0
- package/src/to-typescript.ts +289 -0
- package/src/to-zod.spec.ts +480 -0
- package/src/to-zod.ts +112 -0
- package/src/types/array.spec.ts +222 -0
- package/src/types/array.ts +29 -0
- package/src/types/boolean.spec.ts +18 -0
- package/src/types/boolean.ts +6 -0
- package/src/types/intersection.spec.ts +108 -0
- package/src/types/intersection.ts +14 -0
- package/src/types/number.spec.ts +127 -0
- package/src/types/number.ts +20 -0
- package/src/types/object.spec.ts +302 -0
- package/src/types/object.ts +41 -0
- package/src/types/string.spec.ts +431 -0
- package/src/types/string.ts +75 -0
- package/src/types/types.ts +10 -0
- package/src/types/union.spec.ts +135 -0
- package/src/types/union.ts +10 -0
- package/tsconfig.json +12 -0
- package/tsup.config.ts +11 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { convertOpenAPIStringToZod } from "./string";
|
|
4
|
+
import {
|
|
5
|
+
registerZodSchemaToOpenApiSchema,
|
|
6
|
+
clearZodSchemaToOpenApiSchemaRegistry,
|
|
7
|
+
} from "../registry";
|
|
8
|
+
|
|
9
|
+
describe("convertOpenAPIStringToZod", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
clearZodSchemaToOpenApiSchemaRegistry();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
clearZodSchemaToOpenApiSchemaRegistry();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("basic string conversion", () => {
|
|
19
|
+
it("should convert a basic string schema with no constraints", () => {
|
|
20
|
+
const result = convertOpenAPIStringToZod({ type: "string" });
|
|
21
|
+
expect(result).toBe("z.string()");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("enum handling", () => {
|
|
26
|
+
it("should convert string enum with single value", () => {
|
|
27
|
+
const result = convertOpenAPIStringToZod({
|
|
28
|
+
type: "string",
|
|
29
|
+
enum: ["value1"],
|
|
30
|
+
});
|
|
31
|
+
expect(result).toBe("z.enum(['value1'])");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should convert string enum with multiple values", () => {
|
|
35
|
+
const result = convertOpenAPIStringToZod({
|
|
36
|
+
type: "string",
|
|
37
|
+
enum: ["red", "green", "blue"],
|
|
38
|
+
});
|
|
39
|
+
expect(result).toBe("z.enum(['red', 'green', 'blue'])");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should prioritize enum over other constraints", () => {
|
|
43
|
+
const result = convertOpenAPIStringToZod({
|
|
44
|
+
type: "string",
|
|
45
|
+
enum: ["value1", "value2"],
|
|
46
|
+
minLength: 5,
|
|
47
|
+
maxLength: 10,
|
|
48
|
+
pattern: ".*",
|
|
49
|
+
format: "email",
|
|
50
|
+
});
|
|
51
|
+
expect(result).toBe("z.enum(['value1', 'value2'])");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should handle enum with special characters", () => {
|
|
55
|
+
const result = convertOpenAPIStringToZod({
|
|
56
|
+
type: "string",
|
|
57
|
+
enum: ["hello world", "test@example.com"],
|
|
58
|
+
});
|
|
59
|
+
expect(result).toBe("z.enum(['hello world', 'test@example.com'])");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("format handling - built-in formats", () => {
|
|
64
|
+
it("should convert email format", () => {
|
|
65
|
+
const result = convertOpenAPIStringToZod({
|
|
66
|
+
type: "string",
|
|
67
|
+
format: "email",
|
|
68
|
+
});
|
|
69
|
+
expect(result).toBe("z.string().email()");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should convert url format", () => {
|
|
73
|
+
const result = convertOpenAPIStringToZod({
|
|
74
|
+
type: "string",
|
|
75
|
+
format: "url",
|
|
76
|
+
});
|
|
77
|
+
expect(result).toBe("z.string().url()");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should convert uri format to url", () => {
|
|
81
|
+
const result = convertOpenAPIStringToZod({
|
|
82
|
+
type: "string",
|
|
83
|
+
format: "uri",
|
|
84
|
+
});
|
|
85
|
+
expect(result).toBe("z.string().url()");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should convert uuid format", () => {
|
|
89
|
+
const result = convertOpenAPIStringToZod({
|
|
90
|
+
type: "string",
|
|
91
|
+
format: "uuid",
|
|
92
|
+
});
|
|
93
|
+
expect(result).toBe("z.string().uuid()");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should convert color-hex format", () => {
|
|
97
|
+
const result = convertOpenAPIStringToZod({
|
|
98
|
+
type: "string",
|
|
99
|
+
format: "color-hex",
|
|
100
|
+
});
|
|
101
|
+
expect(result).toBe("z.string().regex(/^[a-fA-F0-9]{6}$/)");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should ignore unknown format", () => {
|
|
105
|
+
const result = convertOpenAPIStringToZod({
|
|
106
|
+
type: "string",
|
|
107
|
+
// @ts-expect-error - testing with unsupported format
|
|
108
|
+
format: "unsupported-format",
|
|
109
|
+
});
|
|
110
|
+
expect(result).toBe("z.string()");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("format handling - custom registered schemas", () => {
|
|
115
|
+
it("should use custom registered schema for format", () => {
|
|
116
|
+
const customSchema = z.string().min(10);
|
|
117
|
+
registerZodSchemaToOpenApiSchema(customSchema, {
|
|
118
|
+
schemaExportedVariableName: "customEmailSchema",
|
|
119
|
+
type: "string",
|
|
120
|
+
format: "email",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const result = convertOpenAPIStringToZod({
|
|
124
|
+
type: "string",
|
|
125
|
+
format: "email",
|
|
126
|
+
});
|
|
127
|
+
expect(result).toBe("customEmailSchema");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should ignore other constraints when using custom registered schema", () => {
|
|
131
|
+
const customSchema = z.string().min(10);
|
|
132
|
+
registerZodSchemaToOpenApiSchema(customSchema, {
|
|
133
|
+
schemaExportedVariableName: "customUuidSchema",
|
|
134
|
+
type: "string",
|
|
135
|
+
format: "uuid",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const result = convertOpenAPIStringToZod({
|
|
139
|
+
type: "string",
|
|
140
|
+
format: "uuid",
|
|
141
|
+
minLength: 5,
|
|
142
|
+
maxLength: 50,
|
|
143
|
+
pattern: ".*",
|
|
144
|
+
});
|
|
145
|
+
expect(result).toBe("customUuidSchema");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should use custom registered schema with multiple formats", () => {
|
|
149
|
+
const customSchema = z.string();
|
|
150
|
+
registerZodSchemaToOpenApiSchema(customSchema, {
|
|
151
|
+
schemaExportedVariableName: "customDateSchema",
|
|
152
|
+
type: "string",
|
|
153
|
+
formats: ["date", "iso-date"],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result1 = convertOpenAPIStringToZod({
|
|
157
|
+
type: "string",
|
|
158
|
+
format: "date",
|
|
159
|
+
});
|
|
160
|
+
expect(result1).toBe("customDateSchema");
|
|
161
|
+
|
|
162
|
+
const result2 = convertOpenAPIStringToZod({
|
|
163
|
+
type: "string",
|
|
164
|
+
format: "iso-date",
|
|
165
|
+
});
|
|
166
|
+
expect(result2).toBe("customDateSchema");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("length constraints", () => {
|
|
171
|
+
it("should apply minLength constraint", () => {
|
|
172
|
+
const result = convertOpenAPIStringToZod({
|
|
173
|
+
type: "string",
|
|
174
|
+
minLength: 5,
|
|
175
|
+
});
|
|
176
|
+
expect(result).toBe("z.string().min(5)");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should apply maxLength constraint", () => {
|
|
180
|
+
const result = convertOpenAPIStringToZod({
|
|
181
|
+
type: "string",
|
|
182
|
+
maxLength: 10,
|
|
183
|
+
});
|
|
184
|
+
expect(result).toBe("z.string().max(10)");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should apply both minLength and maxLength constraints", () => {
|
|
188
|
+
const result = convertOpenAPIStringToZod({
|
|
189
|
+
type: "string",
|
|
190
|
+
minLength: 5,
|
|
191
|
+
maxLength: 10,
|
|
192
|
+
});
|
|
193
|
+
expect(result).toBe("z.string().min(5).max(10)");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should handle minLength of 0", () => {
|
|
197
|
+
const result = convertOpenAPIStringToZod({
|
|
198
|
+
type: "string",
|
|
199
|
+
minLength: 0,
|
|
200
|
+
});
|
|
201
|
+
expect(result).toBe("z.string().min(0)");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should not apply minLength when undefined", () => {
|
|
205
|
+
const result = convertOpenAPIStringToZod({
|
|
206
|
+
type: "string",
|
|
207
|
+
minLength: undefined,
|
|
208
|
+
});
|
|
209
|
+
expect(result).toBe("z.string()");
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("pattern constraints", () => {
|
|
214
|
+
it("should apply pattern constraint", () => {
|
|
215
|
+
const result = convertOpenAPIStringToZod({
|
|
216
|
+
type: "string",
|
|
217
|
+
pattern: "^[A-Z]+$",
|
|
218
|
+
});
|
|
219
|
+
expect(result).toBe("z.string().regex(/^[A-Z]+$/)");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should handle complex regex pattern", () => {
|
|
223
|
+
const result = convertOpenAPIStringToZod({
|
|
224
|
+
type: "string",
|
|
225
|
+
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
|
226
|
+
});
|
|
227
|
+
expect(result).toBe(
|
|
228
|
+
"z.string().regex(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/)",
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should not apply pattern when undefined", () => {
|
|
233
|
+
const result = convertOpenAPIStringToZod({
|
|
234
|
+
type: "string",
|
|
235
|
+
pattern: undefined,
|
|
236
|
+
});
|
|
237
|
+
expect(result).toBe("z.string()");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("combined constraints", () => {
|
|
242
|
+
it("should combine format and length constraints", () => {
|
|
243
|
+
const result = convertOpenAPIStringToZod({
|
|
244
|
+
type: "string",
|
|
245
|
+
format: "email",
|
|
246
|
+
minLength: 5,
|
|
247
|
+
maxLength: 100,
|
|
248
|
+
});
|
|
249
|
+
expect(result).toBe("z.string().email().min(5).max(100)");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should combine format and pattern constraints", () => {
|
|
253
|
+
const result = convertOpenAPIStringToZod({
|
|
254
|
+
type: "string",
|
|
255
|
+
format: "uuid",
|
|
256
|
+
pattern: "^[a-f0-9-]+$",
|
|
257
|
+
});
|
|
258
|
+
expect(result).toBe("z.string().uuid().regex(/^[a-f0-9-]+$/)");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("should combine all constraints", () => {
|
|
262
|
+
const result = convertOpenAPIStringToZod({
|
|
263
|
+
type: "string",
|
|
264
|
+
format: "email",
|
|
265
|
+
minLength: 10,
|
|
266
|
+
maxLength: 50,
|
|
267
|
+
pattern: ".*@example\\.com$",
|
|
268
|
+
});
|
|
269
|
+
expect(result).toBe(
|
|
270
|
+
"z.string().email().min(10).max(50).regex(/.*@example\\.com$/)",
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should combine length and pattern without format", () => {
|
|
275
|
+
const result = convertOpenAPIStringToZod({
|
|
276
|
+
type: "string",
|
|
277
|
+
minLength: 3,
|
|
278
|
+
maxLength: 20,
|
|
279
|
+
pattern: "^[a-z]+$",
|
|
280
|
+
});
|
|
281
|
+
expect(result).toBe("z.string().min(3).max(20).regex(/^[a-z]+$/)");
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("edge cases", () => {
|
|
286
|
+
it("should handle empty enum array", () => {
|
|
287
|
+
const result = convertOpenAPIStringToZod({
|
|
288
|
+
type: "string",
|
|
289
|
+
enum: [],
|
|
290
|
+
});
|
|
291
|
+
expect(result).toBe("z.enum([])");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("should handle format with empty string", () => {
|
|
295
|
+
const result = convertOpenAPIStringToZod({
|
|
296
|
+
type: "string",
|
|
297
|
+
// @ts-expect-error - testing with empty format
|
|
298
|
+
format: "",
|
|
299
|
+
});
|
|
300
|
+
expect(result).toBe("z.string()");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("should handle pattern with empty string", () => {
|
|
304
|
+
const result = convertOpenAPIStringToZod({
|
|
305
|
+
type: "string",
|
|
306
|
+
pattern: "",
|
|
307
|
+
});
|
|
308
|
+
expect(result).toBe("z.string().regex(//)");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("should maintain order: format, minLength, maxLength, pattern", () => {
|
|
312
|
+
const result = convertOpenAPIStringToZod({
|
|
313
|
+
type: "string",
|
|
314
|
+
pattern: "^test$",
|
|
315
|
+
maxLength: 20,
|
|
316
|
+
minLength: 5,
|
|
317
|
+
format: "url",
|
|
318
|
+
});
|
|
319
|
+
expect(result).toBe("z.string().url().min(5).max(20).regex(/^test$/)");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("should handle only maxLength without minLength", () => {
|
|
323
|
+
const result = convertOpenAPIStringToZod({
|
|
324
|
+
type: "string",
|
|
325
|
+
format: "email",
|
|
326
|
+
maxLength: 100,
|
|
327
|
+
});
|
|
328
|
+
expect(result).toBe("z.string().email().max(100)");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("should handle only pattern without other constraints", () => {
|
|
332
|
+
const result = convertOpenAPIStringToZod({
|
|
333
|
+
type: "string",
|
|
334
|
+
pattern: "\\d{3}-\\d{4}",
|
|
335
|
+
});
|
|
336
|
+
expect(result).toBe("z.string().regex(/\\d{3}-\\d{4}/)");
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe("format with supported but not built-in modifiers", () => {
|
|
341
|
+
it("should handle date format without built-in modifier", () => {
|
|
342
|
+
const result = convertOpenAPIStringToZod({
|
|
343
|
+
type: "string",
|
|
344
|
+
format: "date",
|
|
345
|
+
});
|
|
346
|
+
expect(result).toBe("z.string()");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("should handle date-time format without built-in modifier", () => {
|
|
350
|
+
const result = convertOpenAPIStringToZod({
|
|
351
|
+
type: "string",
|
|
352
|
+
format: "date-time",
|
|
353
|
+
});
|
|
354
|
+
expect(result).toBe("z.string()");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("should handle objectid format without built-in modifier", () => {
|
|
358
|
+
const result = convertOpenAPIStringToZod({
|
|
359
|
+
type: "string",
|
|
360
|
+
format: "objectid",
|
|
361
|
+
});
|
|
362
|
+
expect(result).toBe("z.string()");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("should apply constraints to supported formats without built-in modifiers", () => {
|
|
366
|
+
const result = convertOpenAPIStringToZod({
|
|
367
|
+
type: "string",
|
|
368
|
+
format: "date",
|
|
369
|
+
minLength: 10,
|
|
370
|
+
maxLength: 10,
|
|
371
|
+
});
|
|
372
|
+
expect(result).toBe("z.string().min(10).max(10)");
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
describe("integration with registry", () => {
|
|
377
|
+
it("should clear registry between tests", () => {
|
|
378
|
+
const customSchema1 = z.string().min(5);
|
|
379
|
+
registerZodSchemaToOpenApiSchema(customSchema1, {
|
|
380
|
+
schemaExportedVariableName: "schema1",
|
|
381
|
+
type: "string",
|
|
382
|
+
format: "email",
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
clearZodSchemaToOpenApiSchemaRegistry();
|
|
386
|
+
|
|
387
|
+
const result = convertOpenAPIStringToZod({
|
|
388
|
+
type: "string",
|
|
389
|
+
format: "email",
|
|
390
|
+
});
|
|
391
|
+
// Should use built-in email format, not custom schema
|
|
392
|
+
expect(result).toBe("z.string().email()");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should handle multiple schemas registered for different formats", () => {
|
|
396
|
+
const emailSchema = z.string().email();
|
|
397
|
+
const uuidSchema = z.string().uuid();
|
|
398
|
+
|
|
399
|
+
registerZodSchemaToOpenApiSchema(emailSchema, {
|
|
400
|
+
schemaExportedVariableName: "customEmailSchema",
|
|
401
|
+
type: "string",
|
|
402
|
+
format: "email",
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
registerZodSchemaToOpenApiSchema(uuidSchema, {
|
|
406
|
+
schemaExportedVariableName: "customUuidSchema",
|
|
407
|
+
type: "string",
|
|
408
|
+
format: "uuid",
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const emailResult = convertOpenAPIStringToZod({
|
|
412
|
+
type: "string",
|
|
413
|
+
format: "email",
|
|
414
|
+
});
|
|
415
|
+
expect(emailResult).toBe("customEmailSchema");
|
|
416
|
+
|
|
417
|
+
const uuidResult = convertOpenAPIStringToZod({
|
|
418
|
+
type: "string",
|
|
419
|
+
format: "uuid",
|
|
420
|
+
});
|
|
421
|
+
expect(uuidResult).toBe("customUuidSchema");
|
|
422
|
+
|
|
423
|
+
// Unregistered format should use built-in
|
|
424
|
+
const urlResult = convertOpenAPIStringToZod({
|
|
425
|
+
type: "string",
|
|
426
|
+
format: "url",
|
|
427
|
+
});
|
|
428
|
+
expect(urlResult).toBe("z.string().url()");
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSchemaExportedVariableNameForStringFormat,
|
|
3
|
+
SUPPORTED_STRING_FORMATS,
|
|
4
|
+
} from "../registry";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert an OpenAPI v3 string schema to a Zod schema string
|
|
8
|
+
*/
|
|
9
|
+
export function convertOpenAPIStringToZod(schema: {
|
|
10
|
+
type: "string";
|
|
11
|
+
format?: typeof SUPPORTED_STRING_FORMATS;
|
|
12
|
+
minLength?: number;
|
|
13
|
+
maxLength?: number;
|
|
14
|
+
pattern?: string;
|
|
15
|
+
enum?: string[];
|
|
16
|
+
}): string {
|
|
17
|
+
// Handle enum values
|
|
18
|
+
if (schema.enum) {
|
|
19
|
+
return `z.enum([${schema.enum.map((value) => `'${value}'`).join(", ")}])`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check for custom registered format schemas
|
|
23
|
+
if (schema.format && SUPPORTED_STRING_FORMATS.includes(schema.format)) {
|
|
24
|
+
const customSchemaName = getSchemaExportedVariableNameForStringFormat(
|
|
25
|
+
schema.format,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Use custom schema if registered (ignores other constraints)
|
|
29
|
+
if (customSchemaName) {
|
|
30
|
+
return customSchemaName;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Build string schema with format modifiers
|
|
35
|
+
let zodSchema = "z.string()";
|
|
36
|
+
|
|
37
|
+
if (schema.format) {
|
|
38
|
+
zodSchema += getFormatModifier(schema.format);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Apply length constraints
|
|
42
|
+
if (typeof schema.minLength === "number") {
|
|
43
|
+
zodSchema += `.min(${schema.minLength})`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof schema.maxLength === "number") {
|
|
47
|
+
zodSchema += `.max(${schema.maxLength})`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Apply pattern constraint
|
|
51
|
+
if (typeof schema.pattern === "string") {
|
|
52
|
+
zodSchema += `.regex(/${schema.pattern}/)`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return zodSchema;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the Zod modifier for built-in string formats
|
|
60
|
+
*/
|
|
61
|
+
function getFormatModifier(format: string): string {
|
|
62
|
+
switch (format) {
|
|
63
|
+
case "email":
|
|
64
|
+
return ".email()";
|
|
65
|
+
case "url":
|
|
66
|
+
case "uri":
|
|
67
|
+
return ".url()";
|
|
68
|
+
case "uuid":
|
|
69
|
+
return ".uuid()";
|
|
70
|
+
case "color-hex":
|
|
71
|
+
return ".regex(/^[a-fA-F0-9]{6}$/)";
|
|
72
|
+
default:
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type AnySchema = Record<string, any>;
|
|
2
|
+
|
|
3
|
+
export type OpenAPIObjectSchema = {
|
|
4
|
+
type: "object";
|
|
5
|
+
properties?: Record<string, AnySchema>;
|
|
6
|
+
required?: string[];
|
|
7
|
+
additionalProperties?: boolean | AnySchema;
|
|
8
|
+
maxProperties?: number;
|
|
9
|
+
minProperties?: number;
|
|
10
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { convertOpenAPIUnionToZod } from "./union";
|
|
3
|
+
import type { AnySchema } from "./types";
|
|
4
|
+
|
|
5
|
+
describe("convertOpenAPIUnionToZod", () => {
|
|
6
|
+
const mockConvertSchema = (schema: AnySchema): string => {
|
|
7
|
+
if (schema.type === "string") return "z.string()";
|
|
8
|
+
if (schema.type === "number") return "z.number()";
|
|
9
|
+
if (schema.type === "boolean") return "z.boolean()";
|
|
10
|
+
return "z.unknown()";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe("union with two types", () => {
|
|
14
|
+
it("should convert union of string and number", () => {
|
|
15
|
+
const result = convertOpenAPIUnionToZod(
|
|
16
|
+
{
|
|
17
|
+
oneOf: [{ type: "string" }, { type: "number" }],
|
|
18
|
+
},
|
|
19
|
+
mockConvertSchema,
|
|
20
|
+
);
|
|
21
|
+
expect(result).toBe("z.union([z.string(), z.number()])");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should convert union of string and boolean", () => {
|
|
25
|
+
const result = convertOpenAPIUnionToZod(
|
|
26
|
+
{
|
|
27
|
+
oneOf: [{ type: "string" }, { type: "boolean" }],
|
|
28
|
+
},
|
|
29
|
+
mockConvertSchema,
|
|
30
|
+
);
|
|
31
|
+
expect(result).toBe("z.union([z.string(), z.boolean()])");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should convert union of number and boolean", () => {
|
|
35
|
+
const result = convertOpenAPIUnionToZod(
|
|
36
|
+
{
|
|
37
|
+
oneOf: [{ type: "number" }, { type: "boolean" }],
|
|
38
|
+
},
|
|
39
|
+
mockConvertSchema,
|
|
40
|
+
);
|
|
41
|
+
expect(result).toBe("z.union([z.number(), z.boolean()])");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("union with multiple types", () => {
|
|
46
|
+
it("should convert union of three types", () => {
|
|
47
|
+
const result = convertOpenAPIUnionToZod(
|
|
48
|
+
{
|
|
49
|
+
oneOf: [
|
|
50
|
+
{ type: "string" },
|
|
51
|
+
{ type: "number" },
|
|
52
|
+
{ type: "boolean" },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
mockConvertSchema,
|
|
56
|
+
);
|
|
57
|
+
expect(result).toBe(
|
|
58
|
+
"z.union([z.string(), z.number(), z.boolean()])",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should convert union of multiple same types", () => {
|
|
63
|
+
const result = convertOpenAPIUnionToZod(
|
|
64
|
+
{
|
|
65
|
+
oneOf: [{ type: "string" }, { type: "string" }],
|
|
66
|
+
},
|
|
67
|
+
mockConvertSchema,
|
|
68
|
+
);
|
|
69
|
+
expect(result).toBe("z.union([z.string(), z.string()])");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("union with single type", () => {
|
|
74
|
+
it("should convert union with single type", () => {
|
|
75
|
+
const result = convertOpenAPIUnionToZod(
|
|
76
|
+
{
|
|
77
|
+
oneOf: [{ type: "string" }],
|
|
78
|
+
},
|
|
79
|
+
mockConvertSchema,
|
|
80
|
+
);
|
|
81
|
+
expect(result).toBe("z.union([z.string()])");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("union with unknown types", () => {
|
|
86
|
+
it("should convert union containing unknown types", () => {
|
|
87
|
+
const result = convertOpenAPIUnionToZod(
|
|
88
|
+
{
|
|
89
|
+
oneOf: [{ type: "string" }, { type: "unknown-type" }],
|
|
90
|
+
},
|
|
91
|
+
mockConvertSchema,
|
|
92
|
+
);
|
|
93
|
+
expect(result).toBe("z.union([z.string(), z.unknown()])");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should convert union of all unknown types", () => {
|
|
97
|
+
const result = convertOpenAPIUnionToZod(
|
|
98
|
+
{
|
|
99
|
+
oneOf: [{ type: "unknown1" }, { type: "unknown2" }],
|
|
100
|
+
},
|
|
101
|
+
mockConvertSchema,
|
|
102
|
+
);
|
|
103
|
+
expect(result).toBe("z.union([z.unknown(), z.unknown()])");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("edge cases", () => {
|
|
108
|
+
it("should handle empty oneOf array", () => {
|
|
109
|
+
const result = convertOpenAPIUnionToZod(
|
|
110
|
+
{
|
|
111
|
+
oneOf: [],
|
|
112
|
+
},
|
|
113
|
+
mockConvertSchema,
|
|
114
|
+
);
|
|
115
|
+
expect(result).toBe("z.union([])");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should preserve order of union members", () => {
|
|
119
|
+
const result = convertOpenAPIUnionToZod(
|
|
120
|
+
{
|
|
121
|
+
oneOf: [
|
|
122
|
+
{ type: "boolean" },
|
|
123
|
+
{ type: "number" },
|
|
124
|
+
{ type: "string" },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
mockConvertSchema,
|
|
128
|
+
);
|
|
129
|
+
expect(result).toBe(
|
|
130
|
+
"z.union([z.boolean(), z.number(), z.string()])",
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AnySchema } from "./types";
|
|
2
|
+
|
|
3
|
+
export function convertOpenAPIUnionToZod(
|
|
4
|
+
schema: { oneOf: AnySchema[] },
|
|
5
|
+
convertSchema: (schema: AnySchema) => string,
|
|
6
|
+
): string {
|
|
7
|
+
const items = schema.oneOf.map((item) => convertSchema(item));
|
|
8
|
+
return `z.union([${items.join(", ")}])`;
|
|
9
|
+
}
|
|
10
|
+
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED