Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit d38c207

Browse files
authored
feat/allowing multiple tags for operation meta (#84)
* feat: support operation meta using an array of tags for grouping operation meta taking an array of tags to allow multiple groupings meta - tag which took a string has been been renamed to tags which takes an array of strings * docs: update docs to reflect the changes to the operation meta tag string to tags string[] * chore: update examples to use the new tags prop in operations meta update examples to use the new tags prop in operations meta * feat: support both tag and tags for the operation meta reintroduce the tag meta prop, giving priority to the tags prop * docs: restore tag type description in the operation meta * refactor: simplifying nested ternaries for operation meta tags resolution simplifying nested ternaries for operation meta tags resolution
1 parent 9bf8745 commit d38c207

File tree

6 files changed

+59
-28
lines changed

6 files changed

+59
-28
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ export const appRouter = trpc.router<Context, OpenApiMeta>().query('sayHello', {
140140
});
141141

142142
// Client
143-
const res = await fetch('http://localhost:3000/say-hello/James?greeting=Hello' /* 👈 */, { method: 'GET' });
143+
const res = await fetch('http://localhost:3000/say-hello/James?greeting=Hello' /* 👈 */, {
144+
method: 'GET',
145+
});
144146
const body = await res.json(); /* { ok: true, data: { greeting: 'Hello James!' } } */
145147
```
146148

@@ -243,7 +245,7 @@ export const appRouter = trpc.router<Context, OpenApiMeta>().query('sayHello', {
243245
```typescript
244246
const res = await fetch('http://localhost:3000/say-hello', {
245247
method: 'GET',
246-
headers: { 'Authorization': 'Bearer usr_123' }, /* 👈 */
248+
headers: { Authorization: 'Bearer usr_123' } /* 👈 */,
247249
});
248250
const body = await res.json(); /* { ok: true, data: { greeting: 'Hello James!' } } */
249251
```
@@ -311,7 +313,8 @@ Please see [full typings here](src/types.ts).
311313
| `protect` | `boolean` | Requires this endpoint to use an `Authorization` header credential with `Bearer` scheme on OpenAPI document. | `false` | `false` |
312314
| `summary` | `string` | A short summary of the endpoint included in the OpenAPI document. | `false` | `undefined` |
313315
| `description` | `string` | A verbose description of the endpoint included in the OpenAPI document. | `false` | `undefined` |
314-
| `tag` | `string` | A tag used for logical grouping of endpoints in the OpenAPI document. | `false` | `undefined` |
316+
| `tag` | `string` | A single tag used for logical grouping of endpoints in the OpenAPI document. | `false` | `undefined` |
317+
| `tags` | `string[]` | A list of tags used for logical grouping of endpoints in the OpenAPI document. | `false` | `undefined` |
315318

316319
#### CreateOpenApiNodeHttpHandlerOptions
317320

examples/with-express/src/router.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const authRouter = createRouter()
6363
enabled: true,
6464
method: 'POST',
6565
path: '/auth/register',
66-
tag: 'auth',
66+
tags: ['auth'],
6767
summary: 'Register as a new user',
6868
},
6969
},
@@ -107,7 +107,7 @@ const authRouter = createRouter()
107107
enabled: true,
108108
method: 'POST',
109109
path: '/auth/login',
110-
tag: 'auth',
110+
tags: ['auth'],
111111
summary: 'Login as an existing user',
112112
},
113113
},
@@ -147,7 +147,7 @@ const usersRouter = createRouter()
147147
enabled: true,
148148
method: 'GET',
149149
path: '/users',
150-
tag: 'users',
150+
tags: ['users'],
151151
summary: 'Read all users',
152152
},
153153
},
@@ -177,7 +177,7 @@ const usersRouter = createRouter()
177177
enabled: true,
178178
method: 'GET',
179179
path: '/users/{id}',
180-
tag: 'users',
180+
tags: ['users'],
181181
summary: 'Read a user by id',
182182
},
183183
},
@@ -212,7 +212,7 @@ const postsRouter = createRouter()
212212
enabled: true,
213213
method: 'GET',
214214
path: '/posts',
215-
tag: 'posts',
215+
tags: ['posts'],
216216
summary: 'Read all posts',
217217
},
218218
},
@@ -246,7 +246,7 @@ const postsRouter = createRouter()
246246
enabled: true,
247247
method: 'GET',
248248
path: '/posts/{id}',
249-
tag: 'posts',
249+
tags: ['posts'],
250250
summary: 'Read a post by id',
251251
},
252252
},
@@ -281,7 +281,7 @@ const postsProtectedRouter = createProtectedRouter()
281281
enabled: true,
282282
method: 'POST',
283283
path: '/posts',
284-
tag: 'posts',
284+
tags: ['posts'],
285285
protect: true,
286286
summary: 'Create a new post',
287287
},
@@ -314,7 +314,7 @@ const postsProtectedRouter = createProtectedRouter()
314314
enabled: true,
315315
method: 'PUT',
316316
path: '/posts/{id}',
317-
tag: 'posts',
317+
tags: ['posts'],
318318
protect: true,
319319
summary: 'Update an existing post',
320320
},
@@ -357,7 +357,7 @@ const postsProtectedRouter = createProtectedRouter()
357357
enabled: true,
358358
method: 'DELETE',
359359
path: '/posts/{id}',
360-
tag: 'posts',
360+
tags: ['posts'],
361361
protect: true,
362362
summary: 'Delete a post',
363363
},

examples/with-nextjs/src/server/router.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const authRouter = createRouter()
6060
enabled: true,
6161
method: 'POST',
6262
path: '/auth/register',
63-
tag: 'auth',
63+
tags: ['auth'],
6464
summary: 'Register as a new user',
6565
},
6666
},
@@ -104,7 +104,7 @@ const authRouter = createRouter()
104104
enabled: true,
105105
method: 'POST',
106106
path: '/auth/login',
107-
tag: 'auth',
107+
tags: ['auth'],
108108
summary: 'Login as an existing user',
109109
},
110110
},
@@ -144,7 +144,7 @@ const usersRouter = createRouter()
144144
enabled: true,
145145
method: 'GET',
146146
path: '/users',
147-
tag: 'users',
147+
tags: ['users'],
148148
summary: 'Read all users',
149149
},
150150
},
@@ -174,7 +174,7 @@ const usersRouter = createRouter()
174174
enabled: true,
175175
method: 'GET',
176176
path: '/users/{id}',
177-
tag: 'users',
177+
tags: ['users'],
178178
summary: 'Read a user by id',
179179
},
180180
},
@@ -209,7 +209,7 @@ const postsRouter = createRouter()
209209
enabled: true,
210210
method: 'GET',
211211
path: '/posts',
212-
tag: 'posts',
212+
tags: ['posts'],
213213
summary: 'Read all posts',
214214
},
215215
},
@@ -243,7 +243,7 @@ const postsRouter = createRouter()
243243
enabled: true,
244244
method: 'GET',
245245
path: '/posts/{id}',
246-
tag: 'posts',
246+
tags: ['posts'],
247247
summary: 'Read a post by id',
248248
},
249249
},
@@ -278,7 +278,7 @@ const postsProtectedRouter = createProtectedRouter()
278278
enabled: true,
279279
method: 'POST',
280280
path: '/posts',
281-
tag: 'posts',
281+
tags: ['posts'],
282282
protect: true,
283283
summary: 'Create a new post',
284284
},
@@ -311,7 +311,7 @@ const postsProtectedRouter = createProtectedRouter()
311311
enabled: true,
312312
method: 'PUT',
313313
path: '/posts/{id}',
314-
tag: 'posts',
314+
tags: ['posts'],
315315
protect: true,
316316
summary: 'Update an existing post',
317317
},
@@ -354,7 +354,7 @@ const postsProtectedRouter = createProtectedRouter()
354354
enabled: true,
355355
method: 'DELETE',
356356
path: '/posts/{id}',
357-
tag: 'posts',
357+
tags: ['posts'],
358358
protect: true,
359359
summary: 'Delete a post',
360360
},

src/generator/paths.ts

Lines changed: 4 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, tag } = openapi;
17+
const { method, protect, summary, description, tags, tag } = openapi;
1818
if (method !== 'GET' && method !== 'DELETE') {
1919
throw new TRPCError({
2020
message: 'Query method must be GET or DELETE',
@@ -40,7 +40,7 @@ export const getOpenApiPathsObject = (
4040
operationId: queryPath,
4141
summary,
4242
description,
43-
tags: tag ? [tag] : undefined,
43+
tags: tags ?? (tag ? [tag] : undefined),
4444
security: protect ? [{ Authorization: [] }] : undefined,
4545
parameters: getParameterObjects(inputParser, pathParameters, 'all'),
4646
responses: getResponsesObject(outputParser),
@@ -55,7 +55,7 @@ export const getOpenApiPathsObject = (
5555

5656
forEachOpenApiProcedure(mutations, ({ path: mutationPath, procedure, openapi }) => {
5757
try {
58-
const { method, protect, summary, description, tag } = openapi;
58+
const { method, protect, summary, description, tags, tag } = openapi;
5959
if (method !== 'POST' && method !== 'PATCH' && method !== 'PUT') {
6060
throw new TRPCError({
6161
message: 'Mutation method must be POST, PATCH or PUT',
@@ -81,7 +81,7 @@ export const getOpenApiPathsObject = (
8181
operationId: mutationPath,
8282
summary,
8383
description,
84-
tags: tag ? [tag] : undefined,
84+
tags: tags ?? (tag ? [tag] : undefined),
8585
security: protect ? [{ Authorization: [] }] : undefined,
8686
requestBody: getRequestBodyObject(inputParser, pathParameters),
8787
parameters: getParameterObjects(inputParser, pathParameters, 'path'),

src/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ export type OpenApiMeta<TMeta = Record<string, any>> = TMeta & {
1515
summary?: string;
1616
description?: string;
1717
protect?: boolean;
18-
tag?: string;
19-
};
18+
} & ({ tag?: never; tags?: string[] } | { tag?: string; tags?: never });
2019
};
2120

2221
export type OpenApiProcedureRecord<TMeta = Record<string, any>> = ProcedureRecord<

test/generator.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ describe('generator', () => {
888888
expect(Object.keys(openApiDocument.paths).length).toBe(0);
889889
});
890890

891-
test('with summary, description & tag', () => {
891+
test('with summary, description & single tag', () => {
892892
const appRouter = trpc.router<any, OpenApiMeta>().query('all.metadata', {
893893
meta: {
894894
openapi: {
@@ -917,6 +917,35 @@ describe('generator', () => {
917917
expect(openApiDocument.paths['/metadata/all']!.get!.tags).toEqual(['tag']);
918918
});
919919

920+
test('with summary, description & multiple tags', () => {
921+
const appRouter = trpc.router<any, OpenApiMeta>().query('all.metadata', {
922+
meta: {
923+
openapi: {
924+
enabled: true,
925+
path: '/metadata/all',
926+
method: 'GET',
927+
summary: 'Short summary',
928+
description: 'Verbose description',
929+
tags: ['tagA', 'tagB'],
930+
},
931+
},
932+
input: z.object({ name: z.string() }),
933+
output: z.object({ name: z.string() }),
934+
resolve: ({ input }) => ({ name: input.name }),
935+
});
936+
937+
const openApiDocument = generateOpenApiDocument(appRouter, {
938+
title: 'tRPC OpenAPI',
939+
version: '1.0.0',
940+
baseUrl: 'http://localhost:3000/api',
941+
});
942+
943+
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']);
947+
});
948+
920949
test('with security', () => {
921950
const appRouter = trpc.router<any, OpenApiMeta>().mutation('protectedEndpoint', {
922951
meta: {

0 commit comments

Comments
 (0)