galbe 0.1.6 → 0.1.7
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/bin/cli.ts +11 -9
- package/bun.lockb +0 -0
- package/docs/getting-started.md +221 -3
- package/docs/routes.md +124 -0
- package/docs/schemas.md +244 -0
- package/package.json +1 -2
- package/src/index.ts +122 -219
- package/src/parser.ts +98 -100
- package/src/routes.ts +3 -1
- package/src/schema.ts +378 -0
- package/src/server.ts +1 -1
- package/src/types.ts +128 -147
- package/src/validator.ts +33 -35
- package/test/parser.test.ts +67 -67
- package/test/requests.test.ts +64 -118
- package/test/resources/test.route.comment.ts +3 -3
- package/test/responses.test.ts +3 -3
- package/test/routeFiles.test.ts +13 -0
- package/test/test.utils.ts +14 -12
- package/docs/Getting Started/basic.md +0 -1
- package/docs/Guides/routes.md +0 -1
package/src/types.ts
CHANGED
|
@@ -1,117 +1,54 @@
|
|
|
1
|
+
import type { ServeOptions, TLSServeOptions } from 'bun'
|
|
1
2
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
TAny
|
|
18
|
-
} from '@sinclair/typebox'
|
|
3
|
+
STArray,
|
|
4
|
+
STBoolean,
|
|
5
|
+
STByteArray,
|
|
6
|
+
STInteger,
|
|
7
|
+
STLiteral,
|
|
8
|
+
STMultipartForm,
|
|
9
|
+
STNumber,
|
|
10
|
+
STObject,
|
|
11
|
+
STStream,
|
|
12
|
+
STString,
|
|
13
|
+
STUnion,
|
|
14
|
+
STUrlForm,
|
|
15
|
+
Static
|
|
16
|
+
} from './schema'
|
|
17
|
+
import type { Galbe } from './index'
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
| TNumber
|
|
33
|
-
| TInteger
|
|
34
|
-
| TObject
|
|
35
|
-
| TArray
|
|
36
|
-
| TUrlForm
|
|
37
|
-
| TMultipartForm
|
|
38
|
-
| TUnion
|
|
39
|
-
| TStream
|
|
40
|
-
export type TStreamable = TByteArray | TString | TUrlForm | TMultipartForm
|
|
41
|
-
export type TUrlFormParam = TString | TByteArray | TBoolean | TNumber | TInteger | TLiteral | TArray | TAny | TUnion
|
|
42
|
-
export type TMultipartFormParam = TByteArray | TUrlFormParam | TObject | TArray
|
|
19
|
+
export type STBody =
|
|
20
|
+
| STByteArray
|
|
21
|
+
| STString
|
|
22
|
+
| STBoolean
|
|
23
|
+
| STNumber
|
|
24
|
+
| STInteger
|
|
25
|
+
| STObject
|
|
26
|
+
| STArray
|
|
27
|
+
| STUrlForm
|
|
28
|
+
| STMultipartForm
|
|
29
|
+
| STUnion
|
|
30
|
+
| STStream
|
|
43
31
|
export type MaybeArray<T> = T | T[]
|
|
44
32
|
|
|
45
|
-
export
|
|
46
|
-
|
|
47
|
-
V extends Static<TMultipartFormParam> = any
|
|
48
|
-
> {
|
|
49
|
-
headers: { type?: string; name: K; filename?: string }
|
|
50
|
-
content: V
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface TByteArray extends TSchema {
|
|
54
|
-
[Kind]: 'ByteArray'
|
|
55
|
-
static: Uint8Array
|
|
56
|
-
type: 'byteArray'
|
|
57
|
-
}
|
|
58
|
-
export interface TMultipartForm<T extends TMultipartProperties = TMultipartProperties> extends TSchema {
|
|
59
|
-
[Kind]: 'MultipartForm'
|
|
60
|
-
static: MultipartPropertiesReduce<T, this['params']>
|
|
61
|
-
type: 'multipartForm'
|
|
62
|
-
}
|
|
63
|
-
export interface TUrlForm<T extends TUrlFormProperties = TUrlFormProperties> extends TSchema {
|
|
64
|
-
[Kind]: 'UrlForm'
|
|
65
|
-
static: UrlFormPropertiesReduce<T, this['params']>
|
|
66
|
-
type: 'urlForm'
|
|
67
|
-
}
|
|
33
|
+
export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'
|
|
34
|
+
type MaybePromise<T> = T | Promise<T>
|
|
68
35
|
|
|
69
|
-
export type
|
|
70
|
-
|
|
71
|
-
T
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
>
|
|
76
|
-
export type MultipartPropertiesReducer<
|
|
77
|
-
T extends TMultipartProperties,
|
|
78
|
-
R extends Record<keyof any, unknown>
|
|
79
|
-
> = MultipartEvaluate<
|
|
80
|
-
Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> &
|
|
81
|
-
Readonly<Pick<R, ReadonlyPropertyKeys<T>>> &
|
|
82
|
-
Partial<Pick<R, OptionalPropertyKeys<T>>> &
|
|
83
|
-
Required<Pick<R, RequiredPropertyKeys<T>>>
|
|
84
|
-
>
|
|
85
|
-
export type MultipartEvaluate<T> = T extends infer O
|
|
86
|
-
? {
|
|
87
|
-
[K in keyof O]: O[K] extends Static<TMultipartFormParam> ? MultipartFormData<K, O[K]> : never
|
|
88
|
-
}
|
|
36
|
+
export type ExtractParams<T extends string> = T extends `/:${infer P}/${infer Rest}`
|
|
37
|
+
? P | ExtractParams<Rest>
|
|
38
|
+
: T extends `${infer _}:${infer P}/${infer Rest}`
|
|
39
|
+
? P | ExtractParams<Rest>
|
|
40
|
+
: T extends `${infer _}:${infer P}`
|
|
41
|
+
? P
|
|
89
42
|
: never
|
|
90
43
|
|
|
91
|
-
|
|
92
|
-
export type
|
|
93
|
-
T,
|
|
94
|
-
{
|
|
95
|
-
[K in keyof T]: Static<T[K], P>
|
|
96
|
-
}
|
|
97
|
-
>
|
|
98
|
-
export type UrlFormPropertiesReducer<
|
|
99
|
-
T extends TUrlFormProperties,
|
|
100
|
-
R extends Record<keyof any, unknown>
|
|
101
|
-
> = UrlFormEvaluate<
|
|
102
|
-
Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> &
|
|
103
|
-
Readonly<Pick<R, ReadonlyPropertyKeys<T>>> &
|
|
104
|
-
Partial<Pick<R, OptionalPropertyKeys<T>>> &
|
|
105
|
-
Required<Pick<R, RequiredPropertyKeys<T>>>
|
|
106
|
-
>
|
|
107
|
-
export type UrlFormEvaluate<T> = T extends infer O
|
|
108
|
-
? {
|
|
109
|
-
[K in keyof O]: O[K] extends Static<TUrlFormParam> ? O[K] : never
|
|
110
|
-
}
|
|
111
|
-
: never
|
|
44
|
+
type STHeadersValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
|
|
45
|
+
export type STHeaders = Record<string, STHeadersValue>
|
|
112
46
|
|
|
113
|
-
|
|
114
|
-
type
|
|
47
|
+
type STParamsValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
|
|
48
|
+
export type STParams<Path extends string = string> = Record<ExtractParams<Path>, STParamsValue>
|
|
49
|
+
|
|
50
|
+
type STQueryValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
|
|
51
|
+
export type STQuery = Record<string, STQueryValue>
|
|
115
52
|
|
|
116
53
|
/**
|
|
117
54
|
* #### GalbeConfig
|
|
@@ -133,7 +70,8 @@ type MaybePromise<T> = T | Promise<T>
|
|
|
133
70
|
export type GalbeConfig = {
|
|
134
71
|
port?: number
|
|
135
72
|
basePath?: string
|
|
136
|
-
|
|
73
|
+
server?: Exclude<ServeOptions, 'port'> | TLSServeOptions
|
|
74
|
+
routes?: boolean | string | string[]
|
|
137
75
|
plugin?: Record<string, any>
|
|
138
76
|
}
|
|
139
77
|
/**
|
|
@@ -143,33 +81,46 @@ export type GalbeConfig = {
|
|
|
143
81
|
* ---
|
|
144
82
|
* @example
|
|
145
83
|
* ```typescript
|
|
146
|
-
* import { T
|
|
84
|
+
* import { $T } from 'galbe'
|
|
147
85
|
* const MyRequestSchema = {
|
|
148
86
|
* params: {
|
|
149
|
-
* id: T.
|
|
87
|
+
* id: $T.number(),
|
|
150
88
|
* },
|
|
151
|
-
* body: T.
|
|
152
|
-
* name: T.
|
|
153
|
-
* age: T.
|
|
89
|
+
* body: $T.object({
|
|
90
|
+
* name: $T.string()
|
|
91
|
+
* age: $T.optional($T.number({min: 0})),
|
|
154
92
|
* })
|
|
155
93
|
* }
|
|
156
94
|
* ```
|
|
157
95
|
*/
|
|
158
|
-
export type
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
96
|
+
export type RequestSchema<
|
|
97
|
+
Path extends string = string,
|
|
98
|
+
H extends STHeaders = {},
|
|
99
|
+
P extends Partial<STParams<Path>> = {},
|
|
100
|
+
Q extends STQuery = {},
|
|
101
|
+
B extends STBody = STBody
|
|
163
102
|
> = {
|
|
164
103
|
headers?: H
|
|
165
104
|
params?: P
|
|
166
105
|
query?: Q
|
|
167
106
|
body?: B
|
|
168
107
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
params
|
|
172
|
-
|
|
108
|
+
|
|
109
|
+
type OmitNotDefined<S extends RequestSchema> = {
|
|
110
|
+
[K in keyof Exclude<S['params'], undefined> as Exclude<S['params'], undefined>[K] extends Required<
|
|
111
|
+
Exclude<S['params'], undefined>
|
|
112
|
+
>[K]
|
|
113
|
+
? K
|
|
114
|
+
: //@ts-ignore
|
|
115
|
+
never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type Context<Path extends string = string, S extends RequestSchema = RequestSchema> = {
|
|
119
|
+
headers: Static<STObject<Exclude<S['headers'], undefined>>>
|
|
120
|
+
params: {
|
|
121
|
+
[K in ExtractParams<Path>]: K extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[K] : string
|
|
122
|
+
}
|
|
123
|
+
query: Static<STObject<Exclude<S['query'], undefined>>>
|
|
173
124
|
body: Static<Exclude<S['body'], undefined>>
|
|
174
125
|
request: Request
|
|
175
126
|
state: Record<string, any>
|
|
@@ -182,28 +133,57 @@ export type Context<S extends Schema = any> = {
|
|
|
182
133
|
}
|
|
183
134
|
}
|
|
184
135
|
export type Next = () => void | Promise<void>
|
|
185
|
-
export type Hook<
|
|
186
|
-
|
|
136
|
+
export type Hook<Path extends string = string, S extends RequestSchema = RequestSchema> = (
|
|
137
|
+
ctx: Context<Path, S>,
|
|
138
|
+
next: Next
|
|
139
|
+
) => any | Promise<any>
|
|
140
|
+
export type Handler<Path extends string = string, S extends RequestSchema = RequestSchema> = (
|
|
141
|
+
ctx: Context<Path, S>
|
|
142
|
+
) => any
|
|
187
143
|
export type Endpoint = {
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
144
|
+
<
|
|
145
|
+
Path extends string,
|
|
146
|
+
H extends STHeaders,
|
|
147
|
+
P extends Partial<STParams<Path>>,
|
|
148
|
+
Q extends STQuery,
|
|
149
|
+
B extends STBody = STObject
|
|
150
|
+
>(
|
|
151
|
+
path: Path,
|
|
152
|
+
schema: RequestSchema<Path, H, P, Q, B>,
|
|
153
|
+
hooks: Hook<Path, RequestSchema<Path, H, P, Q, B>>[],
|
|
154
|
+
handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
193
155
|
): void
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
156
|
+
<
|
|
157
|
+
Path extends string,
|
|
158
|
+
H extends STHeaders,
|
|
159
|
+
P extends Partial<STParams<Path>>,
|
|
160
|
+
Q extends STQuery,
|
|
161
|
+
B extends STBody = STObject
|
|
162
|
+
>(
|
|
163
|
+
path: Path,
|
|
164
|
+
schema: RequestSchema<Path, H, P, Q, B>,
|
|
165
|
+
handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
198
166
|
): void
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
167
|
+
<
|
|
168
|
+
Path extends string,
|
|
169
|
+
H extends STHeaders,
|
|
170
|
+
P extends Partial<STParams<Path>>,
|
|
171
|
+
Q extends STQuery,
|
|
172
|
+
B extends STBody = STObject
|
|
173
|
+
>(
|
|
174
|
+
path: Path,
|
|
175
|
+
hooks: Hook<Path, RequestSchema<Path, H, P, Q, B>>[],
|
|
176
|
+
handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
203
177
|
): void
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
178
|
+
<
|
|
179
|
+
Path extends string,
|
|
180
|
+
H extends STHeaders,
|
|
181
|
+
P extends Partial<STParams<Path>>,
|
|
182
|
+
Q extends STQuery,
|
|
183
|
+
B extends STBody = STObject
|
|
184
|
+
>(
|
|
185
|
+
path: Path,
|
|
186
|
+
handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
207
187
|
): void
|
|
208
188
|
}
|
|
209
189
|
|
|
@@ -225,17 +205,18 @@ export type RouteNode = {
|
|
|
225
205
|
}
|
|
226
206
|
|
|
227
207
|
export type Route<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
208
|
+
Path extends string = string,
|
|
209
|
+
H extends STHeaders = {},
|
|
210
|
+
P extends Partial<STParams<Path>> = {},
|
|
211
|
+
Q extends STQuery = {},
|
|
212
|
+
B extends STBody = STBody
|
|
232
213
|
> = {
|
|
233
214
|
method: Method
|
|
234
|
-
path:
|
|
235
|
-
schema:
|
|
236
|
-
context: Context<
|
|
215
|
+
path: Path
|
|
216
|
+
schema: RequestSchema<Path, H, P, Q, B>
|
|
217
|
+
context: Context<Path, RequestSchema<Path, H, P, Q, B>>
|
|
237
218
|
hooks: Hook[]
|
|
238
|
-
handler: Handler<
|
|
219
|
+
handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
239
220
|
}
|
|
240
221
|
|
|
241
222
|
export type RouteTree = {
|
package/src/validator.ts
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { STSchema, STProps, STUnion } from './schema'
|
|
2
|
+
import { Kind, Optional } from './schema'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export const validate = (elt: any, schema: TSchema, parse = false): any => {
|
|
4
|
+
export const validate = (elt: any, schema: STSchema, parse = false): any => {
|
|
6
5
|
type ValidationError = string | string[] | { [key: string]: ValidationError }
|
|
7
6
|
const errors: ValidationError[] = []
|
|
8
7
|
const iElt = elt
|
|
9
8
|
|
|
10
|
-
if (schema[Kind] === '
|
|
9
|
+
if (schema[Kind] === 'boolean') {
|
|
11
10
|
if (parse && typeof elt === 'string') elt = elt === 'true' ? true : elt === 'false' ? false : null
|
|
12
11
|
if (elt !== true && elt !== false) throw `${iElt} is not a valid boolean. Should be 'true' or 'false'`
|
|
13
|
-
} else if (schema[Kind] === '
|
|
12
|
+
} else if (schema[Kind] === 'integer') {
|
|
14
13
|
if (parse && typeof elt === 'string') elt = Number(elt)
|
|
15
14
|
if (!Number.isInteger(elt)) throw `${iElt} is not a valid integer`
|
|
16
15
|
schemaValidation(elt, schema)
|
|
17
|
-
} else if (schema[Kind] === '
|
|
16
|
+
} else if (schema[Kind] === 'number') {
|
|
18
17
|
if (parse && typeof elt === 'string') elt = Number(elt)
|
|
19
18
|
if (!Number.isFinite(elt)) throw `${iElt} is not a valid number`
|
|
20
19
|
schemaValidation(elt, schema)
|
|
21
|
-
} else if (schema[Kind] === '
|
|
20
|
+
} else if (schema[Kind] === 'string') {
|
|
22
21
|
if (!(typeof elt === 'string')) throw `${iElt} is not a valid string`
|
|
23
22
|
schemaValidation(elt, schema)
|
|
24
|
-
} else if (schema[Kind] === '
|
|
25
|
-
if (elt !== schema.
|
|
26
|
-
} else if (schema[Kind] === '
|
|
23
|
+
} else if (schema[Kind] === 'literal') {
|
|
24
|
+
if (elt !== schema.value) throw `${iElt} is not a valid value`
|
|
25
|
+
} else if (schema[Kind] === 'object') {
|
|
27
26
|
if (parse && typeof elt === 'string') {
|
|
28
27
|
try {
|
|
29
28
|
elt = JSON.parse(elt)
|
|
@@ -34,9 +33,9 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
|
|
|
34
33
|
if (typeof elt !== 'object') throw `${iElt} is not a valid object`
|
|
35
34
|
if (Array.isArray(elt)) throw `Expected an object, not an array`
|
|
36
35
|
const err: ValidationError = {}
|
|
37
|
-
Object.entries(schema.
|
|
36
|
+
Object.entries(schema.props as STProps).forEach(([k, s]) => {
|
|
38
37
|
if (!(k in elt)) {
|
|
39
|
-
if (!s[Optional]) err[k] = 'Required'
|
|
38
|
+
if (!s?.[Optional]) err[k] = 'Required'
|
|
40
39
|
return
|
|
41
40
|
}
|
|
42
41
|
try {
|
|
@@ -46,7 +45,7 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
|
|
|
46
45
|
}
|
|
47
46
|
})
|
|
48
47
|
if (Object.keys(err).length) errors.push(err)
|
|
49
|
-
} else if (schema[Kind] === '
|
|
48
|
+
} else if (schema[Kind] === 'array') {
|
|
50
49
|
if (parse && typeof elt === 'string') {
|
|
51
50
|
try {
|
|
52
51
|
elt = JSON.parse(elt)
|
|
@@ -56,16 +55,16 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
|
|
|
56
55
|
}
|
|
57
56
|
if (!Array.isArray(elt)) throw 'Not a valid array'
|
|
58
57
|
schemaValidation(elt, schema)
|
|
59
|
-
} else if (schema[Kind] === '
|
|
58
|
+
} else if (schema[Kind] === 'byteArray') {
|
|
60
59
|
if (parse && typeof elt === 'string') elt = Uint8Array.from(elt, c => c.charCodeAt(0))
|
|
61
60
|
else if (parse && Array.isArray(elt)) elt = new Uint8Array(elt)
|
|
62
|
-
if (!(elt instanceof Uint8Array)) throw 'Not a valid
|
|
63
|
-
} else if (schema[Kind] === '
|
|
64
|
-
const union = Object.values(schema.anyOf)
|
|
61
|
+
if (!(elt instanceof Uint8Array)) throw 'Not a valid byteArray'
|
|
62
|
+
} else if (schema[Kind] === 'union') {
|
|
63
|
+
const union = Object.values((schema as STUnion).anyOf)
|
|
65
64
|
let valid = false
|
|
66
65
|
for (const u of union) {
|
|
67
66
|
try {
|
|
68
|
-
elt = validate(elt, u as
|
|
67
|
+
elt = validate(elt, u as STSchema, parse)
|
|
69
68
|
valid = true
|
|
70
69
|
break
|
|
71
70
|
} catch (err) {
|
|
@@ -73,8 +72,8 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
|
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
74
|
// @ts-ignore
|
|
76
|
-
if (!valid) throw `${elt} could not be parsed to any of: ${union.map(u => u?.
|
|
77
|
-
} else if (schema[Kind] === '
|
|
75
|
+
if (!valid) throw `${elt} could not be parsed to any of: ${union.map(u => u?.value ?? u[Kind]).join(', ')}`
|
|
76
|
+
} else if (schema[Kind] === 'any') {
|
|
78
77
|
} else {
|
|
79
78
|
throw `Unsupported schema type ${schema[Kind]}`
|
|
80
79
|
}
|
|
@@ -85,32 +84,31 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
|
|
|
85
84
|
return elt
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
const schemaValidation = (value: any, schema:
|
|
87
|
+
const schemaValidation = (value: any, schema: STSchema) => {
|
|
89
88
|
const errors = []
|
|
90
|
-
if (schema[Kind] === '
|
|
91
|
-
if (schema.
|
|
92
|
-
if ((value as number) <= schema.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
errors.push(`${value} is greater or equal to ${schema.exclusiveMaximum}`)
|
|
89
|
+
if (schema[Kind] === 'integer' || schema[Kind] === 'number') {
|
|
90
|
+
if (schema.exclusiveMin !== undefined)
|
|
91
|
+
if ((value as number) <= schema.exclusiveMin) errors.push(`${value} is less or equal to ${schema.exclusiveMin}`)
|
|
92
|
+
if (schema.exclusiveMax !== undefined)
|
|
93
|
+
if ((value as number) >= schema.exclusiveMax)
|
|
94
|
+
errors.push(`${value} is greater or equal to ${schema.exclusiveMax}`)
|
|
97
95
|
if (schema.minimum !== undefined)
|
|
98
|
-
if ((value as number) < schema.
|
|
96
|
+
if ((value as number) < schema.min) errors.push(`${value} is less than ${schema.min}`)
|
|
99
97
|
if (schema.maximum !== undefined)
|
|
100
|
-
if ((value as number) > schema.
|
|
101
|
-
} else if (schema[Kind] === '
|
|
98
|
+
if ((value as number) > schema.max) errors.push(`${value} is greater than ${schema.max}`)
|
|
99
|
+
} else if (schema[Kind] === 'string') {
|
|
102
100
|
if (schema.minLength !== undefined && (value as string).length < schema.minLength)
|
|
103
101
|
errors.push(`${value} length is too small (${schema.minLength} char min)`)
|
|
104
102
|
if (schema.maxLength !== undefined && (value as string).length > schema.maxLength)
|
|
105
103
|
errors.push(`${value} length is too large (${schema.maxLength} char max)`)
|
|
106
|
-
if (schema.pattern !== undefined && !(value as string).match(
|
|
104
|
+
if (schema.pattern !== undefined && !(value as string).match(schema.pattern))
|
|
107
105
|
errors.push(`${value} does not match pattern ${schema.pattern}`)
|
|
108
|
-
} else if (schema[Kind] === '
|
|
106
|
+
} else if (schema[Kind] === 'array') {
|
|
109
107
|
if (schema.minItems !== undefined && (value as any[]).length < schema.minItems)
|
|
110
108
|
errors.push(`Must contain at least ${schema.minItems} item${schema.minItems > 1 ? 's' : ''}`)
|
|
111
109
|
if (schema.maxItems !== undefined && (value as any[]).length > schema.maxItems)
|
|
112
110
|
errors.push(`Must contain at most (${schema.maxItems} item${schema.maxItems > 1 ? 's' : ''}`)
|
|
113
|
-
if (schema.
|
|
111
|
+
if (schema.unique === true && new Set(value as any[]).size !== (value as any[]).length)
|
|
114
112
|
errors.push(`Has duplicate values`)
|
|
115
113
|
}
|
|
116
114
|
if (errors.length) throw Array.isArray(errors) && errors.length === 1 ? errors[0] : errors
|