starlight-server 0.0.1
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/demo/index.d.ts +1 -0
- package/dist/demo/index.js +27 -0
- package/dist/http/body/form-data.d.ts +35 -0
- package/dist/http/body/form-data.js +141 -0
- package/dist/http/body/index.d.ts +23 -0
- package/dist/http/body/index.js +47 -0
- package/dist/http/body/receive.d.ts +7 -0
- package/dist/http/body/receive.js +39 -0
- package/dist/http/http-status.d.ts +9 -0
- package/dist/http/http-status.js +64 -0
- package/dist/http/index.d.ts +9 -0
- package/dist/http/index.js +9 -0
- package/dist/http/mime-types.d.ts +14 -0
- package/dist/http/mime-types.js +764 -0
- package/dist/http/request.d.ts +25 -0
- package/dist/http/request.js +40 -0
- package/dist/http/response.d.ts +32 -0
- package/dist/http/response.js +66 -0
- package/dist/http/server.d.ts +31 -0
- package/dist/http/server.js +52 -0
- package/dist/http/types.d.ts +26 -0
- package/dist/http/types.js +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/logging.d.ts +24 -0
- package/dist/logging.js +30 -0
- package/dist/router/cors.d.ts +24 -0
- package/dist/router/cors.js +35 -0
- package/dist/router/index.d.ts +38 -0
- package/dist/router/index.js +36 -0
- package/dist/router/match.d.ts +23 -0
- package/dist/router/match.js +172 -0
- package/dist/router/parameters.d.ts +51 -0
- package/dist/router/parameters.js +118 -0
- package/dist/router/router.d.ts +127 -0
- package/dist/router/router.js +97 -0
- package/dist/swagger/index.d.ts +1 -0
- package/dist/swagger/index.js +168 -0
- package/dist/swagger/openapi-spec.d.ts +261 -0
- package/dist/swagger/openapi-spec.js +5 -0
- package/dist/validators/array.d.ts +9 -0
- package/dist/validators/array.js +28 -0
- package/dist/validators/boolean.d.ts +4 -0
- package/dist/validators/boolean.js +28 -0
- package/dist/validators/common.d.ts +23 -0
- package/dist/validators/common.js +25 -0
- package/dist/validators/index.d.ts +20 -0
- package/dist/validators/index.js +38 -0
- package/dist/validators/number.d.ts +10 -0
- package/dist/validators/number.js +30 -0
- package/dist/validators/object.d.ts +13 -0
- package/dist/validators/object.js +36 -0
- package/dist/validators/string.d.ts +11 -0
- package/dist/validators/string.js +29 -0
- package/package.json +54 -0
- package/src/demo/index.ts +33 -0
- package/src/http/body/form-data.ts +164 -0
- package/src/http/body/index.ts +59 -0
- package/src/http/body/receive.ts +49 -0
- package/src/http/http-status.ts +65 -0
- package/src/http/index.ts +9 -0
- package/src/http/mime-types.ts +765 -0
- package/src/http/request.ts +44 -0
- package/src/http/response.ts +73 -0
- package/src/http/server.ts +67 -0
- package/src/http/types.ts +31 -0
- package/src/index.ts +4 -0
- package/src/logging.ts +57 -0
- package/src/router/cors.ts +54 -0
- package/src/router/index.ts +38 -0
- package/src/router/match.ts +194 -0
- package/src/router/parameters.ts +172 -0
- package/src/router/router.ts +233 -0
- package/src/swagger/index.ts +184 -0
- package/src/swagger/openapi-spec.ts +312 -0
- package/src/validators/array.ts +33 -0
- package/src/validators/boolean.ts +23 -0
- package/src/validators/common.ts +46 -0
- package/src/validators/index.ts +50 -0
- package/src/validators/number.ts +36 -0
- package/src/validators/object.ts +41 -0
- package/src/validators/string.ts +38 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI Specification
|
|
3
|
+
* https://swagger.io/specification/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface OpenAPI {
|
|
7
|
+
openapi: '3.1.0'
|
|
8
|
+
info: Info
|
|
9
|
+
servers?: Server[]
|
|
10
|
+
paths: Paths
|
|
11
|
+
components?: Components
|
|
12
|
+
security?: SecurityRequirement[]
|
|
13
|
+
tags?: Tag[]
|
|
14
|
+
externalDocs?: ExternalDocumentation
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Info extends Extendable {
|
|
18
|
+
title: string
|
|
19
|
+
summary?: string
|
|
20
|
+
description?: string
|
|
21
|
+
termsOfService?: string
|
|
22
|
+
contact?: Contact
|
|
23
|
+
license?: License
|
|
24
|
+
// 此文档的版本(注意不是 OpenAPI 规范的版本)
|
|
25
|
+
// Version of this API Document (Not OpenAPI Spec Version)
|
|
26
|
+
version: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Contact extends Extendable {
|
|
30
|
+
name?: string
|
|
31
|
+
url?: string
|
|
32
|
+
email?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface License extends Extendable {
|
|
36
|
+
name: string
|
|
37
|
+
url?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Server extends Extendable {
|
|
41
|
+
url: string
|
|
42
|
+
description?: CommonMark
|
|
43
|
+
variables?: Record<string, ServerVariable> // 插入到 url 中的变量
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ServerVariable extends Extendable {
|
|
47
|
+
enum?: [string] & string[] // 此变量的可选值,不指定则变量可以是任意值,若指定则不允许为空数组(不然就变成所有值都不允许了)
|
|
48
|
+
default: string // 此变量的默认值
|
|
49
|
+
description: CommonMark
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Components extends Extendable {
|
|
53
|
+
schemas?: Record<string, Schema>
|
|
54
|
+
responses?: Record<string, Response | Reference>
|
|
55
|
+
parameters?: Record<string, Parameter | Reference>
|
|
56
|
+
examples?: Record<string, Example | Reference>
|
|
57
|
+
requestBodies?: Record<string, RequestBody | Reference>
|
|
58
|
+
headers?: Record<string, Header | Reference>
|
|
59
|
+
securitySchemes?: Record<string, SecurityScheme | Reference>
|
|
60
|
+
links?: Record<string, Link | Reference>
|
|
61
|
+
callbacks?: Record<string, Callback | Reference>
|
|
62
|
+
pathItems?: Record<string, PathItem | Reference>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Paths extends Extendable {
|
|
66
|
+
[path: string]: PathItem // path 须以 '/' 开头
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface PathItem extends Extendable {
|
|
70
|
+
$ref?: string
|
|
71
|
+
summary?: string // 此路径接口的简短描述(对所有 method 均生效)
|
|
72
|
+
description?: CommonMark // 此路径接口的详细描述(对所有 method 均生效)
|
|
73
|
+
get?: Operation
|
|
74
|
+
put?: Operation
|
|
75
|
+
post?: Operation
|
|
76
|
+
delete?: Operation
|
|
77
|
+
options?: Operation
|
|
78
|
+
head?: Operation
|
|
79
|
+
patch?: Operation
|
|
80
|
+
trace?: Operation
|
|
81
|
+
servers?: Server[]
|
|
82
|
+
parameters?: (Parameter | Reference)[]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface Operation {
|
|
86
|
+
tags?: string[] // 接口分组
|
|
87
|
+
summary?: string
|
|
88
|
+
description?: CommonMark
|
|
89
|
+
externalDocs?: ExternalDocumentation
|
|
90
|
+
operationId?: string
|
|
91
|
+
parameters?: (Parameter | Reference)[]
|
|
92
|
+
requestBody?: RequestBody | Reference
|
|
93
|
+
responses: Responses
|
|
94
|
+
callbacks?: Record<string, Callback | Reference>
|
|
95
|
+
deprecated?: boolean
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ExternalDocumentation {
|
|
99
|
+
description?: CommonMark
|
|
100
|
+
url: string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface Parameter extends Extendable {
|
|
104
|
+
name: string
|
|
105
|
+
in: ParameterLocation
|
|
106
|
+
description?: CommonMark
|
|
107
|
+
required?: boolean // in 为 'path' 时必须为 true
|
|
108
|
+
deprecated?: boolean
|
|
109
|
+
allowEmptyValue?: boolean // 仅 in 为 'query' 时有效,是否允许传空值
|
|
110
|
+
|
|
111
|
+
// 定义简单场景的参数
|
|
112
|
+
style?: ParameterStyle
|
|
113
|
+
explode?: boolean
|
|
114
|
+
allowReserved?: boolean
|
|
115
|
+
schema?: Schema | Reference
|
|
116
|
+
example?: unknown
|
|
117
|
+
examples?: Record<string, Example | Reference>
|
|
118
|
+
|
|
119
|
+
// 定义复杂场景的参数
|
|
120
|
+
content?: Record<string, MediaType> // MIMEType => MediaType
|
|
121
|
+
}
|
|
122
|
+
export type ParameterLocation = 'query' | 'header' | 'path' | 'cookie'
|
|
123
|
+
export type ParameterStyle = string
|
|
124
|
+
|
|
125
|
+
export interface RequestBody extends Extendable {
|
|
126
|
+
description?: CommonMark
|
|
127
|
+
content: Record<string, MediaType> // MIMEType => MediaType
|
|
128
|
+
required?: boolean // body 内容是否必须
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface MediaType extends Extendable {
|
|
132
|
+
schema?: Schema | Reference
|
|
133
|
+
example?: unknown
|
|
134
|
+
examples?: Record<string, Example | Reference>
|
|
135
|
+
encoding?: Record<string, Encoding>
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface Encoding extends Extendable {
|
|
139
|
+
contentType?: string // MIME Type。支持通配符(image/*),可用逗号分隔多项(image/jpg,image/png)
|
|
140
|
+
headers?: Record<string, Header | Reference>
|
|
141
|
+
style?: ParameterStyle
|
|
142
|
+
explode?: boolean
|
|
143
|
+
allowReserved?: boolean
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface Responses extends Extendable {
|
|
147
|
+
default: Response | Reference
|
|
148
|
+
[HTTPStatus: string]: Response | Reference
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface Response extends Extendable {
|
|
152
|
+
descriptoin: CommonMark
|
|
153
|
+
headers?: Record<string, Header | Reference>
|
|
154
|
+
content?: Record<string, MediaType>
|
|
155
|
+
links?: Record<string, Link | Reference>
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface Callback extends Extendable {
|
|
159
|
+
[expression: Expression]: PathItem
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface Example extends Extendable {
|
|
163
|
+
summary?: string
|
|
164
|
+
description?: CommonMark
|
|
165
|
+
value?: unknown
|
|
166
|
+
externalValue?: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface Link extends Extendable {
|
|
170
|
+
operationRef?: string
|
|
171
|
+
operationId?: string
|
|
172
|
+
parameters?: Record<string, unknown> // any | Expression
|
|
173
|
+
requestBody?: unknown // any | Expression
|
|
174
|
+
description?: string
|
|
175
|
+
server?: Server
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export type Header = Omit<Parameter, 'name' | 'in'>
|
|
179
|
+
|
|
180
|
+
export interface Tag extends Extendable {
|
|
181
|
+
name: string
|
|
182
|
+
description?: CommonMark
|
|
183
|
+
externalDocs?: ExternalDocumentation
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface Reference {
|
|
187
|
+
$ref: string
|
|
188
|
+
summary: string
|
|
189
|
+
description: string
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface Schema extends Extendable {
|
|
193
|
+
nullable?: boolean
|
|
194
|
+
discriminator?: Discriminator
|
|
195
|
+
readonly?: boolean
|
|
196
|
+
writeOnly?: boolean
|
|
197
|
+
xml?: XML
|
|
198
|
+
externalDocs?: ExternalDocumentation
|
|
199
|
+
example?: unknown
|
|
200
|
+
deprecated?: boolean
|
|
201
|
+
|
|
202
|
+
// 以下字段的定义取自 JSON Schema 规范,部分字段针对 OpenAPI 的情况做了调整
|
|
203
|
+
// https://datatracker.ietf.org/doc/html/draft-wright-json-schema-validation-00
|
|
204
|
+
multipleOf?: number // 须大于 0,要求数值是此值的倍数
|
|
205
|
+
maximum?: number // 要求数值“小于等于”此值,如果 exclusiveMaximum 为 true 则是“小于”此值
|
|
206
|
+
exclusiveMaximum?: boolean // 控制 maximum 的匹配方式
|
|
207
|
+
minimum?: number // 要求数值“大于等于”此值,如果 exclusiveMinimum 为 true 则是“大于”此值
|
|
208
|
+
exclusiveMinimum?: boolean
|
|
209
|
+
maxLength?: number // 要求字符串长度小于等于此值
|
|
210
|
+
minLength?: number // 要求字符串长度大于等于此值
|
|
211
|
+
pattern?: string // 要求字符串匹配此正则表达式
|
|
212
|
+
items?: Schema // 数组子项类型,type=array 时必须提供
|
|
213
|
+
maxItems?: number // 数组最大长度
|
|
214
|
+
minItems?: number // 数组最小长度
|
|
215
|
+
uniqueItems?: boolean // 若为 true,要求数组各子项不重复
|
|
216
|
+
maxProperties?: number // 对象值最大属性数量
|
|
217
|
+
minProperties?: string // 对象值最小属性数量
|
|
218
|
+
required?: [string] & string[] // 对象值中必须存在的属性
|
|
219
|
+
properties?: Record<string, Schema> // 定义对象值各属性
|
|
220
|
+
additionalProperties?: boolean | Schema // 定义对象值是否允许额外属性(boolean)或者定义所有额外属性的格式(Schema)
|
|
221
|
+
enum?: unknown[] // 要求字段值必须是此数组中的值
|
|
222
|
+
type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null' // 指定字段类型
|
|
223
|
+
allOf?: Schema[] // 要求字段值匹配数组里的所有定义
|
|
224
|
+
anyOf?: Schema[] // 要求字段值匹配数组里的任意定义(也允许匹配多个定义)
|
|
225
|
+
oneOf?: Schema[] // 要求字段值匹配且只匹配数组里的“一个”定义
|
|
226
|
+
not?: Schema // 要求字段值匹配此定义
|
|
227
|
+
title?: string // 字段标题
|
|
228
|
+
description?: CommonMark // 字段描述
|
|
229
|
+
default?: unknown // 默认值
|
|
230
|
+
format?: string // 字段值语义格式(如 email)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface Discriminator {
|
|
234
|
+
propertyName?: string
|
|
235
|
+
mapping?: Record<string, string>
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface XML extends Extendable {
|
|
239
|
+
name?: string
|
|
240
|
+
namespace?: string
|
|
241
|
+
prefix?: string
|
|
242
|
+
attribute?: boolean
|
|
243
|
+
wrapped?: boolean
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface SecurityScheme extends Extendable {
|
|
247
|
+
type: string
|
|
248
|
+
description?: string
|
|
249
|
+
name: string
|
|
250
|
+
in: string
|
|
251
|
+
schema: string
|
|
252
|
+
bearerFormat?: string
|
|
253
|
+
flows: OAuthFlows
|
|
254
|
+
openIdConnectUrl: string
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface OAuthFlows extends Extendable {
|
|
258
|
+
implicit?: OAuthFlow
|
|
259
|
+
password?: OAuthFlow
|
|
260
|
+
clientCredentials?: OAuthFlow
|
|
261
|
+
authorizationCode?: OAuthFlow
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface OAuthFlow extends Extendable {
|
|
265
|
+
authorizationUrl: string
|
|
266
|
+
tokenUrl: string
|
|
267
|
+
refreshUrl?: string
|
|
268
|
+
scopes: Record<string, string>
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface SecurityRequirement {
|
|
272
|
+
[name: string]: string
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ========= 辅助类型 ==========
|
|
276
|
+
|
|
277
|
+
// 可提供扩展字段的对象
|
|
278
|
+
// 扩展字段的 key 需以 'x-' 开头,例如 x-token
|
|
279
|
+
export type Extendable = {} // eslint-disable-line @typescript-eslint/ban-types
|
|
280
|
+
|
|
281
|
+
// 支持 CommonMark 格式的富文本字符串
|
|
282
|
+
export type CommonMark = string
|
|
283
|
+
|
|
284
|
+
export type Expression = string
|
|
285
|
+
|
|
286
|
+
// ========= 扩展类型 =========
|
|
287
|
+
|
|
288
|
+
export interface TypedSchema<T> extends Schema {
|
|
289
|
+
allOf?: TypedSchema<T>[]
|
|
290
|
+
anyOf?: TypedSchema<T>[]
|
|
291
|
+
oneOf?: TypedSchema<T>[]
|
|
292
|
+
not?: TypedSchema<T>
|
|
293
|
+
example?: T
|
|
294
|
+
enum?: T[]
|
|
295
|
+
default?: T
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface StringSchema extends TypedSchema<string> {
|
|
299
|
+
type: 'string'
|
|
300
|
+
}
|
|
301
|
+
export interface NumberSchema extends TypedSchema<number> {
|
|
302
|
+
type: 'number'
|
|
303
|
+
}
|
|
304
|
+
export interface BooleanSchema extends TypedSchema<boolean> {
|
|
305
|
+
type: 'boolean'
|
|
306
|
+
}
|
|
307
|
+
export interface ObjectSchema extends TypedSchema<Record<string, unknown>> {
|
|
308
|
+
type: 'object'
|
|
309
|
+
}
|
|
310
|
+
export interface ArraySchema extends TypedSchema<unknown[]> {
|
|
311
|
+
type: 'array'
|
|
312
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { success, failed } from '@anjianshi/utils'
|
|
2
|
+
import { Validator } from './common.js'
|
|
3
|
+
|
|
4
|
+
export interface ArrayOptions {
|
|
5
|
+
item: Validator
|
|
6
|
+
min?: number
|
|
7
|
+
max?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ArrayValidator extends Validator<ArrayOptions> {
|
|
11
|
+
validate(fieldName: string, value: unknown) {
|
|
12
|
+
const superResult = super.validate(fieldName, value)
|
|
13
|
+
if (!superResult.success) return superResult
|
|
14
|
+
|
|
15
|
+
value = superResult.data
|
|
16
|
+
if (value === null || value === undefined) return superResult
|
|
17
|
+
const opt = this.options
|
|
18
|
+
|
|
19
|
+
if (!Array.isArray(value)) return failed(`${fieldName} should be an array`)
|
|
20
|
+
if (typeof opt.min === 'number' && value.length < opt.min)
|
|
21
|
+
return failed(`array ${fieldName}'s length should >= ${opt.min}`)
|
|
22
|
+
if (typeof opt.max === 'number' && value.length > opt.max)
|
|
23
|
+
return failed(`array ${fieldName}'s length should <= ${opt.max}`)
|
|
24
|
+
|
|
25
|
+
const formatted = []
|
|
26
|
+
for (let i = 0; i < value.length; i++) {
|
|
27
|
+
const itemResult = opt.item.validate(`${fieldName}[${i}]`, value[i])
|
|
28
|
+
if (itemResult.success) formatted.push(itemResult.data)
|
|
29
|
+
else return itemResult
|
|
30
|
+
}
|
|
31
|
+
return success(formatted)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { success, failed } from '@anjianshi/utils'
|
|
2
|
+
import { Validator } from './common.js'
|
|
3
|
+
|
|
4
|
+
export class BooleanValidator extends Validator {
|
|
5
|
+
validate(fieldName: string, value: unknown) {
|
|
6
|
+
const superResult = super.validate(fieldName, value)
|
|
7
|
+
if (!superResult.success) return superResult
|
|
8
|
+
|
|
9
|
+
value = superResult.data
|
|
10
|
+
if (value === null || value === undefined) return superResult
|
|
11
|
+
|
|
12
|
+
if (typeof value === 'string') {
|
|
13
|
+
const str = value.trim().toLocaleLowerCase()
|
|
14
|
+
if (['1', 'true', 'on', 'yes'].includes(str)) value = true
|
|
15
|
+
else if (['0', 'false', 'off', 'no'].includes(str)) value = false
|
|
16
|
+
} else if (typeof value === 'number') {
|
|
17
|
+
if (value === 1) value = true
|
|
18
|
+
else if (value === 0) value = false
|
|
19
|
+
}
|
|
20
|
+
if (typeof value !== 'boolean') return failed(`${fieldName} must be true or false`)
|
|
21
|
+
return success(value)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { success, failed, type MaySuccess } from '@anjianshi/utils'
|
|
2
|
+
|
|
3
|
+
export interface CommonOptions {
|
|
4
|
+
/**
|
|
5
|
+
* allow `null`
|
|
6
|
+
* @default false
|
|
7
|
+
*/
|
|
8
|
+
null: boolean
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* allow `undefined`
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
void: boolean
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 默认值,原值为 undefined 时生效(原值为 null 不会生效),指定后 void 选项将失去作用。
|
|
18
|
+
* default value,used when original value is `undefined`(not for null), and make `void` option no effect.
|
|
19
|
+
*/
|
|
20
|
+
defaults: unknown
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Validator<Opt = unknown> {
|
|
24
|
+
readonly options: Opt & CommonOptions
|
|
25
|
+
|
|
26
|
+
constructor(options: Opt & Partial<CommonOptions>) {
|
|
27
|
+
this.options = {
|
|
28
|
+
null: false,
|
|
29
|
+
void: false,
|
|
30
|
+
defaults: undefined,
|
|
31
|
+
...options,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
validate(fieldName: string, value: unknown): MaySuccess<unknown> {
|
|
36
|
+
if (typeof value === 'undefined') {
|
|
37
|
+
if (typeof this.options.defaults !== 'undefined') {
|
|
38
|
+
value = this.options.defaults
|
|
39
|
+
} else if (!this.options.void) {
|
|
40
|
+
return failed(`${fieldName} must have a value`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (value === null && !this.options.null) return failed(`${fieldName} cannot be null`)
|
|
44
|
+
return success(value)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 实现数据验证、格式化
|
|
3
|
+
* Implement data validation and formatting
|
|
4
|
+
*/
|
|
5
|
+
import { ArrayValidator } from './array.js'
|
|
6
|
+
import { BooleanValidator } from './boolean.js'
|
|
7
|
+
import { Validator, type CommonOptions } from './common.js'
|
|
8
|
+
import { NumberValidator } from './number.js'
|
|
9
|
+
import { ObjectValidator } from './object.js'
|
|
10
|
+
import { StringValidator } from './string.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 仅检查空值,不检查具体格式
|
|
14
|
+
* Check only for empty values, not for specific formatting
|
|
15
|
+
*/
|
|
16
|
+
export function any(options?: Partial<CommonOptions>) {
|
|
17
|
+
return new Validator(options ?? {})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function string(...args: ConstructorParameters<typeof StringValidator>) {
|
|
21
|
+
return new StringValidator(...args)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function number(...args: ConstructorParameters<typeof NumberValidator>) {
|
|
25
|
+
return new NumberValidator(...args)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function boolean(options?: Partial<CommonOptions>) {
|
|
29
|
+
return new BooleanValidator(options ?? {})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function array(
|
|
33
|
+
itemValidator: Validator,
|
|
34
|
+
options?: Omit<ConstructorParameters<typeof ArrayValidator>[0], 'item'>,
|
|
35
|
+
) {
|
|
36
|
+
return new ArrayValidator({
|
|
37
|
+
item: itemValidator,
|
|
38
|
+
...(options ?? {}),
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function object(
|
|
43
|
+
structOrValue: Record<string, Validator> | Validator,
|
|
44
|
+
options?: Partial<CommonOptions>,
|
|
45
|
+
) {
|
|
46
|
+
return new ObjectValidator({
|
|
47
|
+
...(structOrValue instanceof Validator ? { value: structOrValue } : { struct: structOrValue }),
|
|
48
|
+
...options,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { success, failed } from '@anjianshi/utils'
|
|
2
|
+
import { Validator, type CommonOptions } from './common.js'
|
|
3
|
+
|
|
4
|
+
export interface NumberOptions {
|
|
5
|
+
min?: number
|
|
6
|
+
max?: number
|
|
7
|
+
float: boolean // allow float or not
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class NumberValidator extends Validator<NumberOptions> {
|
|
11
|
+
constructor(options?: Partial<NumberOptions> & Partial<CommonOptions>) {
|
|
12
|
+
super({
|
|
13
|
+
float: false,
|
|
14
|
+
...(options ?? {}),
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
validate(fieldName: string, value: unknown) {
|
|
19
|
+
const superResult = super.validate(fieldName, value)
|
|
20
|
+
if (!superResult.success) return superResult
|
|
21
|
+
|
|
22
|
+
value = superResult.data
|
|
23
|
+
if (value === null || value === undefined) return superResult
|
|
24
|
+
const opt = this.options
|
|
25
|
+
|
|
26
|
+
if (typeof value === 'string') value = parseFloat(value)
|
|
27
|
+
if (typeof value !== 'number' || !isFinite(value))
|
|
28
|
+
return failed(`${fieldName} must be a valid number`)
|
|
29
|
+
if (!opt.float && value % 1 !== 0) return failed(`${fieldName} must be a integer`)
|
|
30
|
+
if (typeof opt.min === 'number' && value < opt.min)
|
|
31
|
+
return failed(`${fieldName} must >= ${opt.min}`)
|
|
32
|
+
if (typeof opt.max === 'number' && value > opt.max)
|
|
33
|
+
return failed(`${fieldName} must <= ${opt.max}`)
|
|
34
|
+
return success(value)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { success, failed } from '@anjianshi/utils'
|
|
2
|
+
import isPlainObject from 'lodash/isPlainObject.js'
|
|
3
|
+
import { Validator } from './common.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* struct 用于有明确键值对结构的对象;value 用于有任意 key 的对象
|
|
7
|
+
* use `struct` is used for objects with an explicit key-value structure; `value` is used for objects with arbitrary keys.
|
|
8
|
+
*/
|
|
9
|
+
export type ObjectOptions = { struct: Record<string, Validator> } | { value: Validator }
|
|
10
|
+
|
|
11
|
+
export class ObjectValidator extends Validator<ObjectOptions> {
|
|
12
|
+
validate(fieldName: string, value: unknown) {
|
|
13
|
+
const superResult = super.validate(fieldName, value)
|
|
14
|
+
if (!superResult.success) return superResult
|
|
15
|
+
|
|
16
|
+
value = superResult.data
|
|
17
|
+
if (value === null || value === undefined) return superResult
|
|
18
|
+
const opt = this.options
|
|
19
|
+
|
|
20
|
+
if (!isPlainObject(value)) return failed(`${fieldName} should be a plain object`)
|
|
21
|
+
|
|
22
|
+
const formatted: Record<string, unknown> = {}
|
|
23
|
+
if ('struct' in opt) {
|
|
24
|
+
for (const [key, itemValidator] of Object.entries(opt.struct)) {
|
|
25
|
+
const itemResult = itemValidator.validate(
|
|
26
|
+
`${fieldName}["${key}"]`,
|
|
27
|
+
(value as Record<string, unknown>)[key],
|
|
28
|
+
)
|
|
29
|
+
if (itemResult.success) formatted[key] = itemResult.data
|
|
30
|
+
else return itemResult
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
for (const [key, itemValue] of Object.entries(value as Record<string, unknown>)) {
|
|
34
|
+
const itemResult = opt.value.validate(`${fieldName}["${key}"]`, itemValue)
|
|
35
|
+
if (itemResult.success) formatted[key] = itemResult.data
|
|
36
|
+
else return itemResult
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return success(formatted)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { success, failed } from '@anjianshi/utils'
|
|
2
|
+
import { Validator, type CommonOptions } from './common.js'
|
|
3
|
+
|
|
4
|
+
export interface StringOptions {
|
|
5
|
+
min?: number
|
|
6
|
+
max?: number
|
|
7
|
+
pattern?: RegExp
|
|
8
|
+
trim: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class StringValidator extends Validator<StringOptions> {
|
|
12
|
+
constructor(options?: Partial<StringOptions> & Partial<CommonOptions>) {
|
|
13
|
+
super({
|
|
14
|
+
trim: true,
|
|
15
|
+
...(options ?? {}),
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
validate(fieldName: string, value: unknown) {
|
|
20
|
+
const superResult = super.validate(fieldName, value)
|
|
21
|
+
if (!superResult.success) return superResult
|
|
22
|
+
|
|
23
|
+
value = superResult.data
|
|
24
|
+
if (value === null || value === undefined) return superResult
|
|
25
|
+
const opt = this.options
|
|
26
|
+
|
|
27
|
+
if (typeof value !== 'string') return failed(`${fieldName} should be a string`)
|
|
28
|
+
|
|
29
|
+
const formatted = opt.trim ? value.trim() : value
|
|
30
|
+
if (typeof opt.min === 'number' && formatted.length < opt.min)
|
|
31
|
+
return failed(`${fieldName}'s length must >= ${opt.min}`)
|
|
32
|
+
if (typeof opt.max === 'number' && formatted.length > opt.max)
|
|
33
|
+
return failed(`${fieldName}'s length must <= ${opt.max}`)
|
|
34
|
+
if (opt.pattern && !opt.pattern.exec(formatted))
|
|
35
|
+
return failed(`${fieldName} does not match the pattern.`)
|
|
36
|
+
return success(formatted)
|
|
37
|
+
}
|
|
38
|
+
}
|