Skip to content

Commit 6164a0d

Browse files
committed
feat: Add simple types generator
1 parent 7c88543 commit 6164a0d

8 files changed

Lines changed: 477 additions & 3 deletions

File tree

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
export type Category = { id?: number; name?: string };
2+
3+
export type Tag = { id?: number; name?: string };
4+
5+
export type Pet = {
6+
id?: number;
7+
category?: Category;
8+
name: string;
9+
photoUrls: Array<string>;
10+
tags?: Array<Tag>;
11+
status?: 'available' | 'pending' | 'sold';
12+
};
13+
14+
export type AddPetRequest = {
15+
body: { body: Pet };
16+
path: {};
17+
query: {};
18+
header: {};
19+
};
20+
21+
export type AddPetResponse = { status: 405; body: unknown };
22+
23+
export type UpdatePetRequest = {
24+
body: { body: Pet };
25+
path: {};
26+
query: {};
27+
header: {};
28+
};
29+
30+
export type UpdatePetResponse =
31+
| { status: 400; body: unknown }
32+
| { status: 404; body: unknown }
33+
| { status: 405; body: unknown };
34+
35+
export type FindPetsByStatusRequest = {
36+
body: {};
37+
path: {};
38+
query: { status: string };
39+
header: {};
40+
};
41+
42+
export type FindPetsByStatusResponse =
43+
| { status: 200; body: Array<Pet> }
44+
| { status: 400; body: unknown };
45+
46+
export type FindPetsByTagsRequest = {
47+
body: {};
48+
path: {};
49+
query: { tags: string };
50+
header: {};
51+
};
52+
53+
export type FindPetsByTagsResponse =
54+
| { status: 200; body: Array<Pet> }
55+
| { status: 400; body: unknown };
56+
57+
export type GetPetByIdRequest = {
58+
body: {};
59+
path: { petId: string };
60+
query: {};
61+
header: {};
62+
};
63+
64+
export type GetPetByIdResponse =
65+
| { status: 200; body: Pet }
66+
| { status: 400; body: unknown }
67+
| { status: 404; body: unknown };
68+
69+
export type UpdatePetWithFormRequest = {
70+
body: {};
71+
path: { petId: string };
72+
query: {};
73+
header: {};
74+
};
75+
76+
export type UpdatePetWithFormResponse = { status: 405; body: unknown };
77+
78+
export type DeletePetRequest = {
79+
body: {};
80+
path: { petId: string };
81+
query: {};
82+
header: { apiKey?: string };
83+
};
84+
85+
export type DeletePetResponse =
86+
| { status: 400; body: unknown }
87+
| { status: 404; body: unknown };
88+
89+
export type Order = {
90+
id?: number;
91+
petId?: number;
92+
quantity?: number;
93+
shipDate?: string;
94+
status?: 'placed' | 'approved' | 'delivered';
95+
complete?: boolean;
96+
};
97+
98+
export type PlaceOrderRequest = {
99+
body: { body: Order };
100+
path: {};
101+
query: {};
102+
header: {};
103+
};
104+
105+
export type PlaceOrderResponse =
106+
| { status: 200; body: Order }
107+
| { status: 400; body: unknown };
108+
109+
export type GetOrderByIdRequest = {
110+
body: {};
111+
path: { orderId: string };
112+
query: {};
113+
header: {};
114+
};
115+
116+
export type GetOrderByIdResponse =
117+
| { status: 200; body: Order }
118+
| { status: 400; body: unknown }
119+
| { status: 404; body: unknown };
120+
121+
export type DeleteOrderRequest = {
122+
body: {};
123+
path: { orderId: string };
124+
query: {};
125+
header: {};
126+
};
127+
128+
export type DeleteOrderResponse =
129+
| { status: 400; body: unknown }
130+
| { status: 404; body: unknown };
131+
132+
export type GetInventoryRequest = { body: {}; path: {}; query: {}; header: {} };
133+
134+
export type GetInventoryResponse = { status: 200; body: unknown };
135+
136+
export type User = {
137+
id?: number;
138+
username?: string;
139+
firstName?: string;
140+
lastName?: string;
141+
email?: string;
142+
password?: string;
143+
phone?: string;
144+
userStatus?: number;
145+
};
146+
147+
export type CreateUsersWithArrayInputRequest = {
148+
body: { body: Array<User> };
149+
path: {};
150+
query: {};
151+
header: {};
152+
};
153+
154+
export type CreateUsersWithArrayInputResponse = {
155+
status: default;
156+
body: unknown;
157+
};
158+
159+
export type CreateUsersWithListInputRequest = {
160+
body: { body: Array<User> };
161+
path: {};
162+
query: {};
163+
header: {};
164+
};
165+
166+
export type CreateUsersWithListInputResponse = {
167+
status: default;
168+
body: unknown;
169+
};
170+
171+
export type GetUserByNameRequest = {
172+
body: {};
173+
path: { username: string };
174+
query: {};
175+
header: {};
176+
};
177+
178+
export type GetUserByNameResponse =
179+
| { status: 200; body: User }
180+
| { status: 400; body: unknown }
181+
| { status: 404; body: unknown };
182+
183+
export type UpdateUserRequest = {
184+
body: { body: User };
185+
path: { username: string };
186+
query: {};
187+
header: {};
188+
};
189+
190+
export type UpdateUserResponse =
191+
| { status: 400; body: unknown }
192+
| { status: 404; body: unknown };
193+
194+
export type DeleteUserRequest = {
195+
body: {};
196+
path: { username: string };
197+
query: {};
198+
header: {};
199+
};
200+
201+
export type DeleteUserResponse =
202+
| { status: 400; body: unknown }
203+
| { status: 404; body: unknown };
204+
205+
export type LoginUserRequest = {
206+
body: {};
207+
path: {};
208+
query: { username: string; password: string };
209+
header: {};
210+
};
211+
212+
export type LoginUserResponse =
213+
| { status: 200; body: string }
214+
| { status: 400; body: unknown };
215+
216+
export type LogoutUserRequest = { body: {}; path: {}; query: {}; header: {} };
217+
218+
export type LogoutUserResponse = { status: default; body: unknown };
219+
220+
export type CreateUserRequest = {
221+
body: { body: User };
222+
path: {};
223+
query: {};
224+
header: {};
225+
};
226+
227+
export type CreateUserResponse = { status: default; body: unknown };

src/cli.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const cmd = new commander.Command('generate')
1919
.arguments('<spec-filename-or-url>')
2020
.option(
2121
'--template <template>',
22-
'the type to output. client, server or stubs. Default client'
22+
'the type to output. client, server, stubs or types. Default client'
2323
)
2424
.option(
2525
'--filename <filename>',
@@ -48,7 +48,9 @@ const cmd = new commander.Command('generate')
4848

4949
if (
5050
options.template &&
51-
!['client', 'server', 'stubs'].includes(options.template.toLowerCase())
51+
!['client', 'server', 'stubs', 'types'].includes(
52+
options.template.toLowerCase()
53+
)
5254
) {
5355
commander.help({ error: true });
5456
}

src/generateCode.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'path';
22
import { generateClient } from './generators/client/client';
33
import { generateServerClasses } from './generators/server/server';
44
import { generateStubs } from './generators/stubs';
5+
import { generateTypes } from './generators/types/types';
56
import { assertNever } from './helpers/assertNever';
67
import { initUpper } from './helpers/initUpper';
78
import { load } from './helpers/load';
@@ -63,6 +64,8 @@ export const generateCode = async (
6364
return generateServerClasses;
6465
case 'STUBS':
6566
return generateStubs;
67+
case 'TYPES':
68+
return generateTypes;
6669
default:
6770
return assertNever(options.output);
6871
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as OpenApi from '../../../types/OpenApi';
2+
import { LogFn } from '../../../lib/cli-logging';
3+
import { fromV3 } from './v3';
4+
import { fromV2 } from './v2';
5+
import { TypeDefContext } from '../../../helpers/open-api/TypeDefContext';
6+
7+
export const generate = (
8+
openApiDocument: OpenApi.Document,
9+
options: {
10+
nonRequiredType: 'optional' | 'nullable' | 'both';
11+
},
12+
context: TypeDefContext,
13+
log?: LogFn
14+
): void => {
15+
if ('swagger' in openApiDocument) {
16+
fromV2(openApiDocument, options, context, log);
17+
} else {
18+
fromV3(openApiDocument, options, context, log);
19+
}
20+
};

src/generators/types/methods/v2.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import * as HelpersV2 from '../../../helpers/open-api/v2';
2+
import * as OpenApiV2 from '../../../types/OpenApiV2';
3+
import { LogFn, progress } from '../../../lib/cli-logging';
4+
import { TypeDefContext } from '../../../helpers/open-api/TypeDefContext';
5+
import { makeIdentifier } from '../../../templates/makeIdentifier';
6+
7+
export const fromV2 = (
8+
document: OpenApiV2.Document,
9+
options: {
10+
nonRequiredType: 'optional' | 'nullable' | 'both';
11+
},
12+
context: TypeDefContext,
13+
log?: LogFn
14+
): void => {
15+
HelpersV2.mapOperations(document).forEach(
16+
({ method, parameters, operationId, path, responses }) => {
17+
log?.(progress(`Adding from ${method} ${path}`));
18+
19+
const parameterTypes: OpenApiV2.Parameter['in'][] = [
20+
'header',
21+
'path',
22+
'query',
23+
'body'
24+
];
25+
26+
const [
27+
headerType,
28+
pathType,
29+
queryType,
30+
requestBodyType
31+
] = parameterTypes.map(paramType => {
32+
const paramsOfType = parameters.filter(p => p.in === paramType);
33+
34+
return HelpersV2.typeDefForSchema(
35+
{
36+
type: 'object',
37+
properties: Object.fromEntries(
38+
paramsOfType.map(p => [p.name, p.schema ?? { type: 'string' }])
39+
),
40+
required: paramsOfType.filter(p => p.required).map(p => p.name)
41+
},
42+
document,
43+
options,
44+
context
45+
);
46+
});
47+
48+
context.emitType(
49+
`${makeIdentifier(operationId)}Request`,
50+
`{ body: ${requestBodyType}; path: ${pathType}; query: ${queryType}; header: ${headerType} }`
51+
);
52+
53+
const responseTypes = responses.map(({ statusCode, response }) => {
54+
const bodyType = (() => {
55+
if (response.schema?.type !== 'file') {
56+
const { schema } = response;
57+
58+
if (schema) {
59+
return HelpersV2.typeDefForSchema(
60+
schema,
61+
document,
62+
options,
63+
context
64+
);
65+
}
66+
}
67+
68+
return 'unknown';
69+
})();
70+
71+
return `{ status: ${statusCode}; body: ${bodyType}}`;
72+
});
73+
74+
context.emitType(
75+
`${makeIdentifier(operationId)}Response`,
76+
responseTypes.join('|')
77+
);
78+
}
79+
);
80+
};

0 commit comments

Comments
 (0)