@orpc/openapi 0.0.0-unsafe-pr-2-20241118033608

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,465 @@
1
+ import { oc } from '@orpc/contract'
2
+ import { oz } from '@orpc/zod'
3
+ import type { OpenAPIObject } from 'openapi3-ts/oas31'
4
+ import { z } from 'zod'
5
+ import { generateOpenAPI } from './generator'
6
+
7
+ it('works', () => {
8
+ const o = oc
9
+
10
+ const router = o.router({
11
+ ping: o.output(z.string()),
12
+
13
+ user: {
14
+ find: o
15
+ .route({ method: 'GET', path: '/users/{id}', tags: ['user'] })
16
+ .input(z.object({ id: z.string() }))
17
+ .output(z.object({ name: z.string() })),
18
+ },
19
+ })
20
+
21
+ const spec = generateOpenAPI({
22
+ router,
23
+ info: {
24
+ title: 'test',
25
+ version: '1.0.0',
26
+ },
27
+ })
28
+
29
+ expect(spec).toMatchObject({
30
+ openapi: '3.1.0',
31
+ info: {
32
+ title: 'test',
33
+ version: '1.0.0',
34
+ },
35
+ paths: {
36
+ '/ping': {
37
+ post: {
38
+ responses: {
39
+ '200': {
40
+ description: 'OK',
41
+ content: {
42
+ 'application/json': {
43
+ schema: {
44
+ type: 'string',
45
+ },
46
+ },
47
+ },
48
+ },
49
+ },
50
+ },
51
+ },
52
+ '/users/{id}': {
53
+ get: {
54
+ tags: ['user'],
55
+ parameters: [
56
+ {
57
+ name: 'id',
58
+ in: 'path',
59
+ required: true,
60
+ schema: {
61
+ type: 'string',
62
+ },
63
+ },
64
+ ],
65
+ responses: {
66
+ '200': {
67
+ description: 'OK',
68
+ content: {
69
+ 'application/json': {
70
+ schema: {
71
+ type: 'object',
72
+ properties: {
73
+ name: {
74
+ type: 'string',
75
+ },
76
+ },
77
+ required: ['name'],
78
+ },
79
+ },
80
+ },
81
+ },
82
+ },
83
+ },
84
+ },
85
+ },
86
+ } satisfies OpenAPIObject)
87
+ })
88
+
89
+ it('throwOnMissingTagDefinition option', () => {
90
+ const o = oc
91
+
92
+ const router = o.router({
93
+ ping: o.output(z.string()),
94
+
95
+ user: {
96
+ find: o
97
+ .route({ method: 'GET', path: '/users/{id}', tags: ['user'] })
98
+ .input(z.object({ id: z.string() }))
99
+ .output(z.object({ name: z.string() })),
100
+ },
101
+ })
102
+
103
+ const spec = generateOpenAPI(
104
+ {
105
+ router,
106
+ info: {
107
+ title: 'test',
108
+ version: '1.0.0',
109
+ },
110
+ tags: [
111
+ {
112
+ name: 'user',
113
+ description: 'User related apis',
114
+ },
115
+ ],
116
+ },
117
+ { throwOnMissingTagDefinition: true },
118
+ )
119
+
120
+ expect(spec).toMatchObject({
121
+ openapi: '3.1.0',
122
+ info: {
123
+ title: 'test',
124
+ version: '1.0.0',
125
+ },
126
+ tags: [
127
+ {
128
+ name: 'user',
129
+ description: 'User related apis',
130
+ },
131
+ ],
132
+ paths: {
133
+ '/ping': {
134
+ post: {
135
+ responses: {
136
+ '200': {
137
+ description: 'OK',
138
+ content: {
139
+ 'application/json': {
140
+ schema: {
141
+ type: 'string',
142
+ },
143
+ },
144
+ },
145
+ },
146
+ },
147
+ },
148
+ },
149
+ '/users/{id}': {
150
+ get: {
151
+ tags: ['user'],
152
+ parameters: [
153
+ {
154
+ name: 'id',
155
+ in: 'path',
156
+ required: true,
157
+ schema: {
158
+ type: 'string',
159
+ },
160
+ },
161
+ ],
162
+ responses: {
163
+ '200': {
164
+ description: 'OK',
165
+ content: {
166
+ 'application/json': {
167
+ schema: {
168
+ type: 'object',
169
+ properties: {
170
+ name: {
171
+ type: 'string',
172
+ },
173
+ },
174
+ required: ['name'],
175
+ },
176
+ },
177
+ },
178
+ },
179
+ },
180
+ },
181
+ },
182
+ },
183
+ } satisfies OpenAPIObject)
184
+
185
+ expect(() =>
186
+ generateOpenAPI(
187
+ {
188
+ router,
189
+ info: {
190
+ title: 'test',
191
+ version: '1.0.0',
192
+ },
193
+ },
194
+ { throwOnMissingTagDefinition: true },
195
+ ),
196
+ ).toThrow(
197
+ 'Tag "user" is missing definition. Please define it in OpenAPI root tags object. [user.find]',
198
+ )
199
+ })
200
+
201
+ it('support single file upload', () => {
202
+ const o = oc
203
+
204
+ const router = o.router({
205
+ upload: o
206
+ .input(z.union([z.string(), oz.file()]))
207
+ .output(
208
+ z.union([oz.file().type('image/jpg'), oz.file().type('image/png')]),
209
+ ),
210
+ })
211
+
212
+ const spec = generateOpenAPI({
213
+ router,
214
+ info: {
215
+ title: 'test',
216
+ version: '1.0.0',
217
+ },
218
+ })
219
+
220
+ expect(spec).toMatchObject({
221
+ paths: {
222
+ '/upload': {
223
+ post: {
224
+ requestBody: {
225
+ content: {
226
+ '*/*': {
227
+ schema: {
228
+ type: 'string',
229
+ contentMediaType: '*/*',
230
+ },
231
+ },
232
+ 'application/json': {
233
+ schema: {
234
+ type: 'string',
235
+ },
236
+ },
237
+ },
238
+ },
239
+ responses: {
240
+ '200': {
241
+ content: {
242
+ 'image/jpg': {
243
+ schema: {
244
+ type: 'string',
245
+ contentMediaType: 'image/jpg',
246
+ },
247
+ },
248
+ 'image/png': {
249
+ schema: {
250
+ type: 'string',
251
+ contentMediaType: 'image/png',
252
+ },
253
+ },
254
+ },
255
+ },
256
+ },
257
+ },
258
+ },
259
+ },
260
+ })
261
+ })
262
+
263
+ it('support multipart/form-data', () => {
264
+ const o = oc
265
+
266
+ const router = o.router({
267
+ resize: o
268
+ .input(
269
+ z.object({
270
+ file: oz.file().type('image/*'),
271
+ height: z.number(),
272
+ width: z.number(),
273
+ }),
274
+ )
275
+ .output(oz.file().type('image/*')),
276
+ })
277
+
278
+ const spec = generateOpenAPI({
279
+ router,
280
+ info: {
281
+ title: 'test',
282
+ version: '1.0.0',
283
+ },
284
+ })
285
+
286
+ expect(spec).toMatchObject({
287
+ paths: {
288
+ '/resize': {
289
+ post: {
290
+ requestBody: {
291
+ content: {
292
+ 'multipart/form-data': {
293
+ schema: {
294
+ type: 'object',
295
+ properties: {
296
+ file: {
297
+ type: 'string',
298
+ contentMediaType: 'image/*',
299
+ },
300
+ height: {
301
+ type: 'number',
302
+ },
303
+ width: {
304
+ type: 'number',
305
+ },
306
+ },
307
+ required: ['file', 'height', 'width'],
308
+ },
309
+ },
310
+ },
311
+ },
312
+ responses: {
313
+ '200': {
314
+ content: {
315
+ 'image/*': {
316
+ schema: {
317
+ type: 'string',
318
+ contentMediaType: 'image/*',
319
+ },
320
+ },
321
+ },
322
+ },
323
+ },
324
+ },
325
+ },
326
+ },
327
+ })
328
+ })
329
+
330
+ it('work with example', () => {
331
+ const router = oc.router({
332
+ upload: oc
333
+ .input(
334
+ z.object({
335
+ set: z.set(z.string()),
336
+ map: z.map(z.string(), z.number()),
337
+ }),
338
+ {
339
+ set: new Set(['a', 'b', 'c']),
340
+ map: new Map([
341
+ ['a', 1],
342
+ ['b', 2],
343
+ ['c', 3],
344
+ ]),
345
+ },
346
+ )
347
+ .output(
348
+ z.object({
349
+ set: z.set(z.string()),
350
+ map: z.map(z.string(), z.number()),
351
+ }),
352
+ {
353
+ set: new Set(['a', 'b', 'c']),
354
+ map: new Map([
355
+ ['a', 1],
356
+ ['b', 2],
357
+ ['c', 3],
358
+ ]),
359
+ },
360
+ ),
361
+ })
362
+
363
+ const spec = generateOpenAPI({
364
+ router,
365
+ info: {
366
+ title: 'test',
367
+ version: '1.0.0',
368
+ },
369
+ })
370
+
371
+ expect(spec).toMatchObject({
372
+ paths: {
373
+ '/upload': {
374
+ post: {
375
+ requestBody: {
376
+ content: {
377
+ 'application/json': {
378
+ schema: {
379
+ type: 'object',
380
+ properties: {
381
+ set: {
382
+ type: 'array',
383
+ items: {
384
+ type: 'string',
385
+ },
386
+ },
387
+ map: {
388
+ type: 'array',
389
+ items: {
390
+ type: 'array',
391
+ prefixItems: [
392
+ {
393
+ type: 'string',
394
+ },
395
+ {
396
+ type: 'number',
397
+ },
398
+ ],
399
+ maxItems: 2,
400
+ minItems: 2,
401
+ },
402
+ },
403
+ },
404
+ required: ['set', 'map'],
405
+ },
406
+ example: {
407
+ set: ['a', 'b', 'c'],
408
+ map: [
409
+ ['a', 1],
410
+ ['b', 2],
411
+ ['c', 3],
412
+ ],
413
+ },
414
+ },
415
+ },
416
+ },
417
+ responses: {
418
+ '200': {
419
+ content: {
420
+ 'application/json': {
421
+ schema: {
422
+ type: 'object',
423
+ properties: {
424
+ set: {
425
+ type: 'array',
426
+ items: {
427
+ type: 'string',
428
+ },
429
+ },
430
+ map: {
431
+ type: 'array',
432
+ items: {
433
+ type: 'array',
434
+ prefixItems: [
435
+ {
436
+ type: 'string',
437
+ },
438
+ {
439
+ type: 'number',
440
+ },
441
+ ],
442
+ maxItems: 2,
443
+ minItems: 2,
444
+ },
445
+ },
446
+ },
447
+ required: ['set', 'map'],
448
+ },
449
+ example: {
450
+ set: ['a', 'b', 'c'],
451
+ map: [
452
+ ['a', 1],
453
+ ['b', 2],
454
+ ['c', 3],
455
+ ],
456
+ },
457
+ },
458
+ },
459
+ },
460
+ },
461
+ },
462
+ },
463
+ },
464
+ })
465
+ })