schema-dsl 1.1.0 → 1.1.2
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 +302 -9
- package/README.md +870 -279
- package/STATUS.md +65 -3
- package/docs/conditional-api.md +257 -11
- package/docs/number-operators.md +442 -0
- package/examples/i18n-error.examples.js +181 -0
- package/index.d.ts +3268 -3132
- package/index.js +35 -2
- package/lib/adapters/DslAdapter.js +56 -7
- package/lib/core/ConditionalBuilder.js +115 -13
- package/lib/core/DslBuilder.js +47 -1
- package/lib/core/Validator.js +7 -3
- package/lib/errors/I18nError.js +222 -0
- package/lib/locales/en-US.js +25 -0
- package/lib/locales/zh-CN.js +25 -0
- package/package.json +1 -1
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# 数字比较运算符 (v1.1.2+)
|
|
2
|
+
|
|
3
|
+
**版本**: v1.1.2+
|
|
4
|
+
**适用类型**: `number`, `integer`
|
|
5
|
+
|
|
6
|
+
## 📋 快速概览
|
|
7
|
+
|
|
8
|
+
| 运算符 | 语法 | JSON Schema | 说明 | 示例 |
|
|
9
|
+
|-------|------|------------|------|------|
|
|
10
|
+
| `>` | `number:>0` | `{ exclusiveMinimum: 0 }` | 大于(不包括边界) | 正数 |
|
|
11
|
+
| `>=` | `number:>=18` | `{ minimum: 18 }` | 大于等于 | 年龄限制 |
|
|
12
|
+
| `<` | `number:<100` | `{ exclusiveMaximum: 100 }` | 小于(不包括边界) | 温度上限 |
|
|
13
|
+
| `<=` | `number:<=100` | `{ maximum: 100 }` | 小于等于 | 评分上限 |
|
|
14
|
+
| `=` | `number:=100` | `{ enum: [100] }` | 等于 | 固定值 |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## ✨ 特性
|
|
19
|
+
|
|
20
|
+
- ✅ 支持 5 种比较运算符
|
|
21
|
+
- ✅ 支持小数(如 `number:>0.5`)
|
|
22
|
+
- ✅ 支持负数(如 `number:>-10`)
|
|
23
|
+
- ✅ 支持必填标记(如 `number:>=18!`)
|
|
24
|
+
- ✅ 适用于 `number` 和 `integer` 类型
|
|
25
|
+
- ✅ 完全向后兼容原有范围语法
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🚀 基础用法
|
|
30
|
+
|
|
31
|
+
### 大于 (>)
|
|
32
|
+
|
|
33
|
+
**语法**: `number:>value`
|
|
34
|
+
|
|
35
|
+
**JSON Schema**: `{ exclusiveMinimum: value }`
|
|
36
|
+
|
|
37
|
+
**说明**: 值必须大于指定值(不包括边界值本身)
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
const { dsl, validate } = require('schema-dsl');
|
|
41
|
+
|
|
42
|
+
// 基础用法
|
|
43
|
+
const schema = dsl({ value: 'number:>0' });
|
|
44
|
+
|
|
45
|
+
validate(schema, { value: 1 }); // ✅ true
|
|
46
|
+
validate(schema, { value: 0.1 }); // ✅ true
|
|
47
|
+
validate(schema, { value: 0 }); // ❌ false (0 不满足 >0)
|
|
48
|
+
validate(schema, { value: -1 }); // ❌ false
|
|
49
|
+
|
|
50
|
+
// 支持小数
|
|
51
|
+
const schema2 = dsl({ value: 'number:>0.5' });
|
|
52
|
+
validate(schema2, { value: 0.6 }); // ✅ true
|
|
53
|
+
validate(schema2, { value: 0.5 }); // ❌ false (0.5 不满足 >0.5)
|
|
54
|
+
|
|
55
|
+
// 支持负数
|
|
56
|
+
const schema3 = dsl({ value: 'number:>-10' });
|
|
57
|
+
validate(schema3, { value: -9 }); // ✅ true
|
|
58
|
+
validate(schema3, { value: -10 }); // ❌ false
|
|
59
|
+
|
|
60
|
+
// 配合必填
|
|
61
|
+
const schema4 = dsl({ value: 'number:>0!' });
|
|
62
|
+
validate(schema4, { value: 1 }); // ✅ true
|
|
63
|
+
validate(schema4, {}); // ❌ false (必填)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### 大于等于 (>=)
|
|
69
|
+
|
|
70
|
+
**语法**: `number:>=value`
|
|
71
|
+
|
|
72
|
+
**JSON Schema**: `{ minimum: value }`
|
|
73
|
+
|
|
74
|
+
**说明**: 值必须大于等于指定值(包括边界值)
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// 基础用法
|
|
78
|
+
const schema = dsl({ age: 'number:>=18' });
|
|
79
|
+
|
|
80
|
+
validate(schema, { age: 18 }); // ✅ true (包括18)
|
|
81
|
+
validate(schema, { age: 19 }); // ✅ true
|
|
82
|
+
validate(schema, { age: 17 }); // ❌ false
|
|
83
|
+
|
|
84
|
+
// 实际应用:年龄验证
|
|
85
|
+
const schema2 = dsl({ age: 'number:>=18!' });
|
|
86
|
+
|
|
87
|
+
validate(schema2, { age: 20 }); // ✅ true
|
|
88
|
+
validate(schema2, { age: 17 }); // ❌ false
|
|
89
|
+
validate(schema2, {}); // ❌ false (必填)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### 小于 (<)
|
|
95
|
+
|
|
96
|
+
**语法**: `number:<value`
|
|
97
|
+
|
|
98
|
+
**JSON Schema**: `{ exclusiveMaximum: value }`
|
|
99
|
+
|
|
100
|
+
**说明**: 值必须小于指定值(不包括边界值)
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// 基础用法
|
|
104
|
+
const schema = dsl({ value: 'number:<100' });
|
|
105
|
+
|
|
106
|
+
validate(schema, { value: 99 }); // ✅ true
|
|
107
|
+
validate(schema, { value: 99.9 }); // ✅ true
|
|
108
|
+
validate(schema, { value: 100 }); // ❌ false (100 不满足 <100)
|
|
109
|
+
validate(schema, { value: 101 }); // ❌ false
|
|
110
|
+
|
|
111
|
+
// 实际应用:温度上限
|
|
112
|
+
const schema2 = dsl({ temperature: 'number:<100' });
|
|
113
|
+
|
|
114
|
+
validate(schema2, { temperature: 99.9 }); // ✅ true
|
|
115
|
+
validate(schema2, { temperature: 100 }); // ❌ false
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### 小于等于 (<=)
|
|
121
|
+
|
|
122
|
+
**语法**: `number:<=value`
|
|
123
|
+
|
|
124
|
+
**JSON Schema**: `{ maximum: value }`
|
|
125
|
+
|
|
126
|
+
**说明**: 值必须小于等于指定值(包括边界值)
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
// 基础用法
|
|
130
|
+
const schema = dsl({ score: 'number:<=100' });
|
|
131
|
+
|
|
132
|
+
validate(schema, { score: 100 }); // ✅ true (包括100)
|
|
133
|
+
validate(schema, { score: 99 }); // ✅ true
|
|
134
|
+
validate(schema, { score: 101 }); // ❌ false
|
|
135
|
+
|
|
136
|
+
// 实际应用:评分系统
|
|
137
|
+
const schema2 = dsl({ score: 'number:<=100!' });
|
|
138
|
+
|
|
139
|
+
validate(schema2, { score: 100 }); // ✅ true
|
|
140
|
+
validate(schema2, { score: 101 }); // ❌ false
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### 等于 (=)
|
|
146
|
+
|
|
147
|
+
**语法**: `number:=value`
|
|
148
|
+
|
|
149
|
+
**JSON Schema**: `{ enum: [value] }`
|
|
150
|
+
|
|
151
|
+
**说明**: 值必须等于指定值
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
// 基础用法
|
|
155
|
+
const schema = dsl({ level: 'number:=5' });
|
|
156
|
+
|
|
157
|
+
validate(schema, { level: 5 }); // ✅ true
|
|
158
|
+
validate(schema, { level: 4 }); // ❌ false
|
|
159
|
+
validate(schema, { level: 6 }); // ❌ false
|
|
160
|
+
|
|
161
|
+
// 支持小数精确匹配
|
|
162
|
+
const schema2 = dsl({ price: 'number:=99.99' });
|
|
163
|
+
|
|
164
|
+
validate(schema2, { price: 99.99 }); // ✅ true
|
|
165
|
+
validate(schema2, { price: 99.98 }); // ❌ false
|
|
166
|
+
validate(schema2, { price: 100 }); // ❌ false
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 📊 对比:比较运算符 vs 范围语法
|
|
172
|
+
|
|
173
|
+
| 需求 | 范围语法 | 比较运算符 | 推荐 |
|
|
174
|
+
|------|---------|-----------|------|
|
|
175
|
+
| 18 ≤ x ≤ 120 | `number:18-120` | `number:>=18` + `number:<=120` | 范围语法(更简洁) |
|
|
176
|
+
| x ≥ 18 | `number:18-` | `number:>=18` | **比较运算符**(语义更清晰) |
|
|
177
|
+
| x ≤ 100 | `number:-100` | `number:<=100` | **比较运算符**(语义更清晰) |
|
|
178
|
+
| x > 0(不包括0) | ❌ 无法表达 | `number:>0` | **比较运算符**(唯一方法) |
|
|
179
|
+
| x < 100(不包括100) | ❌ 无法表达 | `number:<100` | **比较运算符**(唯一方法) |
|
|
180
|
+
| x = 100 | `number:100`(实际是≤100) | `number:=100` | **比较运算符**(精确匹配) |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 🎯 实际应用场景
|
|
185
|
+
|
|
186
|
+
### 场景 1:用户注册 - 年龄限制
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
const schema = dsl({
|
|
190
|
+
username: 'string:3-32!',
|
|
191
|
+
email: 'email!',
|
|
192
|
+
age: 'number:>=18!', // 必须年满18岁
|
|
193
|
+
password: 'string:8-!'
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// 测试
|
|
197
|
+
validate(schema, {
|
|
198
|
+
username: 'john',
|
|
199
|
+
email: 'john@example.com',
|
|
200
|
+
age: 20,
|
|
201
|
+
password: '12345678'
|
|
202
|
+
}); // ✅ 通过
|
|
203
|
+
|
|
204
|
+
validate(schema, {
|
|
205
|
+
username: 'tom',
|
|
206
|
+
email: 'tom@example.com',
|
|
207
|
+
age: 17, // ❌ 未满18岁
|
|
208
|
+
password: '12345678'
|
|
209
|
+
}); // ❌ 失败
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### 场景 2:电商系统 - 价格验证
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
const schema = dsl({
|
|
218
|
+
productName: 'string:1-100!',
|
|
219
|
+
price: 'number:>0!', // 价格必须大于0
|
|
220
|
+
discount: 'number:0-100' // 折扣 0-100
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// 测试
|
|
224
|
+
validate(schema, {
|
|
225
|
+
productName: 'iPhone 16',
|
|
226
|
+
price: 999.99, // ✅ 大于0
|
|
227
|
+
discount: 10
|
|
228
|
+
}); // ✅ 通过
|
|
229
|
+
|
|
230
|
+
validate(schema, {
|
|
231
|
+
productName: 'iPad',
|
|
232
|
+
price: 0, // ❌ 不能为0
|
|
233
|
+
discount: 50
|
|
234
|
+
}); // ❌ 失败
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### 场景 3:考试系统 - 评分
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
const schema = dsl({
|
|
243
|
+
studentId: 'string!',
|
|
244
|
+
score: 'number:>=0!', // 分数 ≥ 0
|
|
245
|
+
bonus: 'number:<=20' // 额外加分 ≤ 20
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// 测试
|
|
249
|
+
validate(schema, {
|
|
250
|
+
studentId: 'S001',
|
|
251
|
+
score: 85,
|
|
252
|
+
bonus: 10
|
|
253
|
+
}); // ✅ 通过
|
|
254
|
+
|
|
255
|
+
validate(schema, {
|
|
256
|
+
studentId: 'S002',
|
|
257
|
+
score: -5 // ❌ 不能为负数
|
|
258
|
+
}); // ❌ 失败
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### 场景 4:温度监控 - 范围限制
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
const schema = dsl({
|
|
267
|
+
deviceId: 'string!',
|
|
268
|
+
temperature: 'number:>0', // 温度 > 0
|
|
269
|
+
humidity: 'number:<=100' // 湿度 ≤ 100
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// 测试
|
|
273
|
+
validate(schema, {
|
|
274
|
+
deviceId: 'TEMP-001',
|
|
275
|
+
temperature: 25.5,
|
|
276
|
+
humidity: 60
|
|
277
|
+
}); // ✅ 通过
|
|
278
|
+
|
|
279
|
+
validate(schema, {
|
|
280
|
+
deviceId: 'TEMP-002',
|
|
281
|
+
temperature: 0, // ❌ 不能为0
|
|
282
|
+
humidity: 60
|
|
283
|
+
}); // ❌ 失败
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
### 场景 5:游戏系统 - 等级验证
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
const schema = dsl({
|
|
292
|
+
playerId: 'string!',
|
|
293
|
+
level: 'number:=5!', // 必须是5级
|
|
294
|
+
experience: 'number:>=1000' // 经验 >= 1000
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// 测试
|
|
298
|
+
validate(schema, {
|
|
299
|
+
playerId: 'P001',
|
|
300
|
+
level: 5,
|
|
301
|
+
experience: 1500
|
|
302
|
+
}); // ✅ 通过
|
|
303
|
+
|
|
304
|
+
validate(schema, {
|
|
305
|
+
playerId: 'P002',
|
|
306
|
+
level: 4, // ❌ 必须是5级
|
|
307
|
+
experience: 1500
|
|
308
|
+
}); // ❌ 失败
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## ⚙️ 技术细节
|
|
314
|
+
|
|
315
|
+
### JSON Schema 映射
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
// DSL → JSON Schema
|
|
319
|
+
dsl({ value: 'number:>0' })
|
|
320
|
+
// 生成:
|
|
321
|
+
{
|
|
322
|
+
type: 'object',
|
|
323
|
+
properties: {
|
|
324
|
+
value: {
|
|
325
|
+
type: 'number',
|
|
326
|
+
exclusiveMinimum: 0 // JSON Schema draft-07
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// DSL → JSON Schema
|
|
332
|
+
dsl({ age: 'number:>=18' })
|
|
333
|
+
// 生成:
|
|
334
|
+
{
|
|
335
|
+
type: 'object',
|
|
336
|
+
properties: {
|
|
337
|
+
age: {
|
|
338
|
+
type: 'number',
|
|
339
|
+
minimum: 18
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
### integer 类型支持
|
|
348
|
+
|
|
349
|
+
所有比较运算符同样适用于 `integer` 类型:
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const schema = dsl({
|
|
353
|
+
count: 'integer:>0', // 整数且大于0
|
|
354
|
+
level: 'integer:>=1', // 整数且大于等于1
|
|
355
|
+
maxValue: 'integer:<=100' // 整数且小于等于100
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
validate(schema, {
|
|
359
|
+
count: 5,
|
|
360
|
+
level: 1,
|
|
361
|
+
maxValue: 100
|
|
362
|
+
}); // ✅ 通过
|
|
363
|
+
|
|
364
|
+
validate(schema, {
|
|
365
|
+
count: 1.5, // ❌ 不是整数
|
|
366
|
+
level: 1,
|
|
367
|
+
maxValue: 100
|
|
368
|
+
}); // ❌ 失败
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## 🔄 向后兼容性
|
|
374
|
+
|
|
375
|
+
所有原有语法保持不变,无破坏性变更:
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
// ✅ 原有语法继续有效
|
|
379
|
+
dsl({ age: 'number:18-120' }) // 范围
|
|
380
|
+
dsl({ age: 'number:18-' }) // 最小值
|
|
381
|
+
dsl({ score: 'number:-100' }) // 最大值
|
|
382
|
+
dsl({ count: 'number:100' }) // 最大值
|
|
383
|
+
|
|
384
|
+
// ✅ 新增语法
|
|
385
|
+
dsl({ age: 'number:>=18' }) // 大于等于
|
|
386
|
+
dsl({ value: 'number:>0' }) // 大于
|
|
387
|
+
dsl({ score: 'number:<=100' }) // 小于等于
|
|
388
|
+
dsl({ temp: 'number:<100' }) // 小于
|
|
389
|
+
dsl({ level: 'number:=5' }) // 等于
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## ❓ 常见问题
|
|
395
|
+
|
|
396
|
+
### Q1: 为什么需要比较运算符?范围语法不够用吗?
|
|
397
|
+
|
|
398
|
+
**A**: 范围语法无法表达"不包括边界值"的需求:
|
|
399
|
+
- `number:>0` 表示大于0(不包括0)
|
|
400
|
+
- `number:<100` 表示小于100(不包括100)
|
|
401
|
+
- 这些用范围语法无法表达
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
### Q2: `number:=100` 和 `number:100` 有什么区别?
|
|
406
|
+
|
|
407
|
+
**A**:
|
|
408
|
+
- `number:=100` → `{ enum: [100] }`,精确等于100
|
|
409
|
+
- `number:100` → `{ maximum: 100 }`,小于等于100
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
### Q3: 能否组合多个比较运算符?
|
|
414
|
+
|
|
415
|
+
**A**: 当前版本不支持直接组合(如 `number:>0<100`)。建议:
|
|
416
|
+
- 使用范围语法:`number:0-100`(包括边界)
|
|
417
|
+
- 或分别验证两个字段
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
### Q4: 支持哪些数值?
|
|
422
|
+
|
|
423
|
+
**A**:
|
|
424
|
+
- ✅ 正整数:`number:>0`, `number:>=1`
|
|
425
|
+
- ✅ 负整数:`number:>-10`, `number:<-5`
|
|
426
|
+
- ✅ 小数:`number:>0.5`, `number:<=99.99`
|
|
427
|
+
- ✅ 零:`number:>=0`, `number:<=0`
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## 📚 相关文档
|
|
432
|
+
|
|
433
|
+
- [DSL 语法速查](../README.md#-dsl-语法速查)
|
|
434
|
+
- [完整示例](../examples/number-operators.examples.js)
|
|
435
|
+
- [测试用例](../test/unit/number-operators.test.js)
|
|
436
|
+
- [CHANGELOG](../CHANGELOG.md)
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
**版本**: v1.1.2+
|
|
441
|
+
**更新时间**: 2026-01-06
|
|
442
|
+
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I18nError 使用示例
|
|
3
|
+
*
|
|
4
|
+
* 统一的多语言错误抛出机制
|
|
5
|
+
*
|
|
6
|
+
* @version 1.1.1
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { I18nError, dsl, Locale } = require('../index');
|
|
10
|
+
|
|
11
|
+
console.log('=== I18nError 使用示例 ===\n');
|
|
12
|
+
|
|
13
|
+
// ========== 1. 基础用法 ==========
|
|
14
|
+
console.log('1. 基础用法:');
|
|
15
|
+
try {
|
|
16
|
+
throw I18nError.create('account.notFound');
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.log('错误:', error.message); // "账户不存在"
|
|
19
|
+
console.log('代码:', error.code); // "account.notFound"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ========== 2. 带参数的错误 ==========
|
|
23
|
+
console.log('\n2. 带参数的错误:');
|
|
24
|
+
try {
|
|
25
|
+
throw I18nError.create('account.insufficientBalance', {
|
|
26
|
+
balance: 50,
|
|
27
|
+
required: 100
|
|
28
|
+
});
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.log('错误:', error.message); // "余额不足,当前余额50,需要100"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ========== 3. 使用 throw 方法 ==========
|
|
34
|
+
console.log('\n3. 使用 throw 方法(直接抛错):');
|
|
35
|
+
try {
|
|
36
|
+
I18nError.throw('user.noPermission');
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.log('错误:', error.message); // "没有管理员权限"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ========== 4. 使用 assert 断言 ==========
|
|
42
|
+
console.log('\n4. 使用 assert 断言:');
|
|
43
|
+
const account = { balance: 50 };
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
I18nError.assert(account.balance >= 100, 'account.insufficientBalance', {
|
|
47
|
+
balance: account.balance,
|
|
48
|
+
required: 100
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.log('错误:', error.message);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ========== 5. dsl.error 快捷方法 ==========
|
|
55
|
+
console.log('\n5. dsl.error 快捷方法:');
|
|
56
|
+
try {
|
|
57
|
+
dsl.error.throw('order.notPaid');
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.log('错误:', error.message); // "订单未支付"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ========== 6. 多语言支持 ==========
|
|
63
|
+
console.log('\n6. 多语言支持:');
|
|
64
|
+
try {
|
|
65
|
+
// 中文
|
|
66
|
+
Locale.setLocale('zh-CN');
|
|
67
|
+
throw I18nError.create('account.notFound');
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.log('中文:', error.message); // "账户不存在"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// 英文
|
|
74
|
+
Locale.setLocale('en-US');
|
|
75
|
+
throw I18nError.create('account.notFound');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.log('英文:', error.message); // "Account not found"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 恢复中文
|
|
81
|
+
Locale.setLocale('zh-CN');
|
|
82
|
+
|
|
83
|
+
// ========== 7. 实际业务场景 =========
|
|
84
|
+
console.log('\n7. 实际业务场景:');
|
|
85
|
+
|
|
86
|
+
// 场景1:账户验证函数
|
|
87
|
+
function getAccount(id) {
|
|
88
|
+
const account = id === '123' ? { id: '123', balance: 50, status: 'active' } : null;
|
|
89
|
+
|
|
90
|
+
// 断言账户存在
|
|
91
|
+
I18nError.assert(account, 'account.notFound');
|
|
92
|
+
|
|
93
|
+
// 断言账户状态
|
|
94
|
+
I18nError.assert(account.status === 'active', 'account.inactive');
|
|
95
|
+
|
|
96
|
+
// 断言余额充足
|
|
97
|
+
I18nError.assert(
|
|
98
|
+
account.balance >= 100,
|
|
99
|
+
'account.insufficientBalance',
|
|
100
|
+
{ balance: account.balance, required: 100 }
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return account;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
getAccount('123');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.log('业务错误:', error.message);
|
|
110
|
+
console.log('错误代码:', error.code);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 场景2:与 dsl.if 结合
|
|
114
|
+
console.log('\n8. 与 dsl.if 结合使用:');
|
|
115
|
+
function validateUser(user) {
|
|
116
|
+
// 使用 dsl.if 进行数据验证
|
|
117
|
+
dsl.if(d => !d)
|
|
118
|
+
.message('user.notFound')
|
|
119
|
+
.and(d => !d.isVerified)
|
|
120
|
+
.message('user.notVerified')
|
|
121
|
+
.assert(user);
|
|
122
|
+
|
|
123
|
+
// 使用 I18nError 进行业务逻辑验证
|
|
124
|
+
I18nError.assert(user.role === 'admin', 'user.noPermission');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
validateUser({ isVerified: true, role: 'user' });
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.log('验证错误:', error.message);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ========== 9. Express/Koa 中间件 ==========
|
|
134
|
+
console.log('\n9. Express/Koa 错误处理:');
|
|
135
|
+
|
|
136
|
+
// Express 错误处理中间件
|
|
137
|
+
function expressErrorHandler(error, req, res, next) {
|
|
138
|
+
if (error instanceof I18nError) {
|
|
139
|
+
return res.status(error.statusCode).json(error.toJSON());
|
|
140
|
+
}
|
|
141
|
+
next(error);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 模拟使用
|
|
145
|
+
const mockError = I18nError.create('account.notFound', {}, 404);
|
|
146
|
+
const mockRes = {
|
|
147
|
+
status: (code) => {
|
|
148
|
+
console.log('HTTP Status:', code);
|
|
149
|
+
return mockRes;
|
|
150
|
+
},
|
|
151
|
+
json: (data) => {
|
|
152
|
+
console.log('Response:', JSON.stringify(data, null, 2));
|
|
153
|
+
return mockRes;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
expressErrorHandler(mockError, {}, mockRes, () => {});
|
|
158
|
+
|
|
159
|
+
// ========== 10. 自定义状态码 ==========
|
|
160
|
+
console.log('\n10. 自定义状态码:');
|
|
161
|
+
try {
|
|
162
|
+
throw I18nError.create('user.notFound', {}, 404);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.log('状态码:', error.statusCode); // 404
|
|
165
|
+
console.log('错误:', error.message);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ========== 11. 错误检查 ==========
|
|
169
|
+
console.log('\n11. 错误类型检查:');
|
|
170
|
+
try {
|
|
171
|
+
throw I18nError.create('account.notFound');
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof I18nError) {
|
|
174
|
+
console.log('是 I18nError:', true);
|
|
175
|
+
console.log('是账户不存在:', error.is('account.notFound'));
|
|
176
|
+
console.log('是用户不存在:', error.is('user.notFound'));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log('\n=== 示例完成 ===');
|
|
181
|
+
|