galbe 0.13.0 → 0.14.0

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
@@ -5,6 +5,7 @@ import type {
5
5
  STBoolean,
6
6
  STByteArray,
7
7
  STInteger,
8
+ STIntersection,
8
9
  STJson,
9
10
  STLiteral,
10
11
  STMultipartForm,
@@ -20,6 +21,9 @@ import type {
20
21
  } from './schema'
21
22
  import type { Galbe } from './index'
22
23
  import { HttpStatus } from './util'
24
+ import type { CookieOptions } from './cookies'
25
+
26
+ export type MediaType = `${string}/${string}`
23
27
 
24
28
  export type STResponseValue =
25
29
  | STByteArray
@@ -32,23 +36,54 @@ export type STResponseValue =
32
36
  | STJson
33
37
  | STArray
34
38
  | STUnion
39
+ | STIntersection<any>
35
40
  | STStream
36
41
  | STAny
37
42
  | STNull
38
- export type STBody =
43
+
44
+ export type STBodyValue =
45
+ | STByteArray
46
+ | STStream
47
+ | STString
48
+ | STLiteral
49
+ | STBoolean
50
+ | STNumber
51
+ | STInteger
52
+ | STObject
53
+ | STJson
54
+ | STArray
55
+ | STUnion
56
+ | STIntersection<any>
57
+ | STMultipartForm
58
+ | STAny
59
+
60
+ export type STBodyContent = Partial<Record<MediaType, STBodyValue>>
61
+ export type STBody = STNull | STBodyContent
62
+ export type STBodyType = MediaType
63
+
64
+ export type STResponseBodyValue =
65
+ | STByteArray
66
+ | STStream
67
+ | STString
68
+ | STLiteral
69
+ | STBoolean
70
+ | STNumber
71
+ | STInteger
72
+ | STObject
73
+ | STJson
74
+ | STArray
75
+ | STUnion
76
+ | STIntersection<any>
77
+ | STAny
39
78
  | STNull
40
- | Partial<{
41
- byteArray?: STByteArray | STStream
42
- text?: STString | STLiteral | STBoolean | STNumber | STInteger | STUnion | STStream
43
- json?: STJson | STObject | STBoolean | STInteger | STNumber | STString | STArray | STUnion
44
- urlForm?: STObject | STStream | STUnion
45
- multipart?: STMultipartForm | STStream | STUnion
46
- default?: STString | STByteArray | STStream | STAny
47
- }>
48
- export type STBodyType = keyof STBody
49
- export type STBodyValue = STBody[STBodyType]
50
79
 
51
- export type STResponse = Partial<Record<number | 'default', STResponseValue>>
80
+ export type STResponseContent = Partial<Record<MediaType, STResponseBodyValue>> & {
81
+ description?: string
82
+ responseHeaders?: Record<string, STSchema>
83
+ }
84
+ export type STResponseBodyKey = MediaType
85
+ export type STResponseEntry = STResponseValue | STResponseContent
86
+ export type STResponse = Partial<Record<number | 'default', STResponseEntry>>
52
87
 
53
88
  export type MaybeArray<T> = T | T[]
54
89
  export type MaybeSTArray<T extends STSchema> = T | STArray<T>
@@ -60,10 +95,10 @@ type MaybePromise<T> = T | Promise<T>
60
95
  export type ExtractParams<T extends string> = T extends `/:${infer P}/${infer Rest}`
61
96
  ? P | ExtractParams<Rest>
62
97
  : T extends `${infer _}:${infer P}/${infer Rest}`
63
- ? P | ExtractParams<Rest>
64
- : T extends `${infer _}:${infer P}`
65
- ? P
66
- : never
98
+ ? P | ExtractParams<Rest>
99
+ : T extends `${infer _}:${infer P}`
100
+ ? P
101
+ : never
67
102
 
68
103
  type STHeadersPrimaryValue = STString | STBoolean | STNumber | STInteger | STLiteral
69
104
  type STHeadersValue = MaybeSTUnion<STHeadersPrimaryValue>
@@ -142,7 +177,7 @@ export type RequestSchema<
142
177
  P extends Partial<STParams<Path>> = Partial<STParams<Path>>,
143
178
  Q extends STQuery = STQuery,
144
179
  B extends STBody = STBody,
145
- R extends Partial<STResponse> = STResponse
180
+ R extends Partial<STResponse> = STResponse,
146
181
  > = {
147
182
  headers?: H
148
183
  params?: P
@@ -160,13 +195,21 @@ type OmitNotDefined<S extends RequestSchema> = {
160
195
  never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
161
196
  }
162
197
  type StaticBody<T extends STSchema> = T extends STOptional<STSchema> ? Static<T> | null : Static<T>
198
+ export type ContextSet = {
199
+ headers: {
200
+ 'set-cookie': string[]
201
+ [header: string]: string | string[]
202
+ }
203
+ status?: number
204
+ cookie: (name: string, value: string, opt?: CookieOptions) => void
205
+ }
163
206
  export type Context<
164
207
  M extends Method = Method,
165
208
  Path extends string = string,
166
- S extends RequestSchema = RequestSchema
167
- > = {
168
- [K in STBodyType]: K extends keyof Exclude<S['body'], undefined>
169
- ? {
209
+ S extends RequestSchema = RequestSchema,
210
+ > = 0 extends 1 & Exclude<S['body'], undefined | STNull>
211
+ ? {
212
+ [K in keyof Exclude<S['body'], undefined | STNull>]: {
170
213
  headers: Static<STObject<Exclude<S['headers'], undefined>>>
171
214
  params: {
172
215
  [P in ExtractParams<Path>]: P extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[P] : string
@@ -176,24 +219,51 @@ export type Context<
176
219
  body: M extends 'get' | 'options' | 'head'
177
220
  ? null
178
221
  : Exclude<S['body'], undefined> extends STNull
179
- ? null
180
- : K extends STBodyType
181
- ? StaticBody<Exclude<Exclude<S['body'], undefined>[K], undefined>>
182
- : never
222
+ ? null
223
+ : StaticBody<Extract<Exclude<Exclude<S['body'], undefined | STNull>[K], undefined>, STSchema>>
183
224
  request: Request
184
225
  remoteAddress: SocketAddress | null
185
226
  route?: Route
186
227
  state: Record<string, any>
187
- set: {
188
- headers: {
189
- 'set-cookie': string[]
190
- [header: string]: string | string[]
191
- }
192
- status?: number
228
+ set: ContextSet
229
+ cookies: Record<string, string>
230
+ }
231
+ }[keyof Exclude<S['body'], undefined | STNull>]
232
+ : [Exclude<S['body'], undefined | STNull>] extends [never]
233
+ ? {
234
+ headers: Static<STObject<Exclude<S['headers'], undefined>>>
235
+ params: {
236
+ [P in ExtractParams<Path>]: P extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[P] : string
193
237
  }
238
+ query: Static<STObject<Exclude<S['query'], undefined>>>
239
+ contentType: undefined
240
+ body: null
241
+ request: Request
242
+ remoteAddress: SocketAddress | null
243
+ route?: Route
244
+ state: Record<string, any>
245
+ set: ContextSet
246
+ cookies: Record<string, string>
194
247
  }
195
- : never
196
- }[STBodyType]
248
+ : {
249
+ [K in keyof Exclude<S['body'], undefined | STNull>]: {
250
+ headers: Static<STObject<Exclude<S['headers'], undefined>>>
251
+ params: {
252
+ [P in ExtractParams<Path>]: P extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[P] : string
253
+ }
254
+ query: Static<STObject<Exclude<S['query'], undefined>>>
255
+ contentType: M extends 'get' | 'options' | 'head' ? undefined : K
256
+ body: M extends 'get' | 'options' | 'head'
257
+ ? null
258
+ : StaticBody<Extract<Exclude<Exclude<S['body'], undefined | STNull>[K], undefined>, STSchema>>
259
+ request: Request
260
+ remoteAddress: SocketAddress | null
261
+ route?: Route
262
+ state: Record<string, any>
263
+ set: ContextSet
264
+ cookies: Record<string, string>
265
+ }
266
+ }[keyof Exclude<S['body'], undefined | STNull>]
197
267
  export type Next = () => void | Promise<any>
198
268
  export type Hook<M extends Method = Method, Path extends string = string, S extends RequestSchema = RequestSchema> = (
199
269
  ctx: Context<M, Path, S>,
@@ -202,7 +272,7 @@ export type Hook<M extends Method = Method, Path extends string = string, S exte
202
272
  export type Handler<
203
273
  M extends Method = Method,
204
274
  Path extends string = string,
205
- S extends RequestSchema = RequestSchema
275
+ S extends RequestSchema = RequestSchema,
206
276
  > = (ctx: Context<M, Path, S>) => any
207
277
  export type Endpoint<M extends Method> = {
208
278
  <
@@ -211,7 +281,7 @@ export type Endpoint<M extends Method> = {
211
281
  H extends STHeaders = any,
212
282
  Q extends STQuery = any,
213
283
  B extends STBody = any,
214
- R extends STResponse = STResponse
284
+ R extends STResponse = STResponse,
215
285
  >(
216
286
  path: Path,
217
287
  schema: RequestSchema<M, Path, H, P, Q, B, R>,
@@ -224,7 +294,7 @@ export type Endpoint<M extends Method> = {
224
294
  H extends STHeaders = any,
225
295
  Q extends STQuery = any,
226
296
  B extends STBody = any,
227
- R extends STResponse = STResponse
297
+ R extends STResponse = STResponse,
228
298
  >(
229
299
  path: Path,
230
300
  schema: RequestSchema<M, Path, H, P, Q, B, R>,
@@ -236,7 +306,7 @@ export type Endpoint<M extends Method> = {
236
306
  H extends STHeaders = any,
237
307
  Q extends STQuery = any,
238
308
  B extends STBody = any,
239
- R extends STResponse = STResponse
309
+ R extends STResponse = STResponse,
240
310
  >(
241
311
  path: Path,
242
312
  hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
@@ -248,7 +318,7 @@ export type Endpoint<M extends Method> = {
248
318
  H extends STHeaders = any,
249
319
  Q extends STQuery = any,
250
320
  B extends STBody = any,
251
- R extends STResponse = STResponse
321
+ R extends STResponse = STResponse,
252
322
  >(
253
323
  path: Path,
254
324
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
@@ -264,12 +334,19 @@ export type StaticEndpoint<P extends string = string, T extends string = string>
264
334
  options?: StaticEndpointOptions
265
335
  ) => Route<'get', P, {}, {}, {}, STBody, STResponse, T>
266
336
 
267
- export class RequestError {
337
+ export class RequestError extends Error {
268
338
  status: number
269
339
  payload?: any
270
340
  headers?: Record<string, string>
271
- constructor(options: { status?: number; payload?: any; headers?: Record<string, string> }) {
272
- this.status = options.status ?? 400
341
+ constructor(options: { status?: number; payload?: any; headers?: Record<string, string> } = {}) {
342
+ const status = options.status ?? 400
343
+ const message =
344
+ typeof options.payload === 'string'
345
+ ? options.payload
346
+ : HttpStatus[status as keyof typeof HttpStatus] ?? 'Request Error'
347
+ super(message)
348
+ this.name = new.target?.name ?? 'RequestError'
349
+ this.status = status
273
350
  this.payload = options.payload
274
351
  this.headers = options.headers
275
352
  }
@@ -292,7 +369,7 @@ export type Route<
292
369
  B extends STBody = STBody,
293
370
  R extends STResponse = STResponse,
294
371
  SP extends string = string,
295
- SR extends string = string
372
+ SR extends string = string,
296
373
  > = {
297
374
  method: M
298
375
  path: Path
@@ -303,29 +380,57 @@ export type Route<
303
380
  static?: { path: SP; root: SR }
304
381
  }
305
382
 
306
- export class NotFoundError extends RequestError {
307
- constructor(message?: any) {
308
- super({ status: 404, payload: message ?? HttpStatus[404] })
383
+ const mkErr = (status: number) =>
384
+ class extends RequestError {
385
+ constructor(payload?: any, headers?: Record<string, string>) {
386
+ super({ status, payload: payload ?? HttpStatus[status as keyof typeof HttpStatus], headers })
387
+ }
309
388
  }
310
- }
311
389
 
312
- export class MethodNotAllowedError extends RequestError {
313
- constructor(message?: any) {
314
- super({ status: 405, payload: message ?? HttpStatus[405] })
315
- }
316
- }
390
+ // 4xx
391
+ export class BadRequestError extends mkErr(400) {}
392
+ export class UnauthorizedError extends mkErr(401) {}
393
+ export class PaymentRequiredError extends mkErr(402) {}
394
+ export class ForbiddenError extends mkErr(403) {}
395
+ export class NotFoundError extends mkErr(404) {}
396
+ export class MethodNotAllowedError extends mkErr(405) {}
397
+ export class NotAcceptableError extends mkErr(406) {}
398
+ export class ProxyAuthenticationRequiredError extends mkErr(407) {}
399
+ export class RequestTimeoutError extends mkErr(408) {}
400
+ export class ConflictError extends mkErr(409) {}
401
+ export class GoneError extends mkErr(410) {}
402
+ export class LengthRequiredError extends mkErr(411) {}
403
+ export class PreconditionFailedError extends mkErr(412) {}
404
+ export class PayloadTooLargeError extends mkErr(413) {}
405
+ export class URITooLongError extends mkErr(414) {}
406
+ export class UnsupportedMediaTypeError extends mkErr(415) {}
407
+ export class RangeNotSatisfiableError extends mkErr(416) {}
408
+ export class ExpectationFailedError extends mkErr(417) {}
409
+ export class ImATeapotError extends mkErr(418) {}
410
+ export class MisdirectedRequestError extends mkErr(421) {}
411
+ export class UnprocessableEntityError extends mkErr(422) {}
412
+ export class LockedError extends mkErr(423) {}
413
+ export class FailedDependencyError extends mkErr(424) {}
414
+ export class UpgradeRequiredError extends mkErr(426) {}
415
+ export class PreconditionRequiredError extends mkErr(428) {}
416
+ export class TooManyRequestsError extends mkErr(429) {}
417
+ export class RequestHeaderFieldsTooLargeError extends mkErr(431) {}
418
+ export class UnavailableForLegalReasonsError extends mkErr(451) {}
317
419
 
318
- export class InternalError extends RequestError {
319
- constructor(message?: any) {
320
- super({ status: 500, payload: message ?? HttpStatus[500] })
321
- }
322
- }
420
+ // 5xx
421
+ export class InternalServerError extends mkErr(500) {}
422
+ export class NotImplementedError extends mkErr(501) {}
423
+ export class BadGatewayError extends mkErr(502) {}
424
+ export class ServiceUnavailableError extends mkErr(503) {}
425
+ export class GatewayTimeoutError extends mkErr(504) {}
426
+ export class HTTPVersionNotSupportedError extends mkErr(505) {}
427
+ export class InsufficientStorageError extends mkErr(507) {}
428
+ export class NetworkAuthenticationRequiredError extends mkErr(511) {}
323
429
 
324
- export class NotImplementedError extends RequestError {
325
- constructor(message?: any) {
326
- super({ status: 501, payload: message ?? HttpStatus[501] })
327
- }
328
- }
430
+ /** @deprecated use {@link InternalServerError} */
431
+ export const InternalError = InternalServerError
432
+ /** @deprecated use {@link InternalServerError} */
433
+ export type InternalError = InternalServerError
329
434
 
330
435
  /**
331
436
  * #### GalbePlugin
@@ -363,7 +468,21 @@ export type GalbeCLICommand = {
363
468
  tags: string[]
364
469
  description?: string
365
470
  route: Route
471
+ pathT: string
366
472
  arguments?: { name: string; type: string; description: string }[]
367
473
  options?: { name: string; short: string; type: string; description: string; default: any }[]
368
474
  action?: (props: any) => MaybePromise<void>
475
+ hideOptions?: ('header' | 'query' | 'body' | 'body-file')[]
476
+ }
477
+
478
+ export type GalbeCLIOptions = {
479
+ baseUrl?: string | (() => string)
480
+ headers?: Record<string, string>
481
+ requestInterceptor?: (
482
+ req: Request,
483
+ command: GalbeCLICommand,
484
+ args: Record<string, string>,
485
+ options: Record<string, any>
486
+ ) => MaybePromise<Request>
487
+ responseFormatter?: (res: Response) => MaybePromise<string>
369
488
  }
package/src/util.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Method, Route, RouteNode, STBodyType } from '.'
1
+ import type { Method, Route, RouteNode } from '.'
2
2
  import type { RouteFileMeta, RouteMeta } from './routes'
3
3
 
4
4
  const METHOD_COLOR: Record<string, string> = {
@@ -138,28 +138,20 @@ export const HttpStatus = {
138
138
  511: 'Network Authentication Required',
139
139
  }
140
140
 
141
- const BA_HEADER = 'application/octet-stream'
142
- const JSON_HEADER = 'application/json'
141
+ const BA_HEADER_RX = /^application\/octet-stream\b/
142
+ const JSON_HEADER_RX = /^application\/json\b/
143
143
  const TXT_HEADER_RX = /^text\//
144
- const FORM_HEADER_RX = /^application\/x-www-form-urlencoded/
145
- const MP_HEADER_RX = /^multipart\/form-data/
144
+ const FORM_HEADER_RX = /^application\/x-www-form-urlencoded\b/
145
+ const MP_HEADER_RX = /^multipart\/form-data\b/
146
146
 
147
- export const inferBodyType = (contentType?: string | null): STBodyType | undefined => {
147
+ export type ParseMode = 'json' | 'text' | 'byteArray' | 'urlForm' | 'multipart' | 'default'
148
+
149
+ export const inferBodyType = (contentType?: string | null): ParseMode => {
148
150
  if (!contentType) return 'default'
149
- if (contentType === JSON_HEADER) return 'json'
151
+ if (JSON_HEADER_RX.test(contentType)) return 'json'
150
152
  if (TXT_HEADER_RX.test(contentType)) return 'text'
151
153
  if (FORM_HEADER_RX.test(contentType)) return 'urlForm'
152
154
  if (MP_HEADER_RX.test(contentType)) return 'multipart'
153
- if (contentType === BA_HEADER) return 'byteArray'
154
- return 'default'
155
- }
156
-
157
- export const inferContentType = (bodyType?: string | undefined): string => {
158
- if (!bodyType) return 'default'
159
- if (bodyType === 'json') return JSON_HEADER
160
- if (bodyType === 'text') return 'text/plain'
161
- if (bodyType === 'urlForm') return 'application/x-www-form-urlencoded'
162
- if (bodyType === 'multipart') return 'multipart/form-data'
163
- if (bodyType === 'byteArray') return BA_HEADER
155
+ if (BA_HEADER_RX.test(contentType)) return 'byteArray'
164
156
  return 'default'
165
157
  }
package/src/validator.ts CHANGED
@@ -1,5 +1,17 @@
1
- import { InternalError, type STResponse } from './index'
2
- import type { STSchema, STProps, STUnion, STIntersection } from './schema'
1
+ import { InternalServerError, type STResponse } from './index'
2
+ import type {
3
+ STSchema,
4
+ STProps,
5
+ STUnion,
6
+ STIntersection,
7
+ STJson,
8
+ STLiteral,
9
+ STArray,
10
+ STNumber,
11
+ STInteger,
12
+ STString,
13
+ STObject,
14
+ } from './schema'
3
15
  import { Kind, Optional, Stream } from './schema'
4
16
  import { isIterator } from './util'
5
17
 
@@ -30,7 +42,8 @@ export const validate = (elt: any, schema: STSchema, opt?: { parse?: boolean }):
30
42
  if (!(typeof elt === 'string')) throw `Not a valid string`
31
43
  schemaValidation(elt, schema)
32
44
  } else if (schema[Kind] === 'literal') {
33
- if (elt !== schema.value) throw `Not a valid value. Found "${elt}" but expected "${schema.value}"`
45
+ const lit = schema as STLiteral
46
+ if (elt !== lit.value) throw `Not a valid value. Found "${elt}" but expected "${lit.value}"`
34
47
  } else if (schema[Kind] === 'object') {
35
48
  if (opt?.parse && typeof elt === 'string') {
36
49
  try {
@@ -42,8 +55,8 @@ export const validate = (elt: any, schema: STSchema, opt?: { parse?: boolean }):
42
55
  if (typeof elt !== 'object') throw `Not a valid object`
43
56
  if (Array.isArray(elt)) throw `Expected an object, not an array`
44
57
  const err: ValidationError = {}
45
- Object.entries(schema.props as STProps).forEach(([k, s]) => {
46
- if (!(k in elt)) {
58
+ Object.entries((schema as STObject).props ?? {}).forEach(([k, s]) => {
59
+ if (elt === null || !(k in elt)) {
47
60
  if (!s?.[Optional]) err[k] = 'Required'
48
61
  return
49
62
  }
@@ -55,7 +68,7 @@ export const validate = (elt: any, schema: STSchema, opt?: { parse?: boolean }):
55
68
  })
56
69
  if (Object.keys(err).length) errors.push(err)
57
70
  } else if (schema[Kind] === 'json') {
58
- elt = validate(elt, { ...schema, [Kind]: schema.type }, opt)
71
+ elt = validate(elt, (schema as STJson).value, opt)
59
72
  } else if (schema[Kind] === 'array') {
60
73
  if (opt?.parse && typeof elt === 'string') {
61
74
  try {
@@ -65,14 +78,14 @@ export const validate = (elt: any, schema: STSchema, opt?: { parse?: boolean }):
65
78
  }
66
79
  }
67
80
  if (!Array.isArray(elt)) throw 'Not a valid array'
68
- for (const i of elt) validate(i, schema.items)
81
+ for (const i of elt) validate(i, (schema as STArray).items)
69
82
  schemaValidation(elt, schema)
70
83
  } else if (schema[Kind] === 'byteArray') {
71
84
  if (opt?.parse && typeof elt === 'string') elt = Uint8Array.from(elt, c => c.charCodeAt(0))
72
85
  else if (opt?.parse && Array.isArray(elt)) elt = new Uint8Array(elt)
73
86
  if (!(elt instanceof Uint8Array)) throw 'Not a valid byteArray'
74
- } else if (schema[Kind] === 'union') {
75
- const union = Object.values((schema as STUnion).anyOf)
87
+ } else if (schema[Kind] === 'anyOf' || schema[Kind] === 'oneOf') {
88
+ const union = Object.values((schema as STUnion).members)
76
89
  let valid = false
77
90
  for (const s of union) {
78
91
  try {
@@ -86,7 +99,7 @@ export const validate = (elt: any, schema: STSchema, opt?: { parse?: boolean }):
86
99
  // @ts-ignore
87
100
  if (!valid) throw `Could not be parsed to any of [${union.map(u => u?.value ?? u[Kind]).join(', ')}]`
88
101
  } else if (schema[Kind] === 'intersection') {
89
- const intersection = Object.values((schema as STIntersection).allOf)
102
+ const intersection = Object.values((schema as STIntersection<any>).allOf)
90
103
  for (const s of intersection) validate(elt, s as STSchema, opt)
91
104
  } else if (schema[Kind] === 'any') {
92
105
  } else {
@@ -100,18 +113,32 @@ export const validate = (elt: any, schema: STSchema, opt?: { parse?: boolean }):
100
113
  }
101
114
 
102
115
  export const validateResponse = (response: any, schema: STResponse, status: number) => {
103
- if (!(status in schema)) return
104
- const s = schema?.[status] || schema?.['default']
116
+ const entry = schema?.[status] ?? schema?.['default']
117
+ if (!entry) return
118
+ let s: STSchema | undefined
119
+ if ((entry as STSchema)[Kind]) {
120
+ s = entry as STSchema
121
+ } else {
122
+ const c = entry as any
123
+ if (response instanceof Uint8Array)
124
+ s = c['application/octet-stream'] ?? c['*/*']
125
+ else if (typeof response === 'string')
126
+ s = c['text/plain'] ?? c['application/json'] ?? c['*/*']
127
+ else if (response !== null && typeof response === 'object')
128
+ s = c['application/json'] ?? c['*/*']
129
+ else
130
+ s = c['*/*']
131
+ }
105
132
  if (!s) return
106
133
  if (response instanceof ReadableStream) {
107
- if (!s[Stream]) throw new InternalError(`Expected ${s[Kind]} response, but got ReadableStream`)
134
+ if (!s[Stream]) throw new InternalServerError(`Expected ${s[Kind]} response, but got ReadableStream`)
108
135
  } else if (isIterator(response)) {
109
- if (!s[Stream]) throw new InternalError(`Expected ${s[Kind]} response, but got Iterator`)
136
+ if (!s[Stream]) throw new InternalServerError(`Expected ${s[Kind]} response, but got Iterator`)
110
137
  } else {
111
138
  try {
112
139
  validate(response, s)
113
140
  } catch (error) {
114
- throw new InternalError({ ResponseValidationError: error })
141
+ throw new InternalServerError({ ResponseValidationError: error })
115
142
  }
116
143
  }
117
144
  }
@@ -119,25 +146,28 @@ export const validateResponse = (response: any, schema: STResponse, status: numb
119
146
  const schemaValidation = (value: any, schema: STSchema) => {
120
147
  const errors = []
121
148
  if (schema[Kind] === 'integer' || schema[Kind] === 'number') {
122
- if (schema.exclusiveMin !== undefined)
123
- if ((value as number) <= schema.exclusiveMin) errors.push(`Is less or equal to ${schema.exclusiveMin}`)
124
- if (schema.exclusiveMax !== undefined)
125
- if ((value as number) >= schema.exclusiveMax) errors.push(`Is greater or equal to ${schema.exclusiveMax}`)
126
- if (schema.min !== undefined) if ((value as number) < schema.min) errors.push(`Is less than ${schema.min}`)
127
- if (schema.max !== undefined) if ((value as number) > schema.max) errors.push(`Is greater than ${schema.max}`)
149
+ const n = schema as STNumber | STInteger
150
+ if (n.exclusiveMin !== undefined)
151
+ if ((value as number) <= n.exclusiveMin) errors.push(`Is less or equal to ${n.exclusiveMin}`)
152
+ if (n.exclusiveMax !== undefined)
153
+ if ((value as number) >= n.exclusiveMax) errors.push(`Is greater or equal to ${n.exclusiveMax}`)
154
+ if (n.min !== undefined) if ((value as number) < n.min) errors.push(`Is less than ${n.min}`)
155
+ if (n.max !== undefined) if ((value as number) > n.max) errors.push(`Is greater than ${n.max}`)
128
156
  } else if (schema[Kind] === 'string') {
129
- if (schema.minLength !== undefined && (value as string).length < schema.minLength)
130
- errors.push(`Length is too small (${schema.minLength} char min)`)
131
- if (schema.maxLength !== undefined && (value as string).length > schema.maxLength)
132
- errors.push(`Length is too large (${schema.maxLength} char max)`)
133
- if (schema.pattern !== undefined && !(value as string).match(schema.pattern))
134
- errors.push(`Does not match pattern ${schema.pattern}`)
157
+ const str = schema as STString
158
+ if (str.minLength !== undefined && (value as string).length < str.minLength)
159
+ errors.push(`Length is too small (${str.minLength} char min)`)
160
+ if (str.maxLength !== undefined && (value as string).length > str.maxLength)
161
+ errors.push(`Length is too large (${str.maxLength} char max)`)
162
+ if (str.pattern !== undefined && !(value as string).match(str.pattern))
163
+ errors.push(`Does not match pattern ${str.pattern}`)
135
164
  } else if (schema[Kind] === 'array') {
136
- if (schema.minItems !== undefined && (value as any[]).length < schema.minItems)
137
- errors.push(`Must contain at least ${schema.minItems} item${schema.minItems > 1 ? 's' : ''}`)
138
- if (schema.maxItems !== undefined && (value as any[]).length > schema.maxItems)
139
- errors.push(`Must contain at most (${schema.maxItems} item${schema.maxItems > 1 ? 's' : ''}`)
140
- if (schema.unique === true && new Set(value as any[]).size !== (value as any[]).length)
165
+ const arr = schema as STArray
166
+ if (arr.minLength !== undefined && (value as any[]).length < arr.minLength)
167
+ errors.push(`Must contain at least ${arr.minLength} item${arr.minLength > 1 ? 's' : ''}`)
168
+ if (arr.maxLength !== undefined && (value as any[]).length > arr.maxLength)
169
+ errors.push(`Must contain at most ${arr.maxLength} item${arr.maxLength > 1 ? 's' : ''}`)
170
+ if (arr.unique === true && new Set(value as any[]).size !== (value as any[]).length)
141
171
  errors.push(`Has duplicate values`)
142
172
  }
143
173
  if (errors.length) throw Array.isArray(errors) && errors.length === 1 ? errors[0] : errors