schema-dsl 1.2.4 → 1.2.5
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 +2 -0
- package/CONTRIBUTING.md +6 -6
- package/changelogs/v1.1.3.md +1 -1
- package/index.d.ts +3658 -3540
- package/index.js +475 -457
- package/lib/adapters/DslAdapter.js +995 -871
- package/lib/core/DslBuilder.js +1589 -1400
- package/package.json +1 -1
package/lib/core/DslBuilder.js
CHANGED
|
@@ -1,1400 +1,1589 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DSL Builder - 统一的Schema构建器
|
|
3
|
-
*
|
|
4
|
-
* 支持链式调用扩展DSL功能
|
|
5
|
-
*
|
|
6
|
-
* @module lib/core/DslBuilder
|
|
7
|
-
* @version 2.0.0
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* // 简单使用
|
|
11
|
-
* const schema = dsl('email!');
|
|
12
|
-
*
|
|
13
|
-
* // 链式扩展
|
|
14
|
-
* const schema = dsl('email!')
|
|
15
|
-
* .pattern(/custom/)
|
|
16
|
-
* .messages({ 'string.pattern': '格式不正确' })
|
|
17
|
-
* .label('邮箱地址');
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const ErrorCodes = require(
|
|
21
|
-
const MessageTemplate = require(
|
|
22
|
-
const Locale = require(
|
|
23
|
-
const patterns = require(
|
|
24
|
-
|
|
25
|
-
class DslBuilder {
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* @
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
*
|
|
94
|
-
* @returns {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*
|
|
102
|
-
*/
|
|
103
|
-
static
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
*
|
|
151
|
-
* @
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
if (
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
*
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
.
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
*
|
|
895
|
-
* @param {
|
|
896
|
-
* @returns {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
*
|
|
959
|
-
* @
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
1018
|
-
*/
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
this._baseSchema.
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
*
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
this.
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
* @
|
|
1109
|
-
*
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
*
|
|
1140
|
-
* @
|
|
1141
|
-
*
|
|
1142
|
-
*/
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
* @
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
if (
|
|
1160
|
-
throw new Error(
|
|
1161
|
-
}
|
|
1162
|
-
this.
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
*
|
|
1168
|
-
* @returns {DslBuilder}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
*
|
|
1198
|
-
* @
|
|
1199
|
-
*
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
1214
|
-
* @
|
|
1215
|
-
*
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
*
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
*
|
|
1325
|
-
* @
|
|
1326
|
-
*
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
*
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
this._baseSchema.
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
/**
|
|
1373
|
-
*
|
|
1374
|
-
* @
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1377
|
-
*
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1
|
+
/**
|
|
2
|
+
* DSL Builder - 统一的Schema构建器
|
|
3
|
+
*
|
|
4
|
+
* 支持链式调用扩展DSL功能
|
|
5
|
+
*
|
|
6
|
+
* @module lib/core/DslBuilder
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // 简单使用
|
|
11
|
+
* const schema = dsl('email!');
|
|
12
|
+
*
|
|
13
|
+
* // 链式扩展
|
|
14
|
+
* const schema = dsl('email!')
|
|
15
|
+
* .pattern(/custom/)
|
|
16
|
+
* .messages({ 'string.pattern': '格式不正确' })
|
|
17
|
+
* .label('邮箱地址');
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const ErrorCodes = require("./ErrorCodes");
|
|
21
|
+
const MessageTemplate = require("./MessageTemplate");
|
|
22
|
+
const Locale = require("./Locale");
|
|
23
|
+
const patterns = require("../config/patterns");
|
|
24
|
+
|
|
25
|
+
class DslBuilder {
|
|
26
|
+
/**
|
|
27
|
+
* schema-dsl 自定义验证关键字集合(非 JSON Schema 标准字段)
|
|
28
|
+
*
|
|
29
|
+
* toJsonSchema() 使用此集合过滤非标准字段,确保输出纯净的 JSON Schema。
|
|
30
|
+
* @private
|
|
31
|
+
* @type {Set<string>}
|
|
32
|
+
*/
|
|
33
|
+
static _internalKeys = new Set([
|
|
34
|
+
"exactLength",
|
|
35
|
+
"alphanum",
|
|
36
|
+
"lowercase",
|
|
37
|
+
"uppercase",
|
|
38
|
+
"trim",
|
|
39
|
+
"jsonString",
|
|
40
|
+
"port",
|
|
41
|
+
"requiredAll",
|
|
42
|
+
"strictSchema",
|
|
43
|
+
"noSparse",
|
|
44
|
+
"includesRequired",
|
|
45
|
+
"dateFormat",
|
|
46
|
+
"dateGreater",
|
|
47
|
+
"dateLess",
|
|
48
|
+
"precision",
|
|
49
|
+
"multipleOf",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 静态属性:存储用户自定义类型(插件注册)
|
|
54
|
+
* @private
|
|
55
|
+
* @type {Map<string, Object|Function>}
|
|
56
|
+
*/
|
|
57
|
+
static _customTypes = new Map();
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 注册自定义类型(供插件使用)
|
|
61
|
+
* @param {string} name - 类型名称
|
|
62
|
+
* @param {Object|Function} schema - JSON Schema对象 或 生成函数
|
|
63
|
+
* @throws {Error} 类型名称无效时抛出错误
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // 插件中注册自定义类型
|
|
67
|
+
* DslBuilder.registerType('phone-cn', {
|
|
68
|
+
* type: 'string',
|
|
69
|
+
* pattern: '^1[3-9]\\d{9}$'
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // 在DSL中使用
|
|
73
|
+
* dsl('phone-cn!') // ✅ 可用
|
|
74
|
+
* dsl('types:string|phone-cn') // ✅ 可用
|
|
75
|
+
*/
|
|
76
|
+
static registerType(name, schema) {
|
|
77
|
+
if (!name || typeof name !== "string") {
|
|
78
|
+
throw new Error("Type name must be a non-empty string");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
!schema ||
|
|
83
|
+
(typeof schema !== "object" && typeof schema !== "function")
|
|
84
|
+
) {
|
|
85
|
+
throw new Error("Schema must be an object or function");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this._customTypes.set(name, schema);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 检查类型是否已注册(内置或自定义)
|
|
93
|
+
* @param {string} type - 类型名称
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* DslBuilder.hasType('string') // true (内置)
|
|
98
|
+
* DslBuilder.hasType('phone-cn') // false (未注册)
|
|
99
|
+
*
|
|
100
|
+
* DslBuilder.registerType('phone-cn', { ... });
|
|
101
|
+
* DslBuilder.hasType('phone-cn') // true (已注册)
|
|
102
|
+
*/
|
|
103
|
+
static hasType(type) {
|
|
104
|
+
// 检查自定义类型
|
|
105
|
+
if (this._customTypes.has(type)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 检查内置类型
|
|
110
|
+
const builtInTypes = [
|
|
111
|
+
"string",
|
|
112
|
+
"number",
|
|
113
|
+
"integer",
|
|
114
|
+
"boolean",
|
|
115
|
+
"object",
|
|
116
|
+
"array",
|
|
117
|
+
"null",
|
|
118
|
+
"email",
|
|
119
|
+
"url",
|
|
120
|
+
"uuid",
|
|
121
|
+
"date",
|
|
122
|
+
"datetime",
|
|
123
|
+
"time",
|
|
124
|
+
"ipv4",
|
|
125
|
+
"ipv6",
|
|
126
|
+
"binary",
|
|
127
|
+
"objectId",
|
|
128
|
+
"hexColor",
|
|
129
|
+
"macAddress",
|
|
130
|
+
"cron",
|
|
131
|
+
"slug",
|
|
132
|
+
"alphanum",
|
|
133
|
+
"lower",
|
|
134
|
+
"upper",
|
|
135
|
+
"json",
|
|
136
|
+
"port",
|
|
137
|
+
"phone",
|
|
138
|
+
"idCard",
|
|
139
|
+
"creditCard",
|
|
140
|
+
"licensePlate",
|
|
141
|
+
"postalCode",
|
|
142
|
+
"passport",
|
|
143
|
+
"any",
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
return builtInTypes.includes(type);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 获取所有已注册的自定义类型
|
|
151
|
+
* @returns {Array<string>}
|
|
152
|
+
*/
|
|
153
|
+
static getCustomTypes() {
|
|
154
|
+
return Array.from(this._customTypes.keys());
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 清除所有自定义类型(主要用于测试)
|
|
159
|
+
*/
|
|
160
|
+
static clearCustomTypes() {
|
|
161
|
+
this._customTypes.clear();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 创建 DslBuilder 实例
|
|
166
|
+
* @param {string} dslString - DSL字符串,如 'string:3-32!' 或 'email!'
|
|
167
|
+
*/
|
|
168
|
+
constructor(dslString) {
|
|
169
|
+
if (!dslString || typeof dslString !== "string") {
|
|
170
|
+
throw new Error("DSL string is required");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 解析DSL字符串
|
|
174
|
+
const trimmed = dslString.trim();
|
|
175
|
+
|
|
176
|
+
// 特殊处理:array!数字 → array:数字 + 必填
|
|
177
|
+
// 例如:array!1-10 → array:1-10!
|
|
178
|
+
let processedDsl = trimmed;
|
|
179
|
+
if (/^array![\d-]/.test(trimmed)) {
|
|
180
|
+
processedDsl = trimmed.replace(/^array!/, "array:") + "!";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 🔴 处理必填标记 ! 和可选标记 ?
|
|
184
|
+
// 优先级:! > ?(如果同时存在,! 优先)
|
|
185
|
+
this._required = processedDsl.endsWith("!");
|
|
186
|
+
this._optional = processedDsl.endsWith("?") && !this._required;
|
|
187
|
+
|
|
188
|
+
let dslWithoutMarker = processedDsl;
|
|
189
|
+
if (this._required) {
|
|
190
|
+
dslWithoutMarker = processedDsl.slice(0, -1);
|
|
191
|
+
} else if (this._optional) {
|
|
192
|
+
dslWithoutMarker = processedDsl.slice(0, -1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 简单解析为基础Schema(避免循环依赖)
|
|
196
|
+
this._baseSchema = this._parseSimple(dslWithoutMarker);
|
|
197
|
+
|
|
198
|
+
// 扩展属性
|
|
199
|
+
this._customMessages = {};
|
|
200
|
+
this._label = null;
|
|
201
|
+
this._customValidators = [];
|
|
202
|
+
this._description = null;
|
|
203
|
+
this._whenConditions = [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 简单解析DSL字符串(避免循环依赖)
|
|
208
|
+
* @private
|
|
209
|
+
* @param {string} dsl - DSL字符串(不含!)
|
|
210
|
+
* @returns {Object} JSON Schema对象
|
|
211
|
+
*/
|
|
212
|
+
_parseSimple(dsl) {
|
|
213
|
+
// 🔴 处理跨类型联合:types:type1|type2|type3
|
|
214
|
+
if (dsl.startsWith("types:")) {
|
|
215
|
+
const typesStr = dsl.substring(6); // 去掉 'types:' 前缀
|
|
216
|
+
const types = typesStr
|
|
217
|
+
.split("|")
|
|
218
|
+
.map((t) => t.trim())
|
|
219
|
+
.filter((t) => t);
|
|
220
|
+
|
|
221
|
+
if (types.length === 0) {
|
|
222
|
+
throw new Error("types: requires at least one type");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (types.length === 1) {
|
|
226
|
+
// 只有一个类型,直接解析(避免不必要的oneOf)
|
|
227
|
+
return this._parseSimple(types[0]);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 多个类型,生成oneOf结构
|
|
231
|
+
return {
|
|
232
|
+
oneOf: types.map((type) => this._parseSimple(type)),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 处理数组类型:array:1-10 或 array<string>
|
|
237
|
+
if (dsl.startsWith("array")) {
|
|
238
|
+
const schema = { type: "array" };
|
|
239
|
+
|
|
240
|
+
// 匹配:array:min-max<itemType> 或 array:constraint<itemType> 或 array<itemType>
|
|
241
|
+
const arrayMatch = dsl.match(/^array(?::([^<]+?))?(?:<(.+)>)?$/);
|
|
242
|
+
|
|
243
|
+
if (arrayMatch) {
|
|
244
|
+
const [, constraint, itemType] = arrayMatch;
|
|
245
|
+
|
|
246
|
+
// 解析约束
|
|
247
|
+
if (constraint) {
|
|
248
|
+
const trimmedConstraint = constraint.trim();
|
|
249
|
+
|
|
250
|
+
if (trimmedConstraint.includes("-")) {
|
|
251
|
+
// 范围约束: min-max, min-, -max
|
|
252
|
+
const [min, max] = trimmedConstraint
|
|
253
|
+
.split("-")
|
|
254
|
+
.map((v) => v.trim());
|
|
255
|
+
if (min) schema.minItems = parseInt(min, 10);
|
|
256
|
+
if (max) schema.maxItems = parseInt(max, 10);
|
|
257
|
+
} else {
|
|
258
|
+
// 单个值 = 最大值
|
|
259
|
+
schema.maxItems = parseInt(trimmedConstraint, 10);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 解析元素类型
|
|
264
|
+
if (itemType) {
|
|
265
|
+
schema.items = this._parseSimple(itemType.trim());
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return schema;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 处理 enum: 前缀的枚举(支持逗号分隔和管道分隔)
|
|
273
|
+
// 例如: enum:a,b,c / enum:admin,user,guest / enum:number:1,2,3 / enum:a|b|c
|
|
274
|
+
if (dsl.startsWith("enum:")) {
|
|
275
|
+
const enumBody = dsl.slice(5); // 'a,b,c' 或 'number:1,2,3' 或 'a|b|c'
|
|
276
|
+
|
|
277
|
+
// 检查是否有类型前缀:enum:number:1,2,3
|
|
278
|
+
const colonIdx = enumBody.indexOf(":");
|
|
279
|
+
let enumType = "string";
|
|
280
|
+
let enumValues;
|
|
281
|
+
|
|
282
|
+
if (colonIdx !== -1) {
|
|
283
|
+
// enum:type:values
|
|
284
|
+
enumType = enumBody.slice(0, colonIdx);
|
|
285
|
+
enumValues = enumBody.slice(colonIdx + 1);
|
|
286
|
+
} else {
|
|
287
|
+
// enum:values (默认 string)
|
|
288
|
+
enumValues = enumBody;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 统一分隔符:逗号 → 管道(_parseEnum 使用管道分隔)
|
|
292
|
+
const normalized = enumValues.includes("|")
|
|
293
|
+
? enumValues
|
|
294
|
+
: enumValues.replace(/,/g, "|");
|
|
295
|
+
return this._parseEnum(enumType, normalized);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 处理简写枚举(管道分隔,无 enum: 前缀)
|
|
299
|
+
// 例如: admin|user|guest / 1|2|3
|
|
300
|
+
if (dsl.includes("|")) {
|
|
301
|
+
let enumType = "string"; // 默认字符串
|
|
302
|
+
let enumValues = dsl;
|
|
303
|
+
|
|
304
|
+
if (dsl.includes(":") && !this._isKnownType(dsl.split(":")[0])) {
|
|
305
|
+
// 如果有冒号但不是已知类型(如 string:3-32),不作为枚举
|
|
306
|
+
// 让后续逻辑处理
|
|
307
|
+
} else {
|
|
308
|
+
// 简写形式:value1|value2
|
|
309
|
+
// 自动识别类型
|
|
310
|
+
enumType = this._detectEnumType(enumValues);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 如果是枚举,解析值
|
|
314
|
+
if (enumValues.includes("|")) {
|
|
315
|
+
return this._parseEnum(enumType, enumValues);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 处理类型:约束格式
|
|
320
|
+
const colonIndex = dsl.indexOf(":");
|
|
321
|
+
let type, constraint;
|
|
322
|
+
|
|
323
|
+
if (colonIndex === -1) {
|
|
324
|
+
type = dsl;
|
|
325
|
+
constraint = "";
|
|
326
|
+
} else {
|
|
327
|
+
type = dsl.substring(0, colonIndex);
|
|
328
|
+
constraint = dsl.substring(colonIndex + 1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 特殊处理 phone:country
|
|
332
|
+
if (type === "phone") {
|
|
333
|
+
const country = constraint || "cn";
|
|
334
|
+
const config = patterns.phone[country];
|
|
335
|
+
if (!config) throw new Error(`Unsupported country: ${country}`);
|
|
336
|
+
return {
|
|
337
|
+
type: "string",
|
|
338
|
+
pattern: config.pattern.source,
|
|
339
|
+
minLength: config.min,
|
|
340
|
+
maxLength: config.max,
|
|
341
|
+
_customMessages: { pattern: config.key || config.msg },
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// 特殊处理 idCard:country
|
|
346
|
+
if (type === "idCard") {
|
|
347
|
+
const country = constraint || "cn";
|
|
348
|
+
const config = patterns.idCard[country.toLowerCase()];
|
|
349
|
+
if (!config)
|
|
350
|
+
throw new Error(`Unsupported country for idCard: ${country}`);
|
|
351
|
+
return {
|
|
352
|
+
type: "string",
|
|
353
|
+
pattern: config.pattern.source,
|
|
354
|
+
minLength: config.min,
|
|
355
|
+
maxLength: config.max,
|
|
356
|
+
_customMessages: { pattern: config.key || config.msg },
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// 特殊处理 creditCard:type
|
|
361
|
+
if (type === "creditCard") {
|
|
362
|
+
const cardType = constraint || "visa";
|
|
363
|
+
const config = patterns.creditCard[cardType.toLowerCase()];
|
|
364
|
+
if (!config) throw new Error(`Unsupported credit card type: ${cardType}`);
|
|
365
|
+
return {
|
|
366
|
+
type: "string",
|
|
367
|
+
pattern: config.pattern.source,
|
|
368
|
+
_customMessages: { pattern: config.key || config.msg },
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 特殊处理 licensePlate:country
|
|
373
|
+
if (type === "licensePlate") {
|
|
374
|
+
const country = constraint || "cn";
|
|
375
|
+
const config = patterns.licensePlate[country.toLowerCase()];
|
|
376
|
+
if (!config)
|
|
377
|
+
throw new Error(`Unsupported country for licensePlate: ${country}`);
|
|
378
|
+
return {
|
|
379
|
+
type: "string",
|
|
380
|
+
pattern: config.pattern.source,
|
|
381
|
+
_customMessages: { pattern: config.key || config.msg },
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 特殊处理 postalCode:country
|
|
386
|
+
if (type === "postalCode") {
|
|
387
|
+
const country = constraint || "cn";
|
|
388
|
+
const config = patterns.postalCode[country.toLowerCase()];
|
|
389
|
+
if (!config)
|
|
390
|
+
throw new Error(`Unsupported country for postalCode: ${country}`);
|
|
391
|
+
return {
|
|
392
|
+
type: "string",
|
|
393
|
+
pattern: config.pattern.source,
|
|
394
|
+
_customMessages: { pattern: config.key || config.msg },
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 特殊处理 passport:country
|
|
399
|
+
if (type === "passport") {
|
|
400
|
+
const country = constraint || "cn";
|
|
401
|
+
const config = patterns.passport[country.toLowerCase()];
|
|
402
|
+
if (!config)
|
|
403
|
+
throw new Error(`Unsupported country for passport: ${country}`);
|
|
404
|
+
return {
|
|
405
|
+
type: "string",
|
|
406
|
+
pattern: config.pattern.source,
|
|
407
|
+
_customMessages: { pattern: config.key || config.msg },
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 获取基础类型
|
|
412
|
+
const schema = this._getBaseType(type);
|
|
413
|
+
|
|
414
|
+
// 处理约束
|
|
415
|
+
if (constraint) {
|
|
416
|
+
Object.assign(schema, this._parseConstraint(schema.type, constraint));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return schema;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 获取基础类型Schema
|
|
424
|
+
* @private
|
|
425
|
+
*/
|
|
426
|
+
_getBaseType(type) {
|
|
427
|
+
// 🔴 优先查询自定义类型(插件注册的)
|
|
428
|
+
if (DslBuilder._customTypes.has(type)) {
|
|
429
|
+
const customSchema = DslBuilder._customTypes.get(type);
|
|
430
|
+
// 如果是函数,调用它生成Schema
|
|
431
|
+
if (typeof customSchema === "function") {
|
|
432
|
+
return customSchema();
|
|
433
|
+
}
|
|
434
|
+
// 否则返回Schema对象的深拷贝(避免污染)
|
|
435
|
+
return JSON.parse(JSON.stringify(customSchema));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 🔴 查询内置类型
|
|
439
|
+
const typeMap = {
|
|
440
|
+
string: { type: "string" },
|
|
441
|
+
number: { type: "number" },
|
|
442
|
+
integer: { type: "integer" },
|
|
443
|
+
boolean: { type: "boolean" },
|
|
444
|
+
object: { type: "object" },
|
|
445
|
+
array: { type: "array" },
|
|
446
|
+
null: { type: "null" },
|
|
447
|
+
email: { type: "string", format: "email" },
|
|
448
|
+
url: { type: "string", format: "uri" },
|
|
449
|
+
uuid: { type: "string", format: "uuid" },
|
|
450
|
+
date: { type: "string", format: "date" },
|
|
451
|
+
datetime: { type: "string", format: "date-time" },
|
|
452
|
+
time: { type: "string", format: "time" },
|
|
453
|
+
ipv4: { type: "string", format: "ipv4" },
|
|
454
|
+
ipv6: { type: "string", format: "ipv6" },
|
|
455
|
+
binary: { type: "string", contentEncoding: "base64" },
|
|
456
|
+
objectId: {
|
|
457
|
+
type: "string",
|
|
458
|
+
pattern: "^[0-9a-fA-F]{24}$",
|
|
459
|
+
_customMessages: { pattern: "pattern.objectId" },
|
|
460
|
+
},
|
|
461
|
+
hexColor: {
|
|
462
|
+
type: "string",
|
|
463
|
+
pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
|
|
464
|
+
_customMessages: { pattern: "pattern.hexColor" },
|
|
465
|
+
},
|
|
466
|
+
macAddress: {
|
|
467
|
+
type: "string",
|
|
468
|
+
pattern: "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
|
|
469
|
+
_customMessages: { pattern: "pattern.macAddress" },
|
|
470
|
+
},
|
|
471
|
+
cron: {
|
|
472
|
+
type: "string",
|
|
473
|
+
pattern:
|
|
474
|
+
"^(\\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\\*\\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\\*|([0-9]|1[0-9]|2[0-3])|\\*\\/([0-9]|1[0-9]|2[0-3])) (\\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\\*\\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\\*|([1-9]|1[0-2])|\\*\\/([1-9]|1[0-2])) (\\*|([0-6])|\\*\\/([0-6]))$",
|
|
475
|
+
_customMessages: { pattern: "pattern.cron" },
|
|
476
|
+
},
|
|
477
|
+
slug: {
|
|
478
|
+
type: "string",
|
|
479
|
+
pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
480
|
+
_customMessages: { pattern: "pattern.slug" },
|
|
481
|
+
},
|
|
482
|
+
any: {},
|
|
483
|
+
// v1.0.2 新增类型
|
|
484
|
+
alphanum: { type: "string", alphanum: true },
|
|
485
|
+
lower: { type: "string", lowercase: true },
|
|
486
|
+
upper: { type: "string", uppercase: true },
|
|
487
|
+
json: { type: "string", jsonString: true },
|
|
488
|
+
port: { type: "integer", port: true },
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
return typeMap[type] || { type: "string" };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* 解析约束
|
|
496
|
+
* @private
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* // 比较运算符 (v1.2.0+)
|
|
500
|
+
* _parseConstraint('number', '>0') // { exclusiveMinimum: 0 }
|
|
501
|
+
* _parseConstraint('number', '>=18') // { minimum: 18 }
|
|
502
|
+
* _parseConstraint('number', '<100') // { exclusiveMaximum: 100 }
|
|
503
|
+
* _parseConstraint('number', '<=100') // { maximum: 100 }
|
|
504
|
+
* _parseConstraint('number', '=100') // { enum: [100] }
|
|
505
|
+
*/
|
|
506
|
+
_parseConstraint(type, constraint) {
|
|
507
|
+
const result = {};
|
|
508
|
+
|
|
509
|
+
if (type === "string" || type === "number" || type === "integer") {
|
|
510
|
+
// ========== 比较运算符(v1.1.2新增,仅number/integer,最高优先级)==========
|
|
511
|
+
if (type === "number" || type === "integer") {
|
|
512
|
+
// 1. 大于等于: >=18, >=-10 (支持负数)
|
|
513
|
+
const gteMatch = constraint.match(/^>=(-?\d+(?:\.\d+)?)$/);
|
|
514
|
+
if (gteMatch) {
|
|
515
|
+
result.minimum = parseFloat(gteMatch[1]);
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// 2. 小于等于: <=100, <=-10 (支持负数)
|
|
520
|
+
const lteMatch = constraint.match(/^<=(-?\d+(?:\.\d+)?)$/);
|
|
521
|
+
if (lteMatch) {
|
|
522
|
+
result.maximum = parseFloat(lteMatch[1]);
|
|
523
|
+
return result;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// 3. 大于: >0, >-10 (不包括边界值,支持负数)
|
|
527
|
+
const gtMatch = constraint.match(/^>(-?\d+(?:\.\d+)?)$/);
|
|
528
|
+
if (gtMatch) {
|
|
529
|
+
result.exclusiveMinimum = parseFloat(gtMatch[1]);
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// 4. 小于: <100, <-10 (不包括边界值,支持负数)
|
|
534
|
+
const ltMatch = constraint.match(/^<(-?\d+(?:\.\d+)?)$/);
|
|
535
|
+
if (ltMatch) {
|
|
536
|
+
result.exclusiveMaximum = parseFloat(ltMatch[1]);
|
|
537
|
+
return result;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// 5. 等于: =100, =-10 (支持负数)
|
|
541
|
+
const eqMatch = constraint.match(/^=(-?\d+(?:\.\d+)?)$/);
|
|
542
|
+
if (eqMatch) {
|
|
543
|
+
result.enum = [parseFloat(eqMatch[1])];
|
|
544
|
+
return result;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// ========== 范围约束: min-max ==========
|
|
549
|
+
if (constraint.includes("-")) {
|
|
550
|
+
const [min, max] = constraint.split("-").map((v) => v.trim());
|
|
551
|
+
|
|
552
|
+
if (type === "string") {
|
|
553
|
+
if (min) result.minLength = parseInt(min);
|
|
554
|
+
if (max) result.maxLength = parseInt(max);
|
|
555
|
+
} else {
|
|
556
|
+
if (min) result.minimum = parseFloat(min);
|
|
557
|
+
if (max) result.maximum = parseFloat(max);
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
// 单个值
|
|
561
|
+
const value = constraint.trim();
|
|
562
|
+
if (value) {
|
|
563
|
+
if (type === "string") {
|
|
564
|
+
// 🔴 String单值 = 精确长度(常用于验证码、国家代码等)
|
|
565
|
+
result.exactLength = parseInt(value);
|
|
566
|
+
} else {
|
|
567
|
+
// Number单值 = 最大值(符合直觉:不超过某值)
|
|
568
|
+
result.maximum = parseFloat(value);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* 检查是否为已知类型
|
|
579
|
+
* @private
|
|
580
|
+
*/
|
|
581
|
+
_isKnownType(type) {
|
|
582
|
+
const knownTypes = [
|
|
583
|
+
"string",
|
|
584
|
+
"number",
|
|
585
|
+
"integer",
|
|
586
|
+
"boolean",
|
|
587
|
+
"object",
|
|
588
|
+
"array",
|
|
589
|
+
"null",
|
|
590
|
+
"email",
|
|
591
|
+
"url",
|
|
592
|
+
"uuid",
|
|
593
|
+
"date",
|
|
594
|
+
"datetime",
|
|
595
|
+
"time",
|
|
596
|
+
"ipv4",
|
|
597
|
+
"ipv6",
|
|
598
|
+
"binary",
|
|
599
|
+
"objectId",
|
|
600
|
+
"hexColor",
|
|
601
|
+
"macAddress",
|
|
602
|
+
"cron",
|
|
603
|
+
"any",
|
|
604
|
+
"phone",
|
|
605
|
+
"idCard",
|
|
606
|
+
"creditCard",
|
|
607
|
+
"licensePlate",
|
|
608
|
+
"postalCode",
|
|
609
|
+
"passport",
|
|
610
|
+
// v1.0.2 新增
|
|
611
|
+
"alphanum",
|
|
612
|
+
"lower",
|
|
613
|
+
"upper",
|
|
614
|
+
"json",
|
|
615
|
+
"port",
|
|
616
|
+
];
|
|
617
|
+
return knownTypes.includes(type);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* 自动检测枚举类型
|
|
622
|
+
* @private
|
|
623
|
+
*/
|
|
624
|
+
_detectEnumType(enumValues) {
|
|
625
|
+
const values = enumValues.split("|").map((v) => v.trim());
|
|
626
|
+
|
|
627
|
+
// 检查是否全部为布尔值
|
|
628
|
+
const allBoolean = values.every((v) => v === "true" || v === "false");
|
|
629
|
+
if (allBoolean) return "boolean";
|
|
630
|
+
|
|
631
|
+
// 检查是否全部为数字
|
|
632
|
+
const allNumber = values.every((v) => !isNaN(parseFloat(v)) && isFinite(v));
|
|
633
|
+
if (allNumber) return "number";
|
|
634
|
+
|
|
635
|
+
// 默认字符串
|
|
636
|
+
return "string";
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* 解析枚举值
|
|
641
|
+
* @private
|
|
642
|
+
*/
|
|
643
|
+
_parseEnum(enumType, enumValues) {
|
|
644
|
+
let values = enumValues.split("|").map((v) => v.trim());
|
|
645
|
+
|
|
646
|
+
// 类型转换
|
|
647
|
+
if (enumType === "boolean") {
|
|
648
|
+
values = values.map((v) => {
|
|
649
|
+
if (v === "true") return true;
|
|
650
|
+
if (v === "false") return false;
|
|
651
|
+
throw new Error(
|
|
652
|
+
`Invalid boolean enum value: ${v}. Must be 'true' or 'false'`,
|
|
653
|
+
);
|
|
654
|
+
});
|
|
655
|
+
return { type: "boolean", enum: values };
|
|
656
|
+
} else if (enumType === "number") {
|
|
657
|
+
values = values.map((v) => {
|
|
658
|
+
const num = parseFloat(v);
|
|
659
|
+
if (isNaN(num)) throw new Error(`Invalid number enum value: ${v}`);
|
|
660
|
+
return num;
|
|
661
|
+
});
|
|
662
|
+
return { type: "number", enum: values };
|
|
663
|
+
} else if (enumType === "integer") {
|
|
664
|
+
values = values.map((v) => {
|
|
665
|
+
const num = parseInt(v, 10);
|
|
666
|
+
if (isNaN(num)) throw new Error(`Invalid integer enum value: ${v}`);
|
|
667
|
+
return num;
|
|
668
|
+
});
|
|
669
|
+
return { type: "integer", enum: values };
|
|
670
|
+
} else {
|
|
671
|
+
// 字符串枚举(默认)
|
|
672
|
+
return { type: "string", enum: values };
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* 添加正则表达式验证
|
|
678
|
+
* @param {RegExp|string} regex - 正则表达式
|
|
679
|
+
* @param {string} [message] - 自定义错误消息
|
|
680
|
+
* @returns {DslBuilder}
|
|
681
|
+
*
|
|
682
|
+
* @example
|
|
683
|
+
* dsl('string:3-32!')
|
|
684
|
+
* .pattern(/^[a-zA-Z0-9_]+$/, '只能包含字母、数字和下划线')
|
|
685
|
+
*/
|
|
686
|
+
pattern(regex, message) {
|
|
687
|
+
this._baseSchema.pattern = regex instanceof RegExp ? regex.source : regex;
|
|
688
|
+
|
|
689
|
+
if (message) {
|
|
690
|
+
this._customMessages["string.pattern"] = message;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return this;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* 自定义错误消息
|
|
698
|
+
* @param {Object} messages - 错误消息对象
|
|
699
|
+
* @returns {DslBuilder}
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* dsl('string:3-32!')
|
|
703
|
+
* .messages({
|
|
704
|
+
* 'string.min': '至少{{#limit}}个字符',
|
|
705
|
+
* 'string.max': '最多{{#limit}}个字符'
|
|
706
|
+
* })
|
|
707
|
+
*/
|
|
708
|
+
messages(messages) {
|
|
709
|
+
Object.assign(this._customMessages, messages);
|
|
710
|
+
return this;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* 设置字段标签(用于错误消息)
|
|
715
|
+
* @param {string} labelText - 标签文本
|
|
716
|
+
* @returns {DslBuilder}
|
|
717
|
+
*
|
|
718
|
+
* @example
|
|
719
|
+
* dsl('email!').label('邮箱地址')
|
|
720
|
+
*/
|
|
721
|
+
label(labelText) {
|
|
722
|
+
this._label = labelText;
|
|
723
|
+
return this;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* 添加自定义验证器
|
|
728
|
+
* @param {Function} validatorFn - 验证函数
|
|
729
|
+
* @returns {DslBuilder}
|
|
730
|
+
*
|
|
731
|
+
* 支持多种返回方式:
|
|
732
|
+
* 1. 不返回/返回 undefined → 验证通过
|
|
733
|
+
* 2. 返回 true → 验证通过
|
|
734
|
+
* 3. 返回 false → 验证失败(使用默认消息)
|
|
735
|
+
* 4. 返回字符串 → 验证失败(字符串作为错误消息)
|
|
736
|
+
* 5. 返回对象 { error, message } → 验证失败(自定义错误)
|
|
737
|
+
* 6. 抛出异常 → 验证失败(异常消息作为错误)
|
|
738
|
+
*
|
|
739
|
+
* @example
|
|
740
|
+
* // 方式1: 不返回任何值(推荐)
|
|
741
|
+
* .custom(async (value) => {
|
|
742
|
+
* const exists = await checkEmailExists(value);
|
|
743
|
+
* if (exists) return '邮箱已被占用';
|
|
744
|
+
* })
|
|
745
|
+
*
|
|
746
|
+
* // 方式2: 返回错误消息字符串
|
|
747
|
+
* .custom((value) => {
|
|
748
|
+
* if (value.includes('admin')) return '不能包含敏感词';
|
|
749
|
+
* })
|
|
750
|
+
*
|
|
751
|
+
* // 方式3: 返回错误对象
|
|
752
|
+
* .custom(async (value) => {
|
|
753
|
+
* const exists = await checkExists(value);
|
|
754
|
+
* if (exists) {
|
|
755
|
+
* return { error: 'email.exists', message: '邮箱已被占用' };
|
|
756
|
+
* }
|
|
757
|
+
* })
|
|
758
|
+
*
|
|
759
|
+
* // 方式4: 抛出异常
|
|
760
|
+
* .custom(async (value) => {
|
|
761
|
+
* const user = await findUser(value);
|
|
762
|
+
* if (!user) throw new Error('用户不存在');
|
|
763
|
+
* })
|
|
764
|
+
*/
|
|
765
|
+
custom(validatorFn) {
|
|
766
|
+
if (typeof validatorFn !== "function") {
|
|
767
|
+
throw new Error("Custom validator must be a function");
|
|
768
|
+
}
|
|
769
|
+
this._customValidators.push(validatorFn);
|
|
770
|
+
return this;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* 设置描述
|
|
775
|
+
* @param {string} text - 描述文本
|
|
776
|
+
* @returns {DslBuilder}
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* dsl('string:3-32!').description('用户登录名')
|
|
780
|
+
*/
|
|
781
|
+
description(text) {
|
|
782
|
+
this._description = text;
|
|
783
|
+
return this;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* 设置默认值
|
|
788
|
+
* @param {*} value - 默认值
|
|
789
|
+
* @returns {DslBuilder}
|
|
790
|
+
*/
|
|
791
|
+
default(value) {
|
|
792
|
+
this._baseSchema.default = value;
|
|
793
|
+
return this;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* 转换为 JSON Schema
|
|
798
|
+
* @returns {Object} JSON Schema对象
|
|
799
|
+
*/
|
|
800
|
+
toSchema() {
|
|
801
|
+
const schema = { ...this._baseSchema };
|
|
802
|
+
|
|
803
|
+
// 添加描述
|
|
804
|
+
if (this._description) {
|
|
805
|
+
schema.description = this._description;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// 添加自定义消息
|
|
809
|
+
if (Object.keys(this._customMessages).length > 0) {
|
|
810
|
+
schema._customMessages = this._customMessages;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// 添加标签
|
|
814
|
+
if (this._label) {
|
|
815
|
+
schema._label = this._label;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// 添加自定义验证器
|
|
819
|
+
if (this._customValidators.length > 0) {
|
|
820
|
+
schema._customValidators = this._customValidators;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// 添加when条件
|
|
824
|
+
if (this._whenConditions.length > 0) {
|
|
825
|
+
schema._whenConditions = this._whenConditions;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// 添加必填标记
|
|
829
|
+
schema._required = this._required;
|
|
830
|
+
|
|
831
|
+
return schema;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* 输出纯净的 JSON Schema(无内部标记字段)
|
|
836
|
+
*
|
|
837
|
+
* 与 toSchema() 不同,toJsonSchema() 会自动清理所有 schema-dsl 内部标记:
|
|
838
|
+
* - 下划线前缀字段:_required / _customMessages / _label / _customValidators / _whenConditions
|
|
839
|
+
* - 自定义验证关键字:exactLength / alphanum / lowercase / uppercase / trim / jsonString /
|
|
840
|
+
* port / requiredAll / strictSchema / noSparse / includesRequired / dateFormat /
|
|
841
|
+
* dateGreater / dateLess / precision / multipleOf
|
|
842
|
+
*
|
|
843
|
+
* 返回的对象可直接嵌入 OpenAPI / JSON Schema 等标准文档中,无需下游再做清理。
|
|
844
|
+
*
|
|
845
|
+
* @returns {Object} 纯净的 JSON Schema 对象(符合 JSON Schema 标准)
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* const builder = new DslBuilder('string:3-32!');
|
|
849
|
+
* builder.toSchema(); // { type: 'string', minLength: 3, maxLength: 32, _required: true }
|
|
850
|
+
* builder.toJsonSchema(); // { type: 'string', minLength: 3, maxLength: 32 }
|
|
851
|
+
*
|
|
852
|
+
* @example
|
|
853
|
+
* const builder = new DslBuilder('email!');
|
|
854
|
+
* builder.messages({ format: '邮箱格式不正确' });
|
|
855
|
+
* builder.toSchema(); // { type: 'string', format: 'email', _required: true, _customMessages: { format: '...' } }
|
|
856
|
+
* builder.toJsonSchema(); // { type: 'string', format: 'email' }
|
|
857
|
+
*
|
|
858
|
+
* @since v1.2.5
|
|
859
|
+
*/
|
|
860
|
+
toJsonSchema() {
|
|
861
|
+
const raw = this.toSchema();
|
|
862
|
+
const cleaned = {};
|
|
863
|
+
|
|
864
|
+
for (const key of Object.keys(raw)) {
|
|
865
|
+
// 跳过下划线前缀的内部字段
|
|
866
|
+
if (key.startsWith("_")) continue;
|
|
867
|
+
|
|
868
|
+
// 跳过 schema-dsl 自定义验证关键字(非 JSON Schema 标准)
|
|
869
|
+
if (DslBuilder._internalKeys.has(key)) continue;
|
|
870
|
+
|
|
871
|
+
cleaned[key] = raw[key];
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return cleaned;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* 验证数据
|
|
879
|
+
* @param {*} data - 待验证数据
|
|
880
|
+
* @param {Object} [context] - 验证上下文
|
|
881
|
+
* @returns {Promise<Object>} 验证结果
|
|
882
|
+
*/
|
|
883
|
+
async validate(data, context = {}) {
|
|
884
|
+
const Validator = require("./Validator");
|
|
885
|
+
const validator = new Validator();
|
|
886
|
+
const schema = this.toSchema();
|
|
887
|
+
|
|
888
|
+
return validator.validate(schema, data);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* 验证Schema嵌套深度
|
|
893
|
+
* @static
|
|
894
|
+
* @param {Object} schema - Schema对象
|
|
895
|
+
* @param {number} maxDepth - 最大深度(默认3)
|
|
896
|
+
* @returns {Object} { valid, depth, path, message }
|
|
897
|
+
*/
|
|
898
|
+
static validateNestingDepth(schema, maxDepth = 3) {
|
|
899
|
+
let maxFound = 0;
|
|
900
|
+
let deepestPath = "";
|
|
901
|
+
|
|
902
|
+
function traverse(obj, depth = 0, path = "", isRoot = false) {
|
|
903
|
+
// 更新最大深度(仅当节点是容器时,即包含 properties 或 items)
|
|
904
|
+
// 这样叶子节点(如 string 字段)不会增加嵌套深度
|
|
905
|
+
if (!isRoot && (obj.properties || obj.items)) {
|
|
906
|
+
if (depth > maxFound) {
|
|
907
|
+
maxFound = depth;
|
|
908
|
+
deepestPath = path;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (obj && typeof obj === "object") {
|
|
913
|
+
if (obj.properties) {
|
|
914
|
+
const nextDepth = depth + 1;
|
|
915
|
+
Object.keys(obj.properties).forEach((key) => {
|
|
916
|
+
traverse(
|
|
917
|
+
obj.properties[key],
|
|
918
|
+
nextDepth,
|
|
919
|
+
`${path}.${key}`.replace(/^\./, ""),
|
|
920
|
+
false,
|
|
921
|
+
);
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
if (obj.items) {
|
|
925
|
+
// 数组items不增加深度,或者根据需求增加
|
|
926
|
+
// 这里保持原逻辑:数组本身算一层,items内部继续
|
|
927
|
+
traverse(obj.items, depth, `${path}[]`, false);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
traverse(schema, 0, "", true);
|
|
933
|
+
|
|
934
|
+
return {
|
|
935
|
+
valid: maxFound <= maxDepth,
|
|
936
|
+
depth: maxFound,
|
|
937
|
+
path: deepestPath,
|
|
938
|
+
message:
|
|
939
|
+
maxFound > maxDepth
|
|
940
|
+
? `嵌套深度${maxFound}超过限制${maxDepth},路径: ${deepestPath}`
|
|
941
|
+
: `嵌套深度${maxFound}符合要求`,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// ========== 默认验证方法 ==========
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* 设置格式
|
|
949
|
+
* @param {string} format - 格式名称 (email, url, uuid, etc.)
|
|
950
|
+
* @returns {DslBuilder}
|
|
951
|
+
*/
|
|
952
|
+
format(format) {
|
|
953
|
+
this._baseSchema.format = format;
|
|
954
|
+
return this;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* 手机号别名
|
|
959
|
+
* @param {string} country
|
|
960
|
+
* @returns {DslBuilder}
|
|
961
|
+
*/
|
|
962
|
+
phoneNumber(country) {
|
|
963
|
+
return this.phone(country);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* 身份证验证
|
|
968
|
+
* @param {string} country - 国家代码 (目前仅支持 'cn')
|
|
969
|
+
* @returns {DslBuilder}
|
|
970
|
+
*/
|
|
971
|
+
idCard(country = "cn") {
|
|
972
|
+
if (country.toLowerCase() !== "cn") {
|
|
973
|
+
throw new Error(`Unsupported country for idCard: ${country}`);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// 中国身份证正则 (18位)
|
|
977
|
+
const pattern =
|
|
978
|
+
/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
|
|
979
|
+
|
|
980
|
+
// 自动设置长度
|
|
981
|
+
if (!this._baseSchema.minLength) this._baseSchema.minLength = 18;
|
|
982
|
+
if (!this._baseSchema.maxLength) this._baseSchema.maxLength = 18;
|
|
983
|
+
|
|
984
|
+
return this.pattern(pattern).messages({
|
|
985
|
+
pattern: "pattern.idCard.cn",
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* URL Slug 验证
|
|
991
|
+
* @returns {DslBuilder}
|
|
992
|
+
*/
|
|
993
|
+
slug() {
|
|
994
|
+
// 只能包含小写字母、数字和连字符,不能以连字符开头或结尾
|
|
995
|
+
const pattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
996
|
+
|
|
997
|
+
return this.pattern(pattern).messages({
|
|
998
|
+
pattern: "pattern.slug",
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* 用户名验证(自动设置合理约束)
|
|
1004
|
+
* @param {string|Object} preset - 预设长度或选项
|
|
1005
|
+
* - '5-20' → 长度5-20
|
|
1006
|
+
* - 'short' → 3-16(短用户名)
|
|
1007
|
+
* - 'medium' → 3-32(中等,默认)
|
|
1008
|
+
* - 'long' → 3-64(长用户名)
|
|
1009
|
+
* - { minLength, maxLength, allowUnderscore, allowNumber }
|
|
1010
|
+
* @returns {DslBuilder}
|
|
1011
|
+
*
|
|
1012
|
+
* @example
|
|
1013
|
+
* // 简洁写法(推荐)
|
|
1014
|
+
* username: 'string!'.username() // 自动3-32
|
|
1015
|
+
* username: 'string!'.username('5-20') // 长度5-20
|
|
1016
|
+
* username: 'string!'.username('short') // 短用户名3-16
|
|
1017
|
+
* username: 'string!'.username('long') // 长用户名3-64
|
|
1018
|
+
*/
|
|
1019
|
+
username(preset = "medium") {
|
|
1020
|
+
let minLength,
|
|
1021
|
+
maxLength,
|
|
1022
|
+
allowUnderscore = true,
|
|
1023
|
+
allowNumber = true;
|
|
1024
|
+
|
|
1025
|
+
// 解析预设
|
|
1026
|
+
if (typeof preset === "string") {
|
|
1027
|
+
// 字符串范围格式:'5-20'
|
|
1028
|
+
const rangeMatch = preset.match(/^(\d+)-(\d+)$/);
|
|
1029
|
+
if (rangeMatch) {
|
|
1030
|
+
minLength = parseInt(rangeMatch[1], 10);
|
|
1031
|
+
maxLength = parseInt(rangeMatch[2], 10);
|
|
1032
|
+
}
|
|
1033
|
+
// 预设枚举
|
|
1034
|
+
else {
|
|
1035
|
+
const presets = {
|
|
1036
|
+
short: { min: 3, max: 16 },
|
|
1037
|
+
medium: { min: 3, max: 32 },
|
|
1038
|
+
long: { min: 3, max: 64 },
|
|
1039
|
+
};
|
|
1040
|
+
const p = presets[preset] || presets.medium;
|
|
1041
|
+
minLength = p.min;
|
|
1042
|
+
maxLength = p.max;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
// 对象参数
|
|
1046
|
+
else if (typeof preset === "object") {
|
|
1047
|
+
minLength = preset.minLength || 3;
|
|
1048
|
+
maxLength = preset.maxLength || 32;
|
|
1049
|
+
allowUnderscore = preset.allowUnderscore !== false;
|
|
1050
|
+
allowNumber = preset.allowNumber !== false;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// 自动设置长度约束(如果未设置)
|
|
1054
|
+
if (!this._baseSchema.minLength) this._baseSchema.minLength = minLength;
|
|
1055
|
+
if (!this._baseSchema.maxLength) this._baseSchema.maxLength = maxLength;
|
|
1056
|
+
|
|
1057
|
+
// 设置正则验证
|
|
1058
|
+
let pattern = "^[a-zA-Z]";
|
|
1059
|
+
if (allowUnderscore && allowNumber) {
|
|
1060
|
+
pattern += "[a-zA-Z0-9_]*$";
|
|
1061
|
+
} else if (allowNumber) {
|
|
1062
|
+
pattern += "[a-zA-Z0-9]*$";
|
|
1063
|
+
} else {
|
|
1064
|
+
pattern += "[a-zA-Z]*$";
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
return this.pattern(new RegExp(pattern)).messages({
|
|
1068
|
+
pattern: "pattern.username",
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* 密码强度验证(自动设置合理约束)
|
|
1074
|
+
* @param {string} strength - 强度级别
|
|
1075
|
+
* @returns {DslBuilder}
|
|
1076
|
+
*
|
|
1077
|
+
* @example
|
|
1078
|
+
* password: 'string!'.password('strong') // 自动设置8-64长度
|
|
1079
|
+
*/
|
|
1080
|
+
password(strength = "medium") {
|
|
1081
|
+
const patterns = {
|
|
1082
|
+
weak: /.{6,}/,
|
|
1083
|
+
medium: /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/,
|
|
1084
|
+
strong: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
|
|
1085
|
+
veryStrong: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{10,}$/,
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
const minLengths = { weak: 6, medium: 8, strong: 8, veryStrong: 10 };
|
|
1089
|
+
|
|
1090
|
+
const pattern = patterns[strength];
|
|
1091
|
+
if (!pattern) {
|
|
1092
|
+
throw new Error(`Invalid password strength: ${strength}`);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// 自动设置长度约束
|
|
1096
|
+
if (!this._baseSchema.minLength)
|
|
1097
|
+
this._baseSchema.minLength = minLengths[strength];
|
|
1098
|
+
if (!this._baseSchema.maxLength) this._baseSchema.maxLength = 64;
|
|
1099
|
+
|
|
1100
|
+
return this.pattern(pattern).messages({
|
|
1101
|
+
pattern: `pattern.password.${strength}`,
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* 手机号验证(自动设置合理约束)
|
|
1107
|
+
* @param {string} country - 国家代码: cn|us|uk|hk|tw|international
|
|
1108
|
+
* @returns {DslBuilder}
|
|
1109
|
+
*
|
|
1110
|
+
* @example
|
|
1111
|
+
* phone: 'string!'.phone('cn') // ✅ 推荐
|
|
1112
|
+
* phone: 'number!'.phone('cn') // ✅ 自动纠正为 string
|
|
1113
|
+
*/
|
|
1114
|
+
phone(country = "cn") {
|
|
1115
|
+
// ✨ 自动纠正类型为 string(手机号不应该是 number)
|
|
1116
|
+
if (
|
|
1117
|
+
this._baseSchema.type === "number" ||
|
|
1118
|
+
this._baseSchema.type === "integer"
|
|
1119
|
+
) {
|
|
1120
|
+
this._baseSchema.type = "string";
|
|
1121
|
+
// 清理 number 类型的属性
|
|
1122
|
+
delete this._baseSchema.minimum;
|
|
1123
|
+
delete this._baseSchema.maximum;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const config = patterns.phone[country];
|
|
1127
|
+
if (!config) {
|
|
1128
|
+
throw new Error(`Unsupported country: ${country}`);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// 自动设置长度约束
|
|
1132
|
+
if (!this._baseSchema.minLength) this._baseSchema.minLength = config.min;
|
|
1133
|
+
if (!this._baseSchema.maxLength) this._baseSchema.maxLength = config.max;
|
|
1134
|
+
|
|
1135
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* 信用卡验证
|
|
1140
|
+
* @param {string} type - 卡类型: visa|mastercard|amex|discover|jcb|unionpay
|
|
1141
|
+
* @returns {DslBuilder}
|
|
1142
|
+
*/
|
|
1143
|
+
creditCard(type = "visa") {
|
|
1144
|
+
const config = patterns.creditCard[type.toLowerCase()];
|
|
1145
|
+
if (!config) {
|
|
1146
|
+
throw new Error(`Unsupported credit card type: ${type}`);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* 车牌号验证
|
|
1154
|
+
* @param {string} country - 国家代码
|
|
1155
|
+
* @returns {DslBuilder}
|
|
1156
|
+
*/
|
|
1157
|
+
licensePlate(country = "cn") {
|
|
1158
|
+
const config = patterns.licensePlate[country.toLowerCase()];
|
|
1159
|
+
if (!config) {
|
|
1160
|
+
throw new Error(`Unsupported country for licensePlate: ${country}`);
|
|
1161
|
+
}
|
|
1162
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* 邮政编码验证
|
|
1167
|
+
* @param {string} country - 国家代码
|
|
1168
|
+
* @returns {DslBuilder}
|
|
1169
|
+
*/
|
|
1170
|
+
postalCode(country = "cn") {
|
|
1171
|
+
const config = patterns.postalCode[country.toLowerCase()];
|
|
1172
|
+
if (!config) {
|
|
1173
|
+
throw new Error(`Unsupported country for postalCode: ${country}`);
|
|
1174
|
+
}
|
|
1175
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* 护照号码验证
|
|
1180
|
+
* @param {string} country - 国家代码
|
|
1181
|
+
* @returns {DslBuilder}
|
|
1182
|
+
*/
|
|
1183
|
+
passport(country = "cn") {
|
|
1184
|
+
const config = patterns.passport[country.toLowerCase()];
|
|
1185
|
+
if (!config) {
|
|
1186
|
+
throw new Error(`Unsupported country for passport: ${country}`);
|
|
1187
|
+
}
|
|
1188
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ========== v1.0.2 新增验证器方法 ==========
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* String 最小长度(使用AJV原生minLength)
|
|
1195
|
+
* @param {number} n - 最小长度
|
|
1196
|
+
* @returns {DslBuilder}
|
|
1197
|
+
*
|
|
1198
|
+
* @example
|
|
1199
|
+
* dsl('string!').min(3) // 最少3个字符
|
|
1200
|
+
*/
|
|
1201
|
+
min(n) {
|
|
1202
|
+
if (this._baseSchema.type !== "string") {
|
|
1203
|
+
throw new Error("min() only applies to string type");
|
|
1204
|
+
}
|
|
1205
|
+
this._baseSchema.minLength = n;
|
|
1206
|
+
return this;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* String 最大长度(使用AJV原生maxLength)
|
|
1211
|
+
* @param {number} n - 最大长度
|
|
1212
|
+
* @returns {DslBuilder}
|
|
1213
|
+
*
|
|
1214
|
+
* @example
|
|
1215
|
+
* dsl('string!').max(32) // 最多32个字符
|
|
1216
|
+
*/
|
|
1217
|
+
max(n) {
|
|
1218
|
+
if (this._baseSchema.type !== "string") {
|
|
1219
|
+
throw new Error("max() only applies to string type");
|
|
1220
|
+
}
|
|
1221
|
+
this._baseSchema.maxLength = n;
|
|
1222
|
+
return this;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* String 精确长度
|
|
1227
|
+
* @param {number} n - 精确长度
|
|
1228
|
+
* @returns {DslBuilder}
|
|
1229
|
+
*
|
|
1230
|
+
* @example
|
|
1231
|
+
* dsl('string!').length(11) // 必须是11个字符
|
|
1232
|
+
*/
|
|
1233
|
+
length(n) {
|
|
1234
|
+
if (this._baseSchema.type !== "string") {
|
|
1235
|
+
throw new Error("length() only applies to string type");
|
|
1236
|
+
}
|
|
1237
|
+
this._baseSchema.exactLength = n;
|
|
1238
|
+
return this;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* String 只能包含字母和数字
|
|
1243
|
+
* @returns {DslBuilder}
|
|
1244
|
+
*
|
|
1245
|
+
* @example
|
|
1246
|
+
* dsl('string!').alphanum() // 只能是字母和数字
|
|
1247
|
+
*/
|
|
1248
|
+
alphanum() {
|
|
1249
|
+
if (this._baseSchema.type !== "string") {
|
|
1250
|
+
throw new Error("alphanum() only applies to string type");
|
|
1251
|
+
}
|
|
1252
|
+
this._baseSchema.alphanum = true;
|
|
1253
|
+
return this;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* String 不能包含前后空格
|
|
1258
|
+
* @returns {DslBuilder}
|
|
1259
|
+
*
|
|
1260
|
+
* @example
|
|
1261
|
+
* dsl('string!').trim() // 不能有前后空格
|
|
1262
|
+
*/
|
|
1263
|
+
trim() {
|
|
1264
|
+
if (this._baseSchema.type !== "string") {
|
|
1265
|
+
throw new Error("trim() only applies to string type");
|
|
1266
|
+
}
|
|
1267
|
+
this._baseSchema.trim = true;
|
|
1268
|
+
return this;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* String 必须是小写
|
|
1273
|
+
* @returns {DslBuilder}
|
|
1274
|
+
*
|
|
1275
|
+
* @example
|
|
1276
|
+
* dsl('string!').lowercase() // 必须全小写
|
|
1277
|
+
*/
|
|
1278
|
+
lowercase() {
|
|
1279
|
+
if (this._baseSchema.type !== "string") {
|
|
1280
|
+
throw new Error("lowercase() only applies to string type");
|
|
1281
|
+
}
|
|
1282
|
+
this._baseSchema.lowercase = true;
|
|
1283
|
+
return this;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* String 必须是大写
|
|
1288
|
+
* @returns {DslBuilder}
|
|
1289
|
+
*
|
|
1290
|
+
* @example
|
|
1291
|
+
* dsl('string!').uppercase() // 必须全大写
|
|
1292
|
+
*/
|
|
1293
|
+
uppercase() {
|
|
1294
|
+
if (this._baseSchema.type !== "string") {
|
|
1295
|
+
throw new Error("uppercase() only applies to string type");
|
|
1296
|
+
}
|
|
1297
|
+
this._baseSchema.uppercase = true;
|
|
1298
|
+
return this;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Number 小数位数限制
|
|
1303
|
+
* @param {number} n - 最大小数位数
|
|
1304
|
+
* @returns {DslBuilder}
|
|
1305
|
+
*
|
|
1306
|
+
* @example
|
|
1307
|
+
* dsl('number!').precision(2) // 最多2位小数
|
|
1308
|
+
*/
|
|
1309
|
+
precision(n) {
|
|
1310
|
+
if (
|
|
1311
|
+
this._baseSchema.type !== "number" &&
|
|
1312
|
+
this._baseSchema.type !== "integer"
|
|
1313
|
+
) {
|
|
1314
|
+
throw new Error("precision() only applies to number type");
|
|
1315
|
+
}
|
|
1316
|
+
this._baseSchema.precision = n;
|
|
1317
|
+
return this;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Number 倍数验证(使用AJV原生multipleOf)
|
|
1322
|
+
* @param {number} n - 必须是此数的倍数
|
|
1323
|
+
* @returns {DslBuilder}
|
|
1324
|
+
*
|
|
1325
|
+
* @example
|
|
1326
|
+
* dsl('number!').multiple(5) // 必须是5的倍数
|
|
1327
|
+
*/
|
|
1328
|
+
multiple(n) {
|
|
1329
|
+
if (
|
|
1330
|
+
this._baseSchema.type !== "number" &&
|
|
1331
|
+
this._baseSchema.type !== "integer"
|
|
1332
|
+
) {
|
|
1333
|
+
throw new Error("multiple() only applies to number type");
|
|
1334
|
+
}
|
|
1335
|
+
this._baseSchema.multipleOf = n;
|
|
1336
|
+
return this;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* Number 端口号验证(1-65535)
|
|
1341
|
+
* @returns {DslBuilder}
|
|
1342
|
+
*
|
|
1343
|
+
* @example
|
|
1344
|
+
* dsl('integer!').port() // 必须是有效端口号
|
|
1345
|
+
*/
|
|
1346
|
+
port() {
|
|
1347
|
+
if (
|
|
1348
|
+
this._baseSchema.type !== "number" &&
|
|
1349
|
+
this._baseSchema.type !== "integer"
|
|
1350
|
+
) {
|
|
1351
|
+
throw new Error("port() only applies to number type");
|
|
1352
|
+
}
|
|
1353
|
+
this._baseSchema.port = true;
|
|
1354
|
+
return this;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Object 要求所有属性都必须存在
|
|
1359
|
+
* @returns {DslBuilder}
|
|
1360
|
+
*
|
|
1361
|
+
* @example
|
|
1362
|
+
* dsl({ name: 'string', age: 'number' }).requireAll()
|
|
1363
|
+
*/
|
|
1364
|
+
requireAll() {
|
|
1365
|
+
if (this._baseSchema.type !== "object") {
|
|
1366
|
+
throw new Error("requireAll() only applies to object type");
|
|
1367
|
+
}
|
|
1368
|
+
this._baseSchema.requiredAll = true;
|
|
1369
|
+
return this;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* Object 严格模式,不允许额外属性
|
|
1374
|
+
* @returns {DslBuilder}
|
|
1375
|
+
*
|
|
1376
|
+
* @example
|
|
1377
|
+
* dsl({ name: 'string!' }).strict()
|
|
1378
|
+
*/
|
|
1379
|
+
strict() {
|
|
1380
|
+
if (this._baseSchema.type !== "object") {
|
|
1381
|
+
throw new Error("strict() only applies to object type");
|
|
1382
|
+
}
|
|
1383
|
+
this._baseSchema.strictSchema = true;
|
|
1384
|
+
return this;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* Array 不允许稀疏数组
|
|
1389
|
+
* @returns {DslBuilder}
|
|
1390
|
+
*
|
|
1391
|
+
* @example
|
|
1392
|
+
* dsl('array<string>').noSparse()
|
|
1393
|
+
*/
|
|
1394
|
+
noSparse() {
|
|
1395
|
+
if (this._baseSchema.type !== "array") {
|
|
1396
|
+
throw new Error("noSparse() only applies to array type");
|
|
1397
|
+
}
|
|
1398
|
+
this._baseSchema.noSparse = true;
|
|
1399
|
+
return this;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/**
|
|
1403
|
+
* Array 必须包含指定元素
|
|
1404
|
+
* @param {Array} items - 必须包含的元素
|
|
1405
|
+
* @returns {DslBuilder}
|
|
1406
|
+
*
|
|
1407
|
+
* @example
|
|
1408
|
+
* dsl('array<string>').includesRequired(['admin', 'user'])
|
|
1409
|
+
*/
|
|
1410
|
+
includesRequired(items) {
|
|
1411
|
+
if (this._baseSchema.type !== "array") {
|
|
1412
|
+
throw new Error("includesRequired() only applies to array type");
|
|
1413
|
+
}
|
|
1414
|
+
if (!Array.isArray(items)) {
|
|
1415
|
+
throw new Error("includesRequired() requires an array parameter");
|
|
1416
|
+
}
|
|
1417
|
+
this._baseSchema.includesRequired = items;
|
|
1418
|
+
return this;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* Date 自定义日期格式验证
|
|
1423
|
+
* @param {string} fmt - 日期格式(YYYY-MM-DD, YYYY/MM/DD, DD-MM-YYYY, DD/MM/YYYY, ISO8601)
|
|
1424
|
+
* @returns {DslBuilder}
|
|
1425
|
+
*
|
|
1426
|
+
* @example
|
|
1427
|
+
* dsl('string!').dateFormat('YYYY-MM-DD')
|
|
1428
|
+
*/
|
|
1429
|
+
dateFormat(fmt) {
|
|
1430
|
+
if (this._baseSchema.type !== "string") {
|
|
1431
|
+
throw new Error("dateFormat() only applies to string type");
|
|
1432
|
+
}
|
|
1433
|
+
this._baseSchema.dateFormat = fmt;
|
|
1434
|
+
return this;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* Date 必须晚于指定日期
|
|
1439
|
+
* @param {string} date - 比较日期
|
|
1440
|
+
* @returns {DslBuilder}
|
|
1441
|
+
*
|
|
1442
|
+
* @example
|
|
1443
|
+
* dsl('date!').after('2024-01-01')
|
|
1444
|
+
*/
|
|
1445
|
+
after(date) {
|
|
1446
|
+
if (this._baseSchema.type !== "string") {
|
|
1447
|
+
throw new Error("after() only applies to string type");
|
|
1448
|
+
}
|
|
1449
|
+
this._baseSchema.dateGreater = date;
|
|
1450
|
+
return this;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* Date 必须早于指定日期
|
|
1455
|
+
* @param {string} date - 比较日期
|
|
1456
|
+
* @returns {DslBuilder}
|
|
1457
|
+
*
|
|
1458
|
+
* @example
|
|
1459
|
+
* dsl('date!').before('2025-12-31')
|
|
1460
|
+
*/
|
|
1461
|
+
before(date) {
|
|
1462
|
+
if (this._baseSchema.type !== "string") {
|
|
1463
|
+
throw new Error("before() only applies to string type");
|
|
1464
|
+
}
|
|
1465
|
+
this._baseSchema.dateLess = date;
|
|
1466
|
+
return this;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* Pattern 域名验证
|
|
1471
|
+
* @returns {DslBuilder}
|
|
1472
|
+
*
|
|
1473
|
+
* @example
|
|
1474
|
+
* dsl('string!').domain()
|
|
1475
|
+
*/
|
|
1476
|
+
domain() {
|
|
1477
|
+
if (this._baseSchema.type !== "string") {
|
|
1478
|
+
throw new Error("domain() only applies to string type");
|
|
1479
|
+
}
|
|
1480
|
+
const config = patterns.common.domain;
|
|
1481
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* Pattern IP地址验证(IPv4或IPv6)
|
|
1486
|
+
* @returns {DslBuilder}
|
|
1487
|
+
*
|
|
1488
|
+
* @example
|
|
1489
|
+
* dsl('string!').ip()
|
|
1490
|
+
*/
|
|
1491
|
+
ip() {
|
|
1492
|
+
if (this._baseSchema.type !== "string") {
|
|
1493
|
+
throw new Error("ip() only applies to string type");
|
|
1494
|
+
}
|
|
1495
|
+
const config = patterns.common.ip;
|
|
1496
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Pattern Base64编码验证
|
|
1501
|
+
* @returns {DslBuilder}
|
|
1502
|
+
*
|
|
1503
|
+
* @example
|
|
1504
|
+
* dsl('string!').base64()
|
|
1505
|
+
*/
|
|
1506
|
+
base64() {
|
|
1507
|
+
if (this._baseSchema.type !== "string") {
|
|
1508
|
+
throw new Error("base64() only applies to string type");
|
|
1509
|
+
}
|
|
1510
|
+
const config = patterns.common.base64;
|
|
1511
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Pattern JWT令牌验证
|
|
1516
|
+
* @returns {DslBuilder}
|
|
1517
|
+
*
|
|
1518
|
+
* @example
|
|
1519
|
+
* dsl('string!').jwt()
|
|
1520
|
+
*/
|
|
1521
|
+
jwt() {
|
|
1522
|
+
if (this._baseSchema.type !== "string") {
|
|
1523
|
+
throw new Error("jwt() only applies to string type");
|
|
1524
|
+
}
|
|
1525
|
+
const config = patterns.common.jwt;
|
|
1526
|
+
return this.pattern(config.pattern).messages({ pattern: config.key });
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Pattern JSON字符串验证
|
|
1531
|
+
* @returns {DslBuilder}
|
|
1532
|
+
*
|
|
1533
|
+
* @example
|
|
1534
|
+
* dsl('string!').json()
|
|
1535
|
+
*/
|
|
1536
|
+
json() {
|
|
1537
|
+
if (this._baseSchema.type !== "string") {
|
|
1538
|
+
throw new Error("json() only applies to string type");
|
|
1539
|
+
}
|
|
1540
|
+
this._baseSchema.jsonString = true;
|
|
1541
|
+
return this;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* Pattern URL slug验证 (v1.0.3)
|
|
1546
|
+
* URL slug只能包含小写字母、数字和连字符
|
|
1547
|
+
* @returns {DslBuilder}
|
|
1548
|
+
*
|
|
1549
|
+
* @example
|
|
1550
|
+
* dsl('string!').slug() // my-blog-post, hello-world-123
|
|
1551
|
+
*/
|
|
1552
|
+
slug() {
|
|
1553
|
+
if (this._baseSchema.type !== "string") {
|
|
1554
|
+
throw new Error("slug() only applies to string type");
|
|
1555
|
+
}
|
|
1556
|
+
this._baseSchema.pattern = "^[a-z0-9]+(?:-[a-z0-9]+)*$";
|
|
1557
|
+
this._baseSchema._customMessages = this._baseSchema._customMessages || {};
|
|
1558
|
+
this._baseSchema._customMessages["pattern"] = "pattern.slug";
|
|
1559
|
+
return this;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* 日期大于验证 (v1.0.2)
|
|
1564
|
+
* @param {string} date - 对比日期
|
|
1565
|
+
* @returns {DslBuilder}
|
|
1566
|
+
*
|
|
1567
|
+
* @example
|
|
1568
|
+
* dsl('string!').dateGreater('2025-01-01')
|
|
1569
|
+
*/
|
|
1570
|
+
dateGreater(date) {
|
|
1571
|
+
this._baseSchema.dateGreater = date;
|
|
1572
|
+
return this;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/**
|
|
1576
|
+
* 日期小于验证 (v1.0.2)
|
|
1577
|
+
* @param {string} date - 对比日期
|
|
1578
|
+
* @returns {DslBuilder}
|
|
1579
|
+
*
|
|
1580
|
+
* @example
|
|
1581
|
+
* dsl('string!').dateLess('2025-12-31')
|
|
1582
|
+
*/
|
|
1583
|
+
dateLess(date) {
|
|
1584
|
+
this._baseSchema.dateLess = date;
|
|
1585
|
+
return this;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
module.exports = DslBuilder;
|