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/.prettierrc +1 -1
- package/bin/commands/generate/client.ts +58 -30
- package/bin/commands/generate/code/openapi.parser.ts +72 -45
- package/bin/res/cli.template.js +20 -18
- package/bin/res/client.template.ts +52 -26
- package/bin/util.ts +27 -7
- package/docs/context.md +1 -1
- package/docs/schemas.md +63 -26
- package/package.json +1 -1
- package/src/extras/spec/openapi.serializer.ts +83 -56
- package/src/index.ts +16 -11
- package/src/parser.ts +198 -146
- package/src/schema.ts +156 -66
- package/src/server.ts +18 -16
- package/src/types.ts +54 -42
- package/src/util.ts +37 -9
- package/src/validator.ts +23 -19
- package/test/parser.test.ts +369 -281
- package/test/requests.test.ts +242 -176
- package/test/resources/static/chameleon.png +0 -0
- package/test/resources/static/index.html +13 -0
- package/test/resources/static/sub/index.html +13 -0
- package/test/resources/static/sub/other.html +13 -0
- package/test/responses.test.ts +49 -49
- package/test/test.utils.ts +4 -2
- package/test/types.test.ts +909 -0
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>
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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,
|
|
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 },
|
|
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
|
|
77
|
+
for (const s of union) {
|
|
75
78
|
try {
|
|
76
|
-
elt = validate(elt,
|
|
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)`)
|