schema-dsl 1.0.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/.eslintignore +10 -0
- package/.eslintrc.json +27 -0
- package/.github/CODE_OF_CONDUCT.md +45 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
- package/.github/ISSUE_TEMPLATE/question.md +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +70 -0
- package/.github/SECURITY.md +184 -0
- package/.github/workflows/ci.yml +33 -0
- package/CHANGELOG.md +633 -0
- package/CONTRIBUTING.md +368 -0
- package/LICENSE +21 -0
- package/README.md +1184 -0
- package/STATUS.md +101 -0
- package/docs/FEATURE-INDEX.md +519 -0
- package/docs/INDEX.md +253 -0
- package/docs/api-reference.md +1096 -0
- package/docs/best-practices.md +672 -0
- package/docs/cache-manager.md +336 -0
- package/docs/design-philosophy.md +601 -0
- package/docs/dsl-syntax.md +653 -0
- package/docs/dynamic-locale.md +552 -0
- package/docs/error-handling.md +703 -0
- package/docs/export-guide.md +462 -0
- package/docs/export-limitations.md +551 -0
- package/docs/faq.md +577 -0
- package/docs/frontend-i18n-guide.md +290 -0
- package/docs/i18n-user-guide.md +476 -0
- package/docs/label-vs-description.md +262 -0
- package/docs/markdown-exporter.md +397 -0
- package/docs/mongodb-exporter.md +295 -0
- package/docs/multi-type-support.md +319 -0
- package/docs/mysql-exporter.md +273 -0
- package/docs/plugin-system.md +542 -0
- package/docs/postgresql-exporter.md +304 -0
- package/docs/quick-start.md +761 -0
- package/docs/schema-helper.md +340 -0
- package/docs/schema-utils-chaining.md +143 -0
- package/docs/schema-utils.md +490 -0
- package/docs/string-extensions.md +480 -0
- package/docs/troubleshooting.md +471 -0
- package/docs/type-converter.md +319 -0
- package/docs/type-reference.md +219 -0
- package/docs/validate-async.md +480 -0
- package/docs/validate.md +486 -0
- package/docs/validation-guide.md +484 -0
- package/examples/array-dsl-example.js +227 -0
- package/examples/custom-extension.js +85 -0
- package/examples/dsl-match-example.js +74 -0
- package/examples/dsl-style.js +118 -0
- package/examples/dynamic-locale-configuration.js +348 -0
- package/examples/dynamic-locale-example.js +287 -0
- package/examples/export-demo.js +130 -0
- package/examples/express-integration.js +376 -0
- package/examples/i18n-full-demo.js +310 -0
- package/examples/i18n-memory-safety.examples.js +268 -0
- package/examples/markdown-export.js +71 -0
- package/examples/middleware-usage.js +93 -0
- package/examples/new-features-comparison.js +315 -0
- package/examples/password-reset/README.md +153 -0
- package/examples/password-reset/schema.js +26 -0
- package/examples/password-reset/test.js +101 -0
- package/examples/plugin-system.examples.js +205 -0
- package/examples/schema-utils-chaining.examples.js +250 -0
- package/examples/simple-example.js +122 -0
- package/examples/string-extensions.js +297 -0
- package/examples/user-registration/README.md +156 -0
- package/examples/user-registration/routes.js +92 -0
- package/examples/user-registration/schema.js +150 -0
- package/examples/user-registration/server.js +74 -0
- package/index.d.ts +1999 -0
- package/index.js +282 -0
- package/index.mjs +30 -0
- package/lib/adapters/DslAdapter.js +699 -0
- package/lib/adapters/index.js +20 -0
- package/lib/config/constants.js +286 -0
- package/lib/config/patterns/creditCard.js +9 -0
- package/lib/config/patterns/idCard.js +9 -0
- package/lib/config/patterns/index.js +8 -0
- package/lib/config/patterns/licensePlate.js +4 -0
- package/lib/config/patterns/passport.js +4 -0
- package/lib/config/patterns/phone.js +9 -0
- package/lib/config/patterns/postalCode.js +5 -0
- package/lib/core/CacheManager.js +376 -0
- package/lib/core/DslBuilder.js +740 -0
- package/lib/core/ErrorCodes.js +233 -0
- package/lib/core/ErrorFormatter.js +342 -0
- package/lib/core/JSONSchemaCore.js +347 -0
- package/lib/core/Locale.js +119 -0
- package/lib/core/MessageTemplate.js +89 -0
- package/lib/core/PluginManager.js +448 -0
- package/lib/core/StringExtensions.js +209 -0
- package/lib/core/Validator.js +376 -0
- package/lib/errors/ValidationError.js +191 -0
- package/lib/exporters/MarkdownExporter.js +420 -0
- package/lib/exporters/MongoDBExporter.js +162 -0
- package/lib/exporters/MySQLExporter.js +212 -0
- package/lib/exporters/PostgreSQLExporter.js +289 -0
- package/lib/exporters/index.js +24 -0
- package/lib/locales/en-US.js +65 -0
- package/lib/locales/es-ES.js +66 -0
- package/lib/locales/fr-FR.js +66 -0
- package/lib/locales/index.js +8 -0
- package/lib/locales/ja-JP.js +66 -0
- package/lib/locales/zh-CN.js +93 -0
- package/lib/utils/LRUCache.js +174 -0
- package/lib/utils/SchemaHelper.js +240 -0
- package/lib/utils/SchemaUtils.js +445 -0
- package/lib/utils/TypeConverter.js +245 -0
- package/lib/utils/index.js +13 -0
- package/lib/validators/CustomKeywords.js +203 -0
- package/lib/validators/index.js +11 -0
- package/package.json +70 -0
- package/plugins/custom-format.js +101 -0
- package/plugins/custom-validator.js +200 -0
|
@@ -0,0 +1,484 @@
|
|
|
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, errors: [] },
|
|
162
|
+
// { valid: false, errors: [...] },
|
|
163
|
+
// { valid: false, 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
|
+
const result = validate(schema, data);
|
|
279
|
+
console.log(`验证耗时: ${result.performance?.duration}ms`);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 常见场景
|
|
285
|
+
|
|
286
|
+
### 用户注册表单
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
const registerSchema = dsl({
|
|
290
|
+
username: 'string:3-32!'
|
|
291
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
292
|
+
.label('用户名')
|
|
293
|
+
.messages({
|
|
294
|
+
'pattern': '{{#label}}只能包含字母、数字和下划线'
|
|
295
|
+
}),
|
|
296
|
+
|
|
297
|
+
email: 'email!'
|
|
298
|
+
.label('邮箱地址'),
|
|
299
|
+
|
|
300
|
+
password: 'string:8-64!'
|
|
301
|
+
.password('strong')
|
|
302
|
+
.label('密码'),
|
|
303
|
+
|
|
304
|
+
age: 'number:18-120'
|
|
305
|
+
.label('年龄'),
|
|
306
|
+
|
|
307
|
+
gender: 'male|female|other',
|
|
308
|
+
|
|
309
|
+
terms: 'boolean!'
|
|
310
|
+
.label('服务条款')
|
|
311
|
+
.messages({
|
|
312
|
+
'required': '请同意{{#label}}'
|
|
313
|
+
})
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### API 请求验证
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
const createOrderSchema = dsl({
|
|
321
|
+
userId: 'string!',
|
|
322
|
+
items: 'array!1-100',
|
|
323
|
+
shippingAddress: {
|
|
324
|
+
street: 'string:5-200!',
|
|
325
|
+
city: 'string:2-100!',
|
|
326
|
+
zipCode: 'string:5-10!',
|
|
327
|
+
country: 'string:2!'
|
|
328
|
+
},
|
|
329
|
+
paymentMethod: 'credit_card|paypal|bank_transfer',
|
|
330
|
+
notes: 'string:500'
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Express 中间件
|
|
334
|
+
function validateRequest(schema) {
|
|
335
|
+
return (req, res, next) => {
|
|
336
|
+
const result = validate(schema, req.body);
|
|
337
|
+
if (!result.valid) {
|
|
338
|
+
return res.status(400).json({ errors: result.errors });
|
|
339
|
+
}
|
|
340
|
+
req.validatedData = result.data;
|
|
341
|
+
next();
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
app.post('/orders', validateRequest(createOrderSchema), createOrder);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### 配置文件验证
|
|
349
|
+
|
|
350
|
+
```javascript
|
|
351
|
+
const configSchema = dsl({
|
|
352
|
+
server: {
|
|
353
|
+
host: 'string!',
|
|
354
|
+
port: 'integer:1-65535!',
|
|
355
|
+
ssl: 'boolean'
|
|
356
|
+
},
|
|
357
|
+
database: {
|
|
358
|
+
url: 'url!',
|
|
359
|
+
poolSize: 'integer:1-100',
|
|
360
|
+
timeout: 'integer:1000-60000'
|
|
361
|
+
},
|
|
362
|
+
logging: {
|
|
363
|
+
level: 'debug|info|warn|error',
|
|
364
|
+
format: 'json|text'
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
function loadConfig(configPath) {
|
|
369
|
+
const config = require(configPath);
|
|
370
|
+
const result = validate(configSchema, config);
|
|
371
|
+
|
|
372
|
+
if (!result.valid) {
|
|
373
|
+
throw new Error(`配置文件错误:\n${formatErrors(result.errors)}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return result.data;
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## 最佳实践
|
|
383
|
+
|
|
384
|
+
### 1. 使用 label 提升错误消息质量
|
|
385
|
+
|
|
386
|
+
```javascript
|
|
387
|
+
// ❌ 默认错误消息
|
|
388
|
+
email: 'email!'
|
|
389
|
+
// 错误: "email is required"
|
|
390
|
+
|
|
391
|
+
// ✅ 使用 label
|
|
392
|
+
email: 'email!'.label('邮箱地址')
|
|
393
|
+
// 错误: "邮箱地址不能为空"
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### 2. 集中管理 Schema
|
|
397
|
+
|
|
398
|
+
```javascript
|
|
399
|
+
// schemas/index.js
|
|
400
|
+
const { dsl } = require('schema-dsl');
|
|
401
|
+
|
|
402
|
+
exports.userSchema = dsl({
|
|
403
|
+
username: 'string:3-32!',
|
|
404
|
+
email: 'email!'
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
exports.orderSchema = dsl({
|
|
408
|
+
userId: 'string!',
|
|
409
|
+
items: 'array!1-100'
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### 3. 使用 SchemaUtils 复用字段
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
const { SchemaUtils, dsl } = require('schema-dsl');
|
|
417
|
+
|
|
418
|
+
// 创建可复用字段
|
|
419
|
+
const emailField = SchemaUtils.reusable(() =>
|
|
420
|
+
dsl('email!').label('邮箱地址')
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// 在多个 Schema 中复用
|
|
424
|
+
const loginSchema = dsl({ email: emailField() });
|
|
425
|
+
const registerSchema = dsl({ email: emailField(), name: 'string!' });
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### 4. 分层验证
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
// 基础验证(快速)
|
|
432
|
+
const quickSchema = dsl({
|
|
433
|
+
username: 'string!',
|
|
434
|
+
email: 'string!'
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// 完整验证(详细)
|
|
438
|
+
const fullSchema = dsl({
|
|
439
|
+
username: 'string:3-32!'.pattern(/^[a-z]+$/),
|
|
440
|
+
email: 'email!'.custom(async (v) => checkEmailUnique(v))
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// 先快速验证,再完整验证
|
|
444
|
+
function validateWithFallback(data) {
|
|
445
|
+
const quick = validate(quickSchema, data);
|
|
446
|
+
if (!quick.valid) return quick;
|
|
447
|
+
|
|
448
|
+
return validate(fullSchema, data);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 5. 测试验证逻辑
|
|
453
|
+
|
|
454
|
+
```javascript
|
|
455
|
+
describe('User Schema', () => {
|
|
456
|
+
it('应该验证有效用户', () => {
|
|
457
|
+
const result = validate(userSchema, {
|
|
458
|
+
username: 'john_doe',
|
|
459
|
+
email: 'john@example.com'
|
|
460
|
+
});
|
|
461
|
+
expect(result.valid).to.be.true;
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('应该拒绝短用户名', () => {
|
|
465
|
+
const result = validate(userSchema, {
|
|
466
|
+
username: 'ab',
|
|
467
|
+
email: 'john@example.com'
|
|
468
|
+
});
|
|
469
|
+
expect(result.valid).to.be.false;
|
|
470
|
+
expect(result.errors[0].keyword).to.equal('minLength');
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## 相关文档
|
|
478
|
+
|
|
479
|
+
- [DSL 语法完整指南](dsl-syntax.md)
|
|
480
|
+
- [validate 方法详解](validate.md)
|
|
481
|
+
- [错误处理指南](error-handling.md)
|
|
482
|
+
- [多语言支持](dynamic-locale.md)
|
|
483
|
+
- [String 扩展](string-extensions.md)
|
|
484
|
+
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SchemaIO v2.0.1 新功能完整示例
|
|
3
|
+
*
|
|
4
|
+
* 展示v2.0.1版本的核心功能
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { dsl, validate, SchemaUtils, DslBuilder, Validator } = require('../index');
|
|
8
|
+
|
|
9
|
+
console.log('========== SchemaIO v2.0.1 新功能示例 ==========\n');
|
|
10
|
+
|
|
11
|
+
// ========== 1. DSL数组语法 ==========
|
|
12
|
+
console.log('✨ 1. DSL数组语法');
|
|
13
|
+
|
|
14
|
+
const tagsSchema = dsl({
|
|
15
|
+
// 使用DSL数组语法:array:min-max<itemType>
|
|
16
|
+
tags: 'array:1-10<string:1-20>!'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
console.log('验证标签:', validate(tagsSchema, { tags: ['javascript', 'nodejs'] }).valid);
|
|
20
|
+
console.log('空标签验证:', validate(tagsSchema, { tags: [] }).valid); // false
|
|
21
|
+
console.log('');
|
|
22
|
+
|
|
23
|
+
// ========== 2. when条件验证 ==========
|
|
24
|
+
console.log('✨ 2. when条件验证');
|
|
25
|
+
|
|
26
|
+
const contactSchema = dsl({
|
|
27
|
+
contactType: 'email|phone',
|
|
28
|
+
// 使用 dsl.match 条件验证
|
|
29
|
+
contact: dsl.match('contactType', {
|
|
30
|
+
email: 'email!',
|
|
31
|
+
phone: 'string:11!'
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.log('match条件Schema已创建');
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
// ========== 3. 自定义验证器 ==========
|
|
39
|
+
console.log('✨ 3. 自定义验证器');
|
|
40
|
+
|
|
41
|
+
const customSchema = dsl({
|
|
42
|
+
username: dsl('string:3-32!')
|
|
43
|
+
.custom((value) => {
|
|
44
|
+
// 返回错误消息字符串表示失败
|
|
45
|
+
const forbidden = ['admin', 'root'];
|
|
46
|
+
if (forbidden.includes(value)) {
|
|
47
|
+
return '该用户名已被保留';
|
|
48
|
+
}
|
|
49
|
+
// 不返回任何值表示通过
|
|
50
|
+
})
|
|
51
|
+
.label('用户名')
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log('自定义验证器Schema已创建');
|
|
55
|
+
console.log('');
|
|
56
|
+
|
|
57
|
+
// ========== 4. 默认验证器快捷方法 ==========
|
|
58
|
+
console.log('✨ 4. 默认验证器快捷方法');
|
|
59
|
+
|
|
60
|
+
const userSchema = dsl({
|
|
61
|
+
// ✨ username验证器:自动设置正则和长度
|
|
62
|
+
username: dsl('string!').username().label('用户名'),
|
|
63
|
+
|
|
64
|
+
// ✨ password验证器:根据强度设置验证
|
|
65
|
+
password: dsl('string!').password('medium').label('密码'),
|
|
66
|
+
|
|
67
|
+
// ✨ phone验证器:根据国家设置正则
|
|
68
|
+
phone: dsl('string!').phone('cn').label('手机号')
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
console.log('验证用户:', validate(userSchema, {
|
|
72
|
+
username: 'john_doe',
|
|
73
|
+
password: 'Test1234',
|
|
74
|
+
phone: '13800138000'
|
|
75
|
+
}).valid);
|
|
76
|
+
console.log('');
|
|
77
|
+
|
|
78
|
+
// ========== 5. Schema复用 ==========
|
|
79
|
+
console.log('✨ 5. Schema复用');
|
|
80
|
+
|
|
81
|
+
// ✨ 新特性:创建可复用片段
|
|
82
|
+
const emailField = SchemaUtils.reusable(() =>
|
|
83
|
+
dsl('email!').label('邮箱地址')
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const schema1 = dsl({ email: emailField() });
|
|
87
|
+
const schema2 = dsl({ contactEmail: emailField() });
|
|
88
|
+
|
|
89
|
+
console.log('Schema复用成功:', schema1.properties.email.format === 'email');
|
|
90
|
+
console.log('');
|
|
91
|
+
|
|
92
|
+
// ========== 6. Schema合并 ==========
|
|
93
|
+
console.log('✨ 6. Schema合并');
|
|
94
|
+
|
|
95
|
+
const baseUser = dsl({
|
|
96
|
+
name: 'string!',
|
|
97
|
+
email: 'email!'
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const withAge = dsl({
|
|
101
|
+
age: 'number:18-120'
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ✨ 新特性:merge方法
|
|
105
|
+
const extendedSchema = SchemaUtils.extend(baseUser, withAge);
|
|
106
|
+
|
|
107
|
+
console.log('合并后字段数:', Object.keys(mergedSchema.properties).length);
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
// ========== 7. Schema pick/omit ==========
|
|
111
|
+
console.log('✨ 7. Schema pick/omit');
|
|
112
|
+
|
|
113
|
+
const fullUser = dsl({
|
|
114
|
+
name: 'string!',
|
|
115
|
+
email: 'email!',
|
|
116
|
+
password: 'string!',
|
|
117
|
+
age: 'number'
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ✨ 新特性:pick方法
|
|
121
|
+
const publicUser = SchemaUtils.pick(fullUser, ['name', 'email']);
|
|
122
|
+
|
|
123
|
+
console.log('公开用户字段:', Object.keys(publicUser.properties));
|
|
124
|
+
console.log('');
|
|
125
|
+
|
|
126
|
+
// ========== 8. 性能监控 ==========
|
|
127
|
+
console.log('✨ 8. 性能监控');
|
|
128
|
+
|
|
129
|
+
// Validator 已在文件开头导入
|
|
130
|
+
const validator = new Validator(); // 高级用法:创建实例以增强功能
|
|
131
|
+
|
|
132
|
+
// ✨ 新特性:性能监控
|
|
133
|
+
const enhancedValidator = SchemaUtils.withPerformance(validator);
|
|
134
|
+
|
|
135
|
+
const perfResult = enhancedValidator.validate(
|
|
136
|
+
dsl({ email: 'email!' }),
|
|
137
|
+
{ email: 'test@example.com' }
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
console.log('验证耗时:', perfResult.performance.duration, 'ms');
|
|
141
|
+
console.log('');
|
|
142
|
+
|
|
143
|
+
// ========== 9. 批量验证 ==========
|
|
144
|
+
console.log('✨ 9. 批量验证');
|
|
145
|
+
|
|
146
|
+
const users = [
|
|
147
|
+
{ email: 'user1@example.com' },
|
|
148
|
+
{ email: 'invalid' },
|
|
149
|
+
{ email: 'user3@example.com' }
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
// ✨ 新特性:批量验证优化
|
|
153
|
+
const batchResult = SchemaUtils.validateBatch(
|
|
154
|
+
dsl({ email: 'email!' }),
|
|
155
|
+
users,
|
|
156
|
+
validator
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
console.log('批量验证总数:', batchResult.summary.total);
|
|
160
|
+
console.log('有效数量:', batchResult.summary.valid);
|
|
161
|
+
console.log('无效数量:', batchResult.summary.invalid);
|
|
162
|
+
console.log('平均耗时:', batchResult.summary.averageTime.toFixed(2), 'ms');
|
|
163
|
+
console.log('');
|
|
164
|
+
|
|
165
|
+
// ========== 10. Schema导出文档 ==========
|
|
166
|
+
console.log('✨ 10. Schema导出文档');
|
|
167
|
+
|
|
168
|
+
const docSchema = dsl({
|
|
169
|
+
name: dsl('string:1-50!').label('姓名').description('用户真实姓名'),
|
|
170
|
+
email: dsl('email!').label('邮箱').description('用于登录'),
|
|
171
|
+
age: dsl('number:18-120').label('年龄')
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ✨ 新特性:导出Markdown
|
|
175
|
+
const markdown = SchemaUtils.toMarkdown(docSchema, { title: '用户Schema' });
|
|
176
|
+
|
|
177
|
+
console.log('Markdown文档生成:', markdown.length, '字符');
|
|
178
|
+
console.log('');
|
|
179
|
+
console.log('--- Markdown预览 ---');
|
|
180
|
+
console.log(markdown.substring(0, 300) + '...');
|
|
181
|
+
console.log('');
|
|
182
|
+
|
|
183
|
+
// ========== 11. 嵌套深度验证 ==========
|
|
184
|
+
console.log('✨ 11. 嵌套深度验证');
|
|
185
|
+
|
|
186
|
+
const deepSchema = dsl({
|
|
187
|
+
level1: {
|
|
188
|
+
level2: {
|
|
189
|
+
level3: {
|
|
190
|
+
level4: {
|
|
191
|
+
value: 'string'
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ✨ 新特性:嵌套深度检查
|
|
199
|
+
const depthCheck = DslBuilder.validateNestingDepth(deepSchema, 3);
|
|
200
|
+
|
|
201
|
+
console.log('嵌套深度检查:', depthCheck.message);
|
|
202
|
+
console.log('实际深度:', depthCheck.depth);
|
|
203
|
+
console.log('是否超限:', !depthCheck.valid ? '⚠️ 是' : '✅ 否');
|
|
204
|
+
console.log('');
|
|
205
|
+
|
|
206
|
+
// ========== 总结 ==========
|
|
207
|
+
console.log('========== 功能总结 ==========');
|
|
208
|
+
console.log(`
|
|
209
|
+
✨ SchemaIO v2.0.1 核心功能:
|
|
210
|
+
|
|
211
|
+
1. ✅ DSL数组语法 - array:min-max<itemType>
|
|
212
|
+
2. ✅ when条件验证 - 条件字段验证
|
|
213
|
+
3. ✅ 自定义验证器 - .custom()链式调用
|
|
214
|
+
4. ✅ 默认验证器 - username/password/phone
|
|
215
|
+
5. ✅ Schema复用 - reusable()方法
|
|
216
|
+
6. ✅ Schema合并 - merge()方法
|
|
217
|
+
7. ✅ Schema筛选 - pick/omit方法
|
|
218
|
+
8. ✅ 性能监控 - withPerformance()
|
|
219
|
+
9. ✅ 批量验证 - validateBatch()优化
|
|
220
|
+
10. ✅ 文档导出 - toMarkdown/toHTML
|
|
221
|
+
11. ✅ 嵌套检查 - validateNestingDepth()
|
|
222
|
+
|
|
223
|
+
🎉 SchemaIO v2.0.1 - 功能强大,使用简单!
|
|
224
|
+
`);
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|