WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit f75a78f

Browse files
authored
feat/custom-headers (#119)
1 parent efd94e5 commit f75a78f

File tree

3 files changed

+72
-16
lines changed

3 files changed

+72
-16
lines changed

src/generator/paths.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const getOpenApiPathsObject = (
1414

1515
forEachOpenApiProcedure(queries, ({ path: queryPath, procedure, openapi }) => {
1616
try {
17-
const { method, protect, summary, description, tags, tag } = openapi;
17+
const { method, protect, summary, description, tags, tag, headers } = openapi;
1818
if (method !== 'GET' && method !== 'DELETE') {
1919
throw new TRPCError({
2020
message: 'Query method must be GET or DELETE',
@@ -24,6 +24,7 @@ export const getOpenApiPathsObject = (
2424

2525
const path = normalizePath(openapi.path);
2626
const pathParameters = getPathParameters(path);
27+
const headerParameters = headers?.map((header) => ({ ...header, in: 'header' })) || [];
2728
const httpMethod = OpenAPIV3.HttpMethods[method];
2829
if (pathsObject[path]?.[httpMethod]) {
2930
throw new TRPCError({
@@ -42,7 +43,10 @@ export const getOpenApiPathsObject = (
4243
description,
4344
tags: tags ?? (tag ? [tag] : undefined),
4445
security: protect ? [{ Authorization: [] }] : undefined,
45-
parameters: getParameterObjects(inputParser, pathParameters, 'all'),
46+
parameters: [
47+
...headerParameters,
48+
...(getParameterObjects(inputParser, pathParameters, 'all') || []),
49+
],
4650
responses: getResponsesObject(outputParser),
4751
},
4852
};
@@ -55,7 +59,7 @@ export const getOpenApiPathsObject = (
5559

5660
forEachOpenApiProcedure(mutations, ({ path: mutationPath, procedure, openapi }) => {
5761
try {
58-
const { method, protect, summary, description, tags, tag } = openapi;
62+
const { method, protect, summary, description, tags, tag, headers } = openapi;
5963
if (method !== 'POST' && method !== 'PATCH' && method !== 'PUT') {
6064
throw new TRPCError({
6165
message: 'Mutation method must be POST, PATCH or PUT',
@@ -65,6 +69,7 @@ export const getOpenApiPathsObject = (
6569

6670
const path = normalizePath(openapi.path);
6771
const pathParameters = getPathParameters(path);
72+
const headerParameters = headers?.map((header) => ({ ...header, in: 'header' })) || [];
6873
const httpMethod = OpenAPIV3.HttpMethods[method];
6974
if (pathsObject[path]?.[httpMethod]) {
7075
throw new TRPCError({
@@ -84,7 +89,10 @@ export const getOpenApiPathsObject = (
8489
tags: tags ?? (tag ? [tag] : undefined),
8590
security: protect ? [{ Authorization: [] }] : undefined,
8691
requestBody: getRequestBodyObject(inputParser, pathParameters),
87-
parameters: getParameterObjects(inputParser, pathParameters, 'path'),
92+
parameters: [
93+
...headerParameters,
94+
...(getParameterObjects(inputParser, pathParameters, 'path') || []),
95+
],
8896
responses: getResponsesObject(outputParser),
8997
},
9098
};

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ProcedureRecord } from '@trpc/server';
33
import { DefaultErrorShape, Router } from '@trpc/server/dist/declarations/src/router';
44
// eslint-disable-next-line import/no-unresolved
55
import { TRPC_ERROR_CODE_KEY } from '@trpc/server/dist/declarations/src/rpc';
6+
import { OpenAPIV3 } from 'openapi-types';
67
import { ZodIssue, z } from 'zod';
78

89
export type OpenApiMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
@@ -15,6 +16,7 @@ export type OpenApiMeta<TMeta = Record<string, any>> = TMeta & {
1516
summary?: string;
1617
description?: string;
1718
protect?: boolean;
19+
headers?: (OpenAPIV3.ParameterBaseObject & { name: string; in?: 'header' })[];
1820
} & (
1921
| {
2022
tags?: never;

test/generator.test.ts

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ describe('generator', () => {
548548
"get": Object {
549549
"description": undefined,
550550
"operationId": "readUsers",
551-
"parameters": undefined,
551+
"parameters": Array [],
552552
"responses": Object {
553553
"200": Object {
554554
"content": Object {
@@ -888,7 +888,7 @@ describe('generator', () => {
888888
expect(Object.keys(openApiDocument.paths).length).toBe(0);
889889
});
890890

891-
test('with summary, description & single tag', () => {
891+
test('with summary, description & multiple tags', () => {
892892
const appRouter = trpc.router<any, OpenApiMeta>().query('all.metadata', {
893893
meta: {
894894
openapi: {
@@ -897,7 +897,7 @@ describe('generator', () => {
897897
method: 'GET',
898898
summary: 'Short summary',
899899
description: 'Verbose description',
900-
tag: 'tag',
900+
tags: ['tagA', 'tagB'],
901901
},
902902
},
903903
input: z.object({ name: z.string() }),
@@ -914,19 +914,18 @@ describe('generator', () => {
914914
expect(openApiSchemaValidator.validate(openApiDocument).errors).toEqual([]);
915915
expect(openApiDocument.paths['/metadata/all']!.get!.summary).toBe('Short summary');
916916
expect(openApiDocument.paths['/metadata/all']!.get!.description).toBe('Verbose description');
917-
expect(openApiDocument.paths['/metadata/all']!.get!.tags).toEqual(['tag']);
917+
expect(openApiDocument.paths['/metadata/all']!.get!.tags).toEqual(['tagA', 'tagB']);
918918
});
919919

920-
test('with summary, description & multiple tags', () => {
920+
// @deprecated
921+
test('with single tag', () => {
921922
const appRouter = trpc.router<any, OpenApiMeta>().query('all.metadata', {
922923
meta: {
923924
openapi: {
924925
enabled: true,
925926
path: '/metadata/all',
926927
method: 'GET',
927-
summary: 'Short summary',
928-
description: 'Verbose description',
929-
tags: ['tagA', 'tagB'],
928+
tag: 'tag',
930929
},
931930
},
932931
input: z.object({ name: z.string() }),
@@ -941,9 +940,7 @@ describe('generator', () => {
941940
});
942941

943942
expect(openApiSchemaValidator.validate(openApiDocument).errors).toEqual([]);
944-
expect(openApiDocument.paths['/metadata/all']!.get!.summary).toBe('Short summary');
945-
expect(openApiDocument.paths['/metadata/all']!.get!.description).toBe('Verbose description');
946-
expect(openApiDocument.paths['/metadata/all']!.get!.tags).toEqual(['tagA', 'tagB']);
943+
expect(openApiDocument.paths['/metadata/all']!.get!.tags).toEqual(['tag']);
947944
});
948945

949946
test('with security', () => {
@@ -1185,7 +1182,7 @@ describe('generator', () => {
11851182
});
11861183

11871184
expect(openApiSchemaValidator.validate(openApiDocument).errors).toEqual([]);
1188-
expect(openApiDocument.paths['/void']!.get!.parameters).toMatchInlineSnapshot(`undefined`);
1185+
expect(openApiDocument.paths['/void']!.get!.parameters).toEqual([]);
11891186
expect(openApiDocument.paths['/void']!.get!.responses[200]).toMatchInlineSnapshot(`
11901187
Object {
11911188
"content": Object {
@@ -2125,4 +2122,53 @@ describe('generator', () => {
21252122
}
21262123
`);
21272124
});
2125+
2126+
test('with custom header', () => {
2127+
const appRouter = trpc.router<any, OpenApiMeta>().query('echo', {
2128+
meta: {
2129+
openapi: {
2130+
enabled: true,
2131+
path: '/echo',
2132+
method: 'GET',
2133+
headers: [
2134+
{
2135+
name: 'x-custom-header',
2136+
required: true,
2137+
description: 'Some custom header',
2138+
},
2139+
],
2140+
},
2141+
},
2142+
input: z.object({ id: z.string() }),
2143+
output: z.object({ id: z.string() }),
2144+
resolve: ({ input }) => ({ id: input.id }),
2145+
});
2146+
2147+
const openApiDocument = generateOpenApiDocument(appRouter, {
2148+
title: 'tRPC OpenAPI',
2149+
version: '1.0.0',
2150+
baseUrl: 'http://localhost:3000/api',
2151+
});
2152+
2153+
expect(openApiSchemaValidator.validate(openApiDocument).errors).toEqual([]);
2154+
expect(openApiDocument.paths['/echo']!.get!.parameters).toMatchInlineSnapshot(`
2155+
Array [
2156+
Object {
2157+
"description": "Some custom header",
2158+
"in": "header",
2159+
"name": "x-custom-header",
2160+
"required": true,
2161+
},
2162+
Object {
2163+
"description": undefined,
2164+
"in": "query",
2165+
"name": "id",
2166+
"required": true,
2167+
"schema": Object {
2168+
"type": "string",
2169+
},
2170+
},
2171+
]
2172+
`);
2173+
});
21282174
});

0 commit comments

Comments
 (0)