schema-dsl 2.0.0 → 2.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/CHANGELOG.md +130 -113
- package/LICENSE +21 -21
- package/README.md +628 -628
- package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
- package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
- package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
- package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +75 -29
- package/dist/plugins/custom-format.cjs +33 -17
- package/dist/plugins/custom-format.d.cts +1 -1
- package/dist/plugins/custom-format.d.ts +1 -1
- package/dist/plugins/custom-format.js +33 -17
- package/dist/plugins/custom-type-example.cjs +33 -17
- package/dist/plugins/custom-type-example.d.cts +1 -1
- package/dist/plugins/custom-type-example.d.ts +1 -1
- package/dist/plugins/custom-type-example.js +33 -17
- package/dist/plugins/custom-validator.cjs +0 -2
- package/dist/plugins/custom-validator.d.cts +1 -1
- package/dist/plugins/custom-validator.d.ts +1 -1
- package/dist/plugins/custom-validator.js +0 -2
- package/docs/FEATURE-INDEX.md +553 -553
- package/docs/add-custom-locale.md +496 -496
- package/docs/add-keyword.md +24 -24
- package/docs/api-reference.md +1047 -1047
- package/docs/api.md +13 -13
- package/docs/best-practices-project-structure.md +417 -417
- package/docs/best-practices.md +712 -712
- package/docs/cache-manager.md +344 -344
- package/docs/compile.md +45 -45
- package/docs/conditional-api.md +1307 -1307
- package/docs/custom-extensions-guide.md +339 -339
- package/docs/design-philosophy.md +606 -606
- package/docs/doc-index.md +324 -324
- package/docs/dsl-syntax.md +714 -714
- package/docs/dynamic-locale.md +608 -608
- package/docs/enum.md +482 -482
- package/docs/error-handling.md +1975 -1975
- package/docs/export-guide.md +501 -501
- package/docs/export-limitations.md +567 -567
- package/docs/faq.md +596 -596
- package/docs/frontend-i18n-guide.md +307 -307
- package/docs/i18n-user-guide.md +487 -487
- package/docs/i18n.md +476 -476
- package/docs/index.md +48 -48
- package/docs/json-schema-basics.md +40 -40
- package/docs/label-vs-description.md +271 -271
- package/docs/markdown-exporter.md +406 -406
- package/docs/mongodb-exporter.md +302 -302
- package/docs/multi-language.md +26 -26
- package/docs/multi-type-support.md +322 -322
- package/docs/mysql-exporter.md +280 -280
- package/docs/number-operators.md +449 -449
- package/docs/optional-marker-guide.md +326 -326
- package/docs/performance-guide.md +49 -49
- package/docs/plugin-system.md +381 -381
- package/docs/plugin-type-registration.md +34 -34
- package/docs/postgresql-exporter.md +311 -311
- package/docs/public/favicon.svg +4 -4
- package/docs/quick-start.md +435 -435
- package/docs/runtime-locale-support.md +532 -532
- package/docs/schema-helper.md +345 -345
- package/docs/schema-utils-advanced-issues.md +23 -23
- package/docs/schema-utils-best-practices.md +20 -20
- package/docs/schema-utils-chaining.md +150 -150
- package/docs/schema-utils.md +524 -524
- package/docs/security-checklist.md +20 -20
- package/docs/string-extensions.md +488 -488
- package/docs/troubleshooting.md +486 -486
- package/docs/type-converter.md +310 -310
- package/docs/type-reference.md +242 -242
- package/docs/typescript-guide.md +584 -584
- package/docs/union-type-guide.md +157 -157
- package/docs/union-types.md +284 -284
- package/docs/validate-async.md +491 -491
- package/docs/validate-batch.md +49 -49
- package/docs/validate-dsl-object-support.md +578 -578
- package/docs/validate.md +506 -506
- package/docs/validation-guide.md +502 -502
- package/docs/validator.md +39 -39
- package/package.json +131 -131
- package/plugins/custom-format.cjs +8 -8
- package/plugins/custom-type-example.cjs +8 -8
- package/plugins/custom-validator.cjs +8 -8
- package/src/adapters/DslAdapter.ts +111 -111
- package/src/adapters/index.ts +1 -1
- package/src/config/constants.ts +83 -83
- package/src/config/index.ts +2 -2
- package/src/config/patterns.ts +77 -77
- package/src/core/CacheManager.ts +169 -159
- package/src/core/ConditionalBuilder.ts +382 -382
- package/src/core/ConditionalRuntime.ts +27 -27
- package/src/core/ConditionalValidator.ts +254 -254
- package/src/core/DslBuilder.ts +687 -677
- package/src/core/ErrorCodes.ts +38 -38
- package/src/core/ErrorFormatter.ts +271 -271
- package/src/core/JSONSchemaCore.ts +65 -65
- package/src/core/Locale.ts +187 -187
- package/src/core/MessageTemplate.ts +42 -42
- package/src/core/ObjectDslBuilder.ts +64 -64
- package/src/core/PluginManager.ts +326 -326
- package/src/core/StringExtensions.ts +140 -140
- package/src/core/TemplateEngine.ts +44 -44
- package/src/core/Validator.ts +448 -448
- package/src/errors/I18nError.ts +159 -159
- package/src/errors/ValidationError.ts +105 -105
- package/src/exporters/BaseExporter.ts +60 -60
- package/src/exporters/MarkdownExporter.ts +305 -305
- package/src/exporters/MongoDBExporter.ts +126 -126
- package/src/exporters/MySQLExporter.ts +156 -155
- package/src/exporters/PostgreSQLExporter.ts +222 -222
- package/src/exporters/index.ts +18 -18
- package/src/index.ts +651 -633
- package/src/locales/en-US.ts +160 -160
- package/src/locales/es-ES.ts +160 -160
- package/src/locales/fr-FR.ts +160 -160
- package/src/locales/index.ts +103 -103
- package/src/locales/ja-JP.ts +160 -160
- package/src/locales/types.ts +156 -156
- package/src/locales/zh-CN.ts +160 -160
- package/src/parser/ConstraintParser.ts +101 -101
- package/src/parser/DslParser.ts +470 -470
- package/src/parser/SchemaCompiler.ts +66 -66
- package/src/parser/TypeRegistry.ts +250 -250
- package/src/parser/index.ts +6 -6
- package/src/plugins/custom-format.ts +124 -126
- package/src/plugins/custom-type-example.ts +106 -108
- package/src/plugins/custom-validator.ts +138 -140
- package/src/types/conditional.ts +28 -28
- package/src/types/config.ts +59 -59
- package/src/types/dsl.ts +131 -131
- package/src/types/error.ts +60 -60
- package/src/types/index.ts +17 -17
- package/src/types/infer.ts +127 -127
- package/src/types/plugin.ts +58 -58
- package/src/types/safe-regex.d.ts +9 -9
- package/src/types/schema.ts +66 -66
- package/src/types/validate.ts +71 -71
- package/src/utils/SchemaHelper.ts +196 -196
- package/src/utils/SchemaUtils.ts +365 -346
- package/src/utils/TypeConverter.ts +215 -215
- package/src/utils/index.ts +10 -10
- package/src/validators/CustomKeywords.ts +477 -477
package/docs/validation-guide.md
CHANGED
|
@@ -1,502 +1,502 @@
|
|
|
1
|
-
# 数据验证最佳实践指南
|
|
2
|
-
|
|
3
|
-
> **用途**: 完整的数据验证使用指南
|
|
4
|
-
> **阅读时间**: 15分钟
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 📑 目录
|
|
9
|
-
|
|
10
|
-
- [快速入门](#快速入门)
|
|
11
|
-
- [DSL 语法速查](#dsl-语法速查)
|
|
12
|
-
- [验证模式](#验证模式)
|
|
13
|
-
- [错误处理](#错误处理)
|
|
14
|
-
- [性能优化](#性能优化)
|
|
15
|
-
- [常见场景](#常见场景)
|
|
16
|
-
- [最佳实践](#最佳实践)
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 快速入门
|
|
21
|
-
|
|
22
|
-
### 基本验证流程
|
|
23
|
-
|
|
24
|
-
```javascript
|
|
25
|
-
const { dsl, validate } = require('schema-dsl');
|
|
26
|
-
|
|
27
|
-
// 1. 定义 Schema
|
|
28
|
-
const schema = dsl({
|
|
29
|
-
username: 'string:3-32!',
|
|
30
|
-
email: 'email!',
|
|
31
|
-
age: 'number:18-120'
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// 2. 验证数据
|
|
35
|
-
const result = validate(schema, {
|
|
36
|
-
username: 'john_doe',
|
|
37
|
-
email: 'john@example.com',
|
|
38
|
-
age: 25
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// 3. 处理结果
|
|
42
|
-
if (result.valid) {
|
|
43
|
-
console.log('验证通过', result.data);
|
|
44
|
-
} else {
|
|
45
|
-
console.log('验证失败', result.errors);
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## DSL 语法速查
|
|
52
|
-
|
|
53
|
-
### 基本类型
|
|
54
|
-
|
|
55
|
-
| DSL | 说明 |
|
|
56
|
-
|-----|------|
|
|
57
|
-
| `'string'` | 字符串 |
|
|
58
|
-
| `'number'` | 数字 |
|
|
59
|
-
| `'integer'` | 整数 |
|
|
60
|
-
| `'boolean'` | 布尔值 |
|
|
61
|
-
| `'object'` | 对象 |
|
|
62
|
-
| `'array'` | 数组 |
|
|
63
|
-
|
|
64
|
-
### 格式类型
|
|
65
|
-
|
|
66
|
-
| DSL | 说明 |
|
|
67
|
-
|-----|------|
|
|
68
|
-
| `'email'` | 邮箱格式 |
|
|
69
|
-
| `'url'` | URL 格式 |
|
|
70
|
-
| `'uuid'` | UUID 格式 |
|
|
71
|
-
| `'date'` | 日期格式 |
|
|
72
|
-
| `'datetime'` | 日期时间格式 |
|
|
73
|
-
| `'time'` | 时间格式 |
|
|
74
|
-
| `'ipv4'` | IPv4 地址 |
|
|
75
|
-
| `'ipv6'` | IPv6 地址 |
|
|
76
|
-
|
|
77
|
-
### 约束语法
|
|
78
|
-
|
|
79
|
-
| DSL | 说明 |
|
|
80
|
-
|-----|------|
|
|
81
|
-
| `'string:10'` | 最大长度 10 |
|
|
82
|
-
| `'string:3-32'` | 长度 3-32 |
|
|
83
|
-
| `'string:3-'` | 最小长度 3 |
|
|
84
|
-
| `'number:18-120'` | 数值范围 18-120 |
|
|
85
|
-
| `'array:1-10'` | 数组长度 1-10 |
|
|
86
|
-
|
|
87
|
-
### 特殊标记
|
|
88
|
-
|
|
89
|
-
| DSL | 说明 |
|
|
90
|
-
|-----|------|
|
|
91
|
-
| `'string!'` | 必填字符串 |
|
|
92
|
-
| `'email!'` | 必填邮箱 |
|
|
93
|
-
| `'a\|b\|c'` | 枚举值 |
|
|
94
|
-
| `'array<string>'` | 字符串数组 |
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## 验证模式
|
|
99
|
-
|
|
100
|
-
### 1. 便捷函数验证(推荐)
|
|
101
|
-
|
|
102
|
-
最简单的验证方式,使用内置单例 Validator:
|
|
103
|
-
|
|
104
|
-
```javascript
|
|
105
|
-
const { dsl, validate } = require('schema-dsl');
|
|
106
|
-
|
|
107
|
-
const result = validate(schema, data);
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### 2. Validator 实例验证(高级)
|
|
111
|
-
|
|
112
|
-
需要自定义配置(如类型转换、自定义关键字)时使用:
|
|
113
|
-
|
|
114
|
-
```javascript
|
|
115
|
-
const { dsl, Validator } = require('schema-dsl');
|
|
116
|
-
|
|
117
|
-
// 创建自定义配置的 Validator
|
|
118
|
-
const validator = new Validator({
|
|
119
|
-
allErrors: true, // 返回所有错误
|
|
120
|
-
useDefaults: true, // 使用默认值
|
|
121
|
-
coerceTypes: true // ✨ 启用类型转换
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const result = validator.validate(schema, data);
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
> **注意**: `new Validator()` 会创建一个新的 Ajv 实例,有一定的初始化开销。建议在应用启动时创建并复用,避免在每次请求中创建。
|
|
128
|
-
|
|
129
|
-
### 3. 预编译验证(高性能)
|
|
130
|
-
|
|
131
|
-
频繁验证同一 Schema 时使用:
|
|
132
|
-
|
|
133
|
-
```javascript
|
|
134
|
-
const validator = new Validator();
|
|
135
|
-
|
|
136
|
-
// 预编译 Schema
|
|
137
|
-
const validateUser = validator.compile(userSchema);
|
|
138
|
-
|
|
139
|
-
// 多次验证(无需重复编译)
|
|
140
|
-
const result1 = validateUser(data1);
|
|
141
|
-
const result2 = validateUser(data2);
|
|
142
|
-
const result3 = validateUser(data3);
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### 4. 批量验证
|
|
146
|
-
|
|
147
|
-
验证多条数据时使用:
|
|
148
|
-
|
|
149
|
-
```javascript
|
|
150
|
-
const { Validator } = require('schema-dsl');
|
|
151
|
-
const validator = new Validator();
|
|
152
|
-
|
|
153
|
-
const dataList = [
|
|
154
|
-
{ username: 'user1', email: 'user1@example.com' },
|
|
155
|
-
{ username: 'user2', email: 'invalid' },
|
|
156
|
-
{ username: 'u', email: 'user3@example.com' }
|
|
157
|
-
];
|
|
158
|
-
|
|
159
|
-
const results = validator.validateBatch(schema, dataList);
|
|
160
|
-
// [
|
|
161
|
-
// { valid: true, data: {...}, errors: [] },
|
|
162
|
-
// { valid: false, data: {...}, errors: [...] },
|
|
163
|
-
// { valid: false, data: {...}, errors: [...] }
|
|
164
|
-
// ]
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## 错误处理
|
|
170
|
-
|
|
171
|
-
### 错误对象结构
|
|
172
|
-
|
|
173
|
-
```javascript
|
|
174
|
-
{
|
|
175
|
-
message: '用户名长度不能少于3个字符',
|
|
176
|
-
path: '/username',
|
|
177
|
-
keyword: 'minLength',
|
|
178
|
-
params: { limit: 3 }
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### 自定义错误消息
|
|
183
|
-
|
|
184
|
-
```javascript
|
|
185
|
-
const schema = dsl({
|
|
186
|
-
username: 'string:3-32!'
|
|
187
|
-
.label('用户名')
|
|
188
|
-
.messages({
|
|
189
|
-
'min': '{{#label}}太短了,至少{{#limit}}个字符',
|
|
190
|
-
'max': '{{#label}}太长了,最多{{#limit}}个字符',
|
|
191
|
-
'required': '请输入{{#label}}'
|
|
192
|
-
})
|
|
193
|
-
});
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### 多语言错误消息
|
|
197
|
-
|
|
198
|
-
```javascript
|
|
199
|
-
const { Locale, Validator } = require('schema-dsl');
|
|
200
|
-
|
|
201
|
-
// 添加语言包
|
|
202
|
-
Locale.addLocale('zh-CN', {
|
|
203
|
-
'required': '{{#label}}不能为空',
|
|
204
|
-
'min': '{{#label}}长度不能少于{{#limit}}',
|
|
205
|
-
'email': '请输入有效的{{#label}}'
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// 验证时指定语言
|
|
209
|
-
const validator = new Validator();
|
|
210
|
-
const result = validator.validate(schema, data, { locale: 'zh-CN' });
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### 错误格式化
|
|
214
|
-
|
|
215
|
-
```javascript
|
|
216
|
-
function formatErrors(errors) {
|
|
217
|
-
return errors.map(err => {
|
|
218
|
-
const field = err.path.replace(/^\//, '').replace(/\//g, '.');
|
|
219
|
-
return `[${field}] ${err.message}`;
|
|
220
|
-
}).join('\n');
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (!result.valid) {
|
|
224
|
-
console.log(formatErrors(result.errors));
|
|
225
|
-
// [username] 用户名长度不能少于3个字符
|
|
226
|
-
// [email] 请输入有效的邮箱地址
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
---
|
|
231
|
-
|
|
232
|
-
## 性能优化
|
|
233
|
-
|
|
234
|
-
### 1. 使用预编译
|
|
235
|
-
|
|
236
|
-
```javascript
|
|
237
|
-
// ❌ 每次都编译(慢)
|
|
238
|
-
function validateUser(data) {
|
|
239
|
-
return validate(userSchema, data);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// ✅ 预编译一次,多次使用(快)
|
|
243
|
-
const validator = new Validator();
|
|
244
|
-
const validateUser = validator.compile(userSchema);
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### 2. 缓存 Schema
|
|
248
|
-
|
|
249
|
-
```javascript
|
|
250
|
-
// ❌ 每次都创建 Schema
|
|
251
|
-
function getSchema() {
|
|
252
|
-
return dsl({
|
|
253
|
-
username: 'string:3-32!',
|
|
254
|
-
email: 'email!'
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ✅ 缓存 Schema
|
|
259
|
-
const userSchema = dsl({
|
|
260
|
-
username: 'string:3-32!',
|
|
261
|
-
email: 'email!'
|
|
262
|
-
});
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### 3. 合理使用 allErrors
|
|
266
|
-
|
|
267
|
-
```javascript
|
|
268
|
-
// 只需要第一个错误时
|
|
269
|
-
const validator = new Validator({ allErrors: false });
|
|
270
|
-
|
|
271
|
-
// 需要所有错误时(默认)
|
|
272
|
-
const validator = new Validator({ allErrors: true });
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### 4. 监控性能
|
|
276
|
-
|
|
277
|
-
```javascript
|
|
278
|
-
console.time('schema-dsl.validate');
|
|
279
|
-
const result = validate(schema, data);
|
|
280
|
-
console.timeEnd('schema-dsl.validate');
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## 常见场景
|
|
286
|
-
|
|
287
|
-
### 用户注册表单
|
|
288
|
-
|
|
289
|
-
```javascript
|
|
290
|
-
const registerSchema = dsl({
|
|
291
|
-
username: 'string:3-32!'
|
|
292
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
293
|
-
.label('用户名')
|
|
294
|
-
.messages({
|
|
295
|
-
'pattern': '{{#label}}只能包含字母、数字和下划线'
|
|
296
|
-
}),
|
|
297
|
-
|
|
298
|
-
email: 'email!'
|
|
299
|
-
.label('邮箱地址'),
|
|
300
|
-
|
|
301
|
-
password: 'string:8-64!'
|
|
302
|
-
.password('strong')
|
|
303
|
-
.label('密码'),
|
|
304
|
-
|
|
305
|
-
age: 'number:18-120'
|
|
306
|
-
.label('年龄'),
|
|
307
|
-
|
|
308
|
-
gender: 'male|female|other',
|
|
309
|
-
|
|
310
|
-
terms: 'boolean!'
|
|
311
|
-
.label('服务条款')
|
|
312
|
-
.messages({
|
|
313
|
-
'required': '请同意{{#label}}'
|
|
314
|
-
})
|
|
315
|
-
});
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### API 请求验证
|
|
319
|
-
|
|
320
|
-
```javascript
|
|
321
|
-
const createOrderSchema = dsl({
|
|
322
|
-
userId: 'string!',
|
|
323
|
-
items: 'array!1-100',
|
|
324
|
-
shippingAddress: {
|
|
325
|
-
street: 'string:5-200!',
|
|
326
|
-
city: 'string:2-100!',
|
|
327
|
-
zipCode: 'string:5-10!',
|
|
328
|
-
country: 'string:2!'
|
|
329
|
-
},
|
|
330
|
-
paymentMethod: 'credit_card|paypal|bank_transfer',
|
|
331
|
-
notes: 'string:500'
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Express 中间件
|
|
335
|
-
function validateRequest(schema) {
|
|
336
|
-
return (req, res, next) => {
|
|
337
|
-
const result = validate(schema, req.body);
|
|
338
|
-
if (!result.valid) {
|
|
339
|
-
return res.status(400).json({ errors: result.errors });
|
|
340
|
-
}
|
|
341
|
-
req.validatedData = result.data;
|
|
342
|
-
next();
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
app.post('/orders', validateRequest(createOrderSchema), createOrder);
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### 配置文件验证
|
|
350
|
-
|
|
351
|
-
```javascript
|
|
352
|
-
const configSchema = dsl({
|
|
353
|
-
server: {
|
|
354
|
-
host: 'string!',
|
|
355
|
-
port: 'integer:1-65535!',
|
|
356
|
-
ssl: 'boolean'
|
|
357
|
-
},
|
|
358
|
-
database: {
|
|
359
|
-
url: 'url!',
|
|
360
|
-
poolSize: 'integer:1-100',
|
|
361
|
-
timeout: 'integer:1000-60000'
|
|
362
|
-
},
|
|
363
|
-
logging: {
|
|
364
|
-
level: 'debug|info|warn|error',
|
|
365
|
-
format: 'json|text'
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
function loadConfig(configPath) {
|
|
370
|
-
const config = require(configPath);
|
|
371
|
-
const result = validate(configSchema, config);
|
|
372
|
-
|
|
373
|
-
if (!result.valid) {
|
|
374
|
-
throw new Error(`配置文件错误:\n${formatErrors(result.errors)}`);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return result.data;
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
---
|
|
382
|
-
|
|
383
|
-
## 最佳实践
|
|
384
|
-
|
|
385
|
-
### 1. 使用 label 提升错误消息质量
|
|
386
|
-
|
|
387
|
-
```javascript
|
|
388
|
-
// ❌ 默认错误消息
|
|
389
|
-
email: 'email!'
|
|
390
|
-
// 错误: "email is required"
|
|
391
|
-
|
|
392
|
-
// ✅ 使用 label
|
|
393
|
-
email: 'email!'.label('邮箱地址')
|
|
394
|
-
// 错误: "邮箱地址不能为空"
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### 2. 集中管理 Schema
|
|
398
|
-
|
|
399
|
-
```javascript
|
|
400
|
-
// schemas/index.js
|
|
401
|
-
const { dsl } = require('schema-dsl');
|
|
402
|
-
|
|
403
|
-
exports.userSchema = dsl({
|
|
404
|
-
username: 'string:3-32!',
|
|
405
|
-
email: 'email!'
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
exports.orderSchema = dsl({
|
|
409
|
-
userId: 'string!',
|
|
410
|
-
items: 'array!1-100'
|
|
411
|
-
});
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
### 3. 使用 SchemaUtils 复用字段
|
|
415
|
-
|
|
416
|
-
```javascript
|
|
417
|
-
const { SchemaUtils, dsl } = require('schema-dsl');
|
|
418
|
-
|
|
419
|
-
// 创建可复用字段
|
|
420
|
-
const emailField = SchemaUtils.reusable(() =>
|
|
421
|
-
dsl('email!').label('邮箱地址')
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
// 在多个 Schema 中复用
|
|
425
|
-
const loginSchema = dsl({ email: emailField() });
|
|
426
|
-
const registerSchema = dsl({ email: emailField(), name: 'string!' });
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### 4. 分层验证
|
|
430
|
-
|
|
431
|
-
```javascript
|
|
432
|
-
// 基础验证(快速)
|
|
433
|
-
const quickSchema = dsl({
|
|
434
|
-
username: 'string!',
|
|
435
|
-
email: 'string!'
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// 完整验证(详细)
|
|
439
|
-
const fullSchema = dsl({
|
|
440
|
-
username: 'string:3-32!'.pattern(/^[a-z]+$/),
|
|
441
|
-
email: 'email!'
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
// 先快速验证,再完整验证
|
|
445
|
-
async function validateWithFallback(data) {
|
|
446
|
-
const quick = validate(quickSchema, data);
|
|
447
|
-
if (!quick.valid) return quick;
|
|
448
|
-
|
|
449
|
-
const full = validate(fullSchema, data);
|
|
450
|
-
if (!full.valid) return full;
|
|
451
|
-
|
|
452
|
-
if (await checkEmailUnique(data.email)) {
|
|
453
|
-
return {
|
|
454
|
-
valid: false,
|
|
455
|
-
errors: [{ field: 'email', keyword: 'business', message: '邮箱已被占用' }]
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return full;
|
|
460
|
-
}
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
---
|
|
464
|
-
|
|
465
|
-
## 对应示例文件
|
|
466
|
-
|
|
467
|
-
**示例入口**: [validation-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/validation-guide.ts)
|
|
468
|
-
**说明**: 覆盖推荐的验证流程:定义可复用 schema、格式化错误、预编译复用以及批量验证。
|
|
469
|
-
|
|
470
|
-
### 5. 测试验证逻辑
|
|
471
|
-
|
|
472
|
-
```javascript
|
|
473
|
-
describe('User Schema', () => {
|
|
474
|
-
it('应该验证有效用户', () => {
|
|
475
|
-
const result = validate(userSchema, {
|
|
476
|
-
username: 'john_doe',
|
|
477
|
-
email: 'john@example.com'
|
|
478
|
-
});
|
|
479
|
-
expect(result.valid).to.be.true;
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
it('应该拒绝短用户名', () => {
|
|
483
|
-
const result = validate(userSchema, {
|
|
484
|
-
username: 'ab',
|
|
485
|
-
email: 'john@example.com'
|
|
486
|
-
});
|
|
487
|
-
expect(result.valid).to.be.false;
|
|
488
|
-
expect(result.errors[0].keyword).to.equal('minLength');
|
|
489
|
-
});
|
|
490
|
-
});
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
---
|
|
494
|
-
|
|
495
|
-
## 相关文档
|
|
496
|
-
|
|
497
|
-
- [DSL 语法完整指南](dsl-syntax.md)
|
|
498
|
-
- [validate 方法详解](validate.md)
|
|
499
|
-
- [错误处理指南](error-handling.md)
|
|
500
|
-
- [多语言支持](dynamic-locale.md)
|
|
501
|
-
- [String 扩展](string-extensions.md)
|
|
502
|
-
|
|
1
|
+
# 数据验证最佳实践指南
|
|
2
|
+
|
|
3
|
+
> **用途**: 完整的数据验证使用指南
|
|
4
|
+
> **阅读时间**: 15分钟
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📑 目录
|
|
9
|
+
|
|
10
|
+
- [快速入门](#快速入门)
|
|
11
|
+
- [DSL 语法速查](#dsl-语法速查)
|
|
12
|
+
- [验证模式](#验证模式)
|
|
13
|
+
- [错误处理](#错误处理)
|
|
14
|
+
- [性能优化](#性能优化)
|
|
15
|
+
- [常见场景](#常见场景)
|
|
16
|
+
- [最佳实践](#最佳实践)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 快速入门
|
|
21
|
+
|
|
22
|
+
### 基本验证流程
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const { dsl, validate } = require('schema-dsl');
|
|
26
|
+
|
|
27
|
+
// 1. 定义 Schema
|
|
28
|
+
const schema = dsl({
|
|
29
|
+
username: 'string:3-32!',
|
|
30
|
+
email: 'email!',
|
|
31
|
+
age: 'number:18-120'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 2. 验证数据
|
|
35
|
+
const result = validate(schema, {
|
|
36
|
+
username: 'john_doe',
|
|
37
|
+
email: 'john@example.com',
|
|
38
|
+
age: 25
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 3. 处理结果
|
|
42
|
+
if (result.valid) {
|
|
43
|
+
console.log('验证通过', result.data);
|
|
44
|
+
} else {
|
|
45
|
+
console.log('验证失败', result.errors);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## DSL 语法速查
|
|
52
|
+
|
|
53
|
+
### 基本类型
|
|
54
|
+
|
|
55
|
+
| DSL | 说明 |
|
|
56
|
+
|-----|------|
|
|
57
|
+
| `'string'` | 字符串 |
|
|
58
|
+
| `'number'` | 数字 |
|
|
59
|
+
| `'integer'` | 整数 |
|
|
60
|
+
| `'boolean'` | 布尔值 |
|
|
61
|
+
| `'object'` | 对象 |
|
|
62
|
+
| `'array'` | 数组 |
|
|
63
|
+
|
|
64
|
+
### 格式类型
|
|
65
|
+
|
|
66
|
+
| DSL | 说明 |
|
|
67
|
+
|-----|------|
|
|
68
|
+
| `'email'` | 邮箱格式 |
|
|
69
|
+
| `'url'` | URL 格式 |
|
|
70
|
+
| `'uuid'` | UUID 格式 |
|
|
71
|
+
| `'date'` | 日期格式 |
|
|
72
|
+
| `'datetime'` | 日期时间格式 |
|
|
73
|
+
| `'time'` | 时间格式 |
|
|
74
|
+
| `'ipv4'` | IPv4 地址 |
|
|
75
|
+
| `'ipv6'` | IPv6 地址 |
|
|
76
|
+
|
|
77
|
+
### 约束语法
|
|
78
|
+
|
|
79
|
+
| DSL | 说明 |
|
|
80
|
+
|-----|------|
|
|
81
|
+
| `'string:10'` | 最大长度 10 |
|
|
82
|
+
| `'string:3-32'` | 长度 3-32 |
|
|
83
|
+
| `'string:3-'` | 最小长度 3 |
|
|
84
|
+
| `'number:18-120'` | 数值范围 18-120 |
|
|
85
|
+
| `'array:1-10'` | 数组长度 1-10 |
|
|
86
|
+
|
|
87
|
+
### 特殊标记
|
|
88
|
+
|
|
89
|
+
| DSL | 说明 |
|
|
90
|
+
|-----|------|
|
|
91
|
+
| `'string!'` | 必填字符串 |
|
|
92
|
+
| `'email!'` | 必填邮箱 |
|
|
93
|
+
| `'a\|b\|c'` | 枚举值 |
|
|
94
|
+
| `'array<string>'` | 字符串数组 |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 验证模式
|
|
99
|
+
|
|
100
|
+
### 1. 便捷函数验证(推荐)
|
|
101
|
+
|
|
102
|
+
最简单的验证方式,使用内置单例 Validator:
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
const { dsl, validate } = require('schema-dsl');
|
|
106
|
+
|
|
107
|
+
const result = validate(schema, data);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 2. Validator 实例验证(高级)
|
|
111
|
+
|
|
112
|
+
需要自定义配置(如类型转换、自定义关键字)时使用:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const { dsl, Validator } = require('schema-dsl');
|
|
116
|
+
|
|
117
|
+
// 创建自定义配置的 Validator
|
|
118
|
+
const validator = new Validator({
|
|
119
|
+
allErrors: true, // 返回所有错误
|
|
120
|
+
useDefaults: true, // 使用默认值
|
|
121
|
+
coerceTypes: true // ✨ 启用类型转换
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const result = validator.validate(schema, data);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> **注意**: `new Validator()` 会创建一个新的 Ajv 实例,有一定的初始化开销。建议在应用启动时创建并复用,避免在每次请求中创建。
|
|
128
|
+
|
|
129
|
+
### 3. 预编译验证(高性能)
|
|
130
|
+
|
|
131
|
+
频繁验证同一 Schema 时使用:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
const validator = new Validator();
|
|
135
|
+
|
|
136
|
+
// 预编译 Schema
|
|
137
|
+
const validateUser = validator.compile(userSchema);
|
|
138
|
+
|
|
139
|
+
// 多次验证(无需重复编译)
|
|
140
|
+
const result1 = validateUser(data1);
|
|
141
|
+
const result2 = validateUser(data2);
|
|
142
|
+
const result3 = validateUser(data3);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 4. 批量验证
|
|
146
|
+
|
|
147
|
+
验证多条数据时使用:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const { Validator } = require('schema-dsl');
|
|
151
|
+
const validator = new Validator();
|
|
152
|
+
|
|
153
|
+
const dataList = [
|
|
154
|
+
{ username: 'user1', email: 'user1@example.com' },
|
|
155
|
+
{ username: 'user2', email: 'invalid' },
|
|
156
|
+
{ username: 'u', email: 'user3@example.com' }
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const results = validator.validateBatch(schema, dataList);
|
|
160
|
+
// [
|
|
161
|
+
// { valid: true, data: {...}, errors: [] },
|
|
162
|
+
// { valid: false, data: {...}, errors: [...] },
|
|
163
|
+
// { valid: false, data: {...}, errors: [...] }
|
|
164
|
+
// ]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 错误处理
|
|
170
|
+
|
|
171
|
+
### 错误对象结构
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
{
|
|
175
|
+
message: '用户名长度不能少于3个字符',
|
|
176
|
+
path: '/username',
|
|
177
|
+
keyword: 'minLength',
|
|
178
|
+
params: { limit: 3 }
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 自定义错误消息
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
const schema = dsl({
|
|
186
|
+
username: 'string:3-32!'
|
|
187
|
+
.label('用户名')
|
|
188
|
+
.messages({
|
|
189
|
+
'min': '{{#label}}太短了,至少{{#limit}}个字符',
|
|
190
|
+
'max': '{{#label}}太长了,最多{{#limit}}个字符',
|
|
191
|
+
'required': '请输入{{#label}}'
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 多语言错误消息
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
const { Locale, Validator } = require('schema-dsl');
|
|
200
|
+
|
|
201
|
+
// 添加语言包
|
|
202
|
+
Locale.addLocale('zh-CN', {
|
|
203
|
+
'required': '{{#label}}不能为空',
|
|
204
|
+
'min': '{{#label}}长度不能少于{{#limit}}',
|
|
205
|
+
'email': '请输入有效的{{#label}}'
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// 验证时指定语言
|
|
209
|
+
const validator = new Validator();
|
|
210
|
+
const result = validator.validate(schema, data, { locale: 'zh-CN' });
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 错误格式化
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
function formatErrors(errors) {
|
|
217
|
+
return errors.map(err => {
|
|
218
|
+
const field = err.path.replace(/^\//, '').replace(/\//g, '.');
|
|
219
|
+
return `[${field}] ${err.message}`;
|
|
220
|
+
}).join('\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!result.valid) {
|
|
224
|
+
console.log(formatErrors(result.errors));
|
|
225
|
+
// [username] 用户名长度不能少于3个字符
|
|
226
|
+
// [email] 请输入有效的邮箱地址
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 性能优化
|
|
233
|
+
|
|
234
|
+
### 1. 使用预编译
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
// ❌ 每次都编译(慢)
|
|
238
|
+
function validateUser(data) {
|
|
239
|
+
return validate(userSchema, data);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ✅ 预编译一次,多次使用(快)
|
|
243
|
+
const validator = new Validator();
|
|
244
|
+
const validateUser = validator.compile(userSchema);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 2. 缓存 Schema
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
// ❌ 每次都创建 Schema
|
|
251
|
+
function getSchema() {
|
|
252
|
+
return dsl({
|
|
253
|
+
username: 'string:3-32!',
|
|
254
|
+
email: 'email!'
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ✅ 缓存 Schema
|
|
259
|
+
const userSchema = dsl({
|
|
260
|
+
username: 'string:3-32!',
|
|
261
|
+
email: 'email!'
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 3. 合理使用 allErrors
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
// 只需要第一个错误时
|
|
269
|
+
const validator = new Validator({ allErrors: false });
|
|
270
|
+
|
|
271
|
+
// 需要所有错误时(默认)
|
|
272
|
+
const validator = new Validator({ allErrors: true });
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 4. 监控性能
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
console.time('schema-dsl.validate');
|
|
279
|
+
const result = validate(schema, data);
|
|
280
|
+
console.timeEnd('schema-dsl.validate');
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 常见场景
|
|
286
|
+
|
|
287
|
+
### 用户注册表单
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
const registerSchema = dsl({
|
|
291
|
+
username: 'string:3-32!'
|
|
292
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
293
|
+
.label('用户名')
|
|
294
|
+
.messages({
|
|
295
|
+
'pattern': '{{#label}}只能包含字母、数字和下划线'
|
|
296
|
+
}),
|
|
297
|
+
|
|
298
|
+
email: 'email!'
|
|
299
|
+
.label('邮箱地址'),
|
|
300
|
+
|
|
301
|
+
password: 'string:8-64!'
|
|
302
|
+
.password('strong')
|
|
303
|
+
.label('密码'),
|
|
304
|
+
|
|
305
|
+
age: 'number:18-120'
|
|
306
|
+
.label('年龄'),
|
|
307
|
+
|
|
308
|
+
gender: 'male|female|other',
|
|
309
|
+
|
|
310
|
+
terms: 'boolean!'
|
|
311
|
+
.label('服务条款')
|
|
312
|
+
.messages({
|
|
313
|
+
'required': '请同意{{#label}}'
|
|
314
|
+
})
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### API 请求验证
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
const createOrderSchema = dsl({
|
|
322
|
+
userId: 'string!',
|
|
323
|
+
items: 'array!1-100',
|
|
324
|
+
shippingAddress: {
|
|
325
|
+
street: 'string:5-200!',
|
|
326
|
+
city: 'string:2-100!',
|
|
327
|
+
zipCode: 'string:5-10!',
|
|
328
|
+
country: 'string:2!'
|
|
329
|
+
},
|
|
330
|
+
paymentMethod: 'credit_card|paypal|bank_transfer',
|
|
331
|
+
notes: 'string:500'
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Express 中间件
|
|
335
|
+
function validateRequest(schema) {
|
|
336
|
+
return (req, res, next) => {
|
|
337
|
+
const result = validate(schema, req.body);
|
|
338
|
+
if (!result.valid) {
|
|
339
|
+
return res.status(400).json({ errors: result.errors });
|
|
340
|
+
}
|
|
341
|
+
req.validatedData = result.data;
|
|
342
|
+
next();
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
app.post('/orders', validateRequest(createOrderSchema), createOrder);
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 配置文件验证
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const configSchema = dsl({
|
|
353
|
+
server: {
|
|
354
|
+
host: 'string!',
|
|
355
|
+
port: 'integer:1-65535!',
|
|
356
|
+
ssl: 'boolean'
|
|
357
|
+
},
|
|
358
|
+
database: {
|
|
359
|
+
url: 'url!',
|
|
360
|
+
poolSize: 'integer:1-100',
|
|
361
|
+
timeout: 'integer:1000-60000'
|
|
362
|
+
},
|
|
363
|
+
logging: {
|
|
364
|
+
level: 'debug|info|warn|error',
|
|
365
|
+
format: 'json|text'
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
function loadConfig(configPath) {
|
|
370
|
+
const config = require(configPath);
|
|
371
|
+
const result = validate(configSchema, config);
|
|
372
|
+
|
|
373
|
+
if (!result.valid) {
|
|
374
|
+
throw new Error(`配置文件错误:\n${formatErrors(result.errors)}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return result.data;
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 最佳实践
|
|
384
|
+
|
|
385
|
+
### 1. 使用 label 提升错误消息质量
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
// ❌ 默认错误消息
|
|
389
|
+
email: 'email!'
|
|
390
|
+
// 错误: "email is required"
|
|
391
|
+
|
|
392
|
+
// ✅ 使用 label
|
|
393
|
+
email: 'email!'.label('邮箱地址')
|
|
394
|
+
// 错误: "邮箱地址不能为空"
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 2. 集中管理 Schema
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
// schemas/index.js
|
|
401
|
+
const { dsl } = require('schema-dsl');
|
|
402
|
+
|
|
403
|
+
exports.userSchema = dsl({
|
|
404
|
+
username: 'string:3-32!',
|
|
405
|
+
email: 'email!'
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
exports.orderSchema = dsl({
|
|
409
|
+
userId: 'string!',
|
|
410
|
+
items: 'array!1-100'
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 3. 使用 SchemaUtils 复用字段
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
const { SchemaUtils, dsl } = require('schema-dsl');
|
|
418
|
+
|
|
419
|
+
// 创建可复用字段
|
|
420
|
+
const emailField = SchemaUtils.reusable(() =>
|
|
421
|
+
dsl('email!').label('邮箱地址')
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// 在多个 Schema 中复用
|
|
425
|
+
const loginSchema = dsl({ email: emailField() });
|
|
426
|
+
const registerSchema = dsl({ email: emailField(), name: 'string!' });
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### 4. 分层验证
|
|
430
|
+
|
|
431
|
+
```javascript
|
|
432
|
+
// 基础验证(快速)
|
|
433
|
+
const quickSchema = dsl({
|
|
434
|
+
username: 'string!',
|
|
435
|
+
email: 'string!'
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// 完整验证(详细)
|
|
439
|
+
const fullSchema = dsl({
|
|
440
|
+
username: 'string:3-32!'.pattern(/^[a-z]+$/),
|
|
441
|
+
email: 'email!'
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// 先快速验证,再完整验证
|
|
445
|
+
async function validateWithFallback(data) {
|
|
446
|
+
const quick = validate(quickSchema, data);
|
|
447
|
+
if (!quick.valid) return quick;
|
|
448
|
+
|
|
449
|
+
const full = validate(fullSchema, data);
|
|
450
|
+
if (!full.valid) return full;
|
|
451
|
+
|
|
452
|
+
if (await checkEmailUnique(data.email)) {
|
|
453
|
+
return {
|
|
454
|
+
valid: false,
|
|
455
|
+
errors: [{ field: 'email', keyword: 'business', message: '邮箱已被占用' }]
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return full;
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## 对应示例文件
|
|
466
|
+
|
|
467
|
+
**示例入口**: [validation-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/validation-guide.ts)
|
|
468
|
+
**说明**: 覆盖推荐的验证流程:定义可复用 schema、格式化错误、预编译复用以及批量验证。
|
|
469
|
+
|
|
470
|
+
### 5. 测试验证逻辑
|
|
471
|
+
|
|
472
|
+
```javascript
|
|
473
|
+
describe('User Schema', () => {
|
|
474
|
+
it('应该验证有效用户', () => {
|
|
475
|
+
const result = validate(userSchema, {
|
|
476
|
+
username: 'john_doe',
|
|
477
|
+
email: 'john@example.com'
|
|
478
|
+
});
|
|
479
|
+
expect(result.valid).to.be.true;
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('应该拒绝短用户名', () => {
|
|
483
|
+
const result = validate(userSchema, {
|
|
484
|
+
username: 'ab',
|
|
485
|
+
email: 'john@example.com'
|
|
486
|
+
});
|
|
487
|
+
expect(result.valid).to.be.false;
|
|
488
|
+
expect(result.errors[0].keyword).to.equal('minLength');
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 相关文档
|
|
496
|
+
|
|
497
|
+
- [DSL 语法完整指南](dsl-syntax.md)
|
|
498
|
+
- [validate 方法详解](validate.md)
|
|
499
|
+
- [错误处理指南](error-handling.md)
|
|
500
|
+
- [多语言支持](dynamic-locale.md)
|
|
501
|
+
- [String 扩展](string-extensions.md)
|
|
502
|
+
|