schema-dsl 1.0.0 → 1.0.4

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 (45) hide show
  1. package/CHANGELOG.md +263 -529
  2. package/README.md +814 -896
  3. package/STATUS.md +135 -2
  4. package/docs/INDEX.md +1 -2
  5. package/docs/api-reference.md +1 -292
  6. package/docs/custom-extensions-guide.md +411 -0
  7. package/docs/enum.md +475 -0
  8. package/docs/i18n.md +394 -0
  9. package/docs/performance-benchmark-report.md +179 -0
  10. package/docs/plugin-system.md +8 -8
  11. package/docs/typescript-guide.md +554 -0
  12. package/docs/validate-async.md +1 -1
  13. package/docs/validation-rules-v1.0.2.md +1601 -0
  14. package/examples/README.md +81 -0
  15. package/examples/enum.examples.js +324 -0
  16. package/examples/express-integration.js +54 -54
  17. package/examples/i18n-full-demo.js +15 -24
  18. package/examples/schema-utils-chaining.examples.js +2 -2
  19. package/examples/slug.examples.js +179 -0
  20. package/index.d.ts +246 -17
  21. package/index.js +30 -34
  22. package/lib/config/constants.js +1 -1
  23. package/lib/config/patterns/common.js +47 -0
  24. package/lib/config/patterns/index.js +2 -1
  25. package/lib/core/DslBuilder.js +500 -8
  26. package/lib/core/StringExtensions.js +31 -0
  27. package/lib/core/Validator.js +42 -15
  28. package/lib/errors/ValidationError.js +3 -3
  29. package/lib/locales/en-US.js +79 -19
  30. package/lib/locales/es-ES.js +60 -19
  31. package/lib/locales/fr-FR.js +84 -43
  32. package/lib/locales/ja-JP.js +83 -42
  33. package/lib/locales/zh-CN.js +32 -0
  34. package/lib/validators/CustomKeywords.js +405 -0
  35. package/package.json +1 -1
  36. package/.github/CODE_OF_CONDUCT.md +0 -45
  37. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -57
  38. package/.github/ISSUE_TEMPLATE/config.yml +0 -11
  39. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -45
  40. package/.github/ISSUE_TEMPLATE/question.md +0 -31
  41. package/.github/PULL_REQUEST_TEMPLATE.md +0 -70
  42. package/.github/SECURITY.md +0 -184
  43. package/.github/workflows/ci.yml +0 -33
  44. package/plugins/custom-format.js +0 -101
  45. package/plugins/custom-validator.js +0 -200
@@ -0,0 +1,411 @@
1
+ # 自定义扩展指南
2
+
3
+ > **版本**: v1.0.2
4
+ > **更新日期**: 2025-12-31
5
+ > **用途**: 教你如何扩展schema-dsl,添加自己的验证器
6
+
7
+ ---
8
+
9
+ ## 📑 目录
10
+
11
+ - [快速开始](#快速开始)
12
+ - [添加自定义AJV关键字](#添加自定义ajv关键字)
13
+ - [扩展DslBuilder方法](#扩展dslbuilder方法)
14
+ - [添加预定义模式](#添加预定义模式)
15
+ - [多语言支持](#多语言支持)
16
+ - [完整示例](#完整示例)
17
+
18
+ ---
19
+
20
+ ## 快速开始
21
+
22
+ schema-dsl采用模块化设计,你可以轻松扩展:
23
+
24
+ 1. **AJV关键字** - 底层验证逻辑
25
+ 2. **DslBuilder方法** - DSL语法糖
26
+ 3. **预定义模式** - 常用正则模式
27
+ 4. **多语言消息** - 错误消息国际化
28
+
29
+ ---
30
+
31
+ ## 添加自定义AJV关键字
32
+
33
+ ### 步骤1:注册关键字
34
+
35
+ 在 `lib/validators/CustomKeywords.js` 中添加:
36
+
37
+ ```javascript
38
+ static registerCustomValidators(ajv) {
39
+ // 示例:手机号归属地验证
40
+ ajv.addKeyword({
41
+ keyword: 'phoneLocation',
42
+ type: 'string',
43
+ schemaType: 'string', // location参数类型
44
+ validate: function validate(location, phoneNumber) {
45
+ // location: 期望的归属地,如 'beijing'
46
+ // phoneNumber: 用户输入的手机号
47
+
48
+ const locationPrefixes = {
49
+ 'beijing': ['130', '131', '132'],
50
+ 'shanghai': ['133', '134', '135']
51
+ };
52
+
53
+ const prefixes = locationPrefixes[location];
54
+ if (!prefixes) {
55
+ validate.errors = [{
56
+ keyword: 'phoneLocation',
57
+ message: 'phone.location.unknown',
58
+ params: { location }
59
+ }];
60
+ return false;
61
+ }
62
+
63
+ const prefix = phoneNumber.substring(0, 3);
64
+ if (!prefixes.includes(prefix)) {
65
+ validate.errors = [{
66
+ keyword: 'phoneLocation',
67
+ message: 'phone.location.mismatch',
68
+ params: { expected: location, actual: prefix }
69
+ }];
70
+ return false;
71
+ }
72
+
73
+ return true;
74
+ },
75
+ errors: true
76
+ });
77
+ }
78
+ ```
79
+
80
+ ### 步骤2:在registerAll中调用
81
+
82
+ ```javascript
83
+ static registerAll(ajv) {
84
+ // ...existing keywords...
85
+ this.registerCustomValidators(ajv);
86
+ }
87
+ ```
88
+
89
+ ### 步骤3:添加多语言消息
90
+
91
+ 在 `lib/locales/zh-CN.js` 中:
92
+
93
+ ```javascript
94
+ module.exports = {
95
+ // ...existing messages...
96
+ 'phone.location.unknown': '未知的归属地: {{#location}}',
97
+ 'phone.location.mismatch': '手机号归属地不匹配,期望{{#expected}}'
98
+ };
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 扩展DslBuilder方法
104
+
105
+ ### 步骤1:添加便捷方法
106
+
107
+ 在 `lib/core/DslBuilder.js` 中添加:
108
+
109
+ ```javascript
110
+ /**
111
+ * 手机号归属地验证
112
+ * @param {string} location - 归属地
113
+ * @returns {DslBuilder}
114
+ */
115
+ phoneLocation(location) {
116
+ if (this._baseSchema.type !== 'string') {
117
+ throw new Error('phoneLocation() only applies to string type');
118
+ }
119
+ this._baseSchema.phoneLocation = location;
120
+ return this;
121
+ }
122
+ ```
123
+
124
+ ### 步骤2:使用新方法
125
+
126
+ ```javascript
127
+ const schema = dsl({
128
+ mobile: dsl('string!').phone('cn').phoneLocation('beijing')
129
+ });
130
+
131
+ validate(schema, { mobile: '13012345678' });
132
+ ```
133
+
134
+ ---
135
+
136
+ ## 添加预定义模式
137
+
138
+ ### 步骤1:创建模式文件
139
+
140
+ 创建 `lib/config/patterns/custom.js`:
141
+
142
+ ```javascript
143
+ module.exports = {
144
+ /**
145
+ * 微信号验证
146
+ */
147
+ wechat: {
148
+ pattern: /^[a-zA-Z]([a-zA-Z0-9_-]{5,19})$/,
149
+ key: 'pattern.wechat',
150
+ min: 6,
151
+ max: 20
152
+ },
153
+
154
+ /**
155
+ * QQ号验证
156
+ */
157
+ qq: {
158
+ pattern: /^[1-9][0-9]{4,10}$/,
159
+ key: 'pattern.qq',
160
+ min: 5,
161
+ max: 11
162
+ }
163
+ };
164
+ ```
165
+
166
+ ### 步骤2:导出模式
167
+
168
+ 在 `lib/config/patterns/index.js` 中:
169
+
170
+ ```javascript
171
+ module.exports = {
172
+ // ...existing patterns...
173
+ custom: require('./custom')
174
+ };
175
+ ```
176
+
177
+ ### 步骤3:添加DslBuilder方法
178
+
179
+ ```javascript
180
+ /**
181
+ * 微信号验证
182
+ * @returns {DslBuilder}
183
+ */
184
+ wechat() {
185
+ if (this._baseSchema.type !== 'string') {
186
+ throw new Error('wechat() only applies to string type');
187
+ }
188
+ const config = patterns.custom.wechat;
189
+ return this.pattern(config.pattern).messages({ 'pattern': config.key });
190
+ }
191
+ ```
192
+
193
+ ### 步骤4:添加多语言
194
+
195
+ ```javascript
196
+ // lib/locales/zh-CN.js
197
+ 'pattern.wechat': '{{#label}}必须是有效的微信号',
198
+ 'pattern.qq': '{{#label}}必须是有效的QQ号'
199
+ ```
200
+
201
+ ---
202
+
203
+ ## 多语言支持
204
+
205
+ ### 添加新语言
206
+
207
+ 1. **创建语言文件**
208
+
209
+ 创建 `lib/locales/ko-KR.js`(韩语):
210
+
211
+ ```javascript
212
+ module.exports = {
213
+ required: '{{#label}}은(는) 필수 항목입니다',
214
+ type: '{{#label}}은(는) {{#expected}} 유형이어야 합니다',
215
+ // ...其他73个键
216
+ };
217
+ ```
218
+
219
+ 2. **配置加载**
220
+
221
+ ```javascript
222
+ dsl.config({
223
+ i18n: path.join(__dirname, 'lib/locales')
224
+ });
225
+ ```
226
+
227
+ 3. **使用新语言**
228
+
229
+ ```javascript
230
+ validate(schema, data, { locale: 'ko-KR' });
231
+ ```
232
+
233
+ ### 自定义错误消息
234
+
235
+ ```javascript
236
+ // 全局配置
237
+ dsl.config({
238
+ i18n: {
239
+ 'zh-CN': {
240
+ 'custom.emailTaken': '该邮箱已被注册',
241
+ 'custom.invalidFormat': '格式不正确'
242
+ }
243
+ }
244
+ });
245
+
246
+ // 使用
247
+ const schema = dsl({ email: 'email!' });
248
+ schema.properties.email._customMessages = {
249
+ 'format': 'custom.emailTaken'
250
+ };
251
+ ```
252
+
253
+ ---
254
+
255
+ ## 完整示例
256
+
257
+ ### 示例1:银行卡号验证器
258
+
259
+ ```javascript
260
+ // 1. 添加AJV关键字
261
+ static registerBankCard Validator(ajv) {
262
+ ajv.addKeyword({
263
+ keyword: 'bankCard',
264
+ type: 'string',
265
+ schemaType: 'boolean',
266
+ validate: function validate(schema, cardNumber) {
267
+ if (!schema) return true;
268
+
269
+ // Luhn算法验证
270
+ let sum = 0;
271
+ let isEven = false;
272
+
273
+ for (let i = cardNumber.length - 1; i >= 0; i--) {
274
+ let digit = parseInt(cardNumber[i], 10);
275
+
276
+ if (isEven) {
277
+ digit *= 2;
278
+ if (digit > 9) digit -= 9;
279
+ }
280
+
281
+ sum += digit;
282
+ isEven = !isEven;
283
+ }
284
+
285
+ if (sum % 10 !== 0) {
286
+ validate.errors = [{
287
+ keyword: 'bankCard',
288
+ message: 'pattern.bankCard',
289
+ params: {}
290
+ }];
291
+ return false;
292
+ }
293
+
294
+ return true;
295
+ },
296
+ errors: true
297
+ });
298
+ }
299
+
300
+ // 2. 添加DslBuilder方法
301
+ bankCard() {
302
+ if (this._baseSchema.type !== 'string') {
303
+ throw new Error('bankCard() only applies to string type');
304
+ }
305
+ this._baseSchema.bankCard = true;
306
+ return this;
307
+ }
308
+
309
+ // 3. 添加多语言
310
+ 'pattern.bankCard': '{{#label}}必须是有效的银行卡号'
311
+
312
+ // 4. 使用
313
+ const schema = dsl({ cardNumber: dsl('string!').bankCard() });
314
+ validate(schema, { cardNumber: '6222026006956145' });
315
+ ```
316
+
317
+ ### 示例2:IP段验证器
318
+
319
+ ```javascript
320
+ // 1. 添加AJV关键字
321
+ static registerIPRange(ajv) {
322
+ ajv.addKeyword({
323
+ keyword: 'ipRange',
324
+ type: 'string',
325
+ schemaType: 'array', // [min, max]
326
+ validate: function validate(range, ip) {
327
+ const ipToNumber = (ip) => {
328
+ return ip.split('.').reduce((acc, octet) => {
329
+ return (acc << 8) + parseInt(octet, 10);
330
+ }, 0);
331
+ };
332
+
333
+ const ipNum = ipToNumber(ip);
334
+ const [minIP, maxIP] = range;
335
+ const minNum = ipToNumber(minIP);
336
+ const maxNum = ipToNumber(maxIP);
337
+
338
+ if (ipNum < minNum || ipNum > maxNum) {
339
+ validate.errors = [{
340
+ keyword: 'ipRange',
341
+ message: 'ip.range',
342
+ params: { min: minIP, max: maxIP }
343
+ }];
344
+ return false;
345
+ }
346
+
347
+ return true;
348
+ },
349
+ errors: true
350
+ });
351
+ }
352
+
353
+ // 2. 使用
354
+ const schema = {
355
+ type: 'string',
356
+ format: 'ipv4',
357
+ ipRange: ['192.168.1.1', '192.168.1.255']
358
+ };
359
+ ```
360
+
361
+ ---
362
+
363
+ ## 最佳实践
364
+
365
+ ### 1. 命名规范
366
+
367
+ - **关键字**:小驼峰,如 `phoneLocation`
368
+ - **方法名**:小驼峰,如 `.phoneLocation()`
369
+ - **消息键**:点分隔,如 `phone.location.mismatch`
370
+
371
+ ### 2. 错误消息
372
+
373
+ - 使用占位符:`{{#label}}`, `{{#limit}}`, `{{#expected}}`
374
+ - 提供详细的错误信息
375
+ - 支持多语言
376
+
377
+ ### 3. 性能优化
378
+
379
+ - 预编译正则表达式
380
+ - 避免复杂的循环
381
+ - 使用纯函数
382
+
383
+ ### 4. 测试覆盖
384
+
385
+ ```javascript
386
+ describe('Custom Validator - bankCard', function() {
387
+ it('应该验证有效的银行卡号', function() {
388
+ const schema = dsl({ card: dsl('string!').bankCard() });
389
+ expect(validate(schema, { card: '6222026006956145' }).valid).to.be.true;
390
+ });
391
+
392
+ it('应该拒绝无效的银行卡号', function() {
393
+ const schema = dsl({ card: dsl('string!').bankCard() });
394
+ expect(validate(schema, { card: '1234567890123456' }).valid).to.be.false;
395
+ });
396
+ });
397
+ ```
398
+
399
+ ---
400
+
401
+ ## 参考资料
402
+
403
+ - [AJV自定义关键字文档](https://ajv.js.org/guide/user-keywords.html)
404
+ - [JSON Schema规范](https://json-schema.org/)
405
+ - [schema-dsl API文档](./api-reference.md)
406
+ - [验证指南](./validation-guide.md)
407
+
408
+ ---
409
+
410
+ **需要帮助?** 访问 [GitHub Issues](https://github.com/vextjs/schema-dsl/issues)
411
+