@stdex/validator 0.0.0-stage → 1.2.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/LICENSE +21 -0
- package/README.md +1 -3
- package/dist/array.d.ts +30 -0
- package/dist/array.js +47 -0
- package/dist/base.d.ts +82 -0
- package/dist/base.js +42 -0
- package/dist/boolean.d.ts +3 -0
- package/dist/boolean.js +22 -0
- package/dist/datetime.d.ts +9 -0
- package/dist/datetime.js +30 -0
- package/dist/factory.d.ts +70 -0
- package/dist/factory.js +122 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/number.d.ts +19 -0
- package/dist/number.js +26 -0
- package/dist/object.d.ts +28 -0
- package/dist/object.js +49 -0
- package/dist/one-of.d.ts +10 -0
- package/dist/one-of.js +15 -0
- package/dist/string.d.ts +22 -0
- package/dist/string.js +35 -0
- package/package.json +44 -3
- package/src/array.ts +97 -0
- package/src/base.ts +145 -0
- package/src/boolean.ts +21 -0
- package/src/datetime.ts +39 -0
- package/src/factory.ts +248 -0
- package/src/index.ts +15 -0
- package/src/number.ts +54 -0
- package/src/object.ts +101 -0
- package/src/one-of.ts +33 -0
- package/src/string.ts +72 -0
package/dist/one-of.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type CommonOptions, type Validator } from './base.js';
|
|
2
|
+
/** 要求返回值通过其中一个验证器的检查 */
|
|
3
|
+
export interface OneOfOptions extends CommonOptions {
|
|
4
|
+
/** 验证数组各元素 */
|
|
5
|
+
validators: Validator<unknown, CommonOptions>[];
|
|
6
|
+
}
|
|
7
|
+
export type OneOfValue<Options extends OneOfOptions> = Options extends {
|
|
8
|
+
validators: Validator<infer T, CommonOptions>[];
|
|
9
|
+
} ? T : never;
|
|
10
|
+
export declare function getOneOfValidator<const Options extends OneOfOptions>(options?: Options): Validator<OneOfValue<Options>, Options>;
|
package/dist/one-of.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { failure } from '@stdex/utils';
|
|
2
|
+
import { getValidatorGenerator, } from './base.js';
|
|
3
|
+
export function getOneOfValidator(options = {}) {
|
|
4
|
+
return getValidatorGenerator(function validate(field, value) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
for (const validator of options.validators) {
|
|
7
|
+
const result = validator(field, value);
|
|
8
|
+
if (result.success)
|
|
9
|
+
return result;
|
|
10
|
+
else
|
|
11
|
+
errors.push(result.message);
|
|
12
|
+
}
|
|
13
|
+
return failure(`${field} do not match any valid format:\n- ` + errors.join('\n- '));
|
|
14
|
+
})(options);
|
|
15
|
+
}
|
package/dist/string.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CommonOptions } from './base.js';
|
|
2
|
+
export interface StringOptions extends CommonOptions<string> {
|
|
3
|
+
/** 字符串最小长度。defaults='' 时默认为 0,否则默认为 1 */
|
|
4
|
+
min?: number;
|
|
5
|
+
/** 字符串最大长度。 */
|
|
6
|
+
max?: number;
|
|
7
|
+
/** 字符串需匹配此正则,也可传入关键词使用预置的正则。正则通常应以 ^ 和 $ 开头结尾。 */
|
|
8
|
+
pattern?: RegExp | 'uuid' | 'mobile';
|
|
9
|
+
/**
|
|
10
|
+
* 指定一个数组或 TypeScript enum,字段值必须在此 enum 之中。
|
|
11
|
+
* 若指定,前几个选项将不再生效。
|
|
12
|
+
*/
|
|
13
|
+
choices?: string[] | Record<string, string>;
|
|
14
|
+
/** 验证之前,是否先清除两侧空白字符 @default true */
|
|
15
|
+
trim?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type StringValueWithChoices<Options extends StringOptions> = Options extends {
|
|
18
|
+
choices: (infer T)[];
|
|
19
|
+
} ? T : Options extends {
|
|
20
|
+
choices: Record<string, infer T>;
|
|
21
|
+
} ? T : string;
|
|
22
|
+
export declare function getStringValidator<const Options extends StringOptions>(options?: Options): import("./base.js").Validator<StringValueWithChoices<Options>, Options>;
|
package/dist/string.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { success, failure } from '@stdex/utils';
|
|
2
|
+
import { getValidatorGenerator } from './base.js';
|
|
3
|
+
export function getStringValidator(options = {}) {
|
|
4
|
+
return getValidatorGenerator(function validate(field, value) {
|
|
5
|
+
if (typeof value !== 'string')
|
|
6
|
+
return failure(`${field} must be a string`);
|
|
7
|
+
const trim = options.trim ?? true;
|
|
8
|
+
const formatted = trim ? value.trim() : value;
|
|
9
|
+
if ('choices' in options && options.choices) {
|
|
10
|
+
const validValues = Array.isArray(options.choices)
|
|
11
|
+
? options.choices
|
|
12
|
+
: Object.values(options.choices);
|
|
13
|
+
if (!validValues.includes(formatted))
|
|
14
|
+
return failure(`${field} can only be one of ${validValues.join(', ')}.`);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
const { min = options.defaults === '' ? 0 : 1, max, pattern } = options;
|
|
18
|
+
if (typeof min === 'number' && formatted.length < min)
|
|
19
|
+
return failure(`${field}'s length must >= ${min}`);
|
|
20
|
+
if (typeof max === 'number' && formatted.length > max)
|
|
21
|
+
return failure(`${field}'s length must <= ${max}`);
|
|
22
|
+
if (pattern !== undefined) {
|
|
23
|
+
if (pattern instanceof RegExp && !pattern.exec(formatted))
|
|
24
|
+
return failure(`${field} does not match the pattern`);
|
|
25
|
+
if (pattern === 'mobile' && !/^1\d{10}$/.test(formatted))
|
|
26
|
+
return failure(`${field} is not a valid mobile number`);
|
|
27
|
+
if (pattern === 'uuid') {
|
|
28
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(formatted))
|
|
29
|
+
return failure(`${field} is not a valid uuid`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return success(formatted);
|
|
34
|
+
})(options);
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stdex/validator",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "数据验证、格式化工具,支持自动类型推理",
|
|
5
|
+
"homepage": "https://github.com/anjianshi/stdex-packages/validator",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/anjianshi/stdex-packages/issues",
|
|
8
|
+
"email": "anjianshi@gmail.com"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": "anjianshi <anjianshi@gmail.com>",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/anjianshi/stdex-packages.git"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"registry": "https://registry.npmjs.org/",
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"types": "dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"dist/"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@stdex/utils": "1.2.0",
|
|
29
|
+
"dayjs": "^1.11.23",
|
|
30
|
+
"lodash": "^4.18.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@stdex/presets-eslint-typescript": "1.2.0",
|
|
34
|
+
"@stdex/presets-prettier": "1.2.0",
|
|
35
|
+
"@stdex/presets-typescript": "1.2.0",
|
|
36
|
+
"@types/lodash": "^4.17.27",
|
|
37
|
+
"eslint": "^10.6.0",
|
|
38
|
+
"typescript": "^6.0.3"
|
|
39
|
+
},
|
|
40
|
+
"prettier": "@stdex/presets-prettier/prettierrc",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "npm run clear && tsc",
|
|
43
|
+
"watch": "npm run clear && tsc --watch",
|
|
44
|
+
"clear": "rm -rf dist",
|
|
45
|
+
"lint": "tsc --noEmit && eslint './src/'"
|
|
46
|
+
}
|
|
6
47
|
}
|
package/src/array.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { success, failure } from '@stdex/utils'
|
|
2
|
+
import {
|
|
3
|
+
getValidatorGenerator,
|
|
4
|
+
type CommonOptions,
|
|
5
|
+
type Validator,
|
|
6
|
+
type Validated,
|
|
7
|
+
type PrimitiveType,
|
|
8
|
+
} from './base.js'
|
|
9
|
+
|
|
10
|
+
/** 验证元素数量任意、元素类型相同的数组 */
|
|
11
|
+
export interface ArrayOptions extends CommonOptions {
|
|
12
|
+
/** 验证数组各元素 */
|
|
13
|
+
item: Validator<unknown, CommonOptions>
|
|
14
|
+
|
|
15
|
+
/** 数组最小长度 */
|
|
16
|
+
min?: number
|
|
17
|
+
|
|
18
|
+
/** 数组最大长度 */
|
|
19
|
+
max?: number
|
|
20
|
+
|
|
21
|
+
/** 是否对数组元素进行去重 @defaults false */
|
|
22
|
+
unique?: boolean
|
|
23
|
+
|
|
24
|
+
/** 如果传入的不是数组,是否要将其视为数组内元素,包裹成数组 */
|
|
25
|
+
toArray?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ArrayValues<Options extends ArrayOptions> = Validated<
|
|
29
|
+
Options extends { item: Validator<infer T, CommonOptions> } ? T : never,
|
|
30
|
+
Options extends { item: Validator<unknown, infer T> } ? T : never
|
|
31
|
+
>[]
|
|
32
|
+
|
|
33
|
+
export function getArrayValidator<Options extends ArrayOptions>(options: Options) {
|
|
34
|
+
return getValidatorGenerator<ArrayValues<Options>, Options>(
|
|
35
|
+
function validate(field, value, options) {
|
|
36
|
+
if (!Array.isArray(value)) {
|
|
37
|
+
if (options.toArray) value = [value as PrimitiveType]
|
|
38
|
+
else return failure(`${field} should be an array`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let formatted = []
|
|
42
|
+
if (typeof options.min === 'number' && value.length < options.min)
|
|
43
|
+
return failure(`array ${field}'s length should >= ${options.min}`)
|
|
44
|
+
|
|
45
|
+
if (typeof options.max === 'number' && value.length > options.max)
|
|
46
|
+
return failure(`array ${field}'s length should <= ${options.max}`)
|
|
47
|
+
|
|
48
|
+
const itemValidator = options.item
|
|
49
|
+
for (let i = 0; i < value.length; i++) {
|
|
50
|
+
const itemResult = itemValidator(`${field}[${i}]`, value[i])
|
|
51
|
+
if (itemResult.success) formatted.push(itemResult.data)
|
|
52
|
+
else return itemResult
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (options.unique === true) formatted = [...new Set(formatted)]
|
|
56
|
+
|
|
57
|
+
return success(formatted as ArrayValues<Options>)
|
|
58
|
+
},
|
|
59
|
+
)(options)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/** 验证元素数量固定、类型可以不同的数组 */
|
|
65
|
+
export interface TupleOptions extends CommonOptions {
|
|
66
|
+
/** 验证数组各元素(validator 与元素一一对应) */
|
|
67
|
+
tuple: Validator<unknown, CommonOptions>[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type TupleValues<Options extends TupleOptions> = {
|
|
71
|
+
[Key in keyof Options['tuple']]: Options['tuple'][Key] extends Validator<
|
|
72
|
+
infer Value,
|
|
73
|
+
infer Options
|
|
74
|
+
>
|
|
75
|
+
? Validated<Value, Options>
|
|
76
|
+
: never
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getTupleValidator<const Options extends TupleOptions>(options: Options) {
|
|
80
|
+
return getValidatorGenerator<TupleValues<Options>, Options>(
|
|
81
|
+
function validate(field, value, options) {
|
|
82
|
+
if (!Array.isArray(value)) return failure(`${field} should be an array`)
|
|
83
|
+
if (value.length > options.tuple.length)
|
|
84
|
+
return failure(`${field} should be a tuple with ${options.tuple.length} items`)
|
|
85
|
+
|
|
86
|
+
const formatted = []
|
|
87
|
+
// 这种情况不能遍历 value,因为它的长度可能小于 opt.tuple
|
|
88
|
+
for (let i = 0; i < options.tuple.length; i++) {
|
|
89
|
+
const itemValidator = options.tuple[i]!
|
|
90
|
+
const itemResult = itemValidator(`${field}[${i}]`, value[i])
|
|
91
|
+
if (itemResult.success) formatted.push(itemResult.data)
|
|
92
|
+
else return itemResult
|
|
93
|
+
}
|
|
94
|
+
return success(formatted as TupleValues<Options>)
|
|
95
|
+
},
|
|
96
|
+
)(options)
|
|
97
|
+
}
|
package/src/base.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { success, failure, type Result } from '@stdex/utils'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 支持传入进行验证的值类型
|
|
5
|
+
*/
|
|
6
|
+
export type AllowedInputValue = PrimitiveType | null | undefined
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* JavaScript 基础值类型
|
|
10
|
+
*/
|
|
11
|
+
export type PrimitiveType =
|
|
12
|
+
| string
|
|
13
|
+
| boolean
|
|
14
|
+
| number
|
|
15
|
+
| PrimitiveType[]
|
|
16
|
+
| [...PrimitiveType[]]
|
|
17
|
+
| { [key: string]: PrimitiveType }
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* validator 通用参数
|
|
21
|
+
*/
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
+
export interface CommonOptions<Value = any> {
|
|
24
|
+
/** 是否允许 null 值 @default false */
|
|
25
|
+
null?: boolean
|
|
26
|
+
|
|
27
|
+
/** 字段是否必须有值(不能是 undefined) @default true */
|
|
28
|
+
required?: boolean
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 默认值,字段无值(或值为 undefined)时生效,值为 null 不会生效。
|
|
32
|
+
* 指定后 required 选项将失去作用。
|
|
33
|
+
*/
|
|
34
|
+
defaults?: Value
|
|
35
|
+
|
|
36
|
+
custom?: (input: Value) => Result<Value>
|
|
37
|
+
|
|
38
|
+
// 用来保证传入定制过的 Options 时 TypeScript 不会报不匹配的错
|
|
39
|
+
// 例如不加这句时, `{ other: boolean } extends CommonOptions` 是会被 TypeScript 判定为不匹配的:
|
|
40
|
+
// error TS2559: Type '{ other: boolean }' has no properties in common with type 'CommonOptions<unknown>'
|
|
41
|
+
// 此解决办法来自:https://mariusschulz.com/blog/weak-type-detection-in-typescript
|
|
42
|
+
[key: string]: unknown
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 补全了的 Options
|
|
47
|
+
*/
|
|
48
|
+
type FullfiledOptions<Options extends Partial<CommonOptions>> = Omit<
|
|
49
|
+
Options,
|
|
50
|
+
keyof CommonOptions
|
|
51
|
+
> & {
|
|
52
|
+
null: Options['null'] extends true ? true : false
|
|
53
|
+
required: Options['required'] extends false ? false : true
|
|
54
|
+
defaults: Options extends { defaults: infer T } ? T : undefined
|
|
55
|
+
custom: Options extends { custom: infer T } ? T : undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 验证完成后能得到的值类型
|
|
60
|
+
*/
|
|
61
|
+
export type Validated<Value, InputOptions extends CommonOptions> =
|
|
62
|
+
FullfiledOptions<InputOptions> extends { defaults: undefined }
|
|
63
|
+
? FullfiledOptions<InputOptions> extends { required: false; null: false }
|
|
64
|
+
? Value | undefined
|
|
65
|
+
: FullfiledOptions<InputOptions> extends { required: false; null: true }
|
|
66
|
+
? Value | undefined | null
|
|
67
|
+
: FullfiledOptions<InputOptions> extends { required: true; null: false }
|
|
68
|
+
? Value
|
|
69
|
+
: FullfiledOptions<InputOptions> extends { required: true; null: true }
|
|
70
|
+
? Value | null
|
|
71
|
+
: Value
|
|
72
|
+
: FullfiledOptions<InputOptions> extends {
|
|
73
|
+
defaults: infer T
|
|
74
|
+
null: false
|
|
75
|
+
}
|
|
76
|
+
? Value | T
|
|
77
|
+
: FullfiledOptions<InputOptions> extends { defaults: infer T; null: true }
|
|
78
|
+
? Value | T | null
|
|
79
|
+
: never
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 最终生成的 validator 函数类型
|
|
83
|
+
*/
|
|
84
|
+
export interface Validator<Value, InputOptions extends CommonOptions> {
|
|
85
|
+
(input: AllowedInputValue): Result<Validated<Value, InputOptions>>
|
|
86
|
+
(field: string, input: AllowedInputValue): Result<Validated<Value, InputOptions>>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -----------------------------------
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 返回支持指定格式的 options、并按照传入的逻辑进行验证的 validator 的生成器。
|
|
93
|
+
* 对 CommonOptions 相关内容的验证以自动包含在里面,只需要传入额外的验证逻辑即可。
|
|
94
|
+
*/
|
|
95
|
+
export function getValidatorGenerator<Value, Options extends CommonOptions>(
|
|
96
|
+
validate: (
|
|
97
|
+
field: string,
|
|
98
|
+
input: PrimitiveType | Validated<Value, Options>,
|
|
99
|
+
options: Options,
|
|
100
|
+
) => Result<Value>,
|
|
101
|
+
) {
|
|
102
|
+
return function validatorGenerator<const InputOptions extends Options>(
|
|
103
|
+
inputOptions: InputOptions,
|
|
104
|
+
): Validator<Value, InputOptions> {
|
|
105
|
+
type Return = Result<Validated<Value, InputOptions>>
|
|
106
|
+
function validator(input: AllowedInputValue): Return
|
|
107
|
+
function validator(field: string, input: AllowedInputValue): Return
|
|
108
|
+
function validator(field: string | AllowedInputValue, input?: AllowedInputValue) {
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
110
|
+
const { null: allowNull = false, required = true, defaults, custom } = inputOptions
|
|
111
|
+
|
|
112
|
+
if (typeof field !== 'string') {
|
|
113
|
+
input = field
|
|
114
|
+
field = 'value'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let value: AllowedInputValue | Validated<Value, Options> = input
|
|
118
|
+
if (typeof value === 'undefined') {
|
|
119
|
+
if (typeof defaults !== 'undefined') {
|
|
120
|
+
value = defaults as Validated<Value, Options>
|
|
121
|
+
} else if (required) {
|
|
122
|
+
return failure(`${field} is required`)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (value === null && !allowNull) return failure(`${field} cannot be null`)
|
|
126
|
+
if (value === null || value === undefined) return success(value)
|
|
127
|
+
|
|
128
|
+
const validated = validate(field, value, inputOptions)
|
|
129
|
+
return validated.success && custom ? custom(validated.data) : validated
|
|
130
|
+
}
|
|
131
|
+
return validator
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// -----------------------------------
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 返回只进行基本检查,不带定制的验证逻辑的 validator。
|
|
139
|
+
* 同时也是定制 validator 最小化实现的例子。
|
|
140
|
+
*/
|
|
141
|
+
export const getAnyValidator = getValidatorGenerator<unknown, CommonOptions>(
|
|
142
|
+
function validate(field, input) {
|
|
143
|
+
return success(input)
|
|
144
|
+
},
|
|
145
|
+
)
|
package/src/boolean.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { success, failure } from '@stdex/utils'
|
|
2
|
+
import { getValidatorGenerator, type CommonOptions } from './base.js'
|
|
3
|
+
|
|
4
|
+
export type BooleanOptions = CommonOptions<boolean>
|
|
5
|
+
|
|
6
|
+
export const getBooleanValidator = getValidatorGenerator<boolean, BooleanOptions>(
|
|
7
|
+
function validate(field, input) {
|
|
8
|
+
let value: boolean | null = null
|
|
9
|
+
if (typeof input === 'boolean') {
|
|
10
|
+
value = input
|
|
11
|
+
} else if (typeof input === 'string') {
|
|
12
|
+
const str = input.trim().toLowerCase()
|
|
13
|
+
if (['1', 'true', 'on', 'yes'].includes(str)) value = true
|
|
14
|
+
else if (['0', 'false', 'off', 'no'].includes(str)) value = false
|
|
15
|
+
} else if (typeof input === 'number') {
|
|
16
|
+
if (input === 1) value = true
|
|
17
|
+
else if (input === 0) value = false
|
|
18
|
+
}
|
|
19
|
+
return value === null ? failure(`${field} must be true or false`) : success(value)
|
|
20
|
+
},
|
|
21
|
+
)
|
package/src/datetime.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 验证日期时间类型的值,依赖 dayjs
|
|
3
|
+
*/
|
|
4
|
+
import { success, failure } from '@stdex/utils'
|
|
5
|
+
import dayjs, { type Dayjs } from 'dayjs'
|
|
6
|
+
import { getValidatorGenerator, type CommonOptions } from './base.js'
|
|
7
|
+
|
|
8
|
+
export interface DatetimeOptions extends CommonOptions<number> {
|
|
9
|
+
dayjs?: boolean // 为 true 返回 Dayjs 对象,否则返回原生 Date 对象(默认)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type DatetimeValue<Options extends DatetimeOptions> = Options extends {
|
|
13
|
+
dayjs: true
|
|
14
|
+
}
|
|
15
|
+
? Dayjs
|
|
16
|
+
: Date
|
|
17
|
+
|
|
18
|
+
export function getDatetimeValidator<const Options extends DatetimeOptions>(
|
|
19
|
+
options: Options = {} as Options,
|
|
20
|
+
) {
|
|
21
|
+
return getValidatorGenerator<DatetimeValue<Options>, Options>(function validate(field, value) {
|
|
22
|
+
let dayjsValue: Dayjs
|
|
23
|
+
|
|
24
|
+
if (typeof value === 'number') {
|
|
25
|
+
dayjsValue = dayjs.unix(value)
|
|
26
|
+
if (!dayjsValue.isValid()) return failure(`${field} must be a valid unix timestamp`)
|
|
27
|
+
} else if (typeof value === 'string') {
|
|
28
|
+
dayjsValue = dayjs(value)
|
|
29
|
+
if (!dayjsValue.isValid()) return failure(`${field} must be a valid datetime string`)
|
|
30
|
+
} else if (value instanceof Date || dayjs.isDayjs(value)) {
|
|
31
|
+
dayjsValue = dayjs(value)
|
|
32
|
+
if (!dayjsValue.isValid()) return failure(`${field} must be a valid Date or Dayjs object`)
|
|
33
|
+
} else {
|
|
34
|
+
return failure(`${field} must be a datetime string or unix timestamp`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return success((options.dayjs ? dayjsValue : dayjsValue.toDate()) as DatetimeValue<Options>)
|
|
38
|
+
})(options)
|
|
39
|
+
}
|