Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.
Merged
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
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ export const appRouter = trpc.router<Context, OpenApiMeta>().query('sayHello', {
});

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

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

#### CreateOpenApiNodeHttpHandlerOptions

Expand Down
18 changes: 9 additions & 9 deletions examples/with-express/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const authRouter = createRouter()
enabled: true,
method: 'POST',
path: '/auth/register',
tag: 'auth',
tags: ['auth'],
summary: 'Register as a new user',
},
},
Expand Down Expand Up @@ -107,7 +107,7 @@ const authRouter = createRouter()
enabled: true,
method: 'POST',
path: '/auth/login',
tag: 'auth',
tags: ['auth'],
summary: 'Login as an existing user',
},
},
Expand Down Expand Up @@ -147,7 +147,7 @@ const usersRouter = createRouter()
enabled: true,
method: 'GET',
path: '/users',
tag: 'users',
tags: ['users'],
summary: 'Read all users',
},
},
Expand Down Expand Up @@ -177,7 +177,7 @@ const usersRouter = createRouter()
enabled: true,
method: 'GET',
path: '/users/{id}',
tag: 'users',
tags: ['users'],
summary: 'Read a user by id',
},
},
Expand Down Expand Up @@ -212,7 +212,7 @@ const postsRouter = createRouter()
enabled: true,
method: 'GET',
path: '/posts',
tag: 'posts',
tags: ['posts'],
summary: 'Read all posts',
},
},
Expand Down Expand Up @@ -246,7 +246,7 @@ const postsRouter = createRouter()
enabled: true,
method: 'GET',
path: '/posts/{id}',
tag: 'posts',
tags: ['posts'],
summary: 'Read a post by id',
},
},
Expand Down Expand Up @@ -281,7 +281,7 @@ const postsProtectedRouter = createProtectedRouter()
enabled: true,
method: 'POST',
path: '/posts',
tag: 'posts',
tags: ['posts'],
protect: true,
summary: 'Create a new post',
},
Expand Down Expand Up @@ -314,7 +314,7 @@ const postsProtectedRouter = createProtectedRouter()
enabled: true,
method: 'PUT',
path: '/posts/{id}',
tag: 'posts',
tags: ['posts'],
protect: true,
summary: 'Update an existing post',
},
Expand Down Expand Up @@ -357,7 +357,7 @@ const postsProtectedRouter = createProtectedRouter()
enabled: true,
method: 'DELETE',
path: '/posts/{id}',
tag: 'posts',
tags: ['posts'],
protect: true,
summary: 'Delete a post',
},
Expand Down
18 changes: 9 additions & 9 deletions examples/with-nextjs/src/server/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const authRouter = createRouter()
enabled: true,
method: 'POST',
path: '/auth/register',
tag: 'auth',
tags: ['auth'],
summary: 'Register as a new user',
},
},
Expand Down Expand Up @@ -104,7 +104,7 @@ const authRouter = createRouter()
enabled: true,
method: 'POST',
path: '/auth/login',
tag: 'auth',
tags: ['auth'],
summary: 'Login as an existing user',
},
},
Expand Down Expand Up @@ -144,7 +144,7 @@ const usersRouter = createRouter()
enabled: true,
method: 'GET',
path: '/users',
tag: 'users',
tags: ['users'],
summary: 'Read all users',
},
},
Expand Down Expand Up @@ -174,7 +174,7 @@ const usersRouter = createRouter()
enabled: true,
method: 'GET',
path: '/users/{id}',
tag: 'users',
tags: ['users'],
summary: 'Read a user by id',
},
},
Expand Down Expand Up @@ -209,7 +209,7 @@ const postsRouter = createRouter()
enabled: true,
method: 'GET',
path: '/posts',
tag: 'posts',
tags: ['posts'],
summary: 'Read all posts',
},
},
Expand Down Expand Up @@ -243,7 +243,7 @@ const postsRouter = createRouter()
enabled: true,
method: 'GET',
path: '/posts/{id}',
tag: 'posts',
tags: ['posts'],
summary: 'Read a post by id',
},
},
Expand Down Expand Up @@ -278,7 +278,7 @@ const postsProtectedRouter = createProtectedRouter()
enabled: true,
method: 'POST',
path: '/posts',
tag: 'posts',
tags: ['posts'],
protect: true,
summary: 'Create a new post',
},
Expand Down Expand Up @@ -311,7 +311,7 @@ const postsProtectedRouter = createProtectedRouter()
enabled: true,
method: 'PUT',
path: '/posts/{id}',
tag: 'posts',
tags: ['posts'],
protect: true,
summary: 'Update an existing post',
},
Expand Down Expand Up @@ -354,7 +354,7 @@ const postsProtectedRouter = createProtectedRouter()
enabled: true,
method: 'DELETE',
path: '/posts/{id}',
tag: 'posts',
tags: ['posts'],
protect: true,
summary: 'Delete a post',
},
Expand Down
8 changes: 4 additions & 4 deletions src/generator/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const getOpenApiPathsObject = (

forEachOpenApiProcedure(queries, ({ path: queryPath, procedure, openapi }) => {
try {
const { method, protect, summary, description, tag } = openapi;
const { method, protect, summary, description, tags, tag } = openapi;
if (method !== 'GET' && method !== 'DELETE') {
throw new TRPCError({
message: 'Query method must be GET or DELETE',
Expand All @@ -40,7 +40,7 @@ export const getOpenApiPathsObject = (
operationId: queryPath,
summary,
description,
tags: tag ? [tag] : undefined,
tags: tags ?? (tag ? [tag] : undefined),
security: protect ? [{ Authorization: [] }] : undefined,
parameters: getParameterObjects(inputParser, pathParameters, 'all'),
responses: getResponsesObject(outputParser),
Expand All @@ -55,7 +55,7 @@ export const getOpenApiPathsObject = (

forEachOpenApiProcedure(mutations, ({ path: mutationPath, procedure, openapi }) => {
try {
const { method, protect, summary, description, tag } = openapi;
const { method, protect, summary, description, tags, tag } = openapi;
if (method !== 'POST' && method !== 'PATCH' && method !== 'PUT') {
throw new TRPCError({
message: 'Mutation method must be POST, PATCH or PUT',
Expand All @@ -81,7 +81,7 @@ export const getOpenApiPathsObject = (
operationId: mutationPath,
summary,
description,
tags: tag ? [tag] : undefined,
tags: tags ?? (tag ? [tag] : undefined),
security: protect ? [{ Authorization: [] }] : undefined,
requestBody: getRequestBodyObject(inputParser, pathParameters),
parameters: getParameterObjects(inputParser, pathParameters, 'path'),
Expand Down
3 changes: 1 addition & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ export type OpenApiMeta<TMeta = Record<string, any>> = TMeta & {
summary?: string;
description?: string;
protect?: boolean;
tag?: string;
};
} & ({ tag?: never; tags?: string[] } | { tag?: string; tags?: never });
};

export type OpenApiProcedureRecord<TMeta = Record<string, any>> = ProcedureRecord<
Expand Down
31 changes: 30 additions & 1 deletion test/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ describe('generator', () => {
expect(Object.keys(openApiDocument.paths).length).toBe(0);
});

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

test('with summary, description & multiple tags', () => {
const appRouter = trpc.router<any, OpenApiMeta>().query('all.metadata', {
meta: {
openapi: {
enabled: true,
path: '/metadata/all',
method: 'GET',
summary: 'Short summary',
description: 'Verbose description',
tags: ['tagA', 'tagB'],
},
},
input: z.object({ name: z.string() }),
output: z.object({ name: z.string() }),
resolve: ({ input }) => ({ name: input.name }),
});

const openApiDocument = generateOpenApiDocument(appRouter, {
title: 'tRPC OpenAPI',
version: '1.0.0',
baseUrl: 'http://localhost:3000/api',
});

expect(openApiSchemaValidator.validate(openApiDocument).errors).toEqual([]);
expect(openApiDocument.paths['/metadata/all']!.get!.summary).toBe('Short summary');
expect(openApiDocument.paths['/metadata/all']!.get!.description).toBe('Verbose description');
expect(openApiDocument.paths['/metadata/all']!.get!.tags).toEqual(['tagA', 'tagB']);
});

test('with security', () => {
const appRouter = trpc.router<any, OpenApiMeta>().mutation('protectedEndpoint', {
meta: {
Expand Down