galbe 0.13.1 → 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
@@ -21,6 +21,9 @@ import type {
21
21
  } from './schema'
22
22
  import type { Galbe } from './index'
23
23
  import { HttpStatus } from './util'
24
+ import type { CookieOptions } from './cookies'
25
+
26
+ export type MediaType = `${string}/${string}`
24
27
 
25
28
  export type STResponseValue =
26
29
  | STByteArray
@@ -33,23 +36,54 @@ export type STResponseValue =
33
36
  | STJson
34
37
  | STArray
35
38
  | STUnion
39
+ | STIntersection<any>
36
40
  | STStream
37
41
  | STAny
38
42
  | STNull
39
- 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
40
78
  | STNull
41
- | Partial<{
42
- byteArray?: STByteArray | STStream
43
- text?: STString | STLiteral | STBoolean | STNumber | STInteger | STUnion | STStream
44
- json?: STJson | STObject | STBoolean | STInteger | STNumber | STString | STArray | STUnion | STIntersection
45
- urlForm?: STObject | STStream | STUnion
46
- multipart?: STMultipartForm | STStream | STUnion
47
- default?: STString | STByteArray | STStream | STAny
48
- }>
49
- export type STBodyType = keyof STBody
50
- export type STBodyValue = STBody[STBodyType]
51
79
 
52
- 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>>
53
87
 
54
88
  export type MaybeArray<T> = T | T[]
55
89
  export type MaybeSTArray<T extends STSchema> = T | STArray<T>
@@ -61,10 +95,10 @@ type MaybePromise<T> = T | Promise<T>
61
95
  export type ExtractParams<T extends string> = T extends `/:${infer P}/${infer Rest}`
62
96
  ? P | ExtractParams<Rest>
63
97
  : T extends `${infer _}:${infer P}/${infer Rest}`
64
- ? P | ExtractParams<Rest>
65
- : T extends `${infer _}:${infer P}`
66
- ? P
67
- : never
98
+ ? P | ExtractParams<Rest>
99
+ : T extends `${infer _}:${infer P}`
100
+ ? P
101
+ : never
68
102
 
69
103
  type STHeadersPrimaryValue = STString | STBoolean | STNumber | STInteger | STLiteral
70
104
  type STHeadersValue = MaybeSTUnion<STHeadersPrimaryValue>
@@ -143,7 +177,7 @@ export type RequestSchema<
143
177
  P extends Partial<STParams<Path>> = Partial<STParams<Path>>,
144
178
  Q extends STQuery = STQuery,
145
179
  B extends STBody = STBody,
146
- R extends Partial<STResponse> = STResponse
180
+ R extends Partial<STResponse> = STResponse,
147
181
  > = {
148
182
  headers?: H
149
183
  params?: P
@@ -161,13 +195,21 @@ type OmitNotDefined<S extends RequestSchema> = {
161
195
  never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
162
196
  }
163
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
+ }
164
206
  export type Context<
165
207
  M extends Method = Method,
166
208
  Path extends string = string,
167
- S extends RequestSchema = RequestSchema
168
- > = {
169
- [K in STBodyType]: K extends keyof Exclude<S['body'], undefined>
170
- ? {
209
+ S extends RequestSchema = RequestSchema,
210
+ > = 0 extends 1 & Exclude<S['body'], undefined | STNull>
211
+ ? {
212
+ [K in keyof Exclude<S['body'], undefined | STNull>]: {
171
213
  headers: Static<STObject<Exclude<S['headers'], undefined>>>
172
214
  params: {
173
215
  [P in ExtractParams<Path>]: P extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[P] : string
@@ -177,24 +219,51 @@ export type Context<
177
219
  body: M extends 'get' | 'options' | 'head'
178
220
  ? null
179
221
  : Exclude<S['body'], undefined> extends STNull
180
- ? null
181
- : K extends STBodyType
182
- ? StaticBody<Exclude<Exclude<S['body'], undefined>[K], undefined>>
183
- : never
222
+ ? null
223
+ : StaticBody<Extract<Exclude<Exclude<S['body'], undefined | STNull>[K], undefined>, STSchema>>
184
224
  request: Request
185
225
  remoteAddress: SocketAddress | null
186
226
  route?: Route
187
227
  state: Record<string, any>
188
- set: {
189
- headers: {
190
- 'set-cookie': string[]
191
- [header: string]: string | string[]
192
- }
193
- 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
194
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>
195
247
  }
196
- : never
197
- }[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>]
198
267
  export type Next = () => void | Promise<any>
199
268
  export type Hook<M extends Method = Method, Path extends string = string, S extends RequestSchema = RequestSchema> = (
200
269
  ctx: Context<M, Path, S>,
@@ -203,7 +272,7 @@ export type Hook<M extends Method = Method, Path extends string = string, S exte
203
272
  export type Handler<
204
273
  M extends Method = Method,
205
274
  Path extends string = string,
206
- S extends RequestSchema = RequestSchema
275
+ S extends RequestSchema = RequestSchema,
207
276
  > = (ctx: Context<M, Path, S>) => any
208
277
  export type Endpoint<M extends Method> = {
209
278
  <
@@ -212,7 +281,7 @@ export type Endpoint<M extends Method> = {
212
281
  H extends STHeaders = any,
213
282
  Q extends STQuery = any,
214
283
  B extends STBody = any,
215
- R extends STResponse = STResponse
284
+ R extends STResponse = STResponse,
216
285
  >(
217
286
  path: Path,
218
287
  schema: RequestSchema<M, Path, H, P, Q, B, R>,
@@ -225,7 +294,7 @@ export type Endpoint<M extends Method> = {
225
294
  H extends STHeaders = any,
226
295
  Q extends STQuery = any,
227
296
  B extends STBody = any,
228
- R extends STResponse = STResponse
297
+ R extends STResponse = STResponse,
229
298
  >(
230
299
  path: Path,
231
300
  schema: RequestSchema<M, Path, H, P, Q, B, R>,
@@ -237,7 +306,7 @@ export type Endpoint<M extends Method> = {
237
306
  H extends STHeaders = any,
238
307
  Q extends STQuery = any,
239
308
  B extends STBody = any,
240
- R extends STResponse = STResponse
309
+ R extends STResponse = STResponse,
241
310
  >(
242
311
  path: Path,
243
312
  hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
@@ -249,7 +318,7 @@ export type Endpoint<M extends Method> = {
249
318
  H extends STHeaders = any,
250
319
  Q extends STQuery = any,
251
320
  B extends STBody = any,
252
- R extends STResponse = STResponse
321
+ R extends STResponse = STResponse,
253
322
  >(
254
323
  path: Path,
255
324
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
@@ -265,12 +334,19 @@ export type StaticEndpoint<P extends string = string, T extends string = string>
265
334
  options?: StaticEndpointOptions
266
335
  ) => Route<'get', P, {}, {}, {}, STBody, STResponse, T>
267
336
 
268
- export class RequestError {
337
+ export class RequestError extends Error {
269
338
  status: number
270
339
  payload?: any
271
340
  headers?: Record<string, string>
272
- constructor(options: { status?: number; payload?: any; headers?: Record<string, string> }) {
273
- 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
274
350
  this.payload = options.payload
275
351
  this.headers = options.headers
276
352
  }
@@ -293,7 +369,7 @@ export type Route<
293
369
  B extends STBody = STBody,
294
370
  R extends STResponse = STResponse,
295
371
  SP extends string = string,
296
- SR extends string = string
372
+ SR extends string = string,
297
373
  > = {
298
374
  method: M
299
375
  path: Path
@@ -304,29 +380,57 @@ export type Route<
304
380
  static?: { path: SP; root: SR }
305
381
  }
306
382
 
307
- export class NotFoundError extends RequestError {
308
- constructor(message?: any) {
309
- 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
+ }
310
388
  }
311
- }
312
389
 
313
- export class MethodNotAllowedError extends RequestError {
314
- constructor(message?: any) {
315
- super({ status: 405, payload: message ?? HttpStatus[405] })
316
- }
317
- }
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) {}
318
419
 
319
- export class InternalError extends RequestError {
320
- constructor(message?: any) {
321
- super({ status: 500, payload: message ?? HttpStatus[500] })
322
- }
323
- }
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) {}
324
429
 
325
- export class NotImplementedError extends RequestError {
326
- constructor(message?: any) {
327
- super({ status: 501, payload: message ?? HttpStatus[501] })
328
- }
329
- }
430
+ /** @deprecated use {@link InternalServerError} */
431
+ export const InternalError = InternalServerError
432
+ /** @deprecated use {@link InternalServerError} */
433
+ export type InternalError = InternalServerError
330
434
 
331
435
  /**
332
436
  * #### GalbePlugin
@@ -364,7 +468,21 @@ export type GalbeCLICommand = {
364
468
  tags: string[]
365
469
  description?: string
366
470
  route: Route
471
+ pathT: string
367
472
  arguments?: { name: string; type: string; description: string }[]
368
473
  options?: { name: string; short: string; type: string; description: string; default: any }[]
369
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>
370
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