Skip to content

Commit 6af7801

Browse files
committed
feat(Validator): 重构
1 parent ed801e5 commit 6af7801

File tree

2 files changed

+298
-276
lines changed

2 files changed

+298
-276
lines changed

src/Validator.ts

+146-138
Original file line numberDiff line numberDiff line change
@@ -1,171 +1,179 @@
1-
import { castArray } from './castArray'
2-
import { isArray } from './isArray'
31
import { isChineseIDCardNumber } from './isChineseIDCardNumber'
42
import { isChineseLandlinePhoneNumber, isChineseMobilePhoneNumber, isChinesePhoneNumber } from './isChinesePhoneNumber'
53
import { isChineseName } from './isChineseName'
64
import { isEmail } from './isEmail'
75
import { isFunction } from './isFunction'
86
import { isInteger } from './isInteger'
9-
import { isNil } from './isNil'
10-
import { isNumber } from './isNumber'
117
import { isNumeric } from './isNumeric'
128
import { isPromise } from './isPromise'
139
import { isRegExp } from './isRegExp'
1410
import { isUrl } from './isUrl'
11+
import { promiseSeries } from './promiseSeries'
1512

16-
export type ValidatorRuleType = 'number'
17-
| 'integer'
18-
| 'phone'
19-
| 'mobile'
20-
| 'landline'
21-
| 'id'
22-
| 'url'
23-
| 'email'
24-
| 'name'
25-
export type ValidatorRuleCustom = (
26-
<D extends { [key: string]: any }>(payload: {
27-
key: keyof D,
28-
value: any,
13+
export type ValidatorData = Record<keyof any, any>
14+
15+
export interface ValidatorRuleTestFunction<D extends ValidatorData> {
16+
/**
17+
* 测试函数。
18+
*
19+
* @param value 要测试的值
20+
* @param data 数据对象
21+
* @returns 返回是否测试通过
22+
*/
23+
(
24+
value: D[keyof D],
2925
data: D,
30-
rule: ValidatorRule,
31-
}) => boolean | Promise<boolean>
32-
)
33-
export interface ValidatorRule {
34-
type?: ValidatorRuleType,
26+
): boolean | Promise<boolean>,
27+
}
28+
29+
export interface ValidatorRule<D extends ValidatorData> {
30+
/** 要验证字段在数据对象中的键名 */
31+
key: keyof D,
32+
/** 验证类型 */
33+
type?: (
34+
'number' |
35+
'integer' |
36+
'chinesePhoneNumber' |
37+
'chineseMobilePhoneNumber' |
38+
'chineseLandlinePhoneNumber' |
39+
'chineseIdCardNumber' |
40+
'url' |
41+
'email' |
42+
'chineseName'
43+
),
44+
/** 是否必填 */
3545
required?: boolean,
36-
len?: number,
37-
min?: number,
38-
max?: number,
39-
custom?: RegExp | ValidatorRuleCustom,
46+
/** 自定义测试,支持正则、同步函数、异步函数 */
47+
test?: (
48+
RegExp |
49+
ValidatorRuleTestFunction<D>
50+
),
51+
/** 提示信息 */
4052
message: any,
4153
}
4254

43-
export interface ValidatorRules {
44-
[key: string]: ValidatorRule | ValidatorRule[],
45-
}
55+
export type ValidatorRules<D extends ValidatorData> = Array<ValidatorRule<D>>
4656

47-
const typeValidators: { [key in ValidatorRuleType]: (value: any) => boolean } = {
48-
number: isNumeric,
49-
integer: value => isNumeric(value) && isInteger(+value),
50-
phone: isChinesePhoneNumber,
51-
mobile: isChineseMobilePhoneNumber,
52-
landline: isChineseLandlinePhoneNumber,
53-
id: isChineseIDCardNumber,
54-
url: isUrl,
55-
email: isEmail,
56-
name: isChineseName,
57+
export interface ValidatorValidateReturn<D extends ValidatorData> {
58+
/** 是否验证通过 */
59+
valid: boolean,
60+
/** 未验证通过的规则组成的列表 */
61+
unvalidRules: Array<ValidatorRule<D>>,
5762
}
5863

59-
function validate<D>(data: D, key: keyof D, rule: ValidatorRule): Promise<boolean> {
60-
return new Promise(resolve => {
61-
const value = data[key] as any
62-
const { required, type, len, min, max, custom } = rule
63-
64-
// required
65-
if (required && (isNil(value) || value === '' || (isArray(value) && !value.length))) {
66-
return resolve(false)
67-
}
68-
69-
// type
70-
if (type && typeValidators[type] && !typeValidators[type](value)) {
71-
return resolve(false)
72-
}
73-
74-
// min, max
75-
const shouldValidateMin = isNumber(min)
76-
const shouldValidateMax = isNumber(max)
77-
if (shouldValidateMin || shouldValidateMax) {
78-
const realValue = (
79-
(type === 'number' || type === 'integer') ? value
80-
: isArray(value) ? value.length
81-
: String(value).length
82-
)
83-
if ((shouldValidateMin && realValue < min) || (shouldValidateMax && realValue > max)) {
84-
return resolve(false)
85-
}
86-
}
64+
/**
65+
* 数据对象验证器。
66+
*
67+
* @template D 要验证的数据对象类型
68+
*/
69+
export class Validator<D extends ValidatorData> {
70+
/**
71+
* 构造函数。
72+
*
73+
* @param rules 规律列表
74+
*/
75+
constructor(private rules: ValidatorRules<D>) {}
8776

88-
// len
89-
if (isNumber(len)) {
90-
const realValue = isArray(value) ? value.length : String(value).length
91-
if (len !== realValue) {
92-
return resolve(false)
93-
}
94-
}
77+
private check(rule: ValidatorRule<D>, data: D) {
78+
return new Promise<boolean>(resolve => {
79+
const key = rule.key
80+
const value = data[key]
9581

96-
// custom
97-
if (custom) {
98-
/* istanbul ignore else */
99-
if (isRegExp(custom)) {
100-
return resolve(custom.test(value))
101-
} else if (isFunction(custom)) {
102-
const result = custom({ key, value, data, rule })
103-
if (isPromise(result)) {
104-
result.then(resolve)
105-
} else {
106-
resolve(result)
82+
/* istanbul ignore if */
83+
if (!(key in data)) {
84+
if (rule.required) {
85+
return resolve(false)
86+
}
87+
} else {
88+
if (rule.required && (value == null || value === '')) {
89+
return resolve(false)
90+
}
91+
if (rule.type) {
92+
switch (rule.type) {
93+
case 'number':
94+
if (!isNumeric(value)) return resolve(false)
95+
break
96+
case 'integer':
97+
if (!isNumeric(value) || !isInteger(Number(value))) return resolve(false)
98+
break
99+
case 'chinesePhoneNumber':
100+
if (!isChinesePhoneNumber(value)) return resolve(false)
101+
break
102+
case 'chineseMobilePhoneNumber':
103+
if (!isChineseMobilePhoneNumber(value)) return resolve(false)
104+
break
105+
case 'chineseLandlinePhoneNumber':
106+
if (!isChineseLandlinePhoneNumber(value)) return resolve(false)
107+
break
108+
case 'chineseIdCardNumber':
109+
if (!isChineseIDCardNumber(value)) return resolve(false)
110+
break
111+
case 'url':
112+
if (!isUrl(value)) return resolve(false)
113+
break
114+
case 'email':
115+
if (!isEmail(value)) return resolve(false)
116+
break
117+
case 'chineseName':
118+
if (!isChineseName(value)) return resolve(false)
119+
break
120+
/* istanbul ignore next */
121+
default:
122+
break
123+
}
124+
}
125+
if (rule.test) {
126+
if (isRegExp(rule.test)) {
127+
if (!rule.test.test(value)) {
128+
return resolve(false)
129+
}
130+
} else if (isFunction(rule.test)) {
131+
const result = rule.test(value, data)
132+
if (isPromise(result)) {
133+
return result.then(resolve)
134+
}
135+
return resolve(result)
136+
}
107137
}
108138
}
109-
}
110-
111-
return resolve(true)
112-
})
113-
}
114-
115-
export class Validator<R extends ValidatorRules> {
116-
private rules: R = {} as any
117139

118-
/**
119-
* 表单验证器。
120-
*
121-
* @param rules 验证规则
122-
*/
123-
public constructor(rules: R) {
124-
this.rules = rules
140+
return resolve(true)
141+
})
125142
}
126143

127144
/**
128-
* 验证数据
145+
* 验证数据
129146
*
130147
* @param data 要验证的数据
131-
* @returns 验证结果
148+
* @returns 返回验证结果
132149
*/
133-
public validate<D extends { [key in keyof R]: any }>(data: Partial<D>): Promise<
134-
{ valid: true } | (
135-
ValidatorRule & {
136-
valid: false,
137-
key: keyof D,
138-
value: D[keyof D],
139-
}
140-
)
141-
> {
142-
return new Promise(resolve => {
143-
Promise.all(Object.keys(data).map(key => {
144-
return new Promise((resolveItem, rejectItem) => {
145-
const rules = this.rules[key]
146-
if (!rules) {
147-
return resolveItem()
148-
}
149-
return Promise.all(castArray(rules).map(rule => {
150-
return new Promise((resolveRule, rejectRule) => {
151-
validate(data, key, rule).then(valid => {
152-
if (valid) {
153-
resolveRule()
154-
} else {
155-
rejectRule({
156-
...rule,
157-
key,
158-
value: data[key],
159-
})
160-
}
161-
})
150+
validate(data: D) {
151+
const unvalidKeys: Array<keyof D> = []
152+
const unvalidRules: Array<ValidatorRule<D>> = []
153+
154+
return (
155+
promiseSeries(
156+
this.rules.map(
157+
rule => () => {
158+
return new Promise(resolve => {
159+
if (unvalidKeys.indexOf(rule.key) === -1) {
160+
this.check(rule, data).then(valid => {
161+
if (!valid) {
162+
unvalidKeys.push(rule.key)
163+
unvalidRules.push(rule)
164+
}
165+
resolve()
166+
})
167+
} else {
168+
resolve()
169+
}
162170
})
163-
})).then(resolveItem, rejectItem)
164-
})
165-
})).then(
166-
() => resolve({ valid: true }),
167-
result => resolve({ ...result, valid: false }),
168-
)
169-
})
171+
},
172+
),
173+
).then<ValidatorValidateReturn<D>>(() => ({
174+
valid: unvalidRules.length === 0,
175+
unvalidRules: unvalidRules.slice(),
176+
}))
177+
)
170178
}
171179
}

0 commit comments

Comments
 (0)