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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/demo/index.d.ts +1 -0
  4. package/dist/demo/index.js +27 -0
  5. package/dist/http/body/form-data.d.ts +35 -0
  6. package/dist/http/body/form-data.js +141 -0
  7. package/dist/http/body/index.d.ts +23 -0
  8. package/dist/http/body/index.js +47 -0
  9. package/dist/http/body/receive.d.ts +7 -0
  10. package/dist/http/body/receive.js +39 -0
  11. package/dist/http/http-status.d.ts +9 -0
  12. package/dist/http/http-status.js +64 -0
  13. package/dist/http/index.d.ts +9 -0
  14. package/dist/http/index.js +9 -0
  15. package/dist/http/mime-types.d.ts +14 -0
  16. package/dist/http/mime-types.js +764 -0
  17. package/dist/http/request.d.ts +25 -0
  18. package/dist/http/request.js +40 -0
  19. package/dist/http/response.d.ts +32 -0
  20. package/dist/http/response.js +66 -0
  21. package/dist/http/server.d.ts +31 -0
  22. package/dist/http/server.js +52 -0
  23. package/dist/http/types.d.ts +26 -0
  24. package/dist/http/types.js +12 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.js +4 -0
  27. package/dist/logging.d.ts +24 -0
  28. package/dist/logging.js +30 -0
  29. package/dist/router/cors.d.ts +24 -0
  30. package/dist/router/cors.js +35 -0
  31. package/dist/router/index.d.ts +38 -0
  32. package/dist/router/index.js +36 -0
  33. package/dist/router/match.d.ts +23 -0
  34. package/dist/router/match.js +172 -0
  35. package/dist/router/parameters.d.ts +51 -0
  36. package/dist/router/parameters.js +118 -0
  37. package/dist/router/router.d.ts +127 -0
  38. package/dist/router/router.js +97 -0
  39. package/dist/swagger/index.d.ts +1 -0
  40. package/dist/swagger/index.js +168 -0
  41. package/dist/swagger/openapi-spec.d.ts +261 -0
  42. package/dist/swagger/openapi-spec.js +5 -0
  43. package/dist/validators/array.d.ts +9 -0
  44. package/dist/validators/array.js +28 -0
  45. package/dist/validators/boolean.d.ts +4 -0
  46. package/dist/validators/boolean.js +28 -0
  47. package/dist/validators/common.d.ts +23 -0
  48. package/dist/validators/common.js +25 -0
  49. package/dist/validators/index.d.ts +20 -0
  50. package/dist/validators/index.js +38 -0
  51. package/dist/validators/number.d.ts +10 -0
  52. package/dist/validators/number.js +30 -0
  53. package/dist/validators/object.d.ts +13 -0
  54. package/dist/validators/object.js +36 -0
  55. package/dist/validators/string.d.ts +11 -0
  56. package/dist/validators/string.js +29 -0
  57. package/package.json +54 -0
  58. package/src/demo/index.ts +33 -0
  59. package/src/http/body/form-data.ts +164 -0
  60. package/src/http/body/index.ts +59 -0
  61. package/src/http/body/receive.ts +49 -0
  62. package/src/http/http-status.ts +65 -0
  63. package/src/http/index.ts +9 -0
  64. package/src/http/mime-types.ts +765 -0
  65. package/src/http/request.ts +44 -0
  66. package/src/http/response.ts +73 -0
  67. package/src/http/server.ts +67 -0
  68. package/src/http/types.ts +31 -0
  69. package/src/index.ts +4 -0
  70. package/src/logging.ts +57 -0
  71. package/src/router/cors.ts +54 -0
  72. package/src/router/index.ts +38 -0
  73. package/src/router/match.ts +194 -0
  74. package/src/router/parameters.ts +172 -0
  75. package/src/router/router.ts +233 -0
  76. package/src/swagger/index.ts +184 -0
  77. package/src/swagger/openapi-spec.ts +312 -0
  78. package/src/validators/array.ts +33 -0
  79. package/src/validators/boolean.ts +23 -0
  80. package/src/validators/common.ts +46 -0
  81. package/src/validators/index.ts +50 -0
  82. package/src/validators/number.ts +36 -0
  83. package/src/validators/object.ts +41 -0
  84. 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
+ }