galbe 0.12.0 → 0.12.2

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.
Files changed (42) hide show
  1. package/bin/commands/generate/code/openapi.parser.ts +2 -2
  2. package/package.json +6 -1
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -35
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  5. package/.github/workflows/build_test.yml +0 -17
  6. package/.github/workflows/deploy_website.yml +0 -20
  7. package/.github/workflows/release.yml +0 -39
  8. package/.prettierrc +0 -10
  9. package/bun.lock +0 -904
  10. package/bunfig.toml +0 -2
  11. package/docs/CONTRIBUTING.md +0 -105
  12. package/docs/cli.md +0 -343
  13. package/docs/configuration.md +0 -80
  14. package/docs/context.md +0 -104
  15. package/docs/error-handler.md +0 -54
  16. package/docs/getting-started.md +0 -164
  17. package/docs/handler.md +0 -119
  18. package/docs/hooks.md +0 -90
  19. package/docs/plugins.md +0 -146
  20. package/docs/router.md +0 -10
  21. package/docs/routes.md +0 -133
  22. package/docs/schemas.md +0 -327
  23. package/test/hooks.test.ts +0 -200
  24. package/test/parser.test.ts +0 -1358
  25. package/test/plugins.test.ts +0 -239
  26. package/test/requests.test.ts +0 -917
  27. package/test/resources/image.png +0 -0
  28. package/test/resources/object.badSyntax.json +0 -8
  29. package/test/resources/object.json +0 -8
  30. package/test/resources/object.missing.json +0 -6
  31. package/test/resources/static/chameleon.png +0 -0
  32. package/test/resources/static/index.html +0 -13
  33. package/test/resources/static/sub/index.html +0 -13
  34. package/test/resources/static/sub/other.html +0 -13
  35. package/test/resources/test.route.comment.ts +0 -58
  36. package/test/resources/test.route.empty.ts +0 -9
  37. package/test/responses.test.ts +0 -483
  38. package/test/routeFiles.test.ts +0 -239
  39. package/test/router.test.ts +0 -231
  40. package/test/test.utils.ts +0 -109
  41. package/test/types.test.ts +0 -909
  42. package/tsconfig.json +0 -23
@@ -1,909 +0,0 @@
1
- import { $T, Galbe } from '../src'
2
- import type { Static } from '../src/schema'
3
- import { describe, test, expect, beforeAll } from 'bun:test'
4
- import { formdata } from './test.utils'
5
-
6
- // Test utils
7
- type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
8
- type Expect<T extends true> = T
9
- type Extends<A, B> = A extends B ? true : false
10
-
11
- // Basic Schema types
12
-
13
- const any = $T.any()
14
- const nil = $T.null()
15
- const bool = $T.boolean()
16
- const num = $T.number()
17
- const int = $T.integer()
18
- const str = $T.string()
19
- const literal = $T.literal('foo')
20
- const ba = $T.byteArray()
21
- const obj = $T.object()
22
-
23
- type _any = Expect<Extends<Static<typeof any>, any>>
24
- type _nil = Expect<Extends<Static<typeof nil>, null>>
25
- type _bool = Expect<Extends<Static<typeof bool>, boolean>>
26
- type _num = Expect<Extends<Static<typeof num>, number>>
27
- type _int = Expect<Extends<Static<typeof int>, number>>
28
- type _str = Expect<Extends<Static<typeof str>, string>>
29
- type _literal = Expect<Extends<Static<typeof literal>, 'foo'>>
30
- type _ba = Expect<Extends<Static<typeof ba>, Uint8Array<ArrayBufferLike>>>
31
- type _obj = Expect<Extends<Static<typeof obj>, Record<string | number, any>>>
32
-
33
- // Basic schema wrappers
34
-
35
- const opt_str = $T.optional($T.string())
36
- const null_str = $T.nullable($T.string())
37
- const nullish_str = $T.nullish($T.string())
38
-
39
- type _opt_str = Expect<Extends<Static<typeof opt_str>, string | undefined>>
40
- type _null_str = Expect<Extends<Static<typeof null_str>, string | null>>
41
- type _nullish_str = Expect<Extends<Static<typeof nullish_str>, string | null | undefined>>
42
-
43
- // Array schema type
44
-
45
- const arr_nil = $T.array($T.null())
46
- const arr_bool = $T.array($T.boolean())
47
- const arr_num = $T.array($T.number())
48
- const arr_int = $T.array($T.integer())
49
- const arr_str = $T.array($T.string())
50
- const arr_obj = $T.array($T.object({ foo: $T.literal(42) }))
51
- const arr_arr = $T.array($T.array($T.literal('hello')))
52
-
53
- const arr_union = $T.array($T.union([$T.literal('hello'), $T.literal('mom')]))
54
- const arr_intersection = $T.array($T.intersection([$T.object({ foo: $T.string() }), $T.object({ bar: $T.number() })]))
55
-
56
- type _arr_nil = Expect<Extends<Static<typeof arr_nil>, null[]>>
57
- type _arr_bool = Expect<Extends<Static<typeof arr_bool>, boolean[]>>
58
- type _arr_num = Expect<Extends<Static<typeof arr_num>, number[]>>
59
- type _arr_int = Expect<Extends<Static<typeof arr_int>, number[]>>
60
- type _arr_str = Expect<Extends<Static<typeof arr_str>, string[]>>
61
- type _arr_obj = Expect<Extends<Static<typeof arr_obj>, { foo: 42 }[]>>
62
- type _arr_arr = Expect<Extends<Static<typeof arr_arr>, 'hello'[][]>>
63
-
64
- type _arr_union = Expect<Extends<Static<typeof arr_union>, ('hello' | 'mom')[]>>
65
- type _arr_intersection = Expect<Extends<Static<typeof arr_intersection>, ({ foo: string } & { bar: number })[]>>
66
-
67
- // Object schema type
68
-
69
- const obj_1 = $T.object({
70
- any: $T.any(),
71
- nil: $T.null(),
72
- bool: $T.boolean(),
73
- num: $T.number(),
74
- int: $T.integer(),
75
- str: $T.string(),
76
- literal: $T.literal('foo'),
77
- ba: $T.byteArray(),
78
- arr: $T.array($T.string()),
79
- obj: $T.object({
80
- foo: $T.string(),
81
- }),
82
- })
83
-
84
- type _obj_1 = Expect<
85
- Equal<
86
- Static<typeof obj_1>,
87
- {
88
- literal: 'foo'
89
- any: any
90
- nil: null
91
- bool: boolean
92
- num: number
93
- int: number
94
- str: string
95
- ba: Uint8Array<ArrayBufferLike>
96
- arr: string[]
97
- obj: {
98
- foo: string
99
- }
100
- }
101
- >
102
- >
103
-
104
- // Union type
105
-
106
- const union_str_number = $T.union([$T.string(), $T.number()])
107
- const union_str_any = $T.union([$T.string(), $T.any()])
108
- const union_union_number = $T.union([$T.union([$T.literal('foo'), $T.literal('bar')]), $T.literal('baz')])
109
- const union_intersection = $T.union([
110
- $T.intersection([$T.object({ foo: $T.literal(42) }), $T.object({ bar: $T.number() })]),
111
- $T.object({ test: $T.literal('test') }),
112
- ])
113
-
114
- type _union_str_number = Expect<Extends<Static<typeof union_str_number>, string | number>>
115
- type _union_str_any = Expect<Extends<Static<typeof union_str_any>, any>>
116
- type _union_union_number = Expect<Extends<Static<typeof union_union_number>, 'foo' | 'bar' | 'baz'>>
117
- type _union_intersection = Expect<
118
- Extends<Static<typeof union_intersection>, ({ foo: 42 } & { bar: number }) | { test: 'test' }>
119
- >
120
-
121
- // Intersection type
122
-
123
- const intersection_obj_obj = $T.intersection([$T.object({ foo: $T.string() }), $T.object({ bar: $T.boolean() })])
124
- const intersection_union_obj = $T.intersection([
125
- $T.union([$T.object({ foo: $T.string() }), $T.object({ bar: $T.boolean() })]),
126
- $T.object({ baz: $T.literal(42) }),
127
- ])
128
- const intersection_intersection_obj = $T.intersection([
129
- $T.intersection([$T.object({ foo: $T.string() }), $T.object({ bar: $T.boolean() })]),
130
- $T.object({ baz: $T.literal('42') }),
131
- ])
132
-
133
- type _intersection_obj_obj = Expect<Extends<Static<typeof intersection_obj_obj>, { foo: string } & { bar: boolean }>>
134
- type _intersection_union_obj = Expect<
135
- Extends<Static<typeof intersection_union_obj>, ({ foo: string } | { bar: boolean }) & { baz: 42 }>
136
- >
137
- type _intersection_intersection_obj = Expect<
138
- Extends<Static<typeof intersection_intersection_obj>, { foo: string } & { bar: boolean } & { baz: '42' }>
139
- >
140
-
141
- // Stream types
142
-
143
- const stream_ba = $T.stream($T.byteArray())
144
- const stream_str = $T.stream($T.string())
145
- const stream_obj = $T.stream(
146
- $T.object({ foo: $T.literal('hi'), bar: $T.number(), obj: $T.object({ nested: $T.boolean() }) })
147
- )
148
- const stream_multipart = $T.stream(
149
- $T.multipartForm({ foo: $T.literal('hi'), bar: $T.number(), obj: $T.object({ nested: $T.boolean() }) })
150
- )
151
- const stream_union = $T.stream($T.union([$T.object({ foo: $T.string() }), $T.object({ bar: $T.boolean() })]))
152
- const stream_intersection = $T.stream(
153
- $T.intersection([$T.object({ foo: $T.string() }), $T.object({ bar: $T.boolean() })])
154
- )
155
-
156
- type _stream_ba = Expect<Extends<Static<typeof stream_ba>, AsyncGenerator<Uint8Array<ArrayBufferLike>>>>
157
- type _stream_str = Expect<Extends<Static<typeof stream_str>, AsyncGenerator<string>>>
158
- type _stream_obj = Expect<
159
- Extends<
160
- Static<typeof stream_obj>,
161
- AsyncGenerator<
162
- | ['foo', 'hi']
163
- | ['bar', number]
164
- | [
165
- 'obj',
166
- {
167
- nested: boolean
168
- }
169
- ]
170
- >
171
- >
172
- >
173
- type _stream_multipart = Expect<
174
- Extends<
175
- Static<typeof stream_multipart>,
176
- AsyncGenerator<
177
- | {
178
- headers: {
179
- type?: string
180
- name: 'foo'
181
- filename?: string
182
- }
183
- content: 'hi'
184
- }
185
- | {
186
- headers: {
187
- type?: string
188
- name: 'bar'
189
- filename?: string
190
- }
191
- content: number
192
- }
193
- | {
194
- headers: {
195
- type?: string
196
- name: 'obj'
197
- filename?: string
198
- }
199
- content: {
200
- nested: boolean
201
- }
202
- }
203
- >
204
- >
205
- >
206
- type _stream_union = Expect<Extends<Static<typeof stream_union>, AsyncGenerator<['foo', string] | ['bar', boolean]>>>
207
- type _stream_intersection = Expect<
208
- Extends<Static<typeof stream_intersection>, AsyncGenerator<['foo', string] | ['bar', boolean]>>
209
- >
210
-
211
- // Endpoints
212
-
213
- const g = new Galbe()
214
-
215
- // Path params
216
-
217
- g.get('/params/noschema/:param1/and/:param2', ctx => {
218
- const { params } = ctx
219
- type _ep_params = Expect<Equal<typeof params, { param1: string; param2: string }>>
220
- ctx.set.status = typeof params.param1 === 'string' && typeof params.param2 === 'string' ? 200 : 500
221
- })
222
-
223
- g.get(
224
- '/params/schema/:bool/:num/:int/:str/:literal',
225
- { params: { bool: $T.boolean(), num: $T.number(), int: $T.integer(), str: $T.string(), literal: $T.literal(42) } },
226
- ctx => {
227
- const { params } = ctx
228
- type _ep_params = Expect<
229
- Equal<typeof params, { bool: boolean; num: number; int: number; str: string; literal: 42 }>
230
- >
231
- ctx.set.status =
232
- typeof params.bool === 'boolean' &&
233
- typeof params.num === 'number' &&
234
- typeof params.int === 'number' &&
235
- typeof params.str === 'string' &&
236
- params.literal === 42
237
- ? 200
238
- : 500
239
- }
240
- )
241
-
242
- // Query params
243
-
244
- g.get('/query', ctx => {
245
- const { query } = ctx
246
- type _ep_params = Expect<Equal<typeof query, Record<string | number | symbol, any>>>
247
- })
248
-
249
- g.get(
250
- '/query/schema',
251
- {
252
- query: {
253
- bool: $T.boolean(),
254
- num: $T.number(),
255
- int: $T.integer(),
256
- str: $T.string(),
257
- literal: $T.literal('foo'),
258
- opt: $T.optional($T.literal('opt')),
259
- },
260
- },
261
- ctx => {
262
- const { query } = ctx
263
- type _ep_query = Expect<
264
- Equal<typeof query, { bool: boolean; num: number; int: number; str: string; literal: 'foo'; opt?: 'opt' }>
265
- >
266
- ctx.set.status =
267
- typeof query.bool === 'boolean' &&
268
- typeof query.num === 'number' &&
269
- typeof query.int === 'number' &&
270
- typeof query.str === 'string' &&
271
- (query.opt !== undefined ? query.opt === 'opt' : true) &&
272
- query.literal === 'foo'
273
- ? 200
274
- : 500
275
- }
276
- )
277
-
278
- // Body
279
-
280
- // Null bodies
281
- g.get('/body', ctx => {
282
- const { body } = ctx
283
- type _ep_body = Expect<Equal<typeof body, null>>
284
- ctx.set.status = body === null ? 200 : 500
285
- })
286
- g.get('/body/schema', { body: $T.string() }, ctx => {
287
- const { body } = ctx
288
- type _ep_body = Expect<Equal<typeof body, null>>
289
- ctx.set.status = body === null ? 200 : 500
290
- })
291
- g.options('/body', ctx => {
292
- const { body } = ctx
293
- type _ep_body = Expect<Equal<typeof body, null>>
294
- ctx.set.status = body === null ? 200 : 500
295
- })
296
- g.options('/body/schema', { body: $T.string() }, ctx => {
297
- const { body } = ctx
298
- type _ep_body = Expect<Equal<typeof body, null>>
299
- ctx.set.status = body === null ? 200 : 500
300
- })
301
- g.head('/body', ctx => {
302
- const { body } = ctx
303
- type _ep_body = Expect<Equal<typeof body, null>>
304
- ctx.set.status = body === null ? 200 : 500
305
- })
306
- g.head('/body/schema', { body: $T.string() }, ctx => {
307
- const { body } = ctx
308
- type _ep_body = Expect<Equal<typeof body, null>>
309
- ctx.set.status = body === null ? 200 : 500
310
- })
311
-
312
- g.post('/body/post', ctx => {
313
- const { body } = ctx
314
- type _ep_body = Expect<Equal<typeof body, any>>
315
- })
316
- g.patch('/body/patch', ctx => {
317
- const { body } = ctx
318
- type _ep_body = Expect<Equal<typeof body, any>>
319
- })
320
-
321
- g.post('/body/post', { body: $T.null() }, ctx => {
322
- const { body } = ctx
323
- type _ep_body = Expect<Equal<typeof body, null>>
324
- ctx.set.status = body === null ? 200 : 500
325
- })
326
- g.patch('/body/patch', { body: $T.null() }, ctx => {
327
- const { body } = ctx
328
- type _ep_body = Expect<Equal<typeof body, null>>
329
- ctx.set.status = body === null ? 200 : 500
330
- })
331
-
332
- // Body ByteArray
333
-
334
- g.post('/body/ba', { body: { byteArray: $T.byteArray() } }, ctx => {
335
- const { body, contentType } = ctx
336
- type _ep_body = Expect<Equal<typeof body, Uint8Array<ArrayBufferLike>>>
337
- type _ep_contentType = Expect<Extends<typeof contentType, 'byteArray'>>
338
- ctx.set.status = body instanceof Uint8Array ? 200 : 500
339
- })
340
-
341
- g.post('/body/ba/stream', { body: { byteArray: $T.stream($T.byteArray()) } }, async ctx => {
342
- const { body, contentType } = ctx
343
- type _ep_body = Expect<Extends<typeof body, AsyncGenerator<Uint8Array<ArrayBufferLike>>>>
344
- type _ep_contentType = Expect<Extends<typeof contentType, 'byteArray'>>
345
- for await (const bp of body) {
346
- if (body instanceof Uint8Array) ctx.set.status = 500
347
- }
348
- })
349
-
350
- // Body Text
351
-
352
- g.post('/body/text/str', { body: { text: $T.string() } }, ctx => {
353
- const { body, contentType } = ctx
354
- type _ep_body = Expect<Equal<typeof body, string>>
355
- type _ep_contentType = Expect<Extends<typeof contentType, 'text'>>
356
- ctx.set.status = typeof body === 'string' ? 200 : 500
357
- })
358
-
359
- g.post('/body/text/literal', { body: { text: $T.literal('foo') } }, ctx => {
360
- const { body, contentType } = ctx
361
- type _ep_body = Expect<Equal<typeof body, 'foo'>>
362
- type _ep_contentType = Expect<Extends<typeof contentType, 'text'>>
363
- ctx.set.status = body === 'foo' ? 200 : 500
364
- })
365
-
366
- g.post('/body/text/bool', { body: { text: $T.boolean() } }, ctx => {
367
- const { body, contentType } = ctx
368
- type _ep_body = Expect<Equal<typeof body, boolean>>
369
- type _ep_contentType = Expect<Extends<typeof contentType, 'text'>>
370
- ctx.set.status = typeof body === 'boolean' ? 200 : 500
371
- })
372
-
373
- g.post('/body/text/num', { body: { text: $T.number() } }, ctx => {
374
- const { body, contentType } = ctx
375
- type _ep_body = Expect<Equal<typeof body, number>>
376
- type _ep_contentType = Expect<Extends<typeof contentType, 'text'>>
377
- ctx.set.status = typeof body === 'number' ? 200 : 500
378
- })
379
-
380
- g.post('/body/text/int', { body: { text: $T.integer() } }, ctx => {
381
- const { body, contentType } = ctx
382
- type _ep_body = Expect<Equal<typeof body, number>>
383
- type _ep_contentType = Expect<Extends<typeof contentType, 'text'>>
384
- ctx.set.status = typeof body === 'number' ? 200 : 500
385
- })
386
-
387
- g.post('/body/text/union', { body: { text: $T.union([$T.literal('foo'), $T.number()]) } }, ctx => {
388
- const { body, contentType } = ctx
389
- type _ep_body = Expect<Equal<typeof body, 'foo' | number>>
390
- type _ep_contentType = Expect<Extends<typeof contentType, 'text'>>
391
- ctx.set.status = body === 'foo' || typeof body === 'number' ? 200 : 500
392
- })
393
-
394
- g.post('/body/text/stream', { body: { text: $T.stream($T.string()) } }, async ctx => {
395
- const { body, contentType } = ctx
396
- type _ep_body = Expect<Extends<typeof body, AsyncGenerator<string>>>
397
- type _ep_contentType = Expect<Extends<typeof contentType, 'text'>>
398
- for await (const chunk of body) if (typeof chunk !== 'string') ctx.set.status = 500
399
- })
400
-
401
- // Body Json
402
-
403
- g.post('/body/json/bool', { body: { json: $T.boolean() } }, ctx => {
404
- const { body, contentType } = ctx
405
- type _ep_body = Expect<Extends<typeof body, boolean>>
406
- type _ep_contentType = Expect<Extends<typeof contentType, 'json'>>
407
- ctx.set.status = typeof body === 'boolean' ? 200 : 500
408
- })
409
-
410
- g.post('/body/json/num', { body: { json: $T.number() } }, ctx => {
411
- const { body, contentType } = ctx
412
- type _ep_body = Expect<Extends<typeof body, number>>
413
- type _ep_contentType = Expect<Extends<typeof contentType, 'json'>>
414
- ctx.set.status = typeof body === 'number' ? 200 : 500
415
- })
416
-
417
- g.post('/body/json/int', { body: { json: $T.integer() } }, ctx => {
418
- const { body, contentType } = ctx
419
- type _ep_body = Expect<Extends<typeof body, number>>
420
- type _ep_contentType = Expect<Extends<typeof contentType, 'json'>>
421
- ctx.set.status = typeof body === 'number' ? 200 : 500
422
- })
423
-
424
- g.post('/body/json/str', { body: { json: $T.string() } }, ctx => {
425
- const { body, contentType } = ctx
426
- type _ep_body = Expect<Extends<typeof body, string>>
427
- type _ep_contentType = Expect<Extends<typeof contentType, 'json'>>
428
- ctx.set.status = typeof body === 'string' ? 200 : 500
429
- })
430
-
431
- g.post('/body/json/arr', { body: { json: $T.array($T.string()) } }, ctx => {
432
- const { body, contentType } = ctx
433
- type _ep_body = Expect<Extends<typeof body, string[]>>
434
- type _ep_contentType = Expect<Extends<typeof contentType, 'json'>>
435
- ctx.set.status = Array.isArray(body) && body.every(i => typeof i === 'string') ? 200 : 500
436
- })
437
-
438
- g.post('/body/json/union', { body: { json: $T.union([$T.boolean(), $T.string()]) } }, ctx => {
439
- const { body, contentType } = ctx
440
- type _ep_body = Expect<Extends<typeof body, boolean | string>>
441
- type _ep_contentType = Expect<Extends<typeof contentType, 'json'>>
442
- ctx.set.status = typeof body === 'string' || typeof body === 'boolean' ? 200 : 500
443
- })
444
-
445
- g.post('/body/json/obj', { body: { json: $T.object({ foo: $T.string() }) } }, ctx => {
446
- const { body, contentType } = ctx
447
- type _ep_body = Expect<Extends<typeof body, { foo: string }>>
448
- type _ep_contentType = Expect<Extends<typeof contentType, 'json'>>
449
- ctx.set.status = typeof body.foo === 'string' ? 200 : 500
450
- })
451
-
452
- // Body UrlForm
453
-
454
- g.post(
455
- '/body/urlForm',
456
- { body: { urlForm: $T.object({ foo: $T.string(), bar: $T.number(), opt: $T.optional($T.literal('opt')) }) } },
457
- ctx => {
458
- const { body, contentType } = ctx
459
- type _ep_body = Expect<Equal<typeof body, { foo: string; bar: number; opt?: 'opt' }>>
460
- type _ep_contentType = Expect<Extends<typeof contentType, 'urlForm'>>
461
- ctx.set.status =
462
- typeof body.foo === 'string' && typeof body.bar === 'number' && (body.opt ? body.opt === 'opt' : true) ? 200 : 500
463
- }
464
- )
465
-
466
- g.post(
467
- '/body/urlForm/union',
468
- { body: { urlForm: $T.union([$T.object({ foo: $T.string() }), $T.object({ bar: $T.boolean() })]) } },
469
- ctx => {
470
- const { body, contentType } = ctx
471
- type _ep_body = Expect<Extends<typeof body, { foo: string } | { bar: boolean }>>
472
- type _ep_contentType = Expect<Extends<typeof contentType, 'urlForm'>>
473
- //@ts-ignore
474
- typeof body.foo ? typeof body.foo === 'string' : true && body.bar ? typeof body.bar === 'number' : true ? 200 : 500
475
- }
476
- )
477
-
478
- g.post(
479
- '/body/urlForm/stream',
480
- { body: { urlForm: $T.stream($T.object({ foo: $T.string(), bar: $T.number() })) } },
481
- async ctx => {
482
- const { body, contentType } = ctx
483
- type _ep_body = Expect<Extends<typeof body, AsyncGenerator<['foo', string] | ['bar', number]>>>
484
- type _ep_contentType = Expect<Extends<typeof contentType, 'urlForm'>>
485
- for await (const [k, v] of body) {
486
- if (k === 'foo' && typeof v !== 'string') ctx.set.status = 500
487
- if (k === 'bar' && typeof v !== 'number') ctx.set.status = 500
488
- }
489
- }
490
- )
491
-
492
- // Body MultipartForm
493
-
494
- g.post(
495
- '/body/multipart',
496
- {
497
- body: { multipart: $T.multipartForm({ foo: $T.string(), bar: $T.number(), opt: $T.optional($T.literal('opt')) }) },
498
- },
499
- ctx => {
500
- const { body, contentType } = ctx
501
- type _ep_body = Expect<
502
- Equal<
503
- typeof body,
504
- {
505
- foo: {
506
- headers: {
507
- type?: string
508
- name: 'foo'
509
- filename?: string
510
- }
511
- content: string
512
- }
513
- bar: {
514
- headers: {
515
- type?: string
516
- name: 'bar'
517
- filename?: string
518
- }
519
- content: number
520
- }
521
- opt: {
522
- headers: {
523
- type?: string
524
- name: 'opt'
525
- filename?: string
526
- }
527
- content: 'opt' | undefined
528
- }
529
- }
530
- >
531
- >
532
- type _ep_contentType = Expect<Extends<typeof contentType, 'multipart'>>
533
- if (body.foo) {
534
- if (body.foo.headers?.type && typeof body.foo.headers?.type !== 'string') ctx.set.status = 500
535
- if (body.foo.headers?.filename && typeof body.foo.headers?.filename !== 'string') ctx.set.status = 500
536
- if (body.foo.headers.name !== 'foo') ctx.set.status = 500
537
- if (typeof body.foo.content !== 'string') ctx.set.status = 500
538
- }
539
- if (body.bar) {
540
- if (body.bar.headers?.type && typeof body.bar.headers?.type !== 'string') ctx.set.status = 500
541
- if (body.bar.headers?.filename && typeof body.bar.headers?.filename !== 'string') ctx.set.status = 500
542
- if (body.bar.headers.name !== 'bar') ctx.set.status = 500
543
- if (typeof body.bar.content !== 'number') ctx.set.status = 500
544
- }
545
- if (body.opt) {
546
- if (body.opt.headers?.type && typeof body.opt.headers?.type !== 'string') ctx.set.status = 500
547
- if (body.opt.headers?.filename && typeof body.opt.headers?.filename !== 'string') ctx.set.status = 500
548
- if (body.opt.headers.name !== 'opt') ctx.set.status = 500
549
- if (body.opt.content && body.opt.content !== 'opt') ctx.set.status = 500
550
- }
551
- }
552
- )
553
-
554
- g.post(
555
- '/body/multipart/stream',
556
- {
557
- body: {
558
- multipart: $T.stream(
559
- $T.multipartForm({ foo: $T.string(), bar: $T.number(), opt: $T.optional($T.literal('opt')) })
560
- ),
561
- },
562
- },
563
- async ctx => {
564
- const { body, contentType } = ctx
565
- type _ep_body = Expect<
566
- Extends<
567
- typeof body,
568
- AsyncGenerator<
569
- | {
570
- headers: {
571
- type?: string
572
- name: 'foo'
573
- filename?: string
574
- }
575
- content: string
576
- }
577
- | {
578
- headers: {
579
- type?: string
580
- name: 'bar'
581
- filename?: string
582
- }
583
- content: number
584
- }
585
- | {
586
- headers: {
587
- type?: string
588
- name: 'opt'
589
- filename?: string
590
- }
591
- content: 'opt' | undefined
592
- }
593
- >
594
- >
595
- >
596
- type _ep_contentType = Expect<Extends<typeof contentType, 'multipart'>>
597
- for await (const mp of body) {
598
- if (mp.headers.name === 'foo') {
599
- if (mp.headers?.type && typeof mp.headers?.type !== 'string') ctx.set.status = 500
600
- if (mp.headers?.filename && typeof mp.headers?.filename !== 'string') ctx.set.status = 500
601
- if (mp.headers.name !== 'foo') ctx.set.status = 500
602
- if (typeof mp.content !== 'string') ctx.set.status = 500
603
- }
604
- if (mp.headers.name === 'bar') {
605
- if (mp.headers?.type && typeof mp.headers?.type !== 'string') ctx.set.status = 500
606
- if (mp.headers?.filename && typeof mp.headers?.filename !== 'string') ctx.set.status = 500
607
- if (mp.headers.name !== 'bar') ctx.set.status = 500
608
- if (typeof mp.content !== 'number') ctx.set.status = 500
609
- }
610
- if (mp.headers.name === 'opt') {
611
- if (mp.headers?.type && typeof mp.headers?.type !== 'string') ctx.set.status = 500
612
- if (mp.headers?.filename && typeof mp.headers?.filename !== 'string') ctx.set.status = 500
613
- if (mp.headers.name !== 'opt') ctx.set.status = 500
614
- if (mp.content && mp.content !== 'opt') ctx.set.status = 500
615
- }
616
- }
617
- }
618
- )
619
-
620
- // Body Default
621
-
622
- g.post('/body/default', { body: { default: $T.any() } }, ctx => {
623
- const { body, contentType } = ctx
624
- type _ep_body = Expect<Equal<typeof body, any>>
625
- type _ep_contentType = Expect<Extends<typeof contentType, 'default'>>
626
- })
627
-
628
- g.post('/body/default/ba', { body: { default: $T.byteArray() } }, ctx => {
629
- const { body, contentType } = ctx
630
- type _ep_body = Expect<Equal<typeof body, Uint8Array<ArrayBufferLike>>>
631
- type _ep_contentType = Expect<Extends<typeof contentType, 'default'>>
632
- })
633
-
634
- g.post('/body/default/str', { body: { default: $T.string() } }, ctx => {
635
- const { body, contentType } = ctx
636
- type _ep_body = Expect<Equal<typeof body, string>>
637
- type _ep_contentType = Expect<Extends<typeof contentType, 'default'>>
638
- })
639
-
640
- g.post('/body/default/stream/ba', { body: { default: $T.stream($T.byteArray()) } }, ctx => {
641
- const { body, contentType } = ctx
642
- type _ep_body = Expect<Equal<typeof body, AsyncGenerator<Uint8Array<ArrayBufferLike>>>>
643
- type _ep_contentType = Expect<Extends<typeof contentType, 'default'>>
644
- })
645
-
646
- g.post('/body/default/stream/str', { body: { default: $T.stream($T.string()) } }, ctx => {
647
- const { body, contentType } = ctx
648
- type _ep_body = Expect<Equal<typeof body, AsyncGenerator<string>>>
649
- type _ep_contentType = Expect<Extends<typeof contentType, 'default'>>
650
- })
651
-
652
- const port = 7362
653
- describe('types', () => {
654
- beforeAll(async () => {
655
- await g.listen(port)
656
- })
657
-
658
- test('endpoint request static types match runtime types', async () => {
659
- type Case = {
660
- body?: any
661
- method: string
662
- type?: string
663
- path: string
664
- expected: number
665
- }
666
- const cases: Case[] = [
667
- { method: 'get', path: '/params/noschema/foo/and/42', expected: 200 },
668
- { method: 'get', path: '/params/schema/true/0/1/foo/42', expected: 200 },
669
- { method: 'get', path: '/params/schema/foo/0/1/foo/42', expected: 400 },
670
- { method: 'get', path: '/params/schema/true/x/1/foo/42', expected: 400 },
671
- { method: 'get', path: '/params/schema/true/0/y/foo/42', expected: 400 },
672
- { method: 'get', path: '/params/schema/true/0/y/foo/43', expected: 400 },
673
-
674
- { method: 'get', path: '/query', expected: 200 },
675
- { method: 'get', path: '/query?anything=42', expected: 200 },
676
- { method: 'get', path: '/query/schema?bool=true&num=42&int=0&str=bar&literal=foo', expected: 200 },
677
- { method: 'get', path: '/query/schema?bool=false&num=36&int=1&str=foo&literal=foo&opt=opt', expected: 200 },
678
- { method: 'get', path: '/query/schema?bool=foo&num=42&int=0&str=bar&literal=foo&opt=opt', expected: 400 },
679
- { method: 'get', path: '/query/schema?bool=true&num=foo&int=0&str=bar&literal=foo&opt=opt', expected: 400 },
680
- { method: 'get', path: '/query/schema?bool=true&num=1&int=true&str=bar&literal=foo&opt=opt', expected: 400 },
681
- { method: 'get', path: '/query/schema?bool=true&num=1&int=0&literal=foo&opt=opt', expected: 400 },
682
- { method: 'get', path: '/query/schema?bool=true&num=1&int=0&str=str&literal=bar&opt=opt', expected: 400 },
683
- { method: 'get', path: '/query/schema?bool=true&num=1&int=0&str=str&literal=foo&opt=noop', expected: 400 },
684
-
685
- { method: 'get', path: '/body', body: null, expected: 200 },
686
- { method: 'get', path: '/body/schema', body: null, expected: 200 },
687
- { method: 'option', path: '/body', body: null, expected: 200 },
688
- { method: 'option', path: '/body/schema', body: null, expected: 200 },
689
- { method: 'head', path: '/body', body: null, expected: 200 },
690
- { method: 'head', path: '/body/schema', body: null, expected: 200 },
691
- { method: 'post', path: '/body/post', body: null, expected: 200 },
692
- { method: 'patch', path: '/body/patch', body: null, expected: 200 },
693
- { method: 'post', path: '/body/post', body: null, expected: 200 },
694
- { method: 'patch', path: '/body/patch', body: null, expected: 200 },
695
-
696
- { method: 'post', path: '/body/ba', type: 'application/octet-stream', body: new Uint8Array([]), expected: 200 },
697
- {
698
- method: 'post',
699
- path: '/body/ba/stream',
700
- type: 'application/octet-stream',
701
- body: new Uint8Array([]),
702
- expected: 200,
703
- },
704
-
705
- { method: 'post', path: '/body/text/str', type: 'text/plain', body: 'hello', expected: 200 },
706
- { method: 'post', path: '/body/text/str', type: 'text/plain', body: null, expected: 200 },
707
-
708
- { method: 'post', path: '/body/text/literal', type: 'text/plain', body: 'foo', expected: 200 },
709
- { method: 'post', path: '/body/text/literal', type: 'text/plain', body: 'bar', expected: 400 },
710
-
711
- { method: 'post', path: '/body/text/bool', type: 'text/plain', body: 'true', expected: 200 },
712
- { method: 'post', path: '/body/text/bool', type: 'text/plain', body: 'false', expected: 200 },
713
- { method: 'post', path: '/body/text/bool', type: 'text/plain', body: '0', expected: 400 },
714
-
715
- { method: 'post', path: '/body/text/num', type: 'text/plain', body: '0', expected: 200 },
716
- { method: 'post', path: '/body/text/num', type: 'text/plain', body: '-42', expected: 200 },
717
- { method: 'post', path: '/body/text/num', type: 'text/plain', body: '42.42', expected: 200 },
718
- { method: 'post', path: '/body/text/num', type: 'text/plain', body: 'true', expected: 400 },
719
- { method: 'post', path: '/body/text/num', type: 'text/plain', body: null, expected: 400 },
720
-
721
- { method: 'post', path: '/body/text/int', type: 'text/plain', body: '0', expected: 200 },
722
- { method: 'post', path: '/body/text/int', type: 'text/plain', body: '-42', expected: 200 },
723
- { method: 'post', path: '/body/text/int', type: 'text/plain', body: '42.42', expected: 400 },
724
- { method: 'post', path: '/body/text/int', type: 'text/plain', body: 'true', expected: 400 },
725
- { method: 'post', path: '/body/text/int', type: 'text/plain', body: null, expected: 400 },
726
-
727
- { method: 'post', path: '/body/text/union', type: 'text/plain', body: 'foo', expected: 200 },
728
- { method: 'post', path: '/body/text/union', type: 'text/plain', body: '42', expected: 200 },
729
- { method: 'post', path: '/body/text/union', type: 'text/plain', body: 'hello', expected: 400 },
730
- { method: 'post', path: '/body/text/stream', type: 'text/plain', body: '42424242', expected: 200 },
731
- { method: 'post', path: '/body/text/stream', type: 'text/plain', body: null, expected: 200 },
732
-
733
- { method: 'post', path: '/body/json/bool', type: 'application/json', body: 'true', expected: 200 },
734
- { method: 'post', path: '/body/json/bool', type: 'application/json', body: 'false', expected: 200 },
735
- { method: 'post', path: '/body/json/bool', type: 'application/json', body: '0', expected: 400 },
736
- { method: 'post', path: '/body/json/bool', type: 'application/json', body: '', expected: 400 },
737
- { method: 'post', path: '/body/json/bool', type: 'application/json', body: null, expected: 400 },
738
-
739
- { method: 'post', path: '/body/json/num', type: 'application/json', body: '1', expected: 200 },
740
- { method: 'post', path: '/body/json/num', type: 'application/json', body: '1.5', expected: 200 },
741
- { method: 'post', path: '/body/json/num', type: 'application/json', body: 'foo', expected: 400 },
742
- { method: 'post', path: '/body/json/num', type: 'application/json', body: '', expected: 400 },
743
- { method: 'post', path: '/body/json/num', type: 'application/json', body: null, expected: 400 },
744
-
745
- { method: 'post', path: '/body/json/int', type: 'application/json', body: '1', expected: 200 },
746
- { method: 'post', path: '/body/json/int', type: 'application/json', body: '1.5', expected: 400 },
747
- { method: 'post', path: '/body/json/int', type: 'application/json', body: 'foo', expected: 400 },
748
- { method: 'post', path: '/body/json/int', type: 'application/json', body: '', expected: 400 },
749
- { method: 'post', path: '/body/json/int', type: 'application/json', body: null, expected: 400 },
750
-
751
- { method: 'post', path: '/body/json/str', type: 'application/json', body: '"foo"', expected: 200 },
752
- { method: 'post', path: '/body/json/str', type: 'application/json', body: 'foo', expected: 400 },
753
- { method: 'post', path: '/body/json/str', type: 'application/json', body: '', expected: 400 },
754
- { method: 'post', path: '/body/json/str', type: 'application/json', body: null, expected: 400 },
755
-
756
- { method: 'post', path: '/body/json/arr', type: 'application/json', body: '[]', expected: 200 },
757
- { method: 'post', path: '/body/json/arr', type: 'application/json', body: '["foo","bar"]', expected: 200 },
758
- { method: 'post', path: '/body/json/arr', type: 'application/json', body: '["foo","bar",42]', expected: 400 },
759
- { method: 'post', path: '/body/json/arr', type: 'application/json', body: '', expected: 400 },
760
- { method: 'post', path: '/body/json/arr', type: 'application/json', body: null, expected: 400 },
761
-
762
- { method: 'post', path: '/body/json/union', type: 'application/json', body: '"foo"', expected: 200 },
763
- { method: 'post', path: '/body/json/union', type: 'application/json', body: '""', expected: 200 },
764
- { method: 'post', path: '/body/json/union', type: 'application/json', body: 'true', expected: 200 },
765
- { method: 'post', path: '/body/json/union', type: 'application/json', body: 'false', expected: 200 },
766
- { method: 'post', path: '/body/json/union', type: 'application/json', body: '42', expected: 400 },
767
- { method: 'post', path: '/body/json/union', type: 'application/json', body: '', expected: 400 },
768
- { method: 'post', path: '/body/json/union', type: 'application/json', body: null, expected: 400 },
769
-
770
- { method: 'post', path: '/body/json/obj', type: 'application/json', body: '{"foo":"bar"}', expected: 200 },
771
- { method: 'post', path: '/body/json/obj', type: 'application/json', body: '{"foo":42}', expected: 400 },
772
- { method: 'post', path: '/body/json/obj', type: 'application/json', body: '{}', expected: 400 },
773
- { method: 'post', path: '/body/json/obj', type: 'application/json', body: '', expected: 400 },
774
- { method: 'post', path: '/body/json/obj', type: 'application/json', body: null, expected: 400 },
775
-
776
- {
777
- method: 'post',
778
- path: '/body/urlForm',
779
- type: 'application/x-www-form-urlencoded',
780
- body: 'foo=foo&bar=42&opt=opt',
781
- expected: 200,
782
- },
783
- {
784
- method: 'post',
785
- path: '/body/urlForm',
786
- type: 'application/x-www-form-urlencoded',
787
- body: 'foo=bar&bar=0',
788
- expected: 200,
789
- },
790
- {
791
- method: 'post',
792
- path: '/body/urlForm',
793
- type: 'application/x-www-form-urlencoded',
794
- body: 'foo=test&bar=no',
795
- expected: 400,
796
- },
797
- {
798
- method: 'post',
799
- path: '/body/urlForm',
800
- type: 'application/x-www-form-urlencoded',
801
- body: 'foo=test&bar=1&opt=no',
802
- expected: 400,
803
- },
804
- {
805
- method: 'post',
806
- path: '/body/urlForm',
807
- type: 'application/x-www-form-urlencoded',
808
- body: 'foo=test',
809
- expected: 400,
810
- },
811
- {
812
- method: 'post',
813
- path: '/body/urlForm',
814
- type: 'application/x-www-form-urlencoded',
815
- body: '',
816
- expected: 400,
817
- },
818
- {
819
- method: 'post',
820
- path: '/body/urlForm',
821
- type: 'application/x-www-form-urlencoded',
822
- body: null,
823
- expected: 400,
824
- },
825
-
826
- {
827
- method: 'post',
828
- path: '/body/multipart',
829
- body: formdata({ foo: 'foo', bar: '42', opt: 'opt' }),
830
- expected: 200,
831
- },
832
- {
833
- method: 'post',
834
- path: '/body/multipart',
835
- body: formdata({ foo: 'foo', bar: '42' }),
836
- expected: 200,
837
- },
838
- {
839
- method: 'post',
840
- path: '/body/multipart',
841
- body: formdata({ bar: '42' }),
842
- expected: 400,
843
- },
844
- {
845
- method: 'post',
846
- path: '/body/multipart',
847
- body: formdata({ foo: 'foo', bar: 'bar' }),
848
- expected: 400,
849
- },
850
- {
851
- method: 'post',
852
- path: '/body/multipart',
853
- body: formdata({}),
854
- expected: 400,
855
- },
856
- {
857
- method: 'post',
858
- path: '/body/multipart',
859
- body: null,
860
- expected: 400,
861
- },
862
-
863
- {
864
- method: 'post',
865
- path: '/body/multipart/stream',
866
- body: formdata({ foo: 'foo', bar: '42', opt: 'opt' }),
867
- expected: 200,
868
- },
869
- {
870
- method: 'post',
871
- path: '/body/multipart/stream',
872
- body: formdata({ foo: 'foo', bar: '42' }),
873
- expected: 200,
874
- },
875
- {
876
- method: 'post',
877
- path: '/body/multipart/stream',
878
- body: formdata({ bar: '42' }),
879
- expected: 400,
880
- },
881
- {
882
- method: 'post',
883
- path: '/body/multipart/stream',
884
- body: formdata({ foo: 'foo', bar: 'bar' }),
885
- expected: 400,
886
- },
887
- {
888
- method: 'post',
889
- path: '/body/multipart/stream',
890
- body: formdata({}),
891
- expected: 400,
892
- },
893
- {
894
- method: 'post',
895
- path: '/body/multipart/stream',
896
- body: null,
897
- expected: 400,
898
- },
899
- ]
900
- for (let { method, path, type, body, expected } of cases) {
901
- let resp = await fetch(`http://localhost:${port}/${path}`, {
902
- method: method.toUpperCase(),
903
- body,
904
- headers: { ...(type ? { 'content-type': type } : {}) },
905
- })
906
- expect(resp.status).toBe(expected)
907
- }
908
- })
909
- })