galbe 0.3.0 → 0.4.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/src/types.ts CHANGED
@@ -1,13 +1,17 @@
1
1
  import type { ServeOptions, TLSServeOptions } from 'bun'
2
2
  import type {
3
+ STAny,
3
4
  STArray,
4
5
  STBoolean,
5
6
  STByteArray,
6
7
  STInteger,
8
+ STJson,
7
9
  STLiteral,
8
10
  STMultipartForm,
9
11
  STNumber,
10
12
  STObject,
13
+ STOptional,
14
+ STSchema,
11
15
  STStream,
12
16
  STString,
13
17
  STUnion,
@@ -29,6 +33,7 @@ export type STBody =
29
33
  | STMultipartForm
30
34
  | STUnion
31
35
  | STStream
36
+ | undefined
32
37
 
33
38
  export type STResponseValue =
34
39
  | STByteArray
@@ -38,14 +43,18 @@ export type STResponseValue =
38
43
  | STInteger
39
44
  | STLiteral
40
45
  | STObject
46
+ | STJson
41
47
  | STArray
42
48
  | STUnion
43
49
  | STStream
50
+ | STAny
44
51
  export type STResponse = Record<number, STResponseValue>
45
52
 
46
53
  export type MaybeArray<T> = T | T[]
54
+ export type MaybeSTArray<T extends STSchema> = T | STArray<T>
55
+ export type MaybeSTUnion<T extends STSchema> = T | STUnion<[T, ...T[]]>
47
56
 
48
- export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'
57
+ export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head'
49
58
  type MaybePromise<T> = T | Promise<T>
50
59
 
51
60
  export type ExtractParams<T extends string> = T extends `/:${infer P}/${infer Rest}`
@@ -56,13 +65,16 @@ export type ExtractParams<T extends string> = T extends `/:${infer P}/${infer Re
56
65
  ? P
57
66
  : never
58
67
 
59
- type STHeadersValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
68
+ type STHeadersPrimaryValue = STString | STBoolean | STNumber | STInteger | STLiteral
69
+ type STHeadersValue = MaybeSTUnion<STHeadersPrimaryValue>
60
70
  export type STHeaders = Record<string, STHeadersValue>
61
71
 
62
- type STParamsValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
72
+ type STParamsPrimaryValue = STString | STBoolean | STNumber | STInteger | STLiteral
73
+ type STParamsValue = MaybeSTUnion<STParamsPrimaryValue>
63
74
  export type STParams<Path extends string = string> = Record<ExtractParams<Path>, STParamsValue>
64
75
 
65
- type STQueryValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
76
+ type STQueryPrimaryValue = STString | STBoolean | STNumber | STInteger | STLiteral
77
+ type STQueryValue = MaybeSTArray<MaybeSTUnion<STQueryPrimaryValue>>
66
78
  export type STQuery = Record<string, STQueryValue>
67
79
 
68
80
  /**
@@ -112,6 +124,7 @@ export type GalbeConfig = {
112
124
  * ```
113
125
  */
114
126
  export type RequestSchema<
127
+ M extends Method = Method,
115
128
  Path extends string = string,
116
129
  H extends STHeaders = STHeaders,
117
130
  P extends Partial<STParams<Path>> = Partial<STParams<Path>>,
@@ -134,14 +147,18 @@ type OmitNotDefined<S extends RequestSchema> = {
134
147
  : //@ts-ignore
135
148
  never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
136
149
  }
137
-
138
- export type Context<Path extends string = string, S extends RequestSchema = RequestSchema> = {
150
+ type StaticBody<T extends STSchema> = T extends STOptional<STSchema> ? Static<T> | null : Static<T>
151
+ export type Context<
152
+ M extends Method = Method,
153
+ Path extends string = string,
154
+ S extends RequestSchema = RequestSchema
155
+ > = {
139
156
  headers: Static<STObject<Exclude<S['headers'], undefined>>>
140
157
  params: {
141
158
  [K in ExtractParams<Path>]: K extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[K] : string
142
159
  }
143
160
  query: Static<STObject<Exclude<S['query'], undefined>>>
144
- body: Static<Exclude<S['body'], undefined>>
161
+ body: M extends 'get' | 'options' | 'head' ? null : StaticBody<Exclude<S['body'], undefined>>
145
162
  request: Request
146
163
  route?: Route
147
164
  state: Record<string, any>
@@ -153,61 +170,63 @@ export type Context<Path extends string = string, S extends RequestSchema = Requ
153
170
  }
154
171
  }
155
172
  export type Next = () => void | Promise<void>
156
- export type Hook<Path extends string = string, S extends RequestSchema = RequestSchema> = (
157
- ctx: Context<Path, S>,
173
+ export type Hook<M extends Method = Method, Path extends string = string, S extends RequestSchema = RequestSchema> = (
174
+ ctx: Context<M, Path, S>,
158
175
  next: Next
159
176
  ) => any | Promise<any>
160
- export type Handler<Path extends string = string, S extends RequestSchema = RequestSchema> = (
161
- ctx: Context<Path, S>
162
- ) => any
163
- export type Endpoint = {
177
+ export type Handler<
178
+ M extends Method = Method,
179
+ Path extends string = string,
180
+ S extends RequestSchema = RequestSchema
181
+ > = (ctx: Context<M, Path, S>) => any
182
+ export type Endpoint<M extends Method> = {
164
183
  <
165
184
  Path extends string,
166
- H extends STHeaders,
167
185
  P extends Partial<STParams<Path>>,
168
- Q extends STQuery,
186
+ H extends STHeaders = any,
187
+ Q extends STQuery = any,
169
188
  B extends STBody = any,
170
189
  R extends STResponse = STResponse
171
190
  >(
172
191
  path: Path,
173
- schema: RequestSchema<Path, H, P, Q, B, R>,
174
- hooks: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[],
175
- handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
192
+ schema: RequestSchema<M, Path, H, P, Q, B, R>,
193
+ hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
194
+ handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
176
195
  ): void
177
196
  <
178
197
  Path extends string,
179
- H extends STHeaders,
180
198
  P extends Partial<STParams<Path>>,
181
- Q extends STQuery,
199
+ H extends STHeaders = any,
200
+ Q extends STQuery = any,
182
201
  B extends STBody = any,
183
202
  R extends STResponse = STResponse
184
203
  >(
185
204
  path: Path,
186
- schema: RequestSchema<Path, H, P, Q, B, R>,
187
- handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
205
+ schema: RequestSchema<M, Path, H, P, Q, B, R>,
206
+ handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
188
207
  ): void
189
208
  <
190
209
  Path extends string,
191
- H extends STHeaders,
192
210
  P extends Partial<STParams<Path>>,
193
- Q extends STQuery,
211
+ H extends STHeaders = any,
212
+ Q extends STQuery = any,
194
213
  B extends STBody = any,
195
214
  R extends STResponse = STResponse
196
215
  >(
197
216
  path: Path,
198
- hooks: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[],
199
- handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
217
+ hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
218
+ handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
200
219
  ): void
201
220
  <
202
221
  Path extends string,
203
- H extends STHeaders,
204
222
  P extends Partial<STParams<Path>>,
205
- Q extends STQuery,
223
+ H extends STHeaders = any,
224
+ Q extends STQuery = any,
206
225
  B extends STBody = any,
207
226
  R extends STResponse = STResponse
208
227
  >(
209
228
  path: Path,
210
- handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
229
+ handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
211
230
  ): void
212
231
  }
213
232
 
@@ -223,29 +242,26 @@ export class RequestError {
223
242
  export type ErrorHandler = (error: any, context: Context) => any
224
243
 
225
244
  export type RouteNode = {
226
- route?: Route
245
+ routes: { [K in Method]?: Route }
227
246
  param?: RouteNode
228
247
  children?: Record<string, RouteNode>
229
248
  }
230
249
 
231
250
  export type Route<
251
+ M extends Method = Method,
232
252
  Path extends string = string,
233
- H extends STHeaders = {},
234
253
  P extends Partial<STParams<Path>> = {},
235
- Q extends STQuery = {},
254
+ H extends STHeaders = STHeaders,
255
+ Q extends STQuery = STQuery,
236
256
  B extends STBody = STBody,
237
257
  R extends STResponse = STResponse
238
258
  > = {
239
- method: Method
259
+ method: M
240
260
  path: Path
241
- schema: RequestSchema<Path, H, P, Q, B, R>
242
- context: Context<Path, RequestSchema<Path, H, P, Q, B, R>>
261
+ schema: RequestSchema<M, Path, H, P, Q, B, R>
262
+ context: Context<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
243
263
  hooks: Hook[]
244
- handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
245
- }
246
-
247
- export type RouteTree = {
248
- [key: string]: RouteNode
264
+ handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
249
265
  }
250
266
 
251
267
  export class NotFoundError extends RequestError {
@@ -254,6 +270,12 @@ export class NotFoundError extends RequestError {
254
270
  }
255
271
  }
256
272
 
273
+ export class MethodNotAllowedError extends RequestError {
274
+ constructor(message?: any) {
275
+ super({ status: 405, payload: message ?? 'Method not allowed' })
276
+ }
277
+ }
278
+
257
279
  export class InternalError extends RequestError {
258
280
  constructor(message?: any) {
259
281
  super({ status: 500, payload: message ?? 'Internal Server Error' })
@@ -262,7 +284,7 @@ export class InternalError extends RequestError {
262
284
 
263
285
  /**
264
286
  * #### GalbePlugin
265
- * Define a plugin for a Galbe server
287
+ * Define a plugin for a Galbe application
266
288
  *
267
289
  * ---
268
290
  * @example
@@ -288,4 +310,14 @@ export type GalbePlugin = {
288
310
  onRoute?: (context: Context) => MaybePromise<Response | void>
289
311
  beforeHandle?: (context: Context) => MaybePromise<Response | void>
290
312
  afterHandle?: (response: Response, context: Context) => MaybePromise<Response | void>
313
+ cli?: (commands: GalbeCLICommand[]) => MaybePromise<GalbeCLICommand[] | void>
314
+ }
315
+
316
+ export type GalbeCLICommand = {
317
+ name: string
318
+ description?: string
319
+ route: Route
320
+ arguments?: { name: string; type: string; description: string }[]
321
+ options?: { name: string; short: string; type: string; description: string; default: any }[]
322
+ action?: (props: any) => MaybePromise<void>
291
323
  }
package/src/util.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { RouteFileMeta } from './routes'
1
+ import type { Route, RouteNode } from '.'
2
+ import type { RouteMeta } from './routes'
2
3
 
3
4
  const METHOD_COLOR: Record<string, string> = {
4
5
  get: '\x1b[32m',
@@ -6,23 +7,88 @@ const METHOD_COLOR: Record<string, string> = {
6
7
  put: '\x1b[36m',
7
8
  patch: '\x1b[33m',
8
9
  delete: '\x1b[31m',
9
- options: ''
10
- }
11
-
12
- export const extractMetaRoute = (route: { path: string; method: string }, meta?: Array<RouteFileMeta>) => {
13
- return meta?.map(e => (route.path in e.routes ? e.routes[route.path]?.[route.method] : null)).filter(e => e)?.[0]
10
+ options: '',
11
+ head: ''
14
12
  }
15
13
 
16
14
  export const logRoute = (
17
15
  r: { method: string; path: string },
18
- meta?: Record<string, boolean | string | string[]> | null
16
+ meta?: RouteMeta,
17
+ format?: { maxPathLength?: number }
19
18
  ) => {
20
19
  let color = METHOD_COLOR?.[r.method] || ''
21
20
  console.log(
22
- ` [${color}${`${r.method.toUpperCase()}\x1b[0m]`.padEnd(12, ' ')} ${r.path}${
23
- meta?.head ? ` - ${meta.head}` : ''
24
- }`
21
+ ` [${color}${`${r.method.toUpperCase()}\x1b[0m]`.padEnd(12, ' ')} ${r.path
22
+ .padEnd(format?.maxPathLength ?? r.path.length, ' ')
23
+ .replaceAll(/:([^\/]+)/g, '\x1b[0;33m:$1\x1b[0m')}${meta?.head ? ` ${meta.head}` : ''}`
25
24
  )
26
25
  }
27
26
 
27
+ export const walkRoutes = (node: RouteNode, cb: (route: Route) => void) => {
28
+ if (node?.routes) Object.values(node.routes).forEach(r => cb(r))
29
+ for (let c of Object.values(node?.children || {})) walkRoutes(c, cb)
30
+ if (node?.param) walkRoutes(node.param, cb)
31
+ }
32
+
28
33
  export const isIterator = (obj: any) => typeof obj?.next === 'function'
34
+
35
+ export const HttpStatus = {
36
+ 100: 'Continue',
37
+ 101: 'Switching Protocols',
38
+ 102: 'Processing',
39
+ 103: 'Early Hints',
40
+ 200: 'OK',
41
+ 201: 'Created',
42
+ 202: 'Accepted',
43
+ 203: 'Non Authoritative Information',
44
+ 204: 'No Content',
45
+ 205: 'Reset Content',
46
+ 206: 'Partial Content',
47
+ 207: 'Multi-Status',
48
+ 300: 'Multiple Choices',
49
+ 301: 'Moved Permanently',
50
+ 302: 'Moved Temporarily',
51
+ 303: 'See Other',
52
+ 304: 'Not Modified',
53
+ 305: 'Use Proxy',
54
+ 307: 'Temporary Redirect',
55
+ 308: 'Permanent Redirect',
56
+ 400: 'Bad Request',
57
+ 401: 'Unauthorized',
58
+ 402: 'Payment Required',
59
+ 403: 'Forbidden',
60
+ 404: 'Not Found',
61
+ 405: 'Method Not Allowed',
62
+ 406: 'Not Acceptable',
63
+ 407: 'Proxy Authentication Required',
64
+ 408: 'Request Timeout',
65
+ 409: 'Conflict',
66
+ 410: 'Gone',
67
+ 411: 'Length Required',
68
+ 412: 'Precondition Failed',
69
+ 413: 'Request Entity Too Large',
70
+ 414: 'Request-URI Too Long',
71
+ 415: 'Unsupported Media Type',
72
+ 416: 'Requested Range Not Satisfiable',
73
+ 417: 'Expectation Failed',
74
+ 418: "I'm a teapot",
75
+ 419: 'Insufficient Space on Resource',
76
+ 420: 'Method Failure',
77
+ 421: 'Misdirected Request',
78
+ 422: 'Unprocessable Entity',
79
+ 423: 'Locked',
80
+ 424: 'Failed Dependency',
81
+ 426: 'Upgrade Required',
82
+ 428: 'Precondition Required',
83
+ 429: 'Too Many Requests',
84
+ 431: 'Request Header Fields Too Large',
85
+ 451: 'Unavailable For Legal Reasons',
86
+ 500: 'Internal Server Error',
87
+ 501: 'Not Implemented',
88
+ 502: 'Bad Gateway',
89
+ 503: 'Service Unavailable',
90
+ 504: 'Gateway Timeout',
91
+ 505: 'HTTP Version Not Supported',
92
+ 507: 'Insufficient Storage',
93
+ 511: 'Network Authentication Required'
94
+ }
package/src/validator.ts CHANGED
@@ -50,6 +50,8 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
50
50
  }
51
51
  })
52
52
  if (Object.keys(err).length) errors.push(err)
53
+ } else if (schema[Kind] === 'json') {
54
+ elt = validate(elt, { ...schema, [Kind]: schema.type }, parse)
53
55
  } else if (schema[Kind] === 'array') {
54
56
  if (parse && typeof elt === 'string') {
55
57
  try {
@@ -10,6 +10,7 @@ import {
10
10
  isAsyncIterator
11
11
  } from './test.utils'
12
12
  import { Galbe, $T } from '../src'
13
+ import { schemaToTypeStr } from '../src/schema'
13
14
 
14
15
  const port = 7357
15
16
 
@@ -1195,4 +1196,37 @@ describe('parser', () => {
1195
1196
  expect(body).toEqual(expected.body)
1196
1197
  }
1197
1198
  })
1199
+
1200
+ test('schema to type', async () => {
1201
+ let type = schemaToTypeStr(
1202
+ $T.object({
1203
+ boolean: $T.boolean(),
1204
+ byteArray: $T.byteArray(),
1205
+ number: $T.number(),
1206
+ integer: $T.integer(),
1207
+ string: $T.string(),
1208
+ any: $T.any(),
1209
+ literal: $T.literal('literal'),
1210
+ array: $T.array($T.string()),
1211
+ object: $T.object({
1212
+ foo: $T.string()
1213
+ }),
1214
+ union: $T.union([$T.number(), $T.string()])
1215
+ })
1216
+ )
1217
+ expect(type).toBe(
1218
+ `{` +
1219
+ `'boolean':boolean;` +
1220
+ `'byteArray':Uint8Array;` +
1221
+ `'number':number;` +
1222
+ `'integer':number;` +
1223
+ `'string':string;` +
1224
+ `'any':any;` +
1225
+ `'literal':'literal';` +
1226
+ `'array':Array<string>;` +
1227
+ `'object':{'foo':string};` +
1228
+ `'union':number|string` +
1229
+ `}`
1230
+ )
1231
+ })
1198
1232
  })
@@ -1,18 +1,9 @@
1
1
  import { expect, test, describe, beforeAll } from 'bun:test'
2
2
  import { Galbe, $T } from '../src'
3
- import {
4
- formdata,
5
- type Case,
6
- fileHash,
7
- schema_objectBase,
8
- schema_object,
9
- handleBody,
10
- isAsyncIterator,
11
- handleUrlFormStream
12
- } from './test.utils'
3
+ import { formdata, type Case, fileHash, handleBody, isAsyncIterator } from './test.utils'
13
4
 
14
5
  const port = 7358
15
- const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options']
6
+ const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head']
16
7
 
17
8
  describe('requests', () => {
18
9
  beforeAll(async () => {
@@ -24,6 +15,7 @@ describe('requests', () => {
24
15
  galbe.patch('/test', () => {})
25
16
  galbe.delete('/test', () => {})
26
17
  galbe.options('/test', () => {})
18
+ galbe.head('/test', () => {})
27
19
 
28
20
  galbe.get('/headers', ctx => {
29
21
  return ctx.headers
@@ -76,7 +68,8 @@ describe('requests', () => {
76
68
  p2: $T.number(),
77
69
  p3: $T.boolean(),
78
70
  p4: $T.union([$T.number(), $T.boolean()]),
79
- p5: $T.optional($T.string())
71
+ p5: $T.optional($T.string()),
72
+ p6: $T.array($T.union([$T.string()]))
80
73
  }
81
74
  },
82
75
  ctx => {
@@ -35,4 +35,24 @@ export default (g: Galbe) => {
35
35
  * @other Hello Mom!
36
36
  */
37
37
  g.put('/test', { body: $T.object({ foo: $T.string() }) }, [() => {}], _ => {})
38
+
39
+ /**
40
+ * patch method
41
+ */
42
+ g.patch('/test', _ => {})
43
+
44
+ /**
45
+ * options method
46
+ */
47
+ g.options('/test', _ => {})
48
+
49
+ /**
50
+ * delete method
51
+ */
52
+ g.delete('/test', _ => {})
53
+
54
+ /**
55
+ * head method
56
+ */
57
+ g.head('/test', _ => {})
38
58
  }
@@ -50,6 +50,10 @@ describe('responses', () => {
50
50
  galbe.post('/bool', { response: { 200: $T.boolean() } }, handleResp)
51
51
  galbe.post('/num', { response: { 200: $T.number() } }, handleResp)
52
52
  galbe.post('/str', { response: { 200: $T.string() } }, handleResp)
53
+ galbe.post('/json/bool', { response: { 200: $T.json($T.boolean()) } }, handleResp)
54
+ galbe.post('/json/num', { response: { 200: $T.json($T.number()) } }, handleResp)
55
+ galbe.post('/json/str', { response: { 200: $T.json($T.string()) } }, handleResp)
56
+ galbe.post('/json/obj', { response: { 200: $T.json($T.object()) } }, handleResp)
53
57
  galbe.post('/arr', { response: { 200: $T.array() } }, handleResp)
54
58
  galbe.post('/obj', { response: { 200: $T.object($T.any()) } }, handleResp)
55
59
  galbe.post('/stream/ba', { response: { 200: $T.stream($T.byteArray()) } }, ctx =>
@@ -126,14 +130,23 @@ describe('responses', () => {
126
130
  }
127
131
  })
128
132
 
129
- const body = await resp.json()
133
+ const reader = resp.body?.getReader()
134
+ let body = new Uint8Array()
135
+ while (reader) {
136
+ const { value, done } = await reader.read()
137
+ if (done) break
138
+ let buff = new Uint8Array(body.length + value.length)
139
+ buff.set(body)
140
+ buff.set(value, body.length)
141
+ body = buff
142
+ }
130
143
 
131
144
  expect(resp.status).toBe(200)
132
- expect(resp.headers.get('content-type')).toBe('application/json')
145
+ expect(resp.headers.get('content-type')).toBe('application/octet-stream')
133
146
  expect(body).toEqual(reqBody)
134
147
  })
135
148
 
136
- test('response, ba, validation failed', async () => {
149
+ test('response, ba, validation error', async () => {
137
150
  let bodyStr = 'Hello mom!'
138
151
 
139
152
  let resp = await fetch(`http://localhost:${port}/ba`, {
@@ -166,7 +179,7 @@ describe('responses', () => {
166
179
  expect(body).toEqual(reqBody)
167
180
  })
168
181
 
169
- test('response, bool, validation failed', async () => {
182
+ test('response, bool, validation error', async () => {
170
183
  let bodyStr = 'true'
171
184
 
172
185
  let resp = await fetch(`http://localhost:${port}/bool`, {
@@ -199,7 +212,7 @@ describe('responses', () => {
199
212
  expect(body).toEqual(reqBody)
200
213
  })
201
214
 
202
- test('response, num, validation failed', async () => {
215
+ test('response, num, validation error', async () => {
203
216
  let bodyStr = '"test"'
204
217
 
205
218
  let resp = await fetch(`http://localhost:${port}/num`, {
@@ -231,10 +244,95 @@ describe('responses', () => {
231
244
  expect(body).toEqual(bodyStr)
232
245
  })
233
246
 
234
- test('response, str, validation failed', async () => {
247
+ test('response, json bool, validation OK', async () => {
248
+ let bodyStr = 'false'
249
+ let reqBody = false
250
+
251
+ let resp = await fetch(`http://localhost:${port}/json/bool`, {
252
+ method: 'POST',
253
+ body: bodyStr,
254
+ headers: {
255
+ 'content-type': 'application/json'
256
+ }
257
+ })
258
+
259
+ const body = await resp.json()
260
+
261
+ expect(resp.status).toBe(200)
262
+ expect(resp.headers.get('content-type')).toBe('application/json')
263
+ expect(body).toEqual(reqBody)
264
+ })
265
+
266
+ test('response, json bool, validation error', async () => {
235
267
  let bodyStr = '3.14'
236
268
 
237
- let resp = await fetch(`http://localhost:${port}/str`, {
269
+ let resp = await fetch(`http://localhost:${port}/json/bool`, {
270
+ method: 'POST',
271
+ body: bodyStr,
272
+ headers: {
273
+ 'content-type': 'application/json'
274
+ }
275
+ })
276
+
277
+ expect(resp.status).toBe(500)
278
+ })
279
+
280
+ test('response, json num, validation OK', async () => {
281
+ let bodyStr = '42'
282
+ let reqBody = 42
283
+
284
+ let resp = await fetch(`http://localhost:${port}/json/num`, {
285
+ method: 'POST',
286
+ body: bodyStr,
287
+ headers: {
288
+ 'content-type': 'application/json'
289
+ }
290
+ })
291
+
292
+ const body = await resp.json()
293
+
294
+ expect(resp.status).toBe(200)
295
+ expect(resp.headers.get('content-type')).toBe('application/json')
296
+ expect(body).toEqual(reqBody)
297
+ })
298
+
299
+ test('response, json num, validation error', async () => {
300
+ let bodyStr = '"Hi"'
301
+
302
+ let resp = await fetch(`http://localhost:${port}/json/bool`, {
303
+ method: 'POST',
304
+ body: bodyStr,
305
+ headers: {
306
+ 'content-type': 'application/json'
307
+ }
308
+ })
309
+
310
+ expect(resp.status).toBe(500)
311
+ })
312
+
313
+ test('response, json str, validation OK', async () => {
314
+ let bodyStr = '"Hello Mom!"'
315
+ let reqBody = 'Hello Mom!'
316
+
317
+ let resp = await fetch(`http://localhost:${port}/json/str`, {
318
+ method: 'POST',
319
+ body: bodyStr,
320
+ headers: {
321
+ 'content-type': 'application/json'
322
+ }
323
+ })
324
+
325
+ const body = await resp.json()
326
+
327
+ expect(resp.status).toBe(200)
328
+ expect(resp.headers.get('content-type')).toBe('application/json')
329
+ expect(body).toEqual(reqBody)
330
+ })
331
+
332
+ test('response, json str, validation error', async () => {
333
+ let bodyStr = '3.14'
334
+
335
+ let resp = await fetch(`http://localhost:${port}/json/str`, {
238
336
  method: 'POST',
239
337
  body: bodyStr,
240
338
  headers: {
@@ -264,7 +362,7 @@ describe('responses', () => {
264
362
  expect(body).toEqual(reqBody)
265
363
  })
266
364
 
267
- test('response, array, validation failed', async () => {
365
+ test('response, array, validation error', async () => {
268
366
  let bodyStr = '0'
269
367
 
270
368
  let resp = await fetch(`http://localhost:${port}/arr`, {
@@ -297,7 +395,7 @@ describe('responses', () => {
297
395
  expect(body).toEqual(reqBody)
298
396
  })
299
397
 
300
- test('response, object, validation failed', async () => {
398
+ test('response, object, validation error', async () => {
301
399
  let bodyStr = '"This"'
302
400
 
303
401
  let resp = await fetch(`http://localhost:${port}/obj`, {
@@ -334,7 +432,7 @@ describe('responses', () => {
334
432
  expect(body).toMatch(new RegExp(`id:${UUID_RGX}\ndata:Hello\n\nid:${UUID_RGX}\ndata:Mom!\n\n`))
335
433
  })
336
434
 
337
- test('response, stream ba, validation failed', async () => {
435
+ test('response, stream ba, validation error', async () => {
338
436
  let bodyStr = ''
339
437
 
340
438
  let resp = await fetch(`http://localhost:${port}/stream/ba`, {
@@ -368,7 +466,7 @@ describe('responses', () => {
368
466
  expect(body).toMatch(new RegExp(`id:${UUID_RGX}\ndata:Hello\n\nid:${UUID_RGX}\ndata:Mom!\n\n`))
369
467
  })
370
468
 
371
- test('response, stream str, validation failed', async () => {
469
+ test('response, stream str, validation error', async () => {
372
470
  let bodyStr = '0'
373
471
 
374
472
  let resp = await fetch(`http://localhost:${port}/stream/str`, {