galbe 0.11.0 → 0.12.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/util.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Method, Route, RouteNode } from '.'
1
+ import type { Method, Route, RouteNode, STBodyType } from '.'
2
2
  import type { RouteFileMeta, RouteMeta } from './routes'
3
3
 
4
4
  const METHOD_COLOR: Record<string, string> = {
@@ -8,11 +8,11 @@ const METHOD_COLOR: Record<string, string> = {
8
8
  patch: '\x1b[33m',
9
9
  delete: '\x1b[31m',
10
10
  options: '',
11
- head: ''
11
+ head: '',
12
12
  }
13
13
  const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g
14
14
 
15
- export const softMerge = <T> (base: T, override: T): T => {
15
+ export const softMerge = <T>(base: T, override: T): T => {
16
16
  for (const key in override) {
17
17
  if (override[key] instanceof Object && !(override[key] instanceof Array)) {
18
18
  if (!base[key]) Object.assign(base as any, { [key]: {} })
@@ -22,7 +22,7 @@ export const softMerge = <T> (base: T, override: T): T => {
22
22
  return base
23
23
  }
24
24
  export const logRoute = (
25
- r: { method: string; path: string, static?: { path: string, root: string } },
25
+ r: { method: string; path: string; static?: { path: string; root: string } },
26
26
  meta?: RouteMeta,
27
27
  format?: { maxPathLength?: number }
28
28
  ) => {
@@ -32,16 +32,18 @@ export const logRoute = (
32
32
  let routeLog = ''
33
33
 
34
34
  if (r?.static) {
35
- routeLog = `[\x1b[0;33m${`STATIC\x1b[0m]`.padEnd(12, ' ')} ${path
36
- .padEnd(format?.maxPathLength ?? path.length, ' ')} \x1b[0;33m⇒\x1b[0m ${r.static.path}\x1b[0m`
35
+ routeLog = `[\x1b[0;33m${`STATIC\x1b[0m]`.padEnd(12, ' ')} ${path.padEnd(
36
+ format?.maxPathLength ?? path.length,
37
+ ' '
38
+ )} \x1b[0;33m⇒\x1b[0m ${r.static.path}\x1b[0m`
37
39
  if (meta?.deprecated) routeLog = `\x1b[0;9m\x1b[38;5;244m${routeLog.replaceAll(ansiRegex, '')}\x1b[0m`
38
40
  } else {
39
41
  let color = METHOD_COLOR?.[method] || ''
40
42
  let [_, summary, _description] = meta?.head?.match(/^([^\n]*)\n\n(.*)/) || []
41
- if(!summary) _description = meta?.head || ''
43
+ if (!summary) _description = meta?.head || ''
42
44
  routeLog = `[${color}${`${method.toUpperCase()}\x1b[0m]`.padEnd(12, ' ')} ${path
43
45
  .padEnd(format?.maxPathLength ?? path.length, ' ')
44
- .replaceAll(/:([^\/]+)/g, '\x1b[0;33m:$1\x1b[0m')}${(summary?` ${summary}`:'').replace(/\n/, '')}\x1b[0m`
46
+ .replaceAll(/:([^\/]+)/g, '\x1b[0;33m:$1\x1b[0m')}${(summary ? ` ${summary}` : '').replace(/\n/, '')}\x1b[0m`
45
47
  if (meta?.deprecated) routeLog = `\x1b[0;9m\x1b[38;5;244m${routeLog.replaceAll(ansiRegex, '')}\x1b[0m`
46
48
  }
47
49
 
@@ -133,5 +135,31 @@ export const HttpStatus = {
133
135
  504: 'Gateway Timeout',
134
136
  505: 'HTTP Version Not Supported',
135
137
  507: 'Insufficient Storage',
136
- 511: 'Network Authentication Required'
138
+ 511: 'Network Authentication Required',
139
+ }
140
+
141
+ const BA_HEADER = 'application/octet-stream'
142
+ const JSON_HEADER = 'application/json'
143
+ const TXT_HEADER_RX = /^text\//
144
+ const FORM_HEADER_RX = /^application\/x-www-form-urlencoded/
145
+ const MP_HEADER_RX = /^multipart\/form-data/
146
+
147
+ export const inferBodyType = (contentType?: string | null): STBodyType | undefined => {
148
+ if (!contentType) return 'default'
149
+ if (contentType === JSON_HEADER) return 'json'
150
+ if (TXT_HEADER_RX.test(contentType)) return 'text'
151
+ if (FORM_HEADER_RX.test(contentType)) return 'urlForm'
152
+ 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
164
+ return 'default'
137
165
  }
package/src/validator.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { InternalError, type STResponse } from './index'
2
- import type { STSchema, STProps, STUnion } from './schema'
2
+ import type { STSchema, STProps, STUnion, STIntersection } from './schema'
3
3
  import { Kind, Optional, Stream } from './schema'
4
4
  import { isIterator } from './util'
5
5
 
6
- export const validate = (elt: any, schema: STSchema, parse = false): any => {
6
+ export const validate = (elt: any, schema: STSchema, opt?: { parse?: boolean }): any => {
7
7
  type ValidationError = string | string[] | { [key: string]: ValidationError }
8
8
  const errors: ValidationError[] = []
9
9
  const iElt = elt
@@ -12,25 +12,27 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
12
12
  if (elt !== null) throw `Expected null value got ${iElt}`
13
13
  } else if (schema[Kind] === 'boolean') {
14
14
  if (typeof elt === 'string') {
15
- if (parse) elt = elt === 'true' ? true : elt === 'false' ? false : null
15
+ if (opt?.parse) elt = elt === 'true' ? true : elt === 'false' ? false : null
16
16
  else throw `Expected boolean, got string.`
17
17
  }
18
18
  if (elt !== true && elt !== false) throw `Not a valid boolean. Should be 'true' or 'false'`
19
19
  } else if (schema[Kind] === 'integer') {
20
- if (parse && typeof elt === 'string') elt = Number(elt)
20
+ if (elt === '') throw `Not a valid integer`
21
+ if (opt?.parse && typeof elt === 'string') elt = Number(elt)
21
22
  if (!Number.isInteger(elt)) throw `Not a valid integer`
22
23
  schemaValidation(elt, schema)
23
24
  } else if (schema[Kind] === 'number') {
24
- if (parse && typeof elt === 'string') elt = Number(elt)
25
+ if (elt === '') throw `Not a valid number`
26
+ if (opt?.parse && typeof elt === 'string') elt = Number(elt)
25
27
  if (!Number.isFinite(elt)) throw `Not a valid number`
26
28
  schemaValidation(elt, schema)
27
29
  } else if (schema[Kind] === 'string') {
28
30
  if (!(typeof elt === 'string')) throw `Not a valid string`
29
31
  schemaValidation(elt, schema)
30
32
  } else if (schema[Kind] === 'literal') {
31
- if (elt !== schema.value) throw `Not a valid value`
33
+ if (elt !== schema.value) throw `Not a valid value. Found "${elt}" but expected "${schema.value}"`
32
34
  } else if (schema[Kind] === 'object') {
33
- if (parse && typeof elt === 'string') {
35
+ if (opt?.parse && typeof elt === 'string') {
34
36
  try {
35
37
  elt = JSON.parse(elt)
36
38
  } catch {
@@ -46,16 +48,16 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
46
48
  return
47
49
  }
48
50
  try {
49
- validate(elt[k], s, parse)
51
+ validate(elt[k], s, opt)
50
52
  } catch (e) {
51
53
  err[k] = e as ValidationError
52
54
  }
53
55
  })
54
56
  if (Object.keys(err).length) errors.push(err)
55
57
  } else if (schema[Kind] === 'json') {
56
- elt = validate(elt, { ...schema, [Kind]: schema.type }, parse)
58
+ elt = validate(elt, { ...schema, [Kind]: schema.type }, opt)
57
59
  } else if (schema[Kind] === 'array') {
58
- if (parse && typeof elt === 'string') {
60
+ if (opt?.parse && typeof elt === 'string') {
59
61
  try {
60
62
  elt = JSON.parse(elt)
61
63
  } catch (error) {
@@ -63,17 +65,18 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
63
65
  }
64
66
  }
65
67
  if (!Array.isArray(elt)) throw 'Not a valid array'
68
+ for (const i of elt) validate(i, schema.items)
66
69
  schemaValidation(elt, schema)
67
70
  } else if (schema[Kind] === 'byteArray') {
68
- if (parse && typeof elt === 'string') elt = Uint8Array.from(elt, c => c.charCodeAt(0))
69
- else if (parse && Array.isArray(elt)) elt = new Uint8Array(elt)
71
+ if (opt?.parse && typeof elt === 'string') elt = Uint8Array.from(elt, c => c.charCodeAt(0))
72
+ else if (opt?.parse && Array.isArray(elt)) elt = new Uint8Array(elt)
70
73
  if (!(elt instanceof Uint8Array)) throw 'Not a valid byteArray'
71
74
  } else if (schema[Kind] === 'union') {
72
75
  const union = Object.values((schema as STUnion).anyOf)
73
76
  let valid = false
74
- for (const u of union) {
77
+ for (const s of union) {
75
78
  try {
76
- elt = validate(elt, u as STSchema, parse)
79
+ elt = validate(elt, s as STSchema, opt)
77
80
  valid = true
78
81
  break
79
82
  } catch (err) {
@@ -82,6 +85,9 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
82
85
  }
83
86
  // @ts-ignore
84
87
  if (!valid) throw `Could not be parsed to any of [${union.map(u => u?.value ?? u[Kind]).join(', ')}]`
88
+ } else if (schema[Kind] === 'intersection') {
89
+ const intersection = Object.values((schema as STIntersection).allOf)
90
+ for (const s of intersection) validate(elt, s as STSchema, opt)
85
91
  } else if (schema[Kind] === 'any') {
86
92
  } else {
87
93
  throw `Unsupported schema type ${schema[Kind]}`
@@ -96,7 +102,7 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
96
102
  export const validateResponse = (response: any, schema: STResponse, status: number) => {
97
103
  if (!(status in schema)) return
98
104
  const s = schema?.[status] || schema?.['default']
99
- if(!s) return
105
+ if (!s) return
100
106
  if (response instanceof ReadableStream) {
101
107
  if (!s[Stream]) throw new InternalError(`Expected ${s[Kind]} response, but got ReadableStream`)
102
108
  } else if (isIterator(response)) {
@@ -116,11 +122,9 @@ const schemaValidation = (value: any, schema: STSchema) => {
116
122
  if (schema.exclusiveMin !== undefined)
117
123
  if ((value as number) <= schema.exclusiveMin) errors.push(`Is less or equal to ${schema.exclusiveMin}`)
118
124
  if (schema.exclusiveMax !== undefined)
119
- if ((value as number) >= schema.exclusiveMax)
120
- errors.push(`Is greater or equal to ${schema.exclusiveMax}`)
125
+ if ((value as number) >= schema.exclusiveMax) errors.push(`Is greater or equal to ${schema.exclusiveMax}`)
121
126
  if (schema.min !== undefined) if ((value as number) < schema.min) errors.push(`Is less than ${schema.min}`)
122
- if (schema.max !== undefined)
123
- if ((value as number) > schema.max) errors.push(`Is greater than ${schema.max}`)
127
+ if (schema.max !== undefined) if ((value as number) > schema.max) errors.push(`Is greater than ${schema.max}`)
124
128
  } else if (schema[Kind] === 'string') {
125
129
  if (schema.minLength !== undefined && (value as string).length < schema.minLength)
126
130
  errors.push(`Length is too small (${schema.minLength} char min)`)