From 9a8ec7dc8df870b424752ac80c5f641ccdca9633 Mon Sep 17 00:00:00 2001 From: AarthiT Date: Fri, 23 Jun 2023 17:33:31 +0530 Subject: [PATCH] Feat: Add response headers to OpenApiMeta --- src/generator/paths.ts | 2 +- src/generator/schema.ts | 2 + src/types.ts | 1 + test/generator.test.ts | 122 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) diff --git a/src/generator/paths.ts b/src/generator/paths.ts index 3560fa0e..2d705608 100644 --- a/src/generator/paths.ts +++ b/src/generator/paths.ts @@ -94,7 +94,7 @@ export const getOpenApiPathsObject = ( ) || []), ], }), - responses: getResponsesObject(outputParser, openapi.example?.response), + responses: getResponsesObject(outputParser, openapi.example?.response, openapi.responseHeaders), ...(openapi.deprecated ? { deprecated: openapi.deprecated } : {}), }, }; diff --git a/src/generator/schema.ts b/src/generator/schema.ts index 6defa6eb..d3f41e38 100644 --- a/src/generator/schema.ts +++ b/src/generator/schema.ts @@ -189,6 +189,7 @@ export const errorResponseObject: OpenAPIV3.ResponseObject = { export const getResponsesObject = ( schema: unknown, example: Record | undefined, + headers: Record | undefined ): OpenAPIV3.ResponsesObject => { if (!instanceofZodType(schema)) { throw new TRPCError({ @@ -199,6 +200,7 @@ export const getResponsesObject = ( const successResponseObject: OpenAPIV3.ResponseObject = { description: 'Successful response', + headers: headers, content: { 'application/json': { schema: zodSchemaToOpenApiSchemaObject(schema), diff --git a/src/types.ts b/src/types.ts index ac11b682..71cec3ee 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,6 +31,7 @@ export type OpenApiMeta = TMeta & { request?: Record; response?: Record; }; + responseHeaders?: Record; }; }; diff --git a/test/generator.test.ts b/test/generator.test.ts index 1e846ce6..c2a13c77 100644 --- a/test/generator.test.ts +++ b/test/generator.test.ts @@ -559,6 +559,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -616,6 +617,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -652,6 +654,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -701,6 +704,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -766,6 +770,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -945,6 +950,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -1001,6 +1007,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -1036,6 +1043,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); } @@ -1061,6 +1069,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); } @@ -1092,6 +1101,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -1122,6 +1132,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -1157,6 +1168,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -1186,6 +1198,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -1249,6 +1262,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); expect(openApiDocument.paths['/optional-object']!.get!.parameters).toMatchInlineSnapshot(` @@ -1293,6 +1307,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -1357,6 +1372,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); expect(openApiDocument.paths['/optional-object']!.post!.requestBody).toMatchInlineSnapshot(` @@ -1402,6 +1418,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -1445,6 +1462,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -1681,6 +1699,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -1722,6 +1741,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -2075,6 +2095,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); }); @@ -2316,6 +2337,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -2363,6 +2385,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -2410,6 +2433,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, }, "default": Object { "$ref": "#/components/responses/error", @@ -2704,6 +2728,7 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, } `); expect(openApiDocument.paths['/mutation-example/{name}']!.post!.parameters) @@ -2769,6 +2794,103 @@ describe('generator', () => { }, }, "description": "Successful response", + "headers": undefined, + } + `); + }); + + test('with response headers', () => { + const appRouter = t.router({ + queryExample: t.procedure + .meta({ + openapi: { + method: 'GET', + path: '/query-example/{name}', + responseHeaders: { + "X-RateLimit-Limit": { + description: "Request limit per hour.", + schema: { + type: "integer" + } + }, + "X-RateLimit-Remaining": { + description: "The number of requests left for the time window.", + schema: { + type: "integer" + } + } + } + }, + }) + .input(z.object({ name: z.string(), greeting: z.string() })) + .output(z.object({ output: z.string() })) + .query(({ input }) => ({ + output: `${input.greeting} ${input.name}`, + })) + }); + + const openApiDocument = generateOpenApiDocument(appRouter, defaultDocOpts); + + expect(openApiSchemaValidator.validate(openApiDocument).errors).toEqual([]); + expect(openApiDocument.paths['/query-example/{name}']!.get!.parameters).toMatchInlineSnapshot(` + Array [ + Object { + "description": undefined, + "example": undefined, + "in": "path", + "name": "name", + "required": true, + "schema": Object { + "type": "string", + }, + }, + Object { + "description": undefined, + "example": undefined, + "in": "query", + "name": "greeting", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ] + `); + expect(openApiDocument.paths['/query-example/{name}']!.get!.responses[200]) + .toMatchInlineSnapshot(` + Object { + "content": Object { + "application/json": Object { + "example": undefined, + "schema": Object { + "additionalProperties": false, + "properties": Object { + "output": Object { + "type": "string", + }, + }, + "required": Array [ + "output", + ], + "type": "object", + }, + }, + }, + "description": "Successful response", + "headers": Object { + "X-RateLimit-Limit": Object { + "description": "Request limit per hour.", + "schema": Object { + "type": "integer", + }, + }, + "X-RateLimit-Remaining": Object { + "description": "The number of requests left for the time window.", + "schema": Object { + "type": "integer", + }, + }, + }, } `); });