schema-dsl 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +130 -113
- package/LICENSE +21 -21
- package/README.md +628 -628
- package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
- package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
- package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
- package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +75 -29
- package/dist/plugins/custom-format.cjs +33 -17
- package/dist/plugins/custom-format.d.cts +1 -1
- package/dist/plugins/custom-format.d.ts +1 -1
- package/dist/plugins/custom-format.js +33 -17
- package/dist/plugins/custom-type-example.cjs +33 -17
- package/dist/plugins/custom-type-example.d.cts +1 -1
- package/dist/plugins/custom-type-example.d.ts +1 -1
- package/dist/plugins/custom-type-example.js +33 -17
- package/dist/plugins/custom-validator.cjs +0 -2
- package/dist/plugins/custom-validator.d.cts +1 -1
- package/dist/plugins/custom-validator.d.ts +1 -1
- package/dist/plugins/custom-validator.js +0 -2
- package/docs/FEATURE-INDEX.md +553 -553
- package/docs/add-custom-locale.md +496 -496
- package/docs/add-keyword.md +24 -24
- package/docs/api-reference.md +1047 -1047
- package/docs/api.md +13 -13
- package/docs/best-practices-project-structure.md +417 -417
- package/docs/best-practices.md +712 -712
- package/docs/cache-manager.md +344 -344
- package/docs/compile.md +45 -45
- package/docs/conditional-api.md +1307 -1307
- package/docs/custom-extensions-guide.md +339 -339
- package/docs/design-philosophy.md +606 -606
- package/docs/doc-index.md +324 -324
- package/docs/dsl-syntax.md +714 -714
- package/docs/dynamic-locale.md +608 -608
- package/docs/enum.md +482 -482
- package/docs/error-handling.md +1975 -1975
- package/docs/export-guide.md +501 -501
- package/docs/export-limitations.md +567 -567
- package/docs/faq.md +596 -596
- package/docs/frontend-i18n-guide.md +307 -307
- package/docs/i18n-user-guide.md +487 -487
- package/docs/i18n.md +476 -476
- package/docs/index.md +48 -48
- package/docs/json-schema-basics.md +40 -40
- package/docs/label-vs-description.md +271 -271
- package/docs/markdown-exporter.md +406 -406
- package/docs/mongodb-exporter.md +302 -302
- package/docs/multi-language.md +26 -26
- package/docs/multi-type-support.md +322 -322
- package/docs/mysql-exporter.md +280 -280
- package/docs/number-operators.md +449 -449
- package/docs/optional-marker-guide.md +326 -326
- package/docs/performance-guide.md +49 -49
- package/docs/plugin-system.md +381 -381
- package/docs/plugin-type-registration.md +34 -34
- package/docs/postgresql-exporter.md +311 -311
- package/docs/public/favicon.svg +4 -4
- package/docs/quick-start.md +435 -435
- package/docs/runtime-locale-support.md +532 -532
- package/docs/schema-helper.md +345 -345
- package/docs/schema-utils-advanced-issues.md +23 -23
- package/docs/schema-utils-best-practices.md +20 -20
- package/docs/schema-utils-chaining.md +150 -150
- package/docs/schema-utils.md +524 -524
- package/docs/security-checklist.md +20 -20
- package/docs/string-extensions.md +488 -488
- package/docs/troubleshooting.md +486 -486
- package/docs/type-converter.md +310 -310
- package/docs/type-reference.md +242 -242
- package/docs/typescript-guide.md +584 -584
- package/docs/union-type-guide.md +157 -157
- package/docs/union-types.md +284 -284
- package/docs/validate-async.md +491 -491
- package/docs/validate-batch.md +49 -49
- package/docs/validate-dsl-object-support.md +578 -578
- package/docs/validate.md +506 -506
- package/docs/validation-guide.md +502 -502
- package/docs/validator.md +39 -39
- package/package.json +131 -131
- package/plugins/custom-format.cjs +8 -8
- package/plugins/custom-type-example.cjs +8 -8
- package/plugins/custom-validator.cjs +8 -8
- package/src/adapters/DslAdapter.ts +111 -111
- package/src/adapters/index.ts +1 -1
- package/src/config/constants.ts +83 -83
- package/src/config/index.ts +2 -2
- package/src/config/patterns.ts +77 -77
- package/src/core/CacheManager.ts +169 -159
- package/src/core/ConditionalBuilder.ts +382 -382
- package/src/core/ConditionalRuntime.ts +27 -27
- package/src/core/ConditionalValidator.ts +254 -254
- package/src/core/DslBuilder.ts +687 -677
- package/src/core/ErrorCodes.ts +38 -38
- package/src/core/ErrorFormatter.ts +271 -271
- package/src/core/JSONSchemaCore.ts +65 -65
- package/src/core/Locale.ts +187 -187
- package/src/core/MessageTemplate.ts +42 -42
- package/src/core/ObjectDslBuilder.ts +64 -64
- package/src/core/PluginManager.ts +326 -326
- package/src/core/StringExtensions.ts +140 -140
- package/src/core/TemplateEngine.ts +44 -44
- package/src/core/Validator.ts +448 -448
- package/src/errors/I18nError.ts +159 -159
- package/src/errors/ValidationError.ts +105 -105
- package/src/exporters/BaseExporter.ts +60 -60
- package/src/exporters/MarkdownExporter.ts +305 -305
- package/src/exporters/MongoDBExporter.ts +126 -126
- package/src/exporters/MySQLExporter.ts +156 -155
- package/src/exporters/PostgreSQLExporter.ts +222 -222
- package/src/exporters/index.ts +18 -18
- package/src/index.ts +651 -633
- package/src/locales/en-US.ts +160 -160
- package/src/locales/es-ES.ts +160 -160
- package/src/locales/fr-FR.ts +160 -160
- package/src/locales/index.ts +103 -103
- package/src/locales/ja-JP.ts +160 -160
- package/src/locales/types.ts +156 -156
- package/src/locales/zh-CN.ts +160 -160
- package/src/parser/ConstraintParser.ts +101 -101
- package/src/parser/DslParser.ts +470 -470
- package/src/parser/SchemaCompiler.ts +66 -66
- package/src/parser/TypeRegistry.ts +250 -250
- package/src/parser/index.ts +6 -6
- package/src/plugins/custom-format.ts +124 -126
- package/src/plugins/custom-type-example.ts +106 -108
- package/src/plugins/custom-validator.ts +138 -140
- package/src/types/conditional.ts +28 -28
- package/src/types/config.ts +59 -59
- package/src/types/dsl.ts +131 -131
- package/src/types/error.ts +60 -60
- package/src/types/index.ts +17 -17
- package/src/types/infer.ts +127 -127
- package/src/types/plugin.ts +58 -58
- package/src/types/safe-regex.d.ts +9 -9
- package/src/types/schema.ts +66 -66
- package/src/types/validate.ts +71 -71
- package/src/utils/SchemaHelper.ts +196 -196
- package/src/utils/SchemaUtils.ts +365 -346
- package/src/utils/TypeConverter.ts +215 -215
- package/src/utils/index.ts +10 -10
- package/src/validators/CustomKeywords.ts +477 -477
package/docs/dsl-syntax.md
CHANGED
|
@@ -1,714 +1,714 @@
|
|
|
1
|
-
# DSL 语法完整指南
|
|
2
|
-
|
|
3
|
-
> **更新时间**: 2026-05-22
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 📑 目录
|
|
8
|
-
|
|
9
|
-
- [快速开始](#快速开始)
|
|
10
|
-
- [完整类型列表](#完整类型列表)
|
|
11
|
-
- [基础语法](#基础语法)
|
|
12
|
-
- [约束语法](#约束语法)
|
|
13
|
-
- [数组语法](#数组语法)
|
|
14
|
-
- [对象语法](#对象语法)
|
|
15
|
-
- [条件验证 (Match)](#条件验证-match)
|
|
16
|
-
- [高级用法](#高级用法)
|
|
17
|
-
- [实现方案对比](#实现方案对比)
|
|
18
|
-
- [完整示例](#完整示例)
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 快速开始
|
|
23
|
-
|
|
24
|
-
```javascript
|
|
25
|
-
const { dsl } = require('schema-dsl');
|
|
26
|
-
|
|
27
|
-
// 基本类型
|
|
28
|
-
const schema = dsl({
|
|
29
|
-
name: 'string!', // 必填字符串
|
|
30
|
-
age: 'number', // 可选数字
|
|
31
|
-
email: 'email!', // 必填邮箱
|
|
32
|
-
active: 'boolean', // 布尔值
|
|
33
|
-
tags: 'array<string>' // 字符串数组
|
|
34
|
-
});
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## 完整类型列表
|
|
40
|
-
|
|
41
|
-
### 基本类型
|
|
42
|
-
|
|
43
|
-
| 类型 | DSL | 说明 |
|
|
44
|
-
|------|-----|------|
|
|
45
|
-
| 字符串 | `string` | 文本类型 |
|
|
46
|
-
| 数字 | `number` | 浮点数 |
|
|
47
|
-
| 整数 | `integer` | 整数 |
|
|
48
|
-
| 布尔 | `boolean` | true/false |
|
|
49
|
-
| 对象 | `object` | 嵌套对象 |
|
|
50
|
-
| 数组 | `array` | 数组类型 |
|
|
51
|
-
| 空值 | `null` | null值 |
|
|
52
|
-
| 任意 | `any` | 任意类型 |
|
|
53
|
-
|
|
54
|
-
### 格式类型
|
|
55
|
-
|
|
56
|
-
| 类型 | DSL | 说明 |
|
|
57
|
-
|------|-----|------|
|
|
58
|
-
| 邮箱 | `email` | 邮箱地址 |
|
|
59
|
-
| URL | `url` | 网址 |
|
|
60
|
-
| URI | `uri` | URI 地址 |
|
|
61
|
-
| UUID | `uuid` | UUID格式 |
|
|
62
|
-
| 日期 | `date` | YYYY-MM-DD |
|
|
63
|
-
| 日期时间 | `datetime` | ISO 8601 |
|
|
64
|
-
| 时间 | `time` | HH:mm:ss |
|
|
65
|
-
| 主机名 | `hostname` | 主机名 |
|
|
66
|
-
| IP(IPv4 / IPv6) | `ip` | 自动接受 IPv4 或 IPv6 |
|
|
67
|
-
| IPv4 | `ipv4` | IPv4地址 |
|
|
68
|
-
| IPv6 | `ipv6` | IPv6地址 |
|
|
69
|
-
| 二进制 | `binary` | Base64编码 |
|
|
70
|
-
|
|
71
|
-
### 特殊类型
|
|
72
|
-
|
|
73
|
-
| 类型 | DSL | 说明 |
|
|
74
|
-
|------|-----|------|
|
|
75
|
-
| ObjectId | `objectId` | MongoDB ObjectId |
|
|
76
|
-
| 十六进制颜色 | `hexColor` | CSS 十六进制颜色 |
|
|
77
|
-
| MAC 地址 | `macAddress` | MAC 地址 |
|
|
78
|
-
| Cron 表达式 | `cron` | 标准 Cron 表达式 |
|
|
79
|
-
| URL Slug | `slug` | 小写字母/数字/中横线组成的 URL 友好标识 |
|
|
80
|
-
| 中文姓名 | `chineseName` | 2 到 10 个中文字符 |
|
|
81
|
-
| 纯中文文本 | `chinese` | 仅允许中文字符 |
|
|
82
|
-
| 邮箱域名校验 | `emailDomain` | 邮箱格式基础上的域名约束类型 |
|
|
83
|
-
| 字母数字 | `alphanum` | 仅字母与数字 |
|
|
84
|
-
| 全小写字符串 | `lower` | 自动约束为小写字符串 |
|
|
85
|
-
| 全大写字符串 | `upper` | 自动约束为大写字符串 |
|
|
86
|
-
| JSON 字符串 | `json` | 内容需为合法 JSON 字符串 |
|
|
87
|
-
| 端口号 | `port` | 整数端口号 |
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 基础语法
|
|
93
|
-
|
|
94
|
-
### 1. 类型定义
|
|
95
|
-
|
|
96
|
-
```javascript
|
|
97
|
-
// 基本类型
|
|
98
|
-
'string' // 字符串
|
|
99
|
-
'number' // 数字
|
|
100
|
-
'integer' // 整数
|
|
101
|
-
'boolean' // 布尔
|
|
102
|
-
|
|
103
|
-
// 格式类型
|
|
104
|
-
'email' // 邮箱
|
|
105
|
-
'url' // URL
|
|
106
|
-
'date' // 日期
|
|
107
|
-
'uuid' // UUID
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### 2. 必填与可选标记
|
|
111
|
-
|
|
112
|
-
使用 `!` 标记必填字段,使用 `?` 显式标记可选字段:
|
|
113
|
-
|
|
114
|
-
```javascript
|
|
115
|
-
const schema = dsl({
|
|
116
|
-
username: 'string!', // 必填
|
|
117
|
-
nickname: 'string', // 可选(默认)
|
|
118
|
-
bio: 'string?', // 显式可选(等价于 string)
|
|
119
|
-
email: 'email?' // 可选邮箱
|
|
120
|
-
});
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**说明**:
|
|
124
|
-
- `!` - 必填标记,字段必须存在
|
|
125
|
-
- `?` - 可选标记,字段可省略(显式表达)
|
|
126
|
-
- 不加标记 - 默认可选(字段可省略)
|
|
127
|
-
|
|
128
|
-
**推荐**:
|
|
129
|
-
- 使用 `!` 明确标记必填字段
|
|
130
|
-
- 使用 `?` 在需要明确表达"可选"时增强代码可读性
|
|
131
|
-
|
|
132
|
-
### 3. 对象必填
|
|
133
|
-
|
|
134
|
-
支持两种方式:
|
|
135
|
-
|
|
136
|
-
```javascript
|
|
137
|
-
// 方式1: 字段内部必填
|
|
138
|
-
const schema1 = dsl({
|
|
139
|
-
user: {
|
|
140
|
-
name: 'string!', // name 必填(user 可选)
|
|
141
|
-
email: 'email!' // email 必填
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// 方式2: 对象本身必填 ✅ 推荐
|
|
146
|
-
const schema2 = dsl({
|
|
147
|
-
'user!': { // user 本身必填
|
|
148
|
-
name: 'string', // name 可选
|
|
149
|
-
email: 'email' // email 可选
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## 约束语法
|
|
157
|
-
|
|
158
|
-
### 1. 字符串长度
|
|
159
|
-
|
|
160
|
-
```javascript
|
|
161
|
-
'string:10' // 精确长度10(exactLength:minLength=10, maxLength=10)
|
|
162
|
-
'string:-10' // 最大长度10
|
|
163
|
-
'string:3-32' // 长度范围3-32
|
|
164
|
-
'string:10-' // 最小长度10(无最大限制)
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
**示例**:
|
|
168
|
-
```javascript
|
|
169
|
-
const schema = dsl({
|
|
170
|
-
username: 'string:3-32!', // 3-32字符,必填
|
|
171
|
-
bio: 'string:500', // 最大500字符
|
|
172
|
-
password: 'string:8-' // 最少8字符
|
|
173
|
-
});
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### 2. 数字范围
|
|
177
|
-
|
|
178
|
-
```javascript
|
|
179
|
-
'number:100' // 最大值100
|
|
180
|
-
'number:0-100' // 范围0-100
|
|
181
|
-
'number:18-' // 最小值18
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
**示例**:
|
|
185
|
-
```javascript
|
|
186
|
-
const schema = dsl({
|
|
187
|
-
age: 'number:18-120', // 18-120
|
|
188
|
-
score: 'number:100', // 0-100
|
|
189
|
-
price: 'number:0-' // ≥0
|
|
190
|
-
});
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### 3. 枚举值
|
|
194
|
-
|
|
195
|
-
使用 `|` 分隔枚举值:
|
|
196
|
-
|
|
197
|
-
```javascript
|
|
198
|
-
const schema = dsl({
|
|
199
|
-
status: 'active|inactive|pending',
|
|
200
|
-
gender: 'male|female|other!',
|
|
201
|
-
role: 'admin|user|guest'
|
|
202
|
-
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### 4. `types:` 联合类型
|
|
206
|
-
|
|
207
|
-
当一个字段需要接受多种不同类型时,可以使用 `types:` 前缀生成联合类型:
|
|
208
|
-
|
|
209
|
-
```javascript
|
|
210
|
-
const schema = dsl({
|
|
211
|
-
contact: 'types:email|phone',
|
|
212
|
-
price: 'types:number:0-|string:1-20',
|
|
213
|
-
payload: 'types:object|array<object>'
|
|
214
|
-
});
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
这个语法会被编译为 `oneOf` 结构,适合表达“满足其中任意一种类型即可”的场景。
|
|
218
|
-
|
|
219
|
-
**适用场景**:
|
|
220
|
-
- 联系方式允许邮箱或手机号
|
|
221
|
-
- 价格字段允许数值或说明字符串
|
|
222
|
-
- 兼容历史接口中同字段的多种输入格式
|
|
223
|
-
|
|
224
|
-
### 5. 特殊约束
|
|
225
|
-
|
|
226
|
-
支持特定格式的约束:
|
|
227
|
-
|
|
228
|
-
```javascript
|
|
229
|
-
'phone:cn' // 中国手机号
|
|
230
|
-
'idCard:cn' // 中国身份证
|
|
231
|
-
'creditCard:visa' // Visa信用卡
|
|
232
|
-
'licensePlate:cn' // 中国车牌
|
|
233
|
-
'postalCode:cn' // 中国邮编
|
|
234
|
-
'passport:cn' // 中国护照
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**示例**:
|
|
238
|
-
```javascript
|
|
239
|
-
const schema = dsl({
|
|
240
|
-
mobile: 'phone:cn!',
|
|
241
|
-
id: 'idCard:cn',
|
|
242
|
-
card: 'creditCard:mastercard'
|
|
243
|
-
});
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
|
-
## 数组语法
|
|
249
|
-
|
|
250
|
-
### 1. 基础数组
|
|
251
|
-
|
|
252
|
-
```javascript
|
|
253
|
-
'array' // 任意类型数组
|
|
254
|
-
'array<string>' // 字符串数组
|
|
255
|
-
'array<number>' // 数字数组
|
|
256
|
-
'array<integer>' // 整数数组
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### 2. 数组长度约束
|
|
260
|
-
|
|
261
|
-
```javascript
|
|
262
|
-
'array:1-10' // 1-10个元素
|
|
263
|
-
'array!1-10' // 1-10个元素,必填
|
|
264
|
-
'array:1-' // 至少1个元素
|
|
265
|
-
'array:-10' // 最多10个元素
|
|
266
|
-
'array:1-10<string>' // 1-10个字符串元素
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
**示例**:
|
|
270
|
-
```javascript
|
|
271
|
-
const schema = dsl({
|
|
272
|
-
tags: 'array!1-10<string>', // 必填,1-10个字符串
|
|
273
|
-
scores: 'array:1-5<number>', // 可选,1-5个数字
|
|
274
|
-
items: 'array:1-<string>' // 至少1个字符串
|
|
275
|
-
});
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### 3. 数组元素约束
|
|
279
|
-
|
|
280
|
-
```javascript
|
|
281
|
-
const schema = dsl({
|
|
282
|
-
tags: 'array<string:1-20>', // 每个字符串1-20字符
|
|
283
|
-
scores: 'array<number:0-100>', // 每个数字0-100
|
|
284
|
-
ids: 'array<integer:1->' // 每个整数≥1
|
|
285
|
-
});
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### 4. 嵌套数组
|
|
289
|
-
|
|
290
|
-
```javascript
|
|
291
|
-
// 二维数组
|
|
292
|
-
const schema = dsl({
|
|
293
|
-
matrix: 'array<array<number>>'
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// 对象数组
|
|
297
|
-
const schema = dsl({
|
|
298
|
-
users: 'array<object>',
|
|
299
|
-
// 或更详细定义
|
|
300
|
-
items: {
|
|
301
|
-
type: 'array',
|
|
302
|
-
items: {
|
|
303
|
-
name: 'string!',
|
|
304
|
-
age: 'number'
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
## 对象语法
|
|
313
|
-
|
|
314
|
-
### 1. 基础对象
|
|
315
|
-
|
|
316
|
-
```javascript
|
|
317
|
-
const schema = dsl({
|
|
318
|
-
user: {
|
|
319
|
-
name: 'string!',
|
|
320
|
-
email: 'email!',
|
|
321
|
-
age: 'number'
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### 2. 嵌套对象
|
|
327
|
-
|
|
328
|
-
```javascript
|
|
329
|
-
const schema = dsl({
|
|
330
|
-
user: {
|
|
331
|
-
profile: {
|
|
332
|
-
bio: 'string:500',
|
|
333
|
-
social: {
|
|
334
|
-
twitter: 'url',
|
|
335
|
-
github: 'url'
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### 3. 混合嵌套
|
|
343
|
-
|
|
344
|
-
```javascript
|
|
345
|
-
const schema = dsl({
|
|
346
|
-
'user!': { // user 必填
|
|
347
|
-
name: 'string!', // name 必填
|
|
348
|
-
contacts: 'array!1-5<object>', // 1-5个联系方式
|
|
349
|
-
tags: 'array<string:1-20>' // 字符串数组
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
---
|
|
355
|
-
|
|
356
|
-
## 条件验证 (Match)
|
|
357
|
-
|
|
358
|
-
支持更优雅的条件验证语法 `dsl.match` 和 `dsl.if`。
|
|
359
|
-
|
|
360
|
-
### 1. dsl.match (推荐)
|
|
361
|
-
|
|
362
|
-
类似于 `switch-case`,根据某个字段的值决定当前字段的验证规则。
|
|
363
|
-
|
|
364
|
-
**语法**:
|
|
365
|
-
```javascript
|
|
366
|
-
dsl.match(field, {
|
|
367
|
-
value1: 'schema1',
|
|
368
|
-
value2: 'schema2',
|
|
369
|
-
_default: 'defaultSchema' // 可选
|
|
370
|
-
})
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
**示例**:
|
|
374
|
-
```javascript
|
|
375
|
-
const schema = dsl({
|
|
376
|
-
contactType: 'email|phone',
|
|
377
|
-
|
|
378
|
-
// 根据 contactType 的值决定 contact 的规则
|
|
379
|
-
contact: dsl.match('contactType', {
|
|
380
|
-
email: 'email!', // contactType=email 时
|
|
381
|
-
phone: 'string:11!', // contactType=phone 时
|
|
382
|
-
_default: 'string' // 其他情况
|
|
383
|
-
})
|
|
384
|
-
});
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
**处理非英文值**:
|
|
388
|
-
如果条件值包含中文、数字或特殊字符,给键名加上引号即可:
|
|
389
|
-
|
|
390
|
-
```javascript
|
|
391
|
-
discount: dsl.match('level', {
|
|
392
|
-
'普通用户': 'number:0-5',
|
|
393
|
-
'VIP-1': 'number:0-20',
|
|
394
|
-
'100': 'number:0-50'
|
|
395
|
-
})
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### 2. dsl.if (简单条件)
|
|
399
|
-
|
|
400
|
-
适用于简单的二选一场景。
|
|
401
|
-
|
|
402
|
-
**语法**:
|
|
403
|
-
```javascript
|
|
404
|
-
dsl.if(conditionField, thenSchema, elseSchema)
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
**示例**:
|
|
408
|
-
```javascript
|
|
409
|
-
const schema = dsl({
|
|
410
|
-
isVip: 'boolean',
|
|
411
|
-
|
|
412
|
-
// 如果是VIP,折扣0-50,否则0-10
|
|
413
|
-
discount: dsl.if('isVip', 'number:0-50', 'number:0-10')
|
|
414
|
-
});
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
---
|
|
418
|
-
|
|
419
|
-
## 高级用法
|
|
420
|
-
|
|
421
|
-
### 1. 链式调用
|
|
422
|
-
|
|
423
|
-
> ⚠️ `.custom()` 当前仅支持同步自定义逻辑;异步业务校验请在验证通过后单独执行。
|
|
424
|
-
|
|
425
|
-
```javascript
|
|
426
|
-
const schema = dsl({
|
|
427
|
-
username: 'string:3-32!'
|
|
428
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
429
|
-
.label('用户名')
|
|
430
|
-
.messages({
|
|
431
|
-
'pattern': '只能包含字母、数字和下划线'
|
|
432
|
-
}),
|
|
433
|
-
|
|
434
|
-
email: 'email!'
|
|
435
|
-
.label('邮箱地址')
|
|
436
|
-
.description('用于登录和接收通知')
|
|
437
|
-
.custom((value) => {
|
|
438
|
-
if (value.endsWith('@blocked.example')) return '该邮箱域名不允许注册';
|
|
439
|
-
})
|
|
440
|
-
});
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
### 2. 默认验证器
|
|
444
|
-
|
|
445
|
-
```javascript
|
|
446
|
-
const schema = dsl({
|
|
447
|
-
username: 'string!'.username('5-20'), // 自动正则+长度
|
|
448
|
-
phone: 'string!'.phone('cn'), // 中国手机号
|
|
449
|
-
password: 'string!'.password('strong') // 强密码
|
|
450
|
-
});
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
---
|
|
454
|
-
|
|
455
|
-
## 注意事项
|
|
456
|
-
|
|
457
|
-
### 1. 条件验证
|
|
458
|
-
|
|
459
|
-
⚠️ **注意**: DSL 字符串不支持直接写条件逻辑
|
|
460
|
-
|
|
461
|
-
```javascript
|
|
462
|
-
'string | number' // ❌ 不支持
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
**解决方案**: 使用 `dsl.match` (推荐)
|
|
466
|
-
|
|
467
|
-
```javascript
|
|
468
|
-
// ✅ 推荐:使用 dsl.match
|
|
469
|
-
const schema = dsl({
|
|
470
|
-
vipLevel: 'string',
|
|
471
|
-
discount: dsl.match('vipLevel', {
|
|
472
|
-
gold: 'number:0-50',
|
|
473
|
-
silver: 'number:0-20',
|
|
474
|
-
normal: 'number:0-5'
|
|
475
|
-
})
|
|
476
|
-
});
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
---
|
|
480
|
-
|
|
481
|
-
### 2. 数组约束
|
|
482
|
-
|
|
483
|
-
✅ **推荐**: 使用简洁的 DSL 语法
|
|
484
|
-
```javascript
|
|
485
|
-
'array!1-10<string:1-20>' // 1-10个元素,每个1-20字符
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
⚠️ **也可以**: 使用完整 JSON Schema 格式(不推荐,太繁琐)
|
|
489
|
-
```javascript
|
|
490
|
-
{
|
|
491
|
-
type: 'array',
|
|
492
|
-
items: { type: 'string' },
|
|
493
|
-
minItems: 1,
|
|
494
|
-
maxItems: 10
|
|
495
|
-
}
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
---
|
|
499
|
-
|
|
500
|
-
### 3. 正则验证
|
|
501
|
-
|
|
502
|
-
⚠️ **注意**: DSL 字符串不支持直接写正则
|
|
503
|
-
|
|
504
|
-
```javascript
|
|
505
|
-
'string:/^[a-z]+$/' // ❌ 不支持
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
**解决方案**: 使用 `.pattern()` 方法
|
|
509
|
-
```javascript
|
|
510
|
-
'string!'.pattern(/^[a-z]+$/) // ✅ 推荐
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
---
|
|
514
|
-
|
|
515
|
-
### 4. 自定义验证
|
|
516
|
-
|
|
517
|
-
⚠️ **注意**: DSL 字符串不支持自定义逻辑
|
|
518
|
-
|
|
519
|
-
```javascript
|
|
520
|
-
'string!@custom' // ❌ 不支持
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
**解决方案**: 使用 `.custom()` 方法承载**同步**自定义逻辑
|
|
524
|
-
```javascript
|
|
525
|
-
'string!'.custom((value) => {
|
|
526
|
-
// 自定义同步逻辑
|
|
527
|
-
if (value === 'reserved') {
|
|
528
|
-
return '该值不可用';
|
|
529
|
-
}
|
|
530
|
-
})
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
异步校验(如数据库查重)请在 `validate()` / `validateAsync()` 通过后于业务层单独执行。
|
|
534
|
-
|
|
535
|
-
---
|
|
536
|
-
|
|
537
|
-
### 5. 对象数组详细定义
|
|
538
|
-
|
|
539
|
-
⚠️ **注意**: DSL 简写不支持对象数组的详细定义
|
|
540
|
-
|
|
541
|
-
```javascript
|
|
542
|
-
'array<object{name:string,age:number}>' // ❌ 不支持
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
**解决方案**: 使用完整对象定义
|
|
546
|
-
```javascript
|
|
547
|
-
const schema = dsl({
|
|
548
|
-
users: {
|
|
549
|
-
type: 'array',
|
|
550
|
-
items: {
|
|
551
|
-
name: 'string!',
|
|
552
|
-
age: 'number:18-'
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
---
|
|
559
|
-
|
|
560
|
-
## 完整示例
|
|
561
|
-
|
|
562
|
-
### 用户注册表单
|
|
563
|
-
|
|
564
|
-
```javascript
|
|
565
|
-
const { dsl } = require('schema-dsl');
|
|
566
|
-
|
|
567
|
-
const schema = dsl({
|
|
568
|
-
// 基本信息
|
|
569
|
-
username: 'string:3-32!'.username().label('用户名'),
|
|
570
|
-
password: 'string!'.password('strong').label('密码'),
|
|
571
|
-
email: 'email!'.label('邮箱'),
|
|
572
|
-
phone: 'string!'.phone('cn').label('手机号'),
|
|
573
|
-
|
|
574
|
-
// 个人资料
|
|
575
|
-
'profile!': {
|
|
576
|
-
realName: 'string:2-50',
|
|
577
|
-
gender: 'male|female|other',
|
|
578
|
-
birthday: 'date',
|
|
579
|
-
bio: 'string:500'
|
|
580
|
-
},
|
|
581
|
-
|
|
582
|
-
// 地址信息
|
|
583
|
-
addresses: 'array:1-5<object>', // 1-5个地址
|
|
584
|
-
|
|
585
|
-
// 标签
|
|
586
|
-
tags: 'array:1-10<string:1-20>', // 1-10个标签,每个1-20字符
|
|
587
|
-
|
|
588
|
-
// 同意条款
|
|
589
|
-
agree: 'boolean!'
|
|
590
|
-
});
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
### 电商商品 Schema
|
|
594
|
-
|
|
595
|
-
```javascript
|
|
596
|
-
const schema = dsl({
|
|
597
|
-
// 商品基本信息
|
|
598
|
-
title: 'string:1-100!',
|
|
599
|
-
price: 'number:0-!',
|
|
600
|
-
stock: 'integer:0-',
|
|
601
|
-
status: 'on_sale|off_sale|sold_out!',
|
|
602
|
-
|
|
603
|
-
// 商品详情
|
|
604
|
-
'details!': {
|
|
605
|
-
description: 'string:10000',
|
|
606
|
-
images: 'array!1-10<url>',
|
|
607
|
-
specs: 'array<object>',
|
|
608
|
-
tags: 'array:1-20<string:1-30>'
|
|
609
|
-
},
|
|
610
|
-
|
|
611
|
-
// SKU信息
|
|
612
|
-
skus: {
|
|
613
|
-
type: 'array',
|
|
614
|
-
minItems: 1,
|
|
615
|
-
items: {
|
|
616
|
-
sku_code: 'string!',
|
|
617
|
-
price: 'number!',
|
|
618
|
-
stock: 'integer!'
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
});
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### API 请求验证
|
|
625
|
-
|
|
626
|
-
```javascript
|
|
627
|
-
const schema = dsl({
|
|
628
|
-
// 查询参数
|
|
629
|
-
page: 'integer:1-',
|
|
630
|
-
pageSize: 'integer:10-100',
|
|
631
|
-
keyword: 'string:1-50',
|
|
632
|
-
|
|
633
|
-
// 筛选条件
|
|
634
|
-
filters: {
|
|
635
|
-
category: 'array<string>',
|
|
636
|
-
priceRange: {
|
|
637
|
-
min: 'number:0-',
|
|
638
|
-
max: 'number:0-'
|
|
639
|
-
},
|
|
640
|
-
status: 'active|inactive'
|
|
641
|
-
},
|
|
642
|
-
|
|
643
|
-
// 排序
|
|
644
|
-
sort: {
|
|
645
|
-
field: 'price|created_at|sales',
|
|
646
|
-
order: 'asc|desc'
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
---
|
|
652
|
-
|
|
653
|
-
## 常见问题
|
|
654
|
-
|
|
655
|
-
### Q1: 为什么移除了简写功能?
|
|
656
|
-
|
|
657
|
-
**A**: 为了降低学习成本和减少歧义。使用完整类型名更清晰,特别是对新手更友好。
|
|
658
|
-
|
|
659
|
-
### Q2: 数组长度约束怎么写?
|
|
660
|
-
|
|
661
|
-
**A**: 支持直接在DSL中写:
|
|
662
|
-
```javascript
|
|
663
|
-
'array!1-10<string>' // 推荐
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### Q3: 如何定义对象数组?
|
|
667
|
-
|
|
668
|
-
**A**: 使用完整对象定义:
|
|
669
|
-
```javascript
|
|
670
|
-
const schema = dsl({
|
|
671
|
-
users: {
|
|
672
|
-
type: 'array',
|
|
673
|
-
items: {
|
|
674
|
-
name: 'string!',
|
|
675
|
-
email: 'email!'
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
### Q4: 不支持条件验证吗?
|
|
682
|
-
|
|
683
|
-
**A**: 支持。推荐使用 `dsl.match`:
|
|
684
|
-
```javascript
|
|
685
|
-
dsl.match('vipLevel', { gold: 'number:0-50', silver: 'number:0-20' })
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
### Q5: 能用正则验证吗?
|
|
689
|
-
|
|
690
|
-
**A**: 能,使用 `.pattern()` 方法:
|
|
691
|
-
```javascript
|
|
692
|
-
'string!'.pattern(/^[a-z]+$/)
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
---
|
|
696
|
-
|
|
697
|
-
## 相关文档
|
|
698
|
-
|
|
699
|
-
- [类型参考](./type-reference.md) - 完整类型列表
|
|
700
|
-
- [String 扩展](./string-extensions.md) - 链式调用
|
|
701
|
-
- [快速开始](./quick-start.md) - 5分钟上手
|
|
702
|
-
|
|
703
|
-
---
|
|
704
|
-
|
|
705
|
-
## 对应示例文件
|
|
706
|
-
|
|
707
|
-
**示例入口**: [dsl-syntax.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/dsl-syntax.ts)
|
|
708
|
-
**说明**: 覆盖 Batch 1 中 DSL 语法的基础类型、约束、枚举、数组和嵌套对象写法,可直接运行参考。
|
|
709
|
-
|
|
710
|
-
---
|
|
711
|
-
|
|
712
|
-
**最后更新**: 2026-05-08
|
|
713
|
-
**作者**: schema-dsl Team
|
|
714
|
-
|
|
1
|
+
# DSL 语法完整指南
|
|
2
|
+
|
|
3
|
+
> **更新时间**: 2026-05-22
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📑 目录
|
|
8
|
+
|
|
9
|
+
- [快速开始](#快速开始)
|
|
10
|
+
- [完整类型列表](#完整类型列表)
|
|
11
|
+
- [基础语法](#基础语法)
|
|
12
|
+
- [约束语法](#约束语法)
|
|
13
|
+
- [数组语法](#数组语法)
|
|
14
|
+
- [对象语法](#对象语法)
|
|
15
|
+
- [条件验证 (Match)](#条件验证-match)
|
|
16
|
+
- [高级用法](#高级用法)
|
|
17
|
+
- [实现方案对比](#实现方案对比)
|
|
18
|
+
- [完整示例](#完整示例)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 快速开始
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const { dsl } = require('schema-dsl');
|
|
26
|
+
|
|
27
|
+
// 基本类型
|
|
28
|
+
const schema = dsl({
|
|
29
|
+
name: 'string!', // 必填字符串
|
|
30
|
+
age: 'number', // 可选数字
|
|
31
|
+
email: 'email!', // 必填邮箱
|
|
32
|
+
active: 'boolean', // 布尔值
|
|
33
|
+
tags: 'array<string>' // 字符串数组
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 完整类型列表
|
|
40
|
+
|
|
41
|
+
### 基本类型
|
|
42
|
+
|
|
43
|
+
| 类型 | DSL | 说明 |
|
|
44
|
+
|------|-----|------|
|
|
45
|
+
| 字符串 | `string` | 文本类型 |
|
|
46
|
+
| 数字 | `number` | 浮点数 |
|
|
47
|
+
| 整数 | `integer` | 整数 |
|
|
48
|
+
| 布尔 | `boolean` | true/false |
|
|
49
|
+
| 对象 | `object` | 嵌套对象 |
|
|
50
|
+
| 数组 | `array` | 数组类型 |
|
|
51
|
+
| 空值 | `null` | null值 |
|
|
52
|
+
| 任意 | `any` | 任意类型 |
|
|
53
|
+
|
|
54
|
+
### 格式类型
|
|
55
|
+
|
|
56
|
+
| 类型 | DSL | 说明 |
|
|
57
|
+
|------|-----|------|
|
|
58
|
+
| 邮箱 | `email` | 邮箱地址 |
|
|
59
|
+
| URL | `url` | 网址 |
|
|
60
|
+
| URI | `uri` | URI 地址 |
|
|
61
|
+
| UUID | `uuid` | UUID格式 |
|
|
62
|
+
| 日期 | `date` | YYYY-MM-DD |
|
|
63
|
+
| 日期时间 | `datetime` | ISO 8601 |
|
|
64
|
+
| 时间 | `time` | HH:mm:ss |
|
|
65
|
+
| 主机名 | `hostname` | 主机名 |
|
|
66
|
+
| IP(IPv4 / IPv6) | `ip` | 自动接受 IPv4 或 IPv6 |
|
|
67
|
+
| IPv4 | `ipv4` | IPv4地址 |
|
|
68
|
+
| IPv6 | `ipv6` | IPv6地址 |
|
|
69
|
+
| 二进制 | `binary` | Base64编码 |
|
|
70
|
+
|
|
71
|
+
### 特殊类型
|
|
72
|
+
|
|
73
|
+
| 类型 | DSL | 说明 |
|
|
74
|
+
|------|-----|------|
|
|
75
|
+
| ObjectId | `objectId` | MongoDB ObjectId |
|
|
76
|
+
| 十六进制颜色 | `hexColor` | CSS 十六进制颜色 |
|
|
77
|
+
| MAC 地址 | `macAddress` | MAC 地址 |
|
|
78
|
+
| Cron 表达式 | `cron` | 标准 Cron 表达式 |
|
|
79
|
+
| URL Slug | `slug` | 小写字母/数字/中横线组成的 URL 友好标识 |
|
|
80
|
+
| 中文姓名 | `chineseName` | 2 到 10 个中文字符 |
|
|
81
|
+
| 纯中文文本 | `chinese` | 仅允许中文字符 |
|
|
82
|
+
| 邮箱域名校验 | `emailDomain` | 邮箱格式基础上的域名约束类型 |
|
|
83
|
+
| 字母数字 | `alphanum` | 仅字母与数字 |
|
|
84
|
+
| 全小写字符串 | `lower` | 自动约束为小写字符串 |
|
|
85
|
+
| 全大写字符串 | `upper` | 自动约束为大写字符串 |
|
|
86
|
+
| JSON 字符串 | `json` | 内容需为合法 JSON 字符串 |
|
|
87
|
+
| 端口号 | `port` | 整数端口号 |
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 基础语法
|
|
93
|
+
|
|
94
|
+
### 1. 类型定义
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// 基本类型
|
|
98
|
+
'string' // 字符串
|
|
99
|
+
'number' // 数字
|
|
100
|
+
'integer' // 整数
|
|
101
|
+
'boolean' // 布尔
|
|
102
|
+
|
|
103
|
+
// 格式类型
|
|
104
|
+
'email' // 邮箱
|
|
105
|
+
'url' // URL
|
|
106
|
+
'date' // 日期
|
|
107
|
+
'uuid' // UUID
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 2. 必填与可选标记
|
|
111
|
+
|
|
112
|
+
使用 `!` 标记必填字段,使用 `?` 显式标记可选字段:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const schema = dsl({
|
|
116
|
+
username: 'string!', // 必填
|
|
117
|
+
nickname: 'string', // 可选(默认)
|
|
118
|
+
bio: 'string?', // 显式可选(等价于 string)
|
|
119
|
+
email: 'email?' // 可选邮箱
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**说明**:
|
|
124
|
+
- `!` - 必填标记,字段必须存在
|
|
125
|
+
- `?` - 可选标记,字段可省略(显式表达)
|
|
126
|
+
- 不加标记 - 默认可选(字段可省略)
|
|
127
|
+
|
|
128
|
+
**推荐**:
|
|
129
|
+
- 使用 `!` 明确标记必填字段
|
|
130
|
+
- 使用 `?` 在需要明确表达"可选"时增强代码可读性
|
|
131
|
+
|
|
132
|
+
### 3. 对象必填
|
|
133
|
+
|
|
134
|
+
支持两种方式:
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
// 方式1: 字段内部必填
|
|
138
|
+
const schema1 = dsl({
|
|
139
|
+
user: {
|
|
140
|
+
name: 'string!', // name 必填(user 可选)
|
|
141
|
+
email: 'email!' // email 必填
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 方式2: 对象本身必填 ✅ 推荐
|
|
146
|
+
const schema2 = dsl({
|
|
147
|
+
'user!': { // user 本身必填
|
|
148
|
+
name: 'string', // name 可选
|
|
149
|
+
email: 'email' // email 可选
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 约束语法
|
|
157
|
+
|
|
158
|
+
### 1. 字符串长度
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
'string:10' // 精确长度10(exactLength:minLength=10, maxLength=10)
|
|
162
|
+
'string:-10' // 最大长度10
|
|
163
|
+
'string:3-32' // 长度范围3-32
|
|
164
|
+
'string:10-' // 最小长度10(无最大限制)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**示例**:
|
|
168
|
+
```javascript
|
|
169
|
+
const schema = dsl({
|
|
170
|
+
username: 'string:3-32!', // 3-32字符,必填
|
|
171
|
+
bio: 'string:500', // 最大500字符
|
|
172
|
+
password: 'string:8-' // 最少8字符
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 2. 数字范围
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
'number:100' // 最大值100
|
|
180
|
+
'number:0-100' // 范围0-100
|
|
181
|
+
'number:18-' // 最小值18
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**示例**:
|
|
185
|
+
```javascript
|
|
186
|
+
const schema = dsl({
|
|
187
|
+
age: 'number:18-120', // 18-120
|
|
188
|
+
score: 'number:100', // 0-100
|
|
189
|
+
price: 'number:0-' // ≥0
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 3. 枚举值
|
|
194
|
+
|
|
195
|
+
使用 `|` 分隔枚举值:
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
const schema = dsl({
|
|
199
|
+
status: 'active|inactive|pending',
|
|
200
|
+
gender: 'male|female|other!',
|
|
201
|
+
role: 'admin|user|guest'
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 4. `types:` 联合类型
|
|
206
|
+
|
|
207
|
+
当一个字段需要接受多种不同类型时,可以使用 `types:` 前缀生成联合类型:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
const schema = dsl({
|
|
211
|
+
contact: 'types:email|phone',
|
|
212
|
+
price: 'types:number:0-|string:1-20',
|
|
213
|
+
payload: 'types:object|array<object>'
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
这个语法会被编译为 `oneOf` 结构,适合表达“满足其中任意一种类型即可”的场景。
|
|
218
|
+
|
|
219
|
+
**适用场景**:
|
|
220
|
+
- 联系方式允许邮箱或手机号
|
|
221
|
+
- 价格字段允许数值或说明字符串
|
|
222
|
+
- 兼容历史接口中同字段的多种输入格式
|
|
223
|
+
|
|
224
|
+
### 5. 特殊约束
|
|
225
|
+
|
|
226
|
+
支持特定格式的约束:
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
'phone:cn' // 中国手机号
|
|
230
|
+
'idCard:cn' // 中国身份证
|
|
231
|
+
'creditCard:visa' // Visa信用卡
|
|
232
|
+
'licensePlate:cn' // 中国车牌
|
|
233
|
+
'postalCode:cn' // 中国邮编
|
|
234
|
+
'passport:cn' // 中国护照
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**示例**:
|
|
238
|
+
```javascript
|
|
239
|
+
const schema = dsl({
|
|
240
|
+
mobile: 'phone:cn!',
|
|
241
|
+
id: 'idCard:cn',
|
|
242
|
+
card: 'creditCard:mastercard'
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 数组语法
|
|
249
|
+
|
|
250
|
+
### 1. 基础数组
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
'array' // 任意类型数组
|
|
254
|
+
'array<string>' // 字符串数组
|
|
255
|
+
'array<number>' // 数字数组
|
|
256
|
+
'array<integer>' // 整数数组
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 2. 数组长度约束
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
'array:1-10' // 1-10个元素
|
|
263
|
+
'array!1-10' // 1-10个元素,必填
|
|
264
|
+
'array:1-' // 至少1个元素
|
|
265
|
+
'array:-10' // 最多10个元素
|
|
266
|
+
'array:1-10<string>' // 1-10个字符串元素
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**示例**:
|
|
270
|
+
```javascript
|
|
271
|
+
const schema = dsl({
|
|
272
|
+
tags: 'array!1-10<string>', // 必填,1-10个字符串
|
|
273
|
+
scores: 'array:1-5<number>', // 可选,1-5个数字
|
|
274
|
+
items: 'array:1-<string>' // 至少1个字符串
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 3. 数组元素约束
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
const schema = dsl({
|
|
282
|
+
tags: 'array<string:1-20>', // 每个字符串1-20字符
|
|
283
|
+
scores: 'array<number:0-100>', // 每个数字0-100
|
|
284
|
+
ids: 'array<integer:1->' // 每个整数≥1
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 4. 嵌套数组
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
// 二维数组
|
|
292
|
+
const schema = dsl({
|
|
293
|
+
matrix: 'array<array<number>>'
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// 对象数组
|
|
297
|
+
const schema = dsl({
|
|
298
|
+
users: 'array<object>',
|
|
299
|
+
// 或更详细定义
|
|
300
|
+
items: {
|
|
301
|
+
type: 'array',
|
|
302
|
+
items: {
|
|
303
|
+
name: 'string!',
|
|
304
|
+
age: 'number'
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 对象语法
|
|
313
|
+
|
|
314
|
+
### 1. 基础对象
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
const schema = dsl({
|
|
318
|
+
user: {
|
|
319
|
+
name: 'string!',
|
|
320
|
+
email: 'email!',
|
|
321
|
+
age: 'number'
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### 2. 嵌套对象
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
const schema = dsl({
|
|
330
|
+
user: {
|
|
331
|
+
profile: {
|
|
332
|
+
bio: 'string:500',
|
|
333
|
+
social: {
|
|
334
|
+
twitter: 'url',
|
|
335
|
+
github: 'url'
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 3. 混合嵌套
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
const schema = dsl({
|
|
346
|
+
'user!': { // user 必填
|
|
347
|
+
name: 'string!', // name 必填
|
|
348
|
+
contacts: 'array!1-5<object>', // 1-5个联系方式
|
|
349
|
+
tags: 'array<string:1-20>' // 字符串数组
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## 条件验证 (Match)
|
|
357
|
+
|
|
358
|
+
支持更优雅的条件验证语法 `dsl.match` 和 `dsl.if`。
|
|
359
|
+
|
|
360
|
+
### 1. dsl.match (推荐)
|
|
361
|
+
|
|
362
|
+
类似于 `switch-case`,根据某个字段的值决定当前字段的验证规则。
|
|
363
|
+
|
|
364
|
+
**语法**:
|
|
365
|
+
```javascript
|
|
366
|
+
dsl.match(field, {
|
|
367
|
+
value1: 'schema1',
|
|
368
|
+
value2: 'schema2',
|
|
369
|
+
_default: 'defaultSchema' // 可选
|
|
370
|
+
})
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**示例**:
|
|
374
|
+
```javascript
|
|
375
|
+
const schema = dsl({
|
|
376
|
+
contactType: 'email|phone',
|
|
377
|
+
|
|
378
|
+
// 根据 contactType 的值决定 contact 的规则
|
|
379
|
+
contact: dsl.match('contactType', {
|
|
380
|
+
email: 'email!', // contactType=email 时
|
|
381
|
+
phone: 'string:11!', // contactType=phone 时
|
|
382
|
+
_default: 'string' // 其他情况
|
|
383
|
+
})
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**处理非英文值**:
|
|
388
|
+
如果条件值包含中文、数字或特殊字符,给键名加上引号即可:
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
discount: dsl.match('level', {
|
|
392
|
+
'普通用户': 'number:0-5',
|
|
393
|
+
'VIP-1': 'number:0-20',
|
|
394
|
+
'100': 'number:0-50'
|
|
395
|
+
})
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### 2. dsl.if (简单条件)
|
|
399
|
+
|
|
400
|
+
适用于简单的二选一场景。
|
|
401
|
+
|
|
402
|
+
**语法**:
|
|
403
|
+
```javascript
|
|
404
|
+
dsl.if(conditionField, thenSchema, elseSchema)
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**示例**:
|
|
408
|
+
```javascript
|
|
409
|
+
const schema = dsl({
|
|
410
|
+
isVip: 'boolean',
|
|
411
|
+
|
|
412
|
+
// 如果是VIP,折扣0-50,否则0-10
|
|
413
|
+
discount: dsl.if('isVip', 'number:0-50', 'number:0-10')
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## 高级用法
|
|
420
|
+
|
|
421
|
+
### 1. 链式调用
|
|
422
|
+
|
|
423
|
+
> ⚠️ `.custom()` 当前仅支持同步自定义逻辑;异步业务校验请在验证通过后单独执行。
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
const schema = dsl({
|
|
427
|
+
username: 'string:3-32!'
|
|
428
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
429
|
+
.label('用户名')
|
|
430
|
+
.messages({
|
|
431
|
+
'pattern': '只能包含字母、数字和下划线'
|
|
432
|
+
}),
|
|
433
|
+
|
|
434
|
+
email: 'email!'
|
|
435
|
+
.label('邮箱地址')
|
|
436
|
+
.description('用于登录和接收通知')
|
|
437
|
+
.custom((value) => {
|
|
438
|
+
if (value.endsWith('@blocked.example')) return '该邮箱域名不允许注册';
|
|
439
|
+
})
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### 2. 默认验证器
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
const schema = dsl({
|
|
447
|
+
username: 'string!'.username('5-20'), // 自动正则+长度
|
|
448
|
+
phone: 'string!'.phone('cn'), // 中国手机号
|
|
449
|
+
password: 'string!'.password('strong') // 强密码
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## 注意事项
|
|
456
|
+
|
|
457
|
+
### 1. 条件验证
|
|
458
|
+
|
|
459
|
+
⚠️ **注意**: DSL 字符串不支持直接写条件逻辑
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
'string | number' // ❌ 不支持
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**解决方案**: 使用 `dsl.match` (推荐)
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
// ✅ 推荐:使用 dsl.match
|
|
469
|
+
const schema = dsl({
|
|
470
|
+
vipLevel: 'string',
|
|
471
|
+
discount: dsl.match('vipLevel', {
|
|
472
|
+
gold: 'number:0-50',
|
|
473
|
+
silver: 'number:0-20',
|
|
474
|
+
normal: 'number:0-5'
|
|
475
|
+
})
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
### 2. 数组约束
|
|
482
|
+
|
|
483
|
+
✅ **推荐**: 使用简洁的 DSL 语法
|
|
484
|
+
```javascript
|
|
485
|
+
'array!1-10<string:1-20>' // 1-10个元素,每个1-20字符
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
⚠️ **也可以**: 使用完整 JSON Schema 格式(不推荐,太繁琐)
|
|
489
|
+
```javascript
|
|
490
|
+
{
|
|
491
|
+
type: 'array',
|
|
492
|
+
items: { type: 'string' },
|
|
493
|
+
minItems: 1,
|
|
494
|
+
maxItems: 10
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
### 3. 正则验证
|
|
501
|
+
|
|
502
|
+
⚠️ **注意**: DSL 字符串不支持直接写正则
|
|
503
|
+
|
|
504
|
+
```javascript
|
|
505
|
+
'string:/^[a-z]+$/' // ❌ 不支持
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**解决方案**: 使用 `.pattern()` 方法
|
|
509
|
+
```javascript
|
|
510
|
+
'string!'.pattern(/^[a-z]+$/) // ✅ 推荐
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
### 4. 自定义验证
|
|
516
|
+
|
|
517
|
+
⚠️ **注意**: DSL 字符串不支持自定义逻辑
|
|
518
|
+
|
|
519
|
+
```javascript
|
|
520
|
+
'string!@custom' // ❌ 不支持
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**解决方案**: 使用 `.custom()` 方法承载**同步**自定义逻辑
|
|
524
|
+
```javascript
|
|
525
|
+
'string!'.custom((value) => {
|
|
526
|
+
// 自定义同步逻辑
|
|
527
|
+
if (value === 'reserved') {
|
|
528
|
+
return '该值不可用';
|
|
529
|
+
}
|
|
530
|
+
})
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
异步校验(如数据库查重)请在 `validate()` / `validateAsync()` 通过后于业务层单独执行。
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
### 5. 对象数组详细定义
|
|
538
|
+
|
|
539
|
+
⚠️ **注意**: DSL 简写不支持对象数组的详细定义
|
|
540
|
+
|
|
541
|
+
```javascript
|
|
542
|
+
'array<object{name:string,age:number}>' // ❌ 不支持
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**解决方案**: 使用完整对象定义
|
|
546
|
+
```javascript
|
|
547
|
+
const schema = dsl({
|
|
548
|
+
users: {
|
|
549
|
+
type: 'array',
|
|
550
|
+
items: {
|
|
551
|
+
name: 'string!',
|
|
552
|
+
age: 'number:18-'
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## 完整示例
|
|
561
|
+
|
|
562
|
+
### 用户注册表单
|
|
563
|
+
|
|
564
|
+
```javascript
|
|
565
|
+
const { dsl } = require('schema-dsl');
|
|
566
|
+
|
|
567
|
+
const schema = dsl({
|
|
568
|
+
// 基本信息
|
|
569
|
+
username: 'string:3-32!'.username().label('用户名'),
|
|
570
|
+
password: 'string!'.password('strong').label('密码'),
|
|
571
|
+
email: 'email!'.label('邮箱'),
|
|
572
|
+
phone: 'string!'.phone('cn').label('手机号'),
|
|
573
|
+
|
|
574
|
+
// 个人资料
|
|
575
|
+
'profile!': {
|
|
576
|
+
realName: 'string:2-50',
|
|
577
|
+
gender: 'male|female|other',
|
|
578
|
+
birthday: 'date',
|
|
579
|
+
bio: 'string:500'
|
|
580
|
+
},
|
|
581
|
+
|
|
582
|
+
// 地址信息
|
|
583
|
+
addresses: 'array:1-5<object>', // 1-5个地址
|
|
584
|
+
|
|
585
|
+
// 标签
|
|
586
|
+
tags: 'array:1-10<string:1-20>', // 1-10个标签,每个1-20字符
|
|
587
|
+
|
|
588
|
+
// 同意条款
|
|
589
|
+
agree: 'boolean!'
|
|
590
|
+
});
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### 电商商品 Schema
|
|
594
|
+
|
|
595
|
+
```javascript
|
|
596
|
+
const schema = dsl({
|
|
597
|
+
// 商品基本信息
|
|
598
|
+
title: 'string:1-100!',
|
|
599
|
+
price: 'number:0-!',
|
|
600
|
+
stock: 'integer:0-',
|
|
601
|
+
status: 'on_sale|off_sale|sold_out!',
|
|
602
|
+
|
|
603
|
+
// 商品详情
|
|
604
|
+
'details!': {
|
|
605
|
+
description: 'string:10000',
|
|
606
|
+
images: 'array!1-10<url>',
|
|
607
|
+
specs: 'array<object>',
|
|
608
|
+
tags: 'array:1-20<string:1-30>'
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
// SKU信息
|
|
612
|
+
skus: {
|
|
613
|
+
type: 'array',
|
|
614
|
+
minItems: 1,
|
|
615
|
+
items: {
|
|
616
|
+
sku_code: 'string!',
|
|
617
|
+
price: 'number!',
|
|
618
|
+
stock: 'integer!'
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### API 请求验证
|
|
625
|
+
|
|
626
|
+
```javascript
|
|
627
|
+
const schema = dsl({
|
|
628
|
+
// 查询参数
|
|
629
|
+
page: 'integer:1-',
|
|
630
|
+
pageSize: 'integer:10-100',
|
|
631
|
+
keyword: 'string:1-50',
|
|
632
|
+
|
|
633
|
+
// 筛选条件
|
|
634
|
+
filters: {
|
|
635
|
+
category: 'array<string>',
|
|
636
|
+
priceRange: {
|
|
637
|
+
min: 'number:0-',
|
|
638
|
+
max: 'number:0-'
|
|
639
|
+
},
|
|
640
|
+
status: 'active|inactive'
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
// 排序
|
|
644
|
+
sort: {
|
|
645
|
+
field: 'price|created_at|sales',
|
|
646
|
+
order: 'asc|desc'
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## 常见问题
|
|
654
|
+
|
|
655
|
+
### Q1: 为什么移除了简写功能?
|
|
656
|
+
|
|
657
|
+
**A**: 为了降低学习成本和减少歧义。使用完整类型名更清晰,特别是对新手更友好。
|
|
658
|
+
|
|
659
|
+
### Q2: 数组长度约束怎么写?
|
|
660
|
+
|
|
661
|
+
**A**: 支持直接在DSL中写:
|
|
662
|
+
```javascript
|
|
663
|
+
'array!1-10<string>' // 推荐
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Q3: 如何定义对象数组?
|
|
667
|
+
|
|
668
|
+
**A**: 使用完整对象定义:
|
|
669
|
+
```javascript
|
|
670
|
+
const schema = dsl({
|
|
671
|
+
users: {
|
|
672
|
+
type: 'array',
|
|
673
|
+
items: {
|
|
674
|
+
name: 'string!',
|
|
675
|
+
email: 'email!'
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Q4: 不支持条件验证吗?
|
|
682
|
+
|
|
683
|
+
**A**: 支持。推荐使用 `dsl.match`:
|
|
684
|
+
```javascript
|
|
685
|
+
dsl.match('vipLevel', { gold: 'number:0-50', silver: 'number:0-20' })
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Q5: 能用正则验证吗?
|
|
689
|
+
|
|
690
|
+
**A**: 能,使用 `.pattern()` 方法:
|
|
691
|
+
```javascript
|
|
692
|
+
'string!'.pattern(/^[a-z]+$/)
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## 相关文档
|
|
698
|
+
|
|
699
|
+
- [类型参考](./type-reference.md) - 完整类型列表
|
|
700
|
+
- [String 扩展](./string-extensions.md) - 链式调用
|
|
701
|
+
- [快速开始](./quick-start.md) - 5分钟上手
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## 对应示例文件
|
|
706
|
+
|
|
707
|
+
**示例入口**: [dsl-syntax.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/dsl-syntax.ts)
|
|
708
|
+
**说明**: 覆盖 Batch 1 中 DSL 语法的基础类型、约束、枚举、数组和嵌套对象写法,可直接运行参考。
|
|
709
|
+
|
|
710
|
+
---
|
|
711
|
+
|
|
712
|
+
**最后更新**: 2026-05-08
|
|
713
|
+
**作者**: schema-dsl Team
|
|
714
|
+
|