Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions packages/cli/src/typegen/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,25 @@ export type Property<T, K extends keyof any, D = unknown> =
export type ValueOf<T, D = never> = T extends Record<string, unknown> ? T[keyof T] : D;

export type EmptyObject = Record<never, never>;

export type StatusCode<Status> =
Status extends '4XX' ? FourXX :
Status extends '5XX' ? FiveXX :
Status extends number ? Status :
Status extends \`\${infer N extends number}\` ? N :
never;

export type FourXX = 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 |
410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 |
422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451;

export type FiveXX = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511;
`;

export const requests = `
import {Params, Request, Response} from '@openapi-ts/request-types';
import {Params, Request} from '@openapi-ts/request-types';
import {operations} from './spec';
import {EmptyObject, Property, ValueOf} from './utils';
import {EmptyObject, Property, ValueOf, StatusCode} from './utils';

export type RequestBody<OperationId extends keyof operations> =
operations[OperationId] extends {requestBody: Record<string, any>} ?
Expand All @@ -38,8 +51,23 @@ export type RequestQuery<OperationId extends keyof operations> =
export type RequestHeaders<OperationId extends keyof operations> =
Property<Property<operations[OperationId], 'parameters', EmptyObject>, 'header', EmptyObject>;

export type ResponseBody<OperationId extends keyof operations> =
ValueOf<Property<ValueOf<Property<operations[OperationId], 'responses', EmptyObject>>, 'content'>, void>;
export type OperationResponse<OpName extends keyof operations> = {
[Status in keyof operations[OpName]['responses']]:
operations[OpName]['responses'][Status] extends {
content: infer Content;
}
? {
[CT in keyof Content]: {
statusCode: StatusCode<Status>;
headers: { 'Content-Type': CT };
body: Content[CT];
}
}[keyof Content]
: {
statusCode: StatusCode<Status>;
headers: EmptyObject;
};
}[keyof operations[OpName]['responses']]

export type ResponseHeaders<OperationId extends keyof operations> =
Property<ValueOf<Property<operations[OperationId], 'responses', EmptyObject>>, 'headers'>;
Expand All @@ -49,10 +77,6 @@ export type OperationRequest<OperationId extends keyof operations> = Request<
Params & RequestPathParams<OperationId>,
Params & RequestQuery<OperationId>,
Params & RequestHeaders<OperationId>>;

export type OperationResponse<OperationId extends keyof operations> = Response<
ResponseBody<OperationId>,
Params & ResponseHeaders<OperationId>>;
`;

export const handlers = `
Expand Down
1 change: 1 addition & 0 deletions packages/lib/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './types';
export * from './errors';
export * from './openapi';
export * from './response';
6 changes: 4 additions & 2 deletions packages/lib/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,10 @@ export class OpenApi<T> {

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

// If status code is not specified and a non-ambiguous default status code is available, use it
res.statusCode = res.statusCode ?? this.getDefaultStatusCode(operation);
Expand Down
16 changes: 16 additions & 0 deletions packages/lib/src/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function json<T>(body: T): { statusCode: 200; body: T; headers: { "Content-Type": "application/json" } };
export function json<T, CT extends number>(body: T, status: CT): { statusCode: CT; body: T; headers: { "Content-Type": "application/json" } };

export function json<T, CT extends number = 200>(
body: T,
status?: CT
): {statusCode: CT | 200; body: T; headers: { "Content-Type": "application/json" } } {
const statusCode = status ?? 200
return {
statusCode,
body,
headers: {
"Content-Type": "application/json",
},
}
}
5 changes: 2 additions & 3 deletions packages/lib/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ export type RequestHandler<P = unknown,
Req extends Request = Request,
Res extends Response = Response> = (
req: Req,
res: Res,
params: P) => Awaitable<Res['body'] | void>;

res: Response,
params: P) => Awaitable<Res | void>;

type SecuritySchemeObject = OpenAPIV3_1.SecuritySchemeObject | OpenAPIV3.SecuritySchemeObject;
/**
Expand Down
31 changes: 31 additions & 0 deletions packages/test/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,37 @@ paths:
schema:
type: object
additionalProperties: true
'/multiresponse':
get:
operationId: multiResponse
summary: test multiple possible responses
parameters:
- in: query
name: type
schema:
type: string

responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
okBody:
type: number
text/html:
schema:
type: string

'404':
description: Not found
content:
application/json:
schema:
type: object




2 changes: 1 addition & 1 deletion packages/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "test",
"scripts": {
"pretest": "npm -w ../lib run build && openapi-ts-backend generate-types api.yml src/gen",
"test": "LOG_LEVEL=${LOG_LEVEL:=error} jest"
"test": "tsc && LOG_LEVEL=${LOG_LEVEL:=error} jest"
},
"devDependencies": {
"@openapi-ts/cli": "*",
Expand Down
34 changes: 27 additions & 7 deletions packages/test/src/openapi.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {HttpError, OpenApi} from "@openapi-ts/backend";
import {HttpError, json, OpenApi} from "@openapi-ts/backend";
import { OperationHandlers} from './gen';

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

return {
message: greet(title, name),
};
return json({ message: greet(title, name)})
},
addPerson: req => {
return req.body.person;
return json(req.body.person, 201);
},
getTypes: req => {
return {
return json({
params: getTypeMap(req.params),
headers: getTypeMap(req.headers),
query: getTypeMap(req.query),
cookies: getTypeMap(req.cookies),
}
})
},
deletePerson: () => {
return;
},
multiResponse: (req) => {
if (req.query.type === '') {
return {
statusCode: 404,
body: {},
headers: {
'Content-Type': 'application/json',
'Additional-Header': 'somethingelse'
}
}
}
if (req.query.type === 'html') {
return {
statusCode: 200,
body: 'htmlContent',
headers: {
'Content-Type': 'text/html'
}
}
}
return json({ okBody: 1337 })
}
};

Expand Down
3 changes: 2 additions & 1 deletion packages/test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"noEmit": true,
"noEmit": true
},
"exclude": ["jest.config.ts"]
}