diff --git a/packages/cli/src/typegen/templates.ts b/packages/cli/src/typegen/templates.ts index 00dfdda..132e920 100644 --- a/packages/cli/src/typegen/templates.ts +++ b/packages/cli/src/typegen/templates.ts @@ -17,12 +17,25 @@ export type Property = export type ValueOf = T extends Record ? T[keyof T] : D; export type EmptyObject = Record; + +export type StatusCode = + 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 = operations[OperationId] extends {requestBody: Record} ? @@ -38,8 +51,23 @@ export type RequestQuery = export type RequestHeaders = Property, 'header', EmptyObject>; -export type ResponseBody = - ValueOf>, 'content'>, void>; +export type OperationResponse = { + [Status in keyof operations[OpName]['responses']]: + operations[OpName]['responses'][Status] extends { + content: infer Content; + } + ? { + [CT in keyof Content]: { + statusCode: StatusCode; + headers: { 'Content-Type': CT }; + body: Content[CT]; + } + }[keyof Content] + : { + statusCode: StatusCode; + headers: EmptyObject; + }; +}[keyof operations[OpName]['responses']] export type ResponseHeaders = Property>, 'headers'>; @@ -49,10 +77,6 @@ export type OperationRequest = Request< Params & RequestPathParams, Params & RequestQuery, Params & RequestHeaders>; - -export type OperationResponse = Response< - ResponseBody, - Params & ResponseHeaders>; `; export const handlers = ` diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index 3093384..eee26e6 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -1,3 +1,4 @@ export * from './types'; export * from './errors'; export * from './openapi'; +export * from './response'; diff --git a/packages/lib/src/openapi.ts b/packages/lib/src/openapi.ts index 945e287..714b22a 100644 --- a/packages/lib/src/openapi.ts +++ b/packages/lib/src/openapi.ts @@ -254,8 +254,10 @@ export class OpenApi { // 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); diff --git a/packages/lib/src/response.ts b/packages/lib/src/response.ts new file mode 100644 index 0000000..7b3aa70 --- /dev/null +++ b/packages/lib/src/response.ts @@ -0,0 +1,16 @@ +export function json(body: T): { statusCode: 200; body: T; headers: { "Content-Type": "application/json" } }; +export function json(body: T, status: CT): { statusCode: CT; body: T; headers: { "Content-Type": "application/json" } }; + +export function json( + 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", + }, + } +} diff --git a/packages/lib/src/types.ts b/packages/lib/src/types.ts index d8da799..a41d7a4 100644 --- a/packages/lib/src/types.ts +++ b/packages/lib/src/types.ts @@ -103,9 +103,8 @@ export type RequestHandler

= ( req: Req, - res: Res, - params: P) => Awaitable; - + res: Response, + params: P) => Awaitable; type SecuritySchemeObject = OpenAPIV3_1.SecuritySchemeObject | OpenAPIV3.SecuritySchemeObject; /** diff --git a/packages/test/api.yml b/packages/test/api.yml index 312dd91..7a88286 100644 --- a/packages/test/api.yml +++ b/packages/test/api.yml @@ -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 + diff --git a/packages/test/package.json b/packages/test/package.json index 65ae631..ed631a1 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -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": "*", diff --git a/packages/test/src/openapi.test.ts b/packages/test/src/openapi.test.ts index c822293..7e7bd9a 100644 --- a/packages/test/src/openapi.test.ts +++ b/packages/test/src/openapi.test.ts @@ -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 { @@ -13,23 +13,43 @@ const operations: OperationHandlers = { 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 }) } }; diff --git a/packages/test/tsconfig.json b/packages/test/tsconfig.json index 5cb1093..635c05a 100644 --- a/packages/test/tsconfig.json +++ b/packages/test/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", - "noEmit": true, + "noEmit": true }, + "exclude": ["jest.config.ts"] } \ No newline at end of file