@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.
@@ -0,0 +1,302 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { convertOpenAPIObjectToZod } from "./object";
3
+ import type { AnySchema } from "./types";
4
+
5
+ describe("convertOpenAPIObjectToZod", () => {
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("empty object", () => {
14
+ it("should convert empty object without additionalProperties", () => {
15
+ const result = convertOpenAPIObjectToZod(
16
+ { type: "object", properties: {} },
17
+ mockConvertSchema,
18
+ );
19
+ expect(result).toBe("z.record(z.string(), z.unknown())");
20
+ });
21
+
22
+ it("should convert empty object with additionalProperties false", () => {
23
+ const result = convertOpenAPIObjectToZod(
24
+ {
25
+ type: "object",
26
+ properties: {},
27
+ additionalProperties: false,
28
+ },
29
+ mockConvertSchema,
30
+ );
31
+ expect(result).toBe("z.object({}).strict()");
32
+ });
33
+
34
+ it("should convert object without properties", () => {
35
+ const result = convertOpenAPIObjectToZod(
36
+ { type: "object" },
37
+ mockConvertSchema,
38
+ );
39
+ expect(result).toBe("z.record(z.string(), z.unknown())");
40
+ });
41
+ });
42
+
43
+ describe("object with required properties", () => {
44
+ it("should convert object with single required property", () => {
45
+ const result = convertOpenAPIObjectToZod(
46
+ {
47
+ type: "object",
48
+ properties: {
49
+ name: { type: "string" },
50
+ },
51
+ required: ["name"],
52
+ },
53
+ mockConvertSchema,
54
+ );
55
+ expect(result).toBe("z.object({ name: z.string() })");
56
+ });
57
+
58
+ it("should convert object with multiple required properties", () => {
59
+ const result = convertOpenAPIObjectToZod(
60
+ {
61
+ type: "object",
62
+ properties: {
63
+ name: { type: "string" },
64
+ age: { type: "number" },
65
+ },
66
+ required: ["name", "age"],
67
+ },
68
+ mockConvertSchema,
69
+ );
70
+ expect(result).toBe(
71
+ "z.object({ name: z.string(), age: z.number() })",
72
+ );
73
+ });
74
+
75
+ it("should convert object with all properties required", () => {
76
+ const result = convertOpenAPIObjectToZod(
77
+ {
78
+ type: "object",
79
+ properties: {
80
+ a: { type: "string" },
81
+ b: { type: "number" },
82
+ c: { type: "boolean" },
83
+ },
84
+ required: ["a", "b", "c"],
85
+ },
86
+ mockConvertSchema,
87
+ );
88
+ expect(result).toBe(
89
+ "z.object({ a: z.string(), b: z.number(), c: z.boolean() })",
90
+ );
91
+ });
92
+ });
93
+
94
+ describe("object with optional properties", () => {
95
+ it("should convert object with single optional property", () => {
96
+ const result = convertOpenAPIObjectToZod(
97
+ {
98
+ type: "object",
99
+ properties: {
100
+ name: { type: "string" },
101
+ },
102
+ },
103
+ mockConvertSchema,
104
+ );
105
+ expect(result).toBe("z.object({ name: z.string().optional() })");
106
+ });
107
+
108
+ it("should convert object with multiple optional properties", () => {
109
+ const result = convertOpenAPIObjectToZod(
110
+ {
111
+ type: "object",
112
+ properties: {
113
+ name: { type: "string" },
114
+ age: { type: "number" },
115
+ },
116
+ },
117
+ mockConvertSchema,
118
+ );
119
+ expect(result).toBe(
120
+ "z.object({ name: z.string().optional(), age: z.number().optional() })",
121
+ );
122
+ });
123
+ });
124
+
125
+ describe("object with mixed required and optional properties", () => {
126
+ it("should mark only specified properties as required", () => {
127
+ const result = convertOpenAPIObjectToZod(
128
+ {
129
+ type: "object",
130
+ properties: {
131
+ name: { type: "string" },
132
+ age: { type: "number" },
133
+ email: { type: "string" },
134
+ },
135
+ required: ["name"],
136
+ },
137
+ mockConvertSchema,
138
+ );
139
+ expect(result).toBe(
140
+ "z.object({ name: z.string(), age: z.number().optional(), email: z.string().optional() })",
141
+ );
142
+ });
143
+
144
+ it("should handle multiple required and optional properties", () => {
145
+ const result = convertOpenAPIObjectToZod(
146
+ {
147
+ type: "object",
148
+ properties: {
149
+ id: { type: "number" },
150
+ name: { type: "string" },
151
+ email: { type: "string" },
152
+ age: { type: "number" },
153
+ },
154
+ required: ["id", "name"],
155
+ },
156
+ mockConvertSchema,
157
+ );
158
+ expect(result).toBe(
159
+ "z.object({ id: z.number(), name: z.string(), email: z.string().optional(), age: z.number().optional() })",
160
+ );
161
+ });
162
+ });
163
+
164
+ describe("object with additionalProperties", () => {
165
+ it("should convert object with additionalProperties false", () => {
166
+ const result = convertOpenAPIObjectToZod(
167
+ {
168
+ type: "object",
169
+ properties: {
170
+ name: { type: "string" },
171
+ },
172
+ additionalProperties: false,
173
+ },
174
+ mockConvertSchema,
175
+ );
176
+ expect(result).toBe("z.object({ name: z.string().optional() }).strict()");
177
+ });
178
+
179
+ it("should convert object with additionalProperties false and required properties", () => {
180
+ const result = convertOpenAPIObjectToZod(
181
+ {
182
+ type: "object",
183
+ properties: {
184
+ name: { type: "string" },
185
+ age: { type: "number" },
186
+ },
187
+ required: ["name"],
188
+ additionalProperties: false,
189
+ },
190
+ mockConvertSchema,
191
+ );
192
+ expect(result).toBe(
193
+ "z.object({ name: z.string(), age: z.number().optional() }).strict()",
194
+ );
195
+ });
196
+
197
+ it("should not add strict when additionalProperties is not false", () => {
198
+ const result = convertOpenAPIObjectToZod(
199
+ {
200
+ type: "object",
201
+ properties: {
202
+ name: { type: "string" },
203
+ },
204
+ additionalProperties: true,
205
+ },
206
+ mockConvertSchema,
207
+ );
208
+ expect(result).toBe("z.object({ name: z.string().optional() })");
209
+ });
210
+ });
211
+
212
+ describe("property ordering", () => {
213
+ it("should preserve property order", () => {
214
+ const result = convertOpenAPIObjectToZod(
215
+ {
216
+ type: "object",
217
+ properties: {
218
+ a: { type: "string" },
219
+ b: { type: "number" },
220
+ c: { type: "boolean" },
221
+ },
222
+ required: ["b"],
223
+ },
224
+ mockConvertSchema,
225
+ );
226
+ expect(result).toBe(
227
+ "z.object({ a: z.string().optional(), b: z.number(), c: z.boolean().optional() })",
228
+ );
229
+ });
230
+ });
231
+
232
+ describe("edge cases", () => {
233
+ it("should handle properties with unknown schema types", () => {
234
+ const result = convertOpenAPIObjectToZod(
235
+ {
236
+ type: "object",
237
+ properties: {
238
+ unknown: { type: "unknown-type" },
239
+ },
240
+ },
241
+ mockConvertSchema,
242
+ );
243
+ expect(result).toBe("z.object({ unknown: z.unknown().optional() })");
244
+ });
245
+
246
+ it("should handle null property schemas", () => {
247
+ const result = convertOpenAPIObjectToZod(
248
+ {
249
+ type: "object",
250
+ properties: {
251
+ nullProp: null,
252
+ },
253
+ },
254
+ mockConvertSchema,
255
+ );
256
+ expect(result).toBe("z.object({ nullProp: z.unknown().optional() })");
257
+ });
258
+
259
+ it("should handle properties not in required array", () => {
260
+ const result = convertOpenAPIObjectToZod(
261
+ {
262
+ type: "object",
263
+ properties: {
264
+ name: { type: "string" },
265
+ },
266
+ required: ["other"],
267
+ },
268
+ mockConvertSchema,
269
+ );
270
+ expect(result).toBe("z.object({ name: z.string().optional() })");
271
+ });
272
+
273
+ it("should handle empty required array", () => {
274
+ const result = convertOpenAPIObjectToZod(
275
+ {
276
+ type: "object",
277
+ properties: {
278
+ name: { type: "string" },
279
+ },
280
+ required: [],
281
+ },
282
+ mockConvertSchema,
283
+ );
284
+ expect(result).toBe("z.object({ name: z.string().optional() })");
285
+ });
286
+
287
+ it("should handle required array with duplicate values", () => {
288
+ const result = convertOpenAPIObjectToZod(
289
+ {
290
+ type: "object",
291
+ properties: {
292
+ name: { type: "string" },
293
+ },
294
+ required: ["name", "name"],
295
+ },
296
+ mockConvertSchema,
297
+ );
298
+ expect(result).toBe("z.object({ name: z.string() })");
299
+ });
300
+ });
301
+ });
302
+
@@ -0,0 +1,41 @@
1
+ import { AnySchema, OpenAPIObjectSchema } from "./types";
2
+
3
+ export function convertOpenAPIObjectToZod(
4
+ schema: OpenAPIObjectSchema,
5
+ convertSchema: (schema: AnySchema) => string,
6
+ ): string {
7
+ const properties = schema.properties || {};
8
+ const propertyNames = Object.keys(properties);
9
+
10
+ if (propertyNames.length === 0) {
11
+ if (schema.additionalProperties === false) {
12
+ return "z.object({}).strict()";
13
+ }
14
+ return "z.record(z.string(), z.unknown())";
15
+ }
16
+
17
+ const requiredSet = new Set(schema.required || []);
18
+
19
+ const entries: string[] = [];
20
+ for (const [propName, propSchema] of Object.entries(properties)) {
21
+ let zodProp = "z.unknown()";
22
+
23
+ if (propSchema && typeof propSchema === "object") {
24
+ zodProp = convertSchema(propSchema);
25
+ }
26
+
27
+ if (!requiredSet.has(propName)) {
28
+ zodProp += ".optional()";
29
+ }
30
+
31
+ entries.push(`${propName}: ${zodProp}`);
32
+ }
33
+
34
+ let result = `z.object({ ${entries.join(", ")} })`;
35
+
36
+ if (schema.additionalProperties === false) {
37
+ result += ".strict()";
38
+ }
39
+
40
+ return result;
41
+ }