Skip to content

Commit 4040c68

Browse files
committed
Change response types to ensure proper status and headers are set
1 parent fdc5f27 commit 4040c68

File tree

9 files changed

+116
-22
lines changed

9 files changed

+116
-22
lines changed

packages/cli/src/typegen/templates.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,25 @@ export type Property<T, K extends keyof any, D = unknown> =
1717
export type ValueOf<T, D = never> = T extends Record<string, unknown> ? T[keyof T] : D;
1818
1919
export type EmptyObject = Record<never, never>;
20+
21+
export type StatusCode<Status> =
22+
Status extends '4XX' ? FourXX :
23+
Status extends '5XX' ? FiveXX :
24+
Status extends number ? Status :
25+
Status extends \`\${infer N extends number}\` ? N :
26+
never;
27+
28+
export type FourXX = 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 |
29+
410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 |
30+
422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451;
31+
32+
export type FiveXX = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511;
2033
`;
2134

2235
export const requests = `
23-
import {Params, Request, Response} from '@openapi-ts/request-types';
36+
import {Params, Request} from '@openapi-ts/request-types';
2437
import {operations} from './spec';
25-
import {EmptyObject, Property, ValueOf} from './utils';
38+
import {EmptyObject, Property, ValueOf, StatusCode} from './utils';
2639
2740
export type RequestBody<OperationId extends keyof operations> =
2841
operations[OperationId] extends {requestBody: Record<string, any>} ?
@@ -38,8 +51,23 @@ export type RequestQuery<OperationId extends keyof operations> =
3851
export type RequestHeaders<OperationId extends keyof operations> =
3952
Property<Property<operations[OperationId], 'parameters', EmptyObject>, 'header', EmptyObject>;
4053
41-
export type ResponseBody<OperationId extends keyof operations> =
42-
ValueOf<Property<ValueOf<Property<operations[OperationId], 'responses', EmptyObject>>, 'content'>, void>;
54+
export type OperationResponse<OpName extends keyof operations> = {
55+
[Status in keyof operations[OpName]['responses']]:
56+
operations[OpName]['responses'][Status] extends {
57+
content: infer Content;
58+
}
59+
? {
60+
[CT in keyof Content]: {
61+
statusCode: StatusCode<Status>;
62+
headers: { 'Content-Type': CT };
63+
body: Content[CT];
64+
}
65+
}[keyof Content]
66+
: {
67+
statusCode: StatusCode<Status>;
68+
headers: EmptyObject;
69+
};
70+
}[keyof operations[OpName]['responses']]
4371
4472
export type ResponseHeaders<OperationId extends keyof operations> =
4573
Property<ValueOf<Property<operations[OperationId], 'responses', EmptyObject>>, 'headers'>;
@@ -49,10 +77,6 @@ export type OperationRequest<OperationId extends keyof operations> = Request<
4977
Params & RequestPathParams<OperationId>,
5078
Params & RequestQuery<OperationId>,
5179
Params & RequestHeaders<OperationId>>;
52-
53-
export type OperationResponse<OperationId extends keyof operations> = Response<
54-
ResponseBody<OperationId>,
55-
Params & ResponseHeaders<OperationId>>;
5680
`;
5781

5882
export const handlers = `

packages/lib/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './types';
22
export * from './errors';
33
export * from './openapi';
4+
export * from './response';

packages/lib/src/openapi.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,10 @@ export class OpenApi<T> {
254254

255255
// Note: The handler function may modify the "res" object and/or return a response body.
256256
// If "res.body" is undefined we use the return value as the body.
257-
const resBody = await operationHandler(req, res, operationParams);
258-
res.body = res.body ?? resBody;
257+
const returnedResponse = await operationHandler(req, res, operationParams);
258+
if (returnedResponse !== undefined) {
259+
Object.assign(res, returnedResponse);
260+
}
259261

260262
// If status code is not specified and a non-ambiguous default status code is available, use it
261263
res.statusCode = res.statusCode ?? this.getDefaultStatusCode(operation);

packages/lib/src/response.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function json<T>(body: T): { statusCode: 200; body: T; headers: { "Content-Type": "application/json" } };
2+
export function json<T, CT extends number>(body: T, status: CT): { statusCode: CT; body: T; headers: { "Content-Type": "application/json" } };
3+
4+
export function json<T, CT extends number = 200>(
5+
body: T,
6+
status?: CT
7+
): {statusCode: CT | 200; body: T; headers: { "Content-Type": "application/json" } } {
8+
const statusCode = status ?? 200
9+
return {
10+
statusCode,
11+
body,
12+
headers: {
13+
"Content-Type": "application/json",
14+
},
15+
}
16+
}

packages/lib/src/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,8 @@ export type RequestHandler<P = unknown,
103103
Req extends Request = Request,
104104
Res extends Response = Response> = (
105105
req: Req,
106-
res: Res,
107-
params: P) => Awaitable<Res['body'] | void>;
108-
106+
res: Response,
107+
params: P) => Awaitable<Res | void>;
109108

110109
type SecuritySchemeObject = OpenAPIV3_1.SecuritySchemeObject | OpenAPIV3.SecuritySchemeObject;
111110
/**

packages/test/api.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,37 @@ paths:
152152
schema:
153153
type: object
154154
additionalProperties: true
155+
'/multiresponse':
156+
get:
157+
operationId: multiResponse
158+
summary: test multiple possible responses
159+
parameters:
160+
- in: query
161+
name: type
162+
schema:
163+
type: string
164+
165+
responses:
166+
'200':
167+
description: OK
168+
content:
169+
application/json:
170+
schema:
171+
type: object
172+
properties:
173+
okBody:
174+
type: number
175+
text/html:
176+
schema:
177+
type: string
178+
179+
'404':
180+
description: Not found
181+
content:
182+
application/json:
183+
schema:
184+
type: object
185+
155186

156187

157188

packages/test/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "test",
33
"scripts": {
44
"pretest": "npm -w ../lib run build && openapi-ts-backend generate-types api.yml src/gen",
5-
"test": "LOG_LEVEL=${LOG_LEVEL:=error} jest"
5+
"test": "tsc && LOG_LEVEL=${LOG_LEVEL:=error} jest"
66
},
77
"devDependencies": {
88
"@openapi-ts/cli": "*",

packages/test/src/openapi.test.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {HttpError, OpenApi} from "@openapi-ts/backend";
1+
import {HttpError, json, OpenApi} from "@openapi-ts/backend";
22
import { OperationHandlers} from './gen';
33

44
function greet(title: string, name: string): string {
@@ -13,23 +13,43 @@ const operations: OperationHandlers<unknown> = {
1313
greet: req => {
1414
const {params: {name}, query: {title = ''}} = req;
1515

16-
return {
17-
message: greet(title, name),
18-
};
16+
return json({ message: greet(title, name)})
1917
},
2018
addPerson: req => {
21-
return req.body.person;
19+
return json(req.body.person, 201);
2220
},
2321
getTypes: req => {
24-
return {
22+
return json({
2523
params: getTypeMap(req.params),
2624
headers: getTypeMap(req.headers),
2725
query: getTypeMap(req.query),
2826
cookies: getTypeMap(req.cookies),
29-
}
27+
})
3028
},
3129
deletePerson: () => {
3230
return;
31+
},
32+
multiResponse: (req) => {
33+
if (req.query.type === '') {
34+
return {
35+
statusCode: 404,
36+
body: {},
37+
headers: {
38+
'Content-Type': 'application/json',
39+
'Additional-Header': 'somethingelse'
40+
}
41+
}
42+
}
43+
if (req.query.type === 'html') {
44+
return {
45+
statusCode: 200,
46+
body: 'htmlContent',
47+
headers: {
48+
'Content-Type': 'text/html'
49+
}
50+
}
51+
}
52+
return json({ okBody: 1337 })
3353
}
3454
};
3555

packages/test/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"extends": "../../tsconfig.json",
33
"compilerOptions": {
44
"rootDir": "./src",
5-
"noEmit": true,
5+
"noEmit": true
66
},
7+
"exclude": ["jest.config.ts"]
78
}

0 commit comments

Comments
 (0)