@sapporta/rest-core 3.52.1
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.
- package/.babelrc +10 -0
- package/.eslintrc.json +21 -0
- package/CHANGELOG.md +3 -0
- package/LICENCE +21 -0
- package/README.md +19 -0
- package/jest.config.ts +16 -0
- package/package.json +33 -0
- package/project.json +51 -0
- package/src/index.ts +15 -0
- package/src/lib/client.spec.ts +1330 -0
- package/src/lib/client.ts +481 -0
- package/src/lib/dsl.spec.ts +1308 -0
- package/src/lib/dsl.ts +472 -0
- package/src/lib/fetch.spec.ts +102 -0
- package/src/lib/infer-types.spec.ts +935 -0
- package/src/lib/infer-types.ts +282 -0
- package/src/lib/paths.spec.ts +138 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/query.spec.ts +329 -0
- package/src/lib/query.ts +114 -0
- package/src/lib/response-error.spec.ts +67 -0
- package/src/lib/response-error.ts +61 -0
- package/src/lib/response-validation-error.ts +24 -0
- package/src/lib/server.spec.ts +163 -0
- package/src/lib/server.ts +83 -0
- package/src/lib/standard-schema-utils.spec.ts +218 -0
- package/src/lib/standard-schema-utils.ts +280 -0
- package/src/lib/standard-schema.ts +71 -0
- package/src/lib/status-codes.ts +75 -0
- package/src/lib/test-helpers.ts +7 -0
- package/src/lib/type-guards.spec.ts +355 -0
- package/src/lib/type-guards.ts +99 -0
- package/src/lib/type-utils.spec.ts +59 -0
- package/src/lib/type-utils.ts +234 -0
- package/src/lib/unknown-status-error.ts +15 -0
- package/src/lib/validation-error.ts +36 -0
- package/tsconfig.json +22 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +9 -0
- package/typedoc.json +5 -0
|
@@ -0,0 +1,1308 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import {
|
|
4
|
+
initContract,
|
|
5
|
+
ContractOtherResponse,
|
|
6
|
+
ContractPlainType,
|
|
7
|
+
ContractPlainTypeRuntimeSymbol,
|
|
8
|
+
ContractNoBodyType,
|
|
9
|
+
MergeHeaders,
|
|
10
|
+
AppRoute,
|
|
11
|
+
InferHeadersInput,
|
|
12
|
+
} from './dsl';
|
|
13
|
+
import type { Equal, Expect } from './test-helpers';
|
|
14
|
+
import { Prettify } from './type-utils';
|
|
15
|
+
import * as v from 'valibot';
|
|
16
|
+
|
|
17
|
+
const c = initContract();
|
|
18
|
+
|
|
19
|
+
describe('contract', () => {
|
|
20
|
+
it('should be typed correctly', () => {
|
|
21
|
+
const contract = c.router({
|
|
22
|
+
getPost: {
|
|
23
|
+
method: 'GET',
|
|
24
|
+
path: '/posts/:id',
|
|
25
|
+
responses: {
|
|
26
|
+
200: z.object({
|
|
27
|
+
id: z.number(),
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
type ContractShape = typeof contract;
|
|
34
|
+
type TestContractShape = Expect<
|
|
35
|
+
Equal<
|
|
36
|
+
ContractShape,
|
|
37
|
+
{
|
|
38
|
+
getPost: {
|
|
39
|
+
method: 'GET';
|
|
40
|
+
path: '/posts/:id';
|
|
41
|
+
responses: {
|
|
42
|
+
200: z.ZodObject<
|
|
43
|
+
{
|
|
44
|
+
id: z.ZodNumber;
|
|
45
|
+
},
|
|
46
|
+
z.core.$strip
|
|
47
|
+
>;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
>
|
|
52
|
+
>;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should be typed correctly with nested routers', () => {
|
|
56
|
+
const contract = c.router({
|
|
57
|
+
posts: {
|
|
58
|
+
getPost: {
|
|
59
|
+
method: 'GET',
|
|
60
|
+
path: '/posts/:id',
|
|
61
|
+
responses: {
|
|
62
|
+
200: z.object({
|
|
63
|
+
id: z.number(),
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
type ContractShape = Expect<
|
|
71
|
+
Equal<
|
|
72
|
+
typeof contract,
|
|
73
|
+
{
|
|
74
|
+
posts: {
|
|
75
|
+
getPost: {
|
|
76
|
+
method: 'GET';
|
|
77
|
+
path: '/posts/:id';
|
|
78
|
+
responses: {
|
|
79
|
+
200: z.ZodObject<
|
|
80
|
+
{
|
|
81
|
+
id: z.ZodNumber;
|
|
82
|
+
},
|
|
83
|
+
z.core.$strip
|
|
84
|
+
>;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
>;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should be typed correctly with headers', () => {
|
|
94
|
+
const contract = c.router({
|
|
95
|
+
posts: {
|
|
96
|
+
getPost: {
|
|
97
|
+
method: 'GET',
|
|
98
|
+
path: '/posts/:id',
|
|
99
|
+
responses: {
|
|
100
|
+
200: z.object({
|
|
101
|
+
id: z.number(),
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
headers: {
|
|
105
|
+
'x-foo': z.string(),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
type ContractShape = typeof contract.posts.getPost;
|
|
112
|
+
type TestContractShape = Expect<
|
|
113
|
+
Equal<
|
|
114
|
+
ContractShape,
|
|
115
|
+
{
|
|
116
|
+
method: 'GET';
|
|
117
|
+
path: '/posts/:id';
|
|
118
|
+
responses: {
|
|
119
|
+
200: z.ZodObject<
|
|
120
|
+
{
|
|
121
|
+
id: z.ZodNumber;
|
|
122
|
+
},
|
|
123
|
+
z.core.$strip
|
|
124
|
+
>;
|
|
125
|
+
};
|
|
126
|
+
headers: {
|
|
127
|
+
'x-foo': z.ZodString;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
>;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should be typed correctly with base headers', () => {
|
|
135
|
+
const contract = c.router(
|
|
136
|
+
{
|
|
137
|
+
posts: {
|
|
138
|
+
getPost: {
|
|
139
|
+
method: 'GET',
|
|
140
|
+
path: '/posts/:id',
|
|
141
|
+
responses: {
|
|
142
|
+
200: z.object({
|
|
143
|
+
id: z.number(),
|
|
144
|
+
}),
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
baseHeaders: {
|
|
151
|
+
'x-foo': z.string(),
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
type ContractShape = Expect<
|
|
157
|
+
Equal<
|
|
158
|
+
typeof contract,
|
|
159
|
+
{
|
|
160
|
+
posts: {
|
|
161
|
+
getPost: {
|
|
162
|
+
method: 'GET';
|
|
163
|
+
path: '/posts/:id';
|
|
164
|
+
responses: {
|
|
165
|
+
200: z.ZodObject<
|
|
166
|
+
{
|
|
167
|
+
id: z.ZodNumber;
|
|
168
|
+
},
|
|
169
|
+
z.core.$strip
|
|
170
|
+
>;
|
|
171
|
+
};
|
|
172
|
+
headers: {
|
|
173
|
+
'x-foo': z.ZodString;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
>
|
|
179
|
+
>;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should be typed correctly with merged headers', () => {
|
|
183
|
+
const contract = c.router(
|
|
184
|
+
{
|
|
185
|
+
posts: {
|
|
186
|
+
getPost: {
|
|
187
|
+
method: 'GET',
|
|
188
|
+
path: '/posts/:id',
|
|
189
|
+
responses: {
|
|
190
|
+
200: z.object({
|
|
191
|
+
id: z.number(),
|
|
192
|
+
}),
|
|
193
|
+
},
|
|
194
|
+
headers: {
|
|
195
|
+
'x-bar': z.string(),
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
baseHeaders: {
|
|
202
|
+
'x-foo': z.string(),
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
type ContractShape = Expect<
|
|
208
|
+
Equal<
|
|
209
|
+
typeof contract,
|
|
210
|
+
{
|
|
211
|
+
posts: {
|
|
212
|
+
getPost: {
|
|
213
|
+
method: 'GET';
|
|
214
|
+
path: '/posts/:id';
|
|
215
|
+
responses: {
|
|
216
|
+
200: z.ZodObject<
|
|
217
|
+
{
|
|
218
|
+
id: z.ZodNumber;
|
|
219
|
+
},
|
|
220
|
+
z.core.$strip
|
|
221
|
+
>;
|
|
222
|
+
};
|
|
223
|
+
headers: {
|
|
224
|
+
'x-foo': z.ZodString;
|
|
225
|
+
'x-bar': z.ZodString;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
>
|
|
231
|
+
>;
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should be typed correctly with merged plain type headers', () => {
|
|
235
|
+
const contract = c.router(
|
|
236
|
+
{
|
|
237
|
+
posts: {
|
|
238
|
+
getPost: {
|
|
239
|
+
method: 'GET',
|
|
240
|
+
path: '/posts/:id',
|
|
241
|
+
responses: {
|
|
242
|
+
200: z.object({
|
|
243
|
+
id: z.number(),
|
|
244
|
+
}),
|
|
245
|
+
},
|
|
246
|
+
headers: { 'x-bar': c.type<string>() },
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
baseHeaders: { 'x-foo': c.type<string>() },
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
type ContractShape = Expect<
|
|
256
|
+
Equal<
|
|
257
|
+
typeof contract,
|
|
258
|
+
{
|
|
259
|
+
posts: {
|
|
260
|
+
getPost: {
|
|
261
|
+
method: 'GET';
|
|
262
|
+
path: '/posts/:id';
|
|
263
|
+
responses: {
|
|
264
|
+
200: z.ZodObject<
|
|
265
|
+
{
|
|
266
|
+
id: z.ZodNumber;
|
|
267
|
+
},
|
|
268
|
+
z.core.$strip
|
|
269
|
+
>;
|
|
270
|
+
};
|
|
271
|
+
headers: {
|
|
272
|
+
'x-foo': ContractPlainType<string>;
|
|
273
|
+
'x-bar': ContractPlainType<string>;
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
>
|
|
279
|
+
>;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should be typed correctly with overridden headers', () => {
|
|
283
|
+
const contract = c.router(
|
|
284
|
+
{
|
|
285
|
+
posts: {
|
|
286
|
+
getPost: {
|
|
287
|
+
method: 'GET',
|
|
288
|
+
path: '/posts/:id',
|
|
289
|
+
responses: {
|
|
290
|
+
200: z.object({
|
|
291
|
+
id: z.number(),
|
|
292
|
+
}),
|
|
293
|
+
},
|
|
294
|
+
headers: {
|
|
295
|
+
'x-foo': z.string().optional(),
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
baseHeaders: {
|
|
302
|
+
'x-foo': z.string(),
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
type ContractShape = Expect<
|
|
308
|
+
Equal<
|
|
309
|
+
typeof contract,
|
|
310
|
+
{
|
|
311
|
+
posts: {
|
|
312
|
+
getPost: {
|
|
313
|
+
method: 'GET';
|
|
314
|
+
path: '/posts/:id';
|
|
315
|
+
responses: {
|
|
316
|
+
200: z.ZodObject<
|
|
317
|
+
{
|
|
318
|
+
id: z.ZodNumber;
|
|
319
|
+
},
|
|
320
|
+
z.core.$strip
|
|
321
|
+
>;
|
|
322
|
+
};
|
|
323
|
+
headers: {
|
|
324
|
+
'x-foo': z.ZodOptional<z.ZodString>;
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
>
|
|
330
|
+
>;
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should be typed without zod', () => {
|
|
334
|
+
const contract = c.router({
|
|
335
|
+
getPost: {
|
|
336
|
+
method: 'GET',
|
|
337
|
+
path: '/posts/:id',
|
|
338
|
+
responses: {
|
|
339
|
+
200: c.type<{ id: number }>(),
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
type ContractShape = Expect<
|
|
345
|
+
Equal<
|
|
346
|
+
typeof contract,
|
|
347
|
+
{
|
|
348
|
+
getPost: {
|
|
349
|
+
method: 'GET';
|
|
350
|
+
path: '/posts/:id';
|
|
351
|
+
responses: {
|
|
352
|
+
200: ContractPlainType<{
|
|
353
|
+
id: number;
|
|
354
|
+
}>;
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
>
|
|
359
|
+
>;
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should be typed correctly with separate query route', () => {
|
|
363
|
+
const getPost = c.query({
|
|
364
|
+
method: 'GET',
|
|
365
|
+
path: '/posts/:id',
|
|
366
|
+
responses: {
|
|
367
|
+
200: c.type<{ id: number }>(),
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const contract = c.router({
|
|
372
|
+
getPost,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
type ContractShape = Expect<
|
|
376
|
+
Equal<
|
|
377
|
+
typeof contract,
|
|
378
|
+
{
|
|
379
|
+
getPost: {
|
|
380
|
+
method: 'GET';
|
|
381
|
+
path: '/posts/:id';
|
|
382
|
+
responses: {
|
|
383
|
+
200: ContractPlainType<{
|
|
384
|
+
id: number;
|
|
385
|
+
}>;
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
>
|
|
390
|
+
>;
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should be typed correctly with separate mutation route', () => {
|
|
394
|
+
const createPost = c.mutation({
|
|
395
|
+
method: 'POST',
|
|
396
|
+
path: '/posts',
|
|
397
|
+
responses: {
|
|
398
|
+
200: c.type<{ id: number }>(),
|
|
399
|
+
},
|
|
400
|
+
body: c.type<{ title: string }>(),
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const contract = c.router({
|
|
404
|
+
createPost,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
type ContractShape = Expect<
|
|
408
|
+
Equal<
|
|
409
|
+
typeof contract,
|
|
410
|
+
{
|
|
411
|
+
createPost: {
|
|
412
|
+
method: 'POST';
|
|
413
|
+
path: '/posts';
|
|
414
|
+
responses: {
|
|
415
|
+
200: ContractPlainType<{
|
|
416
|
+
id: number;
|
|
417
|
+
}>;
|
|
418
|
+
};
|
|
419
|
+
body: ContractPlainType<{
|
|
420
|
+
title: string;
|
|
421
|
+
}>;
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
>
|
|
425
|
+
>;
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should be typed correctly with separate responses', () => {
|
|
429
|
+
const responses = c.responses({
|
|
430
|
+
200: c.type<{ id: number }>(),
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const contract = c.router({
|
|
434
|
+
getPost: {
|
|
435
|
+
method: 'GET',
|
|
436
|
+
path: '/posts/:id',
|
|
437
|
+
responses,
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
type ContractShape = Expect<
|
|
442
|
+
Equal<
|
|
443
|
+
typeof contract,
|
|
444
|
+
{
|
|
445
|
+
getPost: {
|
|
446
|
+
method: 'GET';
|
|
447
|
+
path: '/posts/:id';
|
|
448
|
+
responses: {
|
|
449
|
+
200: ContractPlainType<{
|
|
450
|
+
id: number;
|
|
451
|
+
}>;
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
>
|
|
456
|
+
>;
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should be typed correctly with separate responses with spread', () => {
|
|
460
|
+
const responses = c.responses({
|
|
461
|
+
200: c.type<{ id: number }>(),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const contract = c.router({
|
|
465
|
+
getPost: {
|
|
466
|
+
method: 'GET',
|
|
467
|
+
path: '/posts/:id',
|
|
468
|
+
responses: {
|
|
469
|
+
...responses,
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
type ContractShape = Expect<
|
|
475
|
+
Equal<
|
|
476
|
+
typeof contract,
|
|
477
|
+
{
|
|
478
|
+
getPost: {
|
|
479
|
+
method: 'GET';
|
|
480
|
+
path: '/posts/:id';
|
|
481
|
+
responses: {
|
|
482
|
+
200: ContractPlainType<{
|
|
483
|
+
id: number;
|
|
484
|
+
}>;
|
|
485
|
+
};
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
>
|
|
489
|
+
>;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('should add strictStatusCodes=true option to routes', () => {
|
|
493
|
+
const contract = c.router(
|
|
494
|
+
{
|
|
495
|
+
getPost: {
|
|
496
|
+
method: 'GET',
|
|
497
|
+
path: '/posts/:id',
|
|
498
|
+
responses: {
|
|
499
|
+
200: c.type<{ id: number }>(),
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
strictStatusCodes: true,
|
|
505
|
+
},
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
expect(contract.getPost.strictStatusCodes).toStrictEqual(true);
|
|
509
|
+
|
|
510
|
+
type ContractShape = Expect<
|
|
511
|
+
Equal<
|
|
512
|
+
Pick<typeof contract.getPost, 'strictStatusCodes'>,
|
|
513
|
+
{
|
|
514
|
+
strictStatusCodes: true;
|
|
515
|
+
}
|
|
516
|
+
>
|
|
517
|
+
>;
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should add strictStatusCodes=false option to routes', () => {
|
|
521
|
+
const contract = c.router(
|
|
522
|
+
{
|
|
523
|
+
getPost: {
|
|
524
|
+
method: 'GET',
|
|
525
|
+
path: '/posts/:id',
|
|
526
|
+
responses: {
|
|
527
|
+
200: c.type<{ id: number }>(),
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
strictStatusCodes: false,
|
|
533
|
+
},
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
expect(contract.getPost.strictStatusCodes).toStrictEqual(false);
|
|
537
|
+
|
|
538
|
+
type ContractShape = Expect<
|
|
539
|
+
Equal<
|
|
540
|
+
Pick<typeof contract.getPost, 'strictStatusCodes'>,
|
|
541
|
+
{
|
|
542
|
+
strictStatusCodes: false;
|
|
543
|
+
}
|
|
544
|
+
>
|
|
545
|
+
>;
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('should merge strictStatusCodes options correctly is route is true', () => {
|
|
549
|
+
const contract = c.router(
|
|
550
|
+
{
|
|
551
|
+
getPost: {
|
|
552
|
+
method: 'GET',
|
|
553
|
+
path: '/posts/:id',
|
|
554
|
+
responses: {
|
|
555
|
+
200: c.type<{ id: number }>(),
|
|
556
|
+
},
|
|
557
|
+
strictStatusCodes: true,
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
strictStatusCodes: false,
|
|
562
|
+
},
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
expect(contract.getPost.strictStatusCodes).toStrictEqual(true);
|
|
566
|
+
|
|
567
|
+
type ContractShape = Expect<
|
|
568
|
+
Equal<
|
|
569
|
+
Pick<typeof contract.getPost, 'strictStatusCodes'>,
|
|
570
|
+
{
|
|
571
|
+
strictStatusCodes: true;
|
|
572
|
+
}
|
|
573
|
+
>
|
|
574
|
+
>;
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('should merge strictStatusCodes options correctly if route is false', () => {
|
|
578
|
+
const contract = c.router(
|
|
579
|
+
{
|
|
580
|
+
getPost: {
|
|
581
|
+
method: 'GET',
|
|
582
|
+
path: '/posts/:id',
|
|
583
|
+
responses: {
|
|
584
|
+
200: c.type<{ id: number }>(),
|
|
585
|
+
},
|
|
586
|
+
strictStatusCodes: false,
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
strictStatusCodes: true,
|
|
591
|
+
},
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
expect(contract.getPost.strictStatusCodes).toStrictEqual(false);
|
|
595
|
+
|
|
596
|
+
type ContractShape = Expect<
|
|
597
|
+
Equal<
|
|
598
|
+
Pick<typeof contract.getPost, 'strictStatusCodes'>,
|
|
599
|
+
{
|
|
600
|
+
strictStatusCodes: false;
|
|
601
|
+
}
|
|
602
|
+
>
|
|
603
|
+
>;
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('should merge metadata options from router to its routes', () => {
|
|
607
|
+
const contract = c.router(
|
|
608
|
+
{
|
|
609
|
+
getPost: {
|
|
610
|
+
method: 'GET',
|
|
611
|
+
path: '/posts/:id',
|
|
612
|
+
responses: {
|
|
613
|
+
200: c.type<{ id: number }>(),
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
deletePost: {
|
|
617
|
+
method: 'DELETE',
|
|
618
|
+
path: '/posts/:id',
|
|
619
|
+
body: c.type<undefined>(),
|
|
620
|
+
metadata: {
|
|
621
|
+
requireAuth: false,
|
|
622
|
+
headerName: 'x-authorization',
|
|
623
|
+
},
|
|
624
|
+
responses: {
|
|
625
|
+
200: c.type<{ id: number }>(),
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
metadata: {
|
|
631
|
+
requireAuth: true,
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
expect(contract.getPost.metadata).toStrictEqual({
|
|
637
|
+
requireAuth: true,
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
expect(contract.deletePost.metadata).toStrictEqual({
|
|
641
|
+
requireAuth: false,
|
|
642
|
+
headerName: 'x-authorization',
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
type MetadataShape = Expect<
|
|
646
|
+
Equal<
|
|
647
|
+
typeof contract.getPost.metadata,
|
|
648
|
+
{
|
|
649
|
+
requireAuth: boolean;
|
|
650
|
+
}
|
|
651
|
+
>
|
|
652
|
+
>;
|
|
653
|
+
|
|
654
|
+
type MetadataShape2 = Expect<
|
|
655
|
+
Equal<
|
|
656
|
+
Prettify<typeof contract.deletePost.metadata>,
|
|
657
|
+
{
|
|
658
|
+
requireAuth: boolean;
|
|
659
|
+
headerName: string;
|
|
660
|
+
}
|
|
661
|
+
>
|
|
662
|
+
>;
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
describe('pathPrefix', () => {
|
|
666
|
+
it('Should recursively apply pathPrefix to path', () => {
|
|
667
|
+
const postsContractNested = c.router(
|
|
668
|
+
{
|
|
669
|
+
getPost: {
|
|
670
|
+
path: '/:id',
|
|
671
|
+
method: 'GET',
|
|
672
|
+
responses: { 200: c.type<{ id: string }>() },
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
{ pathPrefix: '/posts' },
|
|
676
|
+
);
|
|
677
|
+
const postsContract = c.router(
|
|
678
|
+
{
|
|
679
|
+
posts: postsContractNested,
|
|
680
|
+
},
|
|
681
|
+
{ pathPrefix: '/v1' },
|
|
682
|
+
);
|
|
683
|
+
expect(postsContractNested.getPost.path).toStrictEqual('/posts/:id');
|
|
684
|
+
expect(postsContract.posts.getPost.path).toStrictEqual('/v1/posts/:id');
|
|
685
|
+
|
|
686
|
+
type PostsContractNestedShape = Expect<
|
|
687
|
+
Equal<
|
|
688
|
+
typeof postsContractNested,
|
|
689
|
+
{
|
|
690
|
+
getPost: {
|
|
691
|
+
path: '/posts/:id';
|
|
692
|
+
method: 'GET';
|
|
693
|
+
responses: { 200: ContractPlainType<{ id: string }> };
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
>
|
|
697
|
+
>;
|
|
698
|
+
|
|
699
|
+
type PostsContractShape = Expect<
|
|
700
|
+
Equal<
|
|
701
|
+
typeof postsContract,
|
|
702
|
+
{
|
|
703
|
+
posts: {
|
|
704
|
+
getPost: {
|
|
705
|
+
path: '/v1/posts/:id';
|
|
706
|
+
method: 'GET';
|
|
707
|
+
responses: { 200: ContractPlainType<{ id: string }> };
|
|
708
|
+
};
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
>
|
|
712
|
+
>;
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
describe('validateResponseOnClient', () => {
|
|
717
|
+
it('Should recursively apply validateResponseOnClient to routes', () => {
|
|
718
|
+
const postsContractNested = c.router({
|
|
719
|
+
getPost: {
|
|
720
|
+
path: '/:id',
|
|
721
|
+
method: 'GET',
|
|
722
|
+
responses: { 200: c.type<{ id: string }>() },
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
const postsContract = c.router(
|
|
726
|
+
{
|
|
727
|
+
posts: postsContractNested,
|
|
728
|
+
},
|
|
729
|
+
{ validateResponseOnClient: true },
|
|
730
|
+
);
|
|
731
|
+
expect(postsContractNested.getPost).toHaveProperty(
|
|
732
|
+
'validateResponseOnClient',
|
|
733
|
+
undefined,
|
|
734
|
+
);
|
|
735
|
+
expect(postsContract.posts.getPost).toHaveProperty(
|
|
736
|
+
'validateResponseOnClient',
|
|
737
|
+
true,
|
|
738
|
+
);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it('Should not override validateResponseOnClient if set on nested router', () => {
|
|
742
|
+
const postsContractNested = c.router(
|
|
743
|
+
{
|
|
744
|
+
getPost: {
|
|
745
|
+
path: '/:id',
|
|
746
|
+
method: 'GET',
|
|
747
|
+
responses: { 200: c.type<{ id: string }>() },
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
{ validateResponseOnClient: false },
|
|
751
|
+
);
|
|
752
|
+
const postsContract = c.router(
|
|
753
|
+
{
|
|
754
|
+
posts: postsContractNested,
|
|
755
|
+
},
|
|
756
|
+
{ validateResponseOnClient: true },
|
|
757
|
+
);
|
|
758
|
+
expect(postsContractNested.getPost).toHaveProperty(
|
|
759
|
+
'validateResponseOnClient',
|
|
760
|
+
false,
|
|
761
|
+
);
|
|
762
|
+
expect(postsContract.posts.getPost).toHaveProperty(
|
|
763
|
+
'validateResponseOnClient',
|
|
764
|
+
false,
|
|
765
|
+
);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('Should not override validateResponseOnClient when set directly on route', () => {
|
|
769
|
+
const postsContract = c.router(
|
|
770
|
+
{
|
|
771
|
+
getPost: {
|
|
772
|
+
path: '/:id',
|
|
773
|
+
method: 'GET',
|
|
774
|
+
responses: { 200: c.type<{ id: string }>() },
|
|
775
|
+
},
|
|
776
|
+
getPostDangerously: {
|
|
777
|
+
path: '/:id/dangerous',
|
|
778
|
+
method: 'GET',
|
|
779
|
+
responses: { 200: c.type<{ id: string }>() },
|
|
780
|
+
validateResponseOnClient: false,
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
{ validateResponseOnClient: true },
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
expect(postsContract.getPost).toHaveProperty(
|
|
787
|
+
'validateResponseOnClient',
|
|
788
|
+
true,
|
|
789
|
+
);
|
|
790
|
+
expect(postsContract.getPostDangerously).toHaveProperty(
|
|
791
|
+
'validateResponseOnClient',
|
|
792
|
+
false,
|
|
793
|
+
);
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
it('should set type correctly for non-json response', () => {
|
|
798
|
+
const contract = c.router({
|
|
799
|
+
getCss: {
|
|
800
|
+
method: 'GET',
|
|
801
|
+
path: '/style.css',
|
|
802
|
+
responses: {
|
|
803
|
+
200: c.otherResponse({
|
|
804
|
+
contentType: 'text/css',
|
|
805
|
+
body: c.type<string>(),
|
|
806
|
+
}),
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
expect(contract.getCss.responses['200']).toEqual({
|
|
812
|
+
contentType: 'text/css',
|
|
813
|
+
body: ContractPlainTypeRuntimeSymbol,
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
type ResponseType = Expect<
|
|
817
|
+
Equal<
|
|
818
|
+
(typeof contract.getCss.responses)['200'],
|
|
819
|
+
ContractOtherResponse<ContractPlainType<string>>
|
|
820
|
+
>
|
|
821
|
+
>;
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
it('should set type correctly for no body', () => {
|
|
825
|
+
const contract = c.router({
|
|
826
|
+
get: {
|
|
827
|
+
method: 'GET',
|
|
828
|
+
path: '/',
|
|
829
|
+
responses: {
|
|
830
|
+
204: c.noBody(),
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
type ResponseType = Expect<
|
|
836
|
+
Equal<(typeof contract.get.responses)['204'], ContractNoBodyType>
|
|
837
|
+
>;
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it('should be typed correctly with merged common responses', () => {
|
|
841
|
+
const contract = c.router(
|
|
842
|
+
{
|
|
843
|
+
posts: {
|
|
844
|
+
getPost: {
|
|
845
|
+
method: 'GET',
|
|
846
|
+
path: '/posts/:id',
|
|
847
|
+
responses: {
|
|
848
|
+
200: z.object({
|
|
849
|
+
id: z.number(),
|
|
850
|
+
}),
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
commonResponses: {
|
|
857
|
+
404: z.object({
|
|
858
|
+
message: z.string(),
|
|
859
|
+
}),
|
|
860
|
+
},
|
|
861
|
+
},
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
type ContractShape = Expect<
|
|
865
|
+
Equal<
|
|
866
|
+
typeof contract,
|
|
867
|
+
{
|
|
868
|
+
posts: {
|
|
869
|
+
getPost: {
|
|
870
|
+
method: 'GET';
|
|
871
|
+
path: '/posts/:id';
|
|
872
|
+
responses: {
|
|
873
|
+
200: z.ZodObject<
|
|
874
|
+
{
|
|
875
|
+
id: z.ZodNumber;
|
|
876
|
+
},
|
|
877
|
+
z.core.$strip
|
|
878
|
+
>;
|
|
879
|
+
404: z.ZodObject<
|
|
880
|
+
{
|
|
881
|
+
message: z.ZodString;
|
|
882
|
+
},
|
|
883
|
+
z.core.$strip
|
|
884
|
+
>;
|
|
885
|
+
};
|
|
886
|
+
};
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
>
|
|
890
|
+
>;
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
it('should be typed correctly with merged common responses and overriding common response', () => {
|
|
894
|
+
const contract = c.router(
|
|
895
|
+
{
|
|
896
|
+
posts: {
|
|
897
|
+
getPost: {
|
|
898
|
+
method: 'GET',
|
|
899
|
+
path: '/posts/:id',
|
|
900
|
+
responses: {
|
|
901
|
+
200: z.object({
|
|
902
|
+
id: z.number(),
|
|
903
|
+
}),
|
|
904
|
+
400: z.object({
|
|
905
|
+
overrideReason: z.string(),
|
|
906
|
+
}),
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
commonResponses: {
|
|
913
|
+
400: z.object({
|
|
914
|
+
reason: z.string(),
|
|
915
|
+
}),
|
|
916
|
+
404: z.object({
|
|
917
|
+
message: z.string(),
|
|
918
|
+
}),
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
type ContractShape = Expect<
|
|
924
|
+
Equal<
|
|
925
|
+
typeof contract,
|
|
926
|
+
{
|
|
927
|
+
posts: {
|
|
928
|
+
getPost: {
|
|
929
|
+
method: 'GET';
|
|
930
|
+
path: '/posts/:id';
|
|
931
|
+
responses: {
|
|
932
|
+
200: z.ZodObject<
|
|
933
|
+
{
|
|
934
|
+
id: z.ZodNumber;
|
|
935
|
+
},
|
|
936
|
+
z.core.$strip
|
|
937
|
+
>;
|
|
938
|
+
400: z.ZodObject<
|
|
939
|
+
{
|
|
940
|
+
overrideReason: z.ZodString;
|
|
941
|
+
},
|
|
942
|
+
z.core.$strip
|
|
943
|
+
>;
|
|
944
|
+
404: z.ZodObject<
|
|
945
|
+
{
|
|
946
|
+
message: z.ZodString;
|
|
947
|
+
},
|
|
948
|
+
z.core.$strip
|
|
949
|
+
>;
|
|
950
|
+
};
|
|
951
|
+
};
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
>
|
|
955
|
+
>;
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
describe('header types', () => {
|
|
960
|
+
it('should handle two zod objects', () => {
|
|
961
|
+
const leftSchema = {
|
|
962
|
+
left: z.string(),
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
const rightSchema = {
|
|
966
|
+
right: z.string(),
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
970
|
+
type TestResult = Expect<
|
|
971
|
+
Equal<
|
|
972
|
+
Result,
|
|
973
|
+
{
|
|
974
|
+
left: z.ZodString;
|
|
975
|
+
right: z.ZodString;
|
|
976
|
+
}
|
|
977
|
+
>
|
|
978
|
+
>;
|
|
979
|
+
});
|
|
980
|
+
it('should handle left being undefined', () => {
|
|
981
|
+
const leftSchema = undefined;
|
|
982
|
+
const rightSchema = {
|
|
983
|
+
right: z.string(),
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
987
|
+
type TestResult = Expect<
|
|
988
|
+
Equal<
|
|
989
|
+
Result,
|
|
990
|
+
{
|
|
991
|
+
right: z.ZodString;
|
|
992
|
+
}
|
|
993
|
+
>
|
|
994
|
+
>;
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
it('should handle right being undefined', () => {
|
|
998
|
+
const leftSchema = {
|
|
999
|
+
left: z.string(),
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
const rightSchema = undefined;
|
|
1003
|
+
|
|
1004
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1005
|
+
type TestResult = Expect<
|
|
1006
|
+
Equal<
|
|
1007
|
+
Result,
|
|
1008
|
+
{
|
|
1009
|
+
left: z.ZodString;
|
|
1010
|
+
}
|
|
1011
|
+
>
|
|
1012
|
+
>;
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
it('should handle both being undefined', () => {
|
|
1016
|
+
const leftSchema = undefined;
|
|
1017
|
+
const rightSchema = undefined;
|
|
1018
|
+
|
|
1019
|
+
type Resut = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1020
|
+
type TestResult = Expect<Equal<Resut, unknown>>;
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it('should handle two records', () => {
|
|
1024
|
+
const leftSchema = {
|
|
1025
|
+
left: z.string(),
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const rightSchema = {
|
|
1029
|
+
right: z.string(),
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1033
|
+
|
|
1034
|
+
type TestResult = Expect<
|
|
1035
|
+
Equal<
|
|
1036
|
+
Result,
|
|
1037
|
+
{
|
|
1038
|
+
left: z.ZodString;
|
|
1039
|
+
right: z.ZodString;
|
|
1040
|
+
}
|
|
1041
|
+
>
|
|
1042
|
+
>;
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
it('should handle left undefined and right record', () => {
|
|
1046
|
+
const leftSchema = undefined;
|
|
1047
|
+
const rightSchema = {
|
|
1048
|
+
right: z.string(),
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1052
|
+
|
|
1053
|
+
type TestResult = Expect<
|
|
1054
|
+
Equal<
|
|
1055
|
+
Result,
|
|
1056
|
+
{
|
|
1057
|
+
right: z.ZodString;
|
|
1058
|
+
}
|
|
1059
|
+
>
|
|
1060
|
+
>;
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
it('should handle left record and right undefined', () => {
|
|
1064
|
+
const leftSchema = {
|
|
1065
|
+
left: z.string(),
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
const rightSchema = undefined;
|
|
1069
|
+
|
|
1070
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1071
|
+
|
|
1072
|
+
type TestResult = Expect<Equal<Result, { left: z.ZodString }>>; // left wins
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
it('should handle right null and left record', () => {
|
|
1076
|
+
const leftSchema = {
|
|
1077
|
+
left: z.string(),
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
const rightSchema = {
|
|
1081
|
+
left: null,
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1085
|
+
|
|
1086
|
+
type TestResult = Expect<Equal<Result, {}>>; // correctly unset 'left'
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
it('should handle left null and right record', () => {
|
|
1090
|
+
const leftSchema = {
|
|
1091
|
+
left: null,
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
const rightSchema = {
|
|
1095
|
+
right: z.string(),
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1099
|
+
|
|
1100
|
+
type TestResult = Expect<Equal<Result, { right: z.ZodString }>>; // right wins
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
it('should handle both null', () => {
|
|
1104
|
+
const leftSchema = {
|
|
1105
|
+
left: null,
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
const rightSchema = {
|
|
1109
|
+
right: null,
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1113
|
+
|
|
1114
|
+
type TestResult = Expect<Equal<Result, {}>>; // correctly unset 'left' and 'right'
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
it('should handle both entirely undefined', () => {
|
|
1118
|
+
const leftSchema = undefined;
|
|
1119
|
+
const rightSchema = undefined;
|
|
1120
|
+
|
|
1121
|
+
type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
|
|
1122
|
+
|
|
1123
|
+
type TestResult = Expect<Equal<Result, unknown>>;
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
describe('InferHeadersInput', () => {
|
|
1127
|
+
it('should handle undefined', () => {
|
|
1128
|
+
const route = c.router({
|
|
1129
|
+
getPost: {
|
|
1130
|
+
method: 'GET',
|
|
1131
|
+
path: '/posts/:id',
|
|
1132
|
+
responses: {
|
|
1133
|
+
200: c.type<{ id: number }>(),
|
|
1134
|
+
},
|
|
1135
|
+
headers: undefined,
|
|
1136
|
+
},
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
type Actual = InferHeadersInput<typeof route.getPost>;
|
|
1140
|
+
type TestResult = Expect<Equal<Actual, undefined>>;
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
it('should handle empty object', () => {
|
|
1144
|
+
const route = c.router({
|
|
1145
|
+
getPost: {
|
|
1146
|
+
method: 'GET',
|
|
1147
|
+
path: '/posts/:id',
|
|
1148
|
+
headers: {},
|
|
1149
|
+
responses: {
|
|
1150
|
+
200: c.type<{ id: number }>(),
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
type Actual = InferHeadersInput<typeof route.getPost>;
|
|
1156
|
+
type TestResult = Expect<Equal<Actual, {}>>;
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
it('should handle zod object', () => {
|
|
1160
|
+
const route = c.router({
|
|
1161
|
+
getPost: {
|
|
1162
|
+
method: 'GET',
|
|
1163
|
+
path: '/posts/:id',
|
|
1164
|
+
headers: {
|
|
1165
|
+
'x-foo': z.string(),
|
|
1166
|
+
},
|
|
1167
|
+
responses: {
|
|
1168
|
+
200: c.type<{ id: number }>(),
|
|
1169
|
+
},
|
|
1170
|
+
},
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
type Actual = InferHeadersInput<typeof route.getPost>;
|
|
1174
|
+
type TestResult = Expect<Equal<Actual, { 'x-foo': string }>>;
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it('should lowercase keys', () => {
|
|
1178
|
+
const route = c.router({
|
|
1179
|
+
getPost: {
|
|
1180
|
+
method: 'GET',
|
|
1181
|
+
path: '/posts/:id',
|
|
1182
|
+
headers: {
|
|
1183
|
+
'X-FOO': z.string(),
|
|
1184
|
+
},
|
|
1185
|
+
responses: {
|
|
1186
|
+
200: c.type<{ id: number }>(),
|
|
1187
|
+
},
|
|
1188
|
+
},
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
type Actual = InferHeadersInput<typeof route.getPost>;
|
|
1192
|
+
type TestResult = Expect<Equal<Actual, { 'x-foo': string }>>;
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
it('should handle standard schema', () => {
|
|
1196
|
+
const route = c.router({
|
|
1197
|
+
getPost: {
|
|
1198
|
+
method: 'GET',
|
|
1199
|
+
path: '/posts/:id',
|
|
1200
|
+
headers: {
|
|
1201
|
+
'x-foo': v.string(),
|
|
1202
|
+
},
|
|
1203
|
+
responses: {
|
|
1204
|
+
200: c.type<{ id: number }>(),
|
|
1205
|
+
},
|
|
1206
|
+
},
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
type Actual = InferHeadersInput<typeof route.getPost>;
|
|
1210
|
+
type TestResult = Expect<Equal<Actual, { 'x-foo': string }>>;
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
it('should handle mixed object and zod', () => {
|
|
1214
|
+
const route = c.router({
|
|
1215
|
+
getPost: {
|
|
1216
|
+
method: 'GET',
|
|
1217
|
+
path: '/posts/:id',
|
|
1218
|
+
headers: {
|
|
1219
|
+
'x-foo': v.string(),
|
|
1220
|
+
'x-bar': z.string(),
|
|
1221
|
+
},
|
|
1222
|
+
responses: {
|
|
1223
|
+
200: c.type<{ id: number }>(),
|
|
1224
|
+
},
|
|
1225
|
+
},
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
type Actual = InferHeadersInput<typeof route.getPost>;
|
|
1229
|
+
type TestResult = Expect<
|
|
1230
|
+
Equal<Actual, { 'x-foo': string; 'x-bar': string }>
|
|
1231
|
+
>;
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
it('should handle optional headers', () => {
|
|
1235
|
+
const route = c.router({
|
|
1236
|
+
getPost: {
|
|
1237
|
+
method: 'GET',
|
|
1238
|
+
path: '/posts/:id',
|
|
1239
|
+
headers: {
|
|
1240
|
+
'x-foo': v.optional(v.string()),
|
|
1241
|
+
},
|
|
1242
|
+
responses: {
|
|
1243
|
+
200: c.type<{ id: number }>(),
|
|
1244
|
+
},
|
|
1245
|
+
},
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
const headers = {
|
|
1249
|
+
'x-foo': v.optional(v.string()),
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
type Actual = InferHeadersInput<typeof route.getPost, typeof headers>;
|
|
1253
|
+
type TestResult = Expect<Equal<Actual, { 'x-foo'?: string | undefined }>>;
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
it('should handle optional headers with base headers', () => {
|
|
1257
|
+
const route = c.router(
|
|
1258
|
+
{
|
|
1259
|
+
getPost: {
|
|
1260
|
+
method: 'GET',
|
|
1261
|
+
path: '/posts/:id',
|
|
1262
|
+
headers: {
|
|
1263
|
+
'x-foo': v.string(),
|
|
1264
|
+
},
|
|
1265
|
+
responses: {
|
|
1266
|
+
200: c.type<{ id: number }>(),
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
baseHeaders: {
|
|
1272
|
+
'x-bar': v.string(),
|
|
1273
|
+
},
|
|
1274
|
+
},
|
|
1275
|
+
);
|
|
1276
|
+
|
|
1277
|
+
type Actual = InferHeadersInput<typeof route.getPost>;
|
|
1278
|
+
type TestResult = Expect<
|
|
1279
|
+
Equal<Actual, { 'x-foo': string; 'x-bar': string }>
|
|
1280
|
+
>;
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
it('should unset header with null', () => {
|
|
1284
|
+
const route = c.router(
|
|
1285
|
+
{
|
|
1286
|
+
getPost: {
|
|
1287
|
+
method: 'GET',
|
|
1288
|
+
path: '/posts/:id',
|
|
1289
|
+
headers: {
|
|
1290
|
+
'x-foo': null,
|
|
1291
|
+
},
|
|
1292
|
+
responses: {
|
|
1293
|
+
200: c.type<{ id: number }>(),
|
|
1294
|
+
},
|
|
1295
|
+
},
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
baseHeaders: {
|
|
1299
|
+
'x-foo': v.string(),
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
type Actual = InferHeadersInput<typeof route.getPost>;
|
|
1305
|
+
type TestResult = Expect<Equal<Actual, {}>>;
|
|
1306
|
+
});
|
|
1307
|
+
});
|
|
1308
|
+
});
|