@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
package/src/routes.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { AnySchema } from "./types/types";
|
|
2
|
+
|
|
3
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
4
|
+
|
|
5
|
+
export interface RouteParameter {
|
|
6
|
+
name: string;
|
|
7
|
+
in: "path" | "query" | "header" | "cookie";
|
|
8
|
+
required: boolean;
|
|
9
|
+
schema: AnySchema;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface RouteInfo {
|
|
13
|
+
path: string;
|
|
14
|
+
method: HttpMethod;
|
|
15
|
+
parameters: RouteParameter[];
|
|
16
|
+
requestBody?: AnySchema;
|
|
17
|
+
responses: Record<string, AnySchema>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RouteSchemaNames {
|
|
21
|
+
paramsSchemaName?: string;
|
|
22
|
+
querySchemaName?: string;
|
|
23
|
+
headersSchemaName?: string;
|
|
24
|
+
bodySchemaName?: string;
|
|
25
|
+
responseSchemaName?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toUpperCaseMethod(method: string): HttpMethod {
|
|
29
|
+
const upper = method.toUpperCase();
|
|
30
|
+
if (
|
|
31
|
+
upper === "GET" ||
|
|
32
|
+
upper === "POST" ||
|
|
33
|
+
upper === "PUT" ||
|
|
34
|
+
upper === "PATCH" ||
|
|
35
|
+
upper === "DELETE" ||
|
|
36
|
+
upper === "HEAD" ||
|
|
37
|
+
upper === "OPTIONS"
|
|
38
|
+
) {
|
|
39
|
+
return upper as HttpMethod;
|
|
40
|
+
}
|
|
41
|
+
return "GET";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toPascalCase(str: string): string {
|
|
45
|
+
return str
|
|
46
|
+
.replace(/[^a-zA-Z0-9]/g, " ")
|
|
47
|
+
.split(" ")
|
|
48
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
49
|
+
.join("");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function generateRouteSchemaName(
|
|
53
|
+
path: string,
|
|
54
|
+
method: HttpMethod,
|
|
55
|
+
suffix: string,
|
|
56
|
+
): string {
|
|
57
|
+
const pathParts = path
|
|
58
|
+
.split("/")
|
|
59
|
+
.filter((p) => p)
|
|
60
|
+
.map((p) => {
|
|
61
|
+
if (p.startsWith("{") && p.endsWith("}")) {
|
|
62
|
+
return p.slice(1, -1);
|
|
63
|
+
}
|
|
64
|
+
return p;
|
|
65
|
+
})
|
|
66
|
+
.map(toPascalCase);
|
|
67
|
+
const methodPrefix = method.charAt(0) + method.slice(1).toLowerCase();
|
|
68
|
+
const parts = [methodPrefix, ...pathParts, suffix];
|
|
69
|
+
return parts.join("");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function parseOpenApiPaths(
|
|
73
|
+
openapi: Record<string, unknown>,
|
|
74
|
+
): RouteInfo[] {
|
|
75
|
+
const paths = (openapi as AnySchema)["paths"] as
|
|
76
|
+
| Record<string, AnySchema>
|
|
77
|
+
| undefined;
|
|
78
|
+
if (!paths) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const routes: RouteInfo[] = [];
|
|
83
|
+
|
|
84
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
85
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
86
|
+
|
|
87
|
+
const methods = [
|
|
88
|
+
"get",
|
|
89
|
+
"post",
|
|
90
|
+
"put",
|
|
91
|
+
"patch",
|
|
92
|
+
"delete",
|
|
93
|
+
"head",
|
|
94
|
+
"options",
|
|
95
|
+
] as const;
|
|
96
|
+
|
|
97
|
+
for (const method of methods) {
|
|
98
|
+
const operation = pathItem[method] as AnySchema | undefined;
|
|
99
|
+
if (!operation) continue;
|
|
100
|
+
|
|
101
|
+
const parameters: RouteParameter[] = [];
|
|
102
|
+
const responses: Record<string, AnySchema> = {};
|
|
103
|
+
|
|
104
|
+
if (Array.isArray(pathItem["parameters"])) {
|
|
105
|
+
for (const param of pathItem["parameters"]) {
|
|
106
|
+
if (param && typeof param === "object") {
|
|
107
|
+
parameters.push({
|
|
108
|
+
name: String(param["name"] || ""),
|
|
109
|
+
in: param["in"] || "query",
|
|
110
|
+
required: Boolean(param["required"]),
|
|
111
|
+
schema: param["schema"] || {},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (Array.isArray(operation["parameters"])) {
|
|
118
|
+
for (const param of operation["parameters"]) {
|
|
119
|
+
if (param && typeof param === "object") {
|
|
120
|
+
parameters.push({
|
|
121
|
+
name: String(param["name"] || ""),
|
|
122
|
+
in: param["in"] || "query",
|
|
123
|
+
required: Boolean(param["required"]),
|
|
124
|
+
schema: param["schema"] || {},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let requestBody: AnySchema | undefined;
|
|
131
|
+
if (operation["requestBody"]) {
|
|
132
|
+
const rb = operation["requestBody"];
|
|
133
|
+
if (rb && typeof rb === "object") {
|
|
134
|
+
const content = rb["content"];
|
|
135
|
+
if (content && typeof content === "object") {
|
|
136
|
+
const jsonContent = content["application/json"];
|
|
137
|
+
if (jsonContent && typeof jsonContent === "object") {
|
|
138
|
+
requestBody = jsonContent["schema"] || {};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (operation["responses"] && typeof operation["responses"] === "object") {
|
|
145
|
+
for (const [statusCode, response] of Object.entries(
|
|
146
|
+
operation["responses"],
|
|
147
|
+
)) {
|
|
148
|
+
if (response && typeof response === "object") {
|
|
149
|
+
const responseSchema = response as AnySchema;
|
|
150
|
+
const content = responseSchema["content"];
|
|
151
|
+
if (content && typeof content === "object") {
|
|
152
|
+
const jsonContent = content["application/json"];
|
|
153
|
+
if (jsonContent && typeof jsonContent === "object") {
|
|
154
|
+
const schema = jsonContent["schema"];
|
|
155
|
+
if (schema) {
|
|
156
|
+
responses[statusCode] = schema;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
routes.push({
|
|
165
|
+
path,
|
|
166
|
+
method: toUpperCaseMethod(method),
|
|
167
|
+
parameters,
|
|
168
|
+
requestBody,
|
|
169
|
+
responses,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return routes;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function generateRouteSchemaNames(
|
|
178
|
+
route: RouteInfo,
|
|
179
|
+
): RouteSchemaNames {
|
|
180
|
+
const pathParams = route.parameters.filter((p) => p.in === "path");
|
|
181
|
+
const queryParams = route.parameters.filter((p) => p.in === "query");
|
|
182
|
+
const headerParams = route.parameters.filter((p) => p.in === "header");
|
|
183
|
+
const successStatuses = Object.keys(route.responses).filter((s) =>
|
|
184
|
+
s.startsWith("2"),
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const result: RouteSchemaNames = {
|
|
188
|
+
responseSchemaName: successStatuses.length > 0
|
|
189
|
+
? generateRouteSchemaName(
|
|
190
|
+
route.path,
|
|
191
|
+
route.method,
|
|
192
|
+
"Response",
|
|
193
|
+
)
|
|
194
|
+
: undefined,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
if (pathParams.length > 0) {
|
|
198
|
+
result.paramsSchemaName = generateRouteSchemaName(
|
|
199
|
+
route.path,
|
|
200
|
+
route.method,
|
|
201
|
+
"Params",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (queryParams.length > 0) {
|
|
206
|
+
result.querySchemaName = generateRouteSchemaName(
|
|
207
|
+
route.path,
|
|
208
|
+
route.method,
|
|
209
|
+
"Query",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (headerParams.length > 0) {
|
|
214
|
+
result.headersSchemaName = generateRouteSchemaName(
|
|
215
|
+
route.path,
|
|
216
|
+
route.method,
|
|
217
|
+
"Headers",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (route.requestBody) {
|
|
222
|
+
result.bodySchemaName = generateRouteSchemaName(
|
|
223
|
+
route.path,
|
|
224
|
+
route.method,
|
|
225
|
+
"Body",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { openApiToZodTsCode } from "./to-typescript";
|
|
3
|
+
import {
|
|
4
|
+
registerZodSchemaToOpenApiSchema,
|
|
5
|
+
clearZodSchemaToOpenApiSchemaRegistry,
|
|
6
|
+
} from "./registry";
|
|
7
|
+
|
|
8
|
+
describe("openApiToZodTsCode with routes", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
clearZodSchemaToOpenApiSchemaRegistry();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
clearZodSchemaToOpenApiSchemaRegistry();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("route generation", () => {
|
|
18
|
+
it("should generate Request and Response objects for paths", () => {
|
|
19
|
+
const openapi = {
|
|
20
|
+
components: {
|
|
21
|
+
schemas: {
|
|
22
|
+
User: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
id: { type: "string" },
|
|
26
|
+
name: { type: "string" },
|
|
27
|
+
},
|
|
28
|
+
required: ["id", "name"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
paths: {
|
|
33
|
+
"/users/{id}": {
|
|
34
|
+
get: {
|
|
35
|
+
parameters: [
|
|
36
|
+
{
|
|
37
|
+
name: "id",
|
|
38
|
+
in: "path",
|
|
39
|
+
required: true,
|
|
40
|
+
schema: { type: "string" },
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
responses: {
|
|
44
|
+
"200": {
|
|
45
|
+
content: {
|
|
46
|
+
"application/json": {
|
|
47
|
+
schema: {
|
|
48
|
+
$ref: "#/components/schemas/User",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
60
|
+
includeRoutes: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(result).toContain("export const GetUsersIdParamsSchema");
|
|
64
|
+
expect(result).toContain("export const GetUsersIdResponseSchema");
|
|
65
|
+
expect(result).toContain("export const Request = {");
|
|
66
|
+
expect(result).toContain("export const Response = {");
|
|
67
|
+
expect(result).toContain("'/users/{id}':");
|
|
68
|
+
expect(result).toContain("GET:");
|
|
69
|
+
expect(result).toContain("params: GetUsersIdParamsSchema");
|
|
70
|
+
expect(result).toContain("GET: GetUsersIdResponseSchema");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should generate Request with body schema", () => {
|
|
74
|
+
const openapi = {
|
|
75
|
+
components: {
|
|
76
|
+
schemas: {
|
|
77
|
+
CreateUser: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
name: { type: "string" },
|
|
81
|
+
},
|
|
82
|
+
required: ["name"],
|
|
83
|
+
},
|
|
84
|
+
User: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
id: { type: "string" },
|
|
88
|
+
name: { type: "string" },
|
|
89
|
+
},
|
|
90
|
+
required: ["id", "name"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
paths: {
|
|
95
|
+
"/users": {
|
|
96
|
+
post: {
|
|
97
|
+
requestBody: {
|
|
98
|
+
content: {
|
|
99
|
+
"application/json": {
|
|
100
|
+
schema: {
|
|
101
|
+
$ref: "#/components/schemas/CreateUser",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
responses: {
|
|
107
|
+
"201": {
|
|
108
|
+
content: {
|
|
109
|
+
"application/json": {
|
|
110
|
+
schema: {
|
|
111
|
+
$ref: "#/components/schemas/User",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
123
|
+
includeRoutes: true,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(result).toContain("export const PostUsersBodySchema");
|
|
127
|
+
expect(result).toContain("export const PostUsersResponseSchema");
|
|
128
|
+
expect(result).toContain("body: PostUsersBodySchema");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should generate Request with query parameters", () => {
|
|
132
|
+
const openapi = {
|
|
133
|
+
components: {
|
|
134
|
+
schemas: {
|
|
135
|
+
User: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
id: { type: "string" },
|
|
139
|
+
},
|
|
140
|
+
required: ["id"],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
paths: {
|
|
145
|
+
"/users": {
|
|
146
|
+
get: {
|
|
147
|
+
parameters: [
|
|
148
|
+
{
|
|
149
|
+
name: "limit",
|
|
150
|
+
in: "query",
|
|
151
|
+
required: false,
|
|
152
|
+
schema: { type: "number" },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "offset",
|
|
156
|
+
in: "query",
|
|
157
|
+
required: false,
|
|
158
|
+
schema: { type: "number" },
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
responses: {
|
|
162
|
+
"200": {
|
|
163
|
+
content: {
|
|
164
|
+
"application/json": {
|
|
165
|
+
schema: {
|
|
166
|
+
$ref: "#/components/schemas/User",
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
178
|
+
includeRoutes: true,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(result).toContain("export const GetUsersQuerySchema");
|
|
182
|
+
expect(result).toContain("query: GetUsersQuerySchema");
|
|
183
|
+
expect(result).toContain("limit: z.number().optional()");
|
|
184
|
+
expect(result).toContain("offset: z.number().optional()");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should generate Request with headers", () => {
|
|
188
|
+
const openapi = {
|
|
189
|
+
components: {
|
|
190
|
+
schemas: {
|
|
191
|
+
User: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
id: { type: "string" },
|
|
195
|
+
},
|
|
196
|
+
required: ["id"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
paths: {
|
|
201
|
+
"/users": {
|
|
202
|
+
get: {
|
|
203
|
+
parameters: [
|
|
204
|
+
{
|
|
205
|
+
name: "Authorization",
|
|
206
|
+
in: "header",
|
|
207
|
+
required: true,
|
|
208
|
+
schema: { type: "string" },
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
responses: {
|
|
212
|
+
"200": {
|
|
213
|
+
content: {
|
|
214
|
+
"application/json": {
|
|
215
|
+
schema: {
|
|
216
|
+
$ref: "#/components/schemas/User",
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
228
|
+
includeRoutes: true,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(result).toContain("export const GetUsersHeadersSchema");
|
|
232
|
+
expect(result).toContain("headers: GetUsersHeadersSchema");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should handle multiple 2xx responses as union", () => {
|
|
236
|
+
const openapi = {
|
|
237
|
+
components: {
|
|
238
|
+
schemas: {
|
|
239
|
+
User: {
|
|
240
|
+
type: "object",
|
|
241
|
+
properties: {
|
|
242
|
+
id: { type: "string" },
|
|
243
|
+
},
|
|
244
|
+
required: ["id"],
|
|
245
|
+
},
|
|
246
|
+
Error: {
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: {
|
|
249
|
+
message: { type: "string" },
|
|
250
|
+
},
|
|
251
|
+
required: ["message"],
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
paths: {
|
|
256
|
+
"/users": {
|
|
257
|
+
post: {
|
|
258
|
+
responses: {
|
|
259
|
+
"200": {
|
|
260
|
+
content: {
|
|
261
|
+
"application/json": {
|
|
262
|
+
schema: {
|
|
263
|
+
$ref: "#/components/schemas/User",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
"201": {
|
|
269
|
+
content: {
|
|
270
|
+
"application/json": {
|
|
271
|
+
schema: {
|
|
272
|
+
$ref: "#/components/schemas/User",
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
284
|
+
includeRoutes: true,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
expect(result).toContain("export const PostUsersResponseSchema");
|
|
288
|
+
expect(result).toContain("z.union([");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("should not generate routes when includeRoutes is false", () => {
|
|
292
|
+
const openapi = {
|
|
293
|
+
components: {
|
|
294
|
+
schemas: {
|
|
295
|
+
User: {
|
|
296
|
+
type: "object",
|
|
297
|
+
properties: {
|
|
298
|
+
id: { type: "string" },
|
|
299
|
+
},
|
|
300
|
+
required: ["id"],
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
paths: {
|
|
305
|
+
"/users/{id}": {
|
|
306
|
+
get: {
|
|
307
|
+
parameters: [
|
|
308
|
+
{
|
|
309
|
+
name: "id",
|
|
310
|
+
in: "path",
|
|
311
|
+
required: true,
|
|
312
|
+
schema: { type: "string" },
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
responses: {
|
|
316
|
+
"200": {
|
|
317
|
+
content: {
|
|
318
|
+
"application/json": {
|
|
319
|
+
schema: {
|
|
320
|
+
$ref: "#/components/schemas/User",
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
332
|
+
includeRoutes: false,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(result).not.toContain("export const Request");
|
|
336
|
+
expect(result).not.toContain("export const Response");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("should handle paths without routes", () => {
|
|
340
|
+
const openapi = {
|
|
341
|
+
components: {
|
|
342
|
+
schemas: {
|
|
343
|
+
User: {
|
|
344
|
+
type: "object",
|
|
345
|
+
properties: {
|
|
346
|
+
id: { type: "string" },
|
|
347
|
+
},
|
|
348
|
+
required: ["id"],
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
355
|
+
includeRoutes: true,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
expect(result).not.toContain("export const Request");
|
|
359
|
+
expect(result).not.toContain("export const Response");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("should handle routes without requestBody", () => {
|
|
363
|
+
const openapi = {
|
|
364
|
+
components: {
|
|
365
|
+
schemas: {
|
|
366
|
+
User: {
|
|
367
|
+
type: "object",
|
|
368
|
+
properties: {
|
|
369
|
+
id: { type: "string" },
|
|
370
|
+
},
|
|
371
|
+
required: ["id"],
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
paths: {
|
|
376
|
+
"/users": {
|
|
377
|
+
get: {
|
|
378
|
+
responses: {
|
|
379
|
+
"200": {
|
|
380
|
+
content: {
|
|
381
|
+
"application/json": {
|
|
382
|
+
schema: {
|
|
383
|
+
$ref: "#/components/schemas/User",
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
395
|
+
includeRoutes: true,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(result).toContain("export const Response");
|
|
399
|
+
expect(result).toContain("GET: GetUsersResponseSchema");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("should handle multiple methods on same path", () => {
|
|
403
|
+
const openapi = {
|
|
404
|
+
components: {
|
|
405
|
+
schemas: {
|
|
406
|
+
User: {
|
|
407
|
+
type: "object",
|
|
408
|
+
properties: {
|
|
409
|
+
id: { type: "string" },
|
|
410
|
+
},
|
|
411
|
+
required: ["id"],
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
paths: {
|
|
416
|
+
"/users/{id}": {
|
|
417
|
+
get: {
|
|
418
|
+
parameters: [
|
|
419
|
+
{
|
|
420
|
+
name: "id",
|
|
421
|
+
in: "path",
|
|
422
|
+
required: true,
|
|
423
|
+
schema: { type: "string" },
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
responses: {
|
|
427
|
+
"200": {
|
|
428
|
+
content: {
|
|
429
|
+
"application/json": {
|
|
430
|
+
schema: {
|
|
431
|
+
$ref: "#/components/schemas/User",
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
delete: {
|
|
439
|
+
parameters: [
|
|
440
|
+
{
|
|
441
|
+
name: "id",
|
|
442
|
+
in: "path",
|
|
443
|
+
required: true,
|
|
444
|
+
schema: { type: "string" },
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
responses: {
|
|
448
|
+
"204": {
|
|
449
|
+
content: {
|
|
450
|
+
"application/json": {
|
|
451
|
+
schema: {
|
|
452
|
+
type: "object",
|
|
453
|
+
properties: {},
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const result = openApiToZodTsCode(openapi, undefined, {
|
|
465
|
+
includeRoutes: true,
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
expect(result).toContain("GET:");
|
|
469
|
+
expect(result).toContain("DELETE:");
|
|
470
|
+
expect(result).toContain("'/users/{id}':");
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|