Skip to content

Commit b3526df

Browse files
authored
Add support for paths (#328)
#236
1 parent b77e3b7 commit b3526df

File tree

10 files changed

+97882
-39
lines changed

10 files changed

+97882
-39
lines changed

.eslintrc.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
module.exports = {
2-
parser: '@typescript-eslint/parser',
3-
extends: ['plugin:@typescript-eslint/recommended', 'prettier', 'prettier/@typescript-eslint'],
4-
plugins: ['@typescript-eslint', 'prettier'],
2+
parser: "@typescript-eslint/parser",
3+
extends: [
4+
"plugin:@typescript-eslint/recommended",
5+
"prettier",
6+
"prettier/@typescript-eslint",
7+
],
8+
plugins: ["@typescript-eslint", "prettier"],
59
rules: {
6-
'@typescript-eslint/camelcase': 'off',
7-
'@typescript-eslint/no-use-before-define': 'off',
8-
'prettier/prettier': 'error',
10+
"@typescript-eslint/camelcase": "off",
11+
"@typescript-eslint/no-use-before-define": "off",
12+
"prettier/prettier": "error",
13+
"prefer-const": "off",
914
},
1015
env: {
1116
jest: true,

src/types/OpenAPI3.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,44 @@ export interface OpenAPI3Schemas {
88
[key: string]: OpenAPI3SchemaObject | OpenAPI3Reference;
99
}
1010

11+
export interface OpenAPI3Paths {
12+
[path: string]: {
13+
[method: string]: OpenAPI3Response;
14+
};
15+
}
16+
17+
export interface OpenAPI3Response {
18+
description?: string;
19+
parameters?: OpenAPI3Parameter[];
20+
responses: {
21+
[statusCode: string]: OpenAPI3ResponseObject;
22+
};
23+
}
24+
25+
export interface OpenAPI3Parameter {
26+
name: string;
27+
description?: string;
28+
required?: boolean;
29+
in: "query" | "header" | "path" | "cookie";
30+
schema: OpenAPI3SchemaObject | OpenAPI3Reference;
31+
}
32+
33+
export interface OpenAPI3ResponseObject {
34+
description?: string;
35+
content: {
36+
[contentType: string]: { schema: OpenAPI3SchemaObject | OpenAPI3Reference };
37+
};
38+
}
39+
1140
export interface OpenAPI3Components {
1241
schemas: OpenAPI3Schemas;
13-
responses?: OpenAPI3Schemas;
42+
responses?: { [key: string]: OpenAPI3ResponseObject };
1443
}
1544

1645
export interface OpenAPI3 {
1746
openapi: string;
18-
components: OpenAPI3Components;
47+
paths?: OpenAPI3Paths; // technically required by spec, but this library tries to be lenient
48+
components?: OpenAPI3Components;
1949
[key: string]: any; // handle other properties beyond swagger-to-ts’ concern
2050
}
2151

src/v3.ts

Lines changed: 105 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import propertyMapper from "./property-mapper";
22
import {
33
OpenAPI3,
4-
OpenAPI3Components,
4+
OpenAPI3Parameter,
5+
OpenAPI3Paths,
56
OpenAPI3SchemaObject,
67
OpenAPI3Schemas,
78
SwaggerToTSOptions,
@@ -34,16 +35,14 @@ export default function generateTypesV3(
3435
options?: SwaggerToTSOptions
3536
): string {
3637
const { rawSchema = false } = options || {};
37-
let components: OpenAPI3Components;
38+
let { paths = {}, components = { schemas: {} } } = input as OpenAPI3;
3839

3940
if (rawSchema) {
4041
components = { schemas: input };
4142
} else {
42-
components = (input as OpenAPI3).components;
43-
44-
if (!components || !components.schemas) {
43+
if (!input.components && !input.paths) {
4544
throw new Error(
46-
`⛔️ 'components' missing from schema https://swagger.io/specification`
45+
`No components or paths found. Specify --raw-schema to load a raw schema.`
4746
);
4847
}
4948
}
@@ -111,7 +110,7 @@ export default function generateTypesV3(
111110
if (Array.isArray(node.items)) {
112111
return tsTupleOf(node.items.map(transform));
113112
} else {
114-
return tsArrayOf(transform(node.items as any));
113+
return tsArrayOf(node.items ? transform(node.items as any) : "any");
115114
}
116115
}
117116
}
@@ -154,27 +153,107 @@ export default function generateTypesV3(
154153
return output;
155154
}
156155

157-
if (rawSchema) {
158-
const schemas = createKeys(propertyMapped, Object.keys(propertyMapped));
156+
function transformPaths(paths: OpenAPI3Paths): string {
157+
let output = "";
158+
Object.entries(paths).forEach(([path, methods]) => {
159+
output += `"${path}": {\n`;
160+
Object.entries(methods).forEach(([method, responses]) => {
161+
if (responses.description) output += comment(responses.description);
162+
output += `"${method}": {\n`;
163+
164+
// handle parameters
165+
if (responses.parameters) {
166+
output += `parameters: {\n`;
167+
const allParameters: Record<
168+
string,
169+
Record<string, OpenAPI3Parameter>
170+
> = {};
171+
responses.parameters.forEach((p) => {
172+
if (!allParameters[p.in]) allParameters[p.in] = {};
173+
// TODO: handle $ref parameters
174+
if (p.name) {
175+
allParameters[p.in][p.name] = p;
176+
}
177+
});
178+
179+
Object.entries(allParameters).forEach(([loc, locParams]) => {
180+
output += `"${loc}": {\n`;
181+
Object.entries(locParams).forEach(([paramName, paramProps]) => {
182+
if (paramProps.description)
183+
output += comment(paramProps.description);
184+
output += `"${paramName}"${
185+
paramProps.required === true ? "" : "?"
186+
}: ${transform(paramProps.schema)};\n`;
187+
});
188+
output += `}\n`;
189+
});
190+
output += `}\n`;
191+
}
159192

193+
// handle responses
194+
output += `responses: {\n`;
195+
Object.entries(responses.responses).forEach(
196+
([statusCode, response]) => {
197+
if (response.description) output += comment(response.description);
198+
if (!response.content || !Object.keys(response.content).length) {
199+
output += `"${statusCode}": any;\n`;
200+
return;
201+
}
202+
output += `"${statusCode}": {\n`;
203+
Object.entries(response.content).forEach(
204+
([contentType, encodedResponse]) => {
205+
output += `"${contentType}": ${transform(
206+
encodedResponse.schema
207+
)};\n`;
208+
}
209+
);
210+
output += `}\n`;
211+
}
212+
);
213+
output += `}\n`;
214+
output += `}\n`;
215+
});
216+
output += `}\n`;
217+
});
218+
return output;
219+
}
220+
221+
if (rawSchema) {
160222
return `export interface schemas {
161-
${schemas}
162-
}`;
223+
${createKeys(propertyMapped, Object.keys(propertyMapped))}
224+
}`;
163225
}
164226

165-
const schemas = `schemas: {
166-
${createKeys(propertyMapped, Object.keys(propertyMapped))}
167-
}`;
168-
169-
const responses = !components.responses
170-
? ``
171-
: `responses: {
172-
${createKeys(components.responses, Object.keys(components.responses))}
173-
}`;
174-
175-
// note: make sure that base-level schemas are required
176-
return `export interface components {
177-
${schemas}
178-
${responses}
179-
}`;
227+
// now put everything together
228+
let finalOutput = "";
229+
230+
// handle paths
231+
if (Object.keys(paths).length) {
232+
finalOutput += `export interface paths {
233+
${transformPaths(paths)}
234+
}
235+
236+
`;
237+
}
238+
239+
finalOutput += "export interface components {\n";
240+
241+
// TODO: handle components.parameters
242+
243+
if (Object.keys(propertyMapped).length) {
244+
finalOutput += `schemas: {
245+
${createKeys(propertyMapped, Object.keys(propertyMapped))}
246+
}`;
247+
}
248+
if (components.responses && Object.keys(components.responses).length) {
249+
finalOutput += `
250+
responses: {
251+
${createKeys(components.responses, Object.keys(components.responses))}
252+
}`;
253+
}
254+
255+
// close components wrapper
256+
finalOutput += "\n}";
257+
258+
return finalOutput;
180259
}

0 commit comments

Comments
 (0)