Skip to content

Commit cb2a794

Browse files
authored
feat: better OpenAPI generation and include zod pathParams (#169)
1 parent b3f944b commit cb2a794

6 files changed

Lines changed: 228 additions & 119 deletions

File tree

.changeset/hot-ways-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ts-rest/open-api': minor
3+
---
4+
5+
Improved OpenAPI generation

libs/ts-rest/open-api/package.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@
22
"name": "@ts-rest/open-api",
33
"version": "3.16.2",
44
"dependencies": {
5-
"openapi3-ts": "^2.0.2",
6-
"zod-to-json-schema": "^3.17.1"
5+
"@anatine/zod-openapi": "^1.12.0",
6+
"openapi3-ts": "^2.0.2"
77
},
88
"peerDependencies": {
99
"zod": "^3.0.0"
1010
},
11-
"peerDependenciesMeta": {
12-
"zod": {
13-
"optional": true
14-
}
15-
},
1611
"typedoc": {
1712
"entryPoint": "./src/index.ts",
1813
"tsconfig": "./tsconfig.lib.json"

libs/ts-rest/open-api/src/lib/ts-rest-open-api.spec.ts

Lines changed: 121 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ const postsRouter = c.router({
2626
method: 'GET',
2727
path: `/posts`,
2828
query: z.object({
29-
search: z.string().optional(),
29+
search: z.string().nullish(),
3030
sortBy: z.enum(['title', 'date']).default('date').optional(),
3131
sort: z.enum(['asc', 'desc']).default('asc').optional(),
32+
obj: z.object({
33+
a: z.string(),
34+
}),
3235
}),
3336
responses: {
3437
200: c.response<Post[]>(),
@@ -52,6 +55,7 @@ const postsRouter = c.router({
5255
path: '/posts/:id/comments',
5356
responses: {
5457
200: z.object({
58+
booleanString: z.boolean().transform((v) => v.toString()),
5559
comments: z.union([
5660
z.array(commentSchema),
5761
z.array(commentSchema.extend({ author: z.string() })),
@@ -60,6 +64,16 @@ const postsRouter = c.router({
6064
},
6165
},
6266
}),
67+
getPostComment: {
68+
method: 'GET',
69+
path: `/posts/:id/comments/:commentId`,
70+
pathParams: z.object({
71+
commentId: z.string().length(5),
72+
}),
73+
responses: {
74+
200: c.response<Post | null>(),
75+
},
76+
},
6377
});
6478

6579
const router = c.router({
@@ -102,25 +116,43 @@ const expectedApiDoc = {
102116
description: undefined,
103117
parameters: [
104118
{
105-
name: 'query',
119+
name: 'search',
120+
in: 'query',
121+
schema: {
122+
nullable: true,
123+
type: 'string',
124+
},
125+
},
126+
{
127+
name: 'sortBy',
128+
in: 'query',
129+
schema: {
130+
type: 'string',
131+
default: 'date',
132+
enum: ['title', 'date'],
133+
},
134+
},
135+
{
136+
name: 'sort',
137+
in: 'query',
138+
schema: {
139+
type: 'string',
140+
default: 'asc',
141+
enum: ['asc', 'desc'],
142+
},
143+
},
144+
{
106145
in: 'query',
146+
name: 'obj',
147+
required: true,
148+
style: 'deepObject',
107149
schema: {
108-
additionalProperties: false,
109150
properties: {
110-
search: {
111-
type: 'string',
112-
},
113-
sortBy: {
114-
type: 'string',
115-
default: 'date',
116-
enum: ['title', 'date'],
117-
},
118-
sort: {
151+
a: {
119152
type: 'string',
120-
default: 'asc',
121-
enum: ['asc', 'desc'],
122153
},
123154
},
155+
required: ['a'],
124156
type: 'object',
125157
},
126158
},
@@ -141,7 +173,6 @@ const expectedApiDoc = {
141173
content: {
142174
'application/json': {
143175
schema: {
144-
additionalProperties: false,
145176
properties: {
146177
published: {
147178
type: 'boolean',
@@ -175,6 +206,9 @@ const expectedApiDoc = {
175206
in: 'path',
176207
name: 'id',
177208
required: true,
209+
schema: {
210+
type: 'string',
211+
},
178212
},
179213
],
180214
responses: {
@@ -195,20 +229,21 @@ const expectedApiDoc = {
195229
in: 'path',
196230
name: 'id',
197231
required: true,
232+
schema: {
233+
type: 'string',
234+
},
198235
},
199236
],
200237
responses: {
201238
'200': {
202239
content: {
203240
'application/json': {
204241
schema: {
205-
additionalProperties: false,
206242
properties: {
207243
comments: {
208-
anyOf: [
244+
oneOf: [
209245
{
210246
items: {
211-
additionalProperties: false,
212247
properties: {
213248
id: {
214249
type: 'number',
@@ -224,7 +259,6 @@ const expectedApiDoc = {
224259
},
225260
{
226261
items: {
227-
additionalProperties: false,
228262
properties: {
229263
author: {
230264
type: 'string',
@@ -243,8 +277,11 @@ const expectedApiDoc = {
243277
},
244278
],
245279
},
280+
booleanString: {
281+
type: 'string',
282+
},
246283
},
247-
required: ['comments'],
284+
required: ['booleanString', 'comments'],
248285
type: 'object',
249286
},
250287
},
@@ -256,6 +293,39 @@ const expectedApiDoc = {
256293
tags: ['posts', 'comments'],
257294
},
258295
},
296+
'/posts/{id}/comments/{commentId}': {
297+
get: {
298+
deprecated: undefined,
299+
description: undefined,
300+
parameters: [
301+
{
302+
in: 'path',
303+
name: 'id',
304+
required: true,
305+
schema: {
306+
type: 'string',
307+
},
308+
},
309+
{
310+
in: 'path',
311+
name: 'commentId',
312+
required: true,
313+
schema: {
314+
type: 'string',
315+
minLength: 5,
316+
maxLength: 5,
317+
},
318+
},
319+
],
320+
responses: {
321+
'200': {
322+
description: '200',
323+
},
324+
},
325+
summary: undefined,
326+
tags: ['posts'],
327+
},
328+
},
259329
},
260330
};
261331

@@ -309,6 +379,12 @@ describe('ts-rest-open-api', () => {
309379
operationId: 'getPostComments',
310380
},
311381
},
382+
'/posts/{id}/comments/{commentId}': {
383+
get: {
384+
...expectedApiDoc.paths['/posts/{id}/comments/{commentId}'].get,
385+
operationId: 'getPostComment',
386+
},
387+
},
312388
},
313389
});
314390
});
@@ -335,14 +411,8 @@ describe('ts-rest-open-api', () => {
335411
content: {
336412
'application/json': {
337413
schema: {
338-
anyOf: [
339-
{
340-
not: {},
341-
},
342-
{
343-
type: 'string',
344-
},
345-
],
414+
type: 'string',
415+
nullable: true,
346416
},
347417
},
348418
},
@@ -353,16 +423,9 @@ describe('ts-rest-open-api', () => {
353423
content: {
354424
'application/json': {
355425
schema: {
356-
anyOf: [
357-
{
358-
not: {},
359-
},
360-
{
361-
default: 'date',
362-
enum: ['title', 'date'],
363-
type: 'string',
364-
},
365-
],
426+
default: 'date',
427+
enum: ['title', 'date'],
428+
type: 'string',
366429
},
367430
},
368431
},
@@ -373,21 +436,32 @@ describe('ts-rest-open-api', () => {
373436
content: {
374437
'application/json': {
375438
schema: {
376-
anyOf: [
377-
{
378-
not: {},
379-
},
380-
{
381-
default: 'asc',
382-
enum: ['asc', 'desc'],
439+
default: 'asc',
440+
enum: ['asc', 'desc'],
441+
type: 'string',
442+
},
443+
},
444+
},
445+
in: 'query',
446+
name: 'sort',
447+
},
448+
{
449+
content: {
450+
'application/json': {
451+
schema: {
452+
properties: {
453+
a: {
383454
type: 'string',
384455
},
385-
],
456+
},
457+
required: ['a'],
458+
type: 'object',
386459
},
387460
},
388461
},
389462
in: 'query',
390-
name: 'sort',
463+
name: 'obj',
464+
required: true,
391465
},
392466
],
393467
},

0 commit comments

Comments
 (0)