schema-dsl 1.0.6 → 1.0.8

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 CHANGED
@@ -9,16 +9,227 @@
9
9
 
10
10
  ## 版本概览
11
11
 
12
- | 版本 | 日期 | 变更摘要 | 详细 |
13
- |------|------|---------|------|
14
- | [v1.0.6](#v106) | 2026-01-04 | 🚨 紧急修复:TypeScript 类型污染 | [查看详情](#v106) |
15
- | [v1.0.5](#v105) | 2026-01-04 | 测试覆盖率提升至 97% | [查看详情](#v105) |
16
- | [v1.0.5](#v105) | 2026-01-04 | 测试覆盖率提升至 97% | [查看详情](#v105) |
17
- | [v1.0.4](#v104) | 2025-12-31 | TypeScript 完整支持、validateAsync、ValidationError | [查看详情](#v104) |
18
- | [v1.0.3](#v103) | 2025-12-31 | ⚠️ 破坏性变更:单值语法修复 | [查看详情](#v103) |
19
- | [v1.0.2](#v102) | 2025-12-31 | 15个新增验证器、完整文档、75个测试 | [查看详情](#v102) |
20
- | [v1.0.1](#v101) | 2025-12-31 | 枚举功能、自动类型识别、统一错误消息 | [查看详情](#v101) |
21
- | [v1.0.0](#v100) | 2025-12-29 | 初始发布版本 | [查看详情](#v100) |
12
+ | 版本 | 日期 | 变更摘要 | 详细 |
13
+ |------------------|------|---------|------|
14
+ | [v1.0.8](#v1071) | 2026-01-04 | 优化:错误消息过滤增强 | [查看详情](#v1071) |
15
+ | [v1.0.7](#v107) | 2026-01-04 | 修复:dsl.match/dsl.if 嵌套支持 dsl() 包裹 | [查看详情](#v107) |
16
+ | [v1.0.6](#v106) | 2026-01-04 | 🚨 紧急修复:TypeScript 类型污染 | [查看详情](#v106) |
17
+ | [v1.0.7](#v107) | 2026-01-04 | 修复:dsl.match/dsl.if 嵌套支持 dsl() 包裹 | [查看详情](#v107) |
18
+ | [v1.0.6](#v106) | 2026-01-04 | 🚨 紧急修复:TypeScript 类型污染 | [查看详情](#v106) |
19
+ | [v1.0.5](#v105) | 2026-01-04 | 测试覆盖率提升至 97% | [查看详情](#v105) |
20
+ | [v1.0.4](#v104) | 2025-12-31 | TypeScript 完整支持、validateAsync、ValidationError | [查看详情](#v104) |
21
+ | [v1.0.3](#v103) | 2025-12-31 | ⚠️ 破坏性变更:单值语法修复 | [查看详情](#v103) |
22
+ | [v1.0.2](#v102) | 2025-12-31 | 15个新增验证器、完整文档、75个测试 | [查看详情](#v102) |
23
+ | [v1.0.1](#v101) | 2025-12-31 | 枚举功能、自动类型识别、统一错误消息 | [查看详情](#v101) |
24
+ | [v1.0.0](#v100) | 2025-12-29 | 初始发布版本 | [查看详情](#v100) |
25
+
26
+ ---
27
+
28
+ ## [v1.0.8] - 2026-01-04
29
+
30
+ ### Improved (优化)
31
+
32
+ #### 错误消息过滤增强 ⭐
33
+
34
+ **问题描述**:
35
+ 用户报告错误消息存在冗余问题:
36
+ - 在嵌套 If/Match 结构中,会显示重复的 "must match then schema" 包装错误
37
+ - 当已有具体字段错误时(如"Credit price is required"),还会额外显示2-3个通用包装错误
38
+
39
+ **修复内容**:
40
+ - ✅ 过滤 `if` 关键字的包装错误
41
+ - ✅ 过滤 `anyOf` 关键字的包装错误
42
+ - ✅ 过滤 `oneOf` 关键字的包装错误
43
+ - ✅ 当存在具体字段错误时,自动移除所有包装错误
44
+
45
+ **修复效果**:
46
+
47
+ 修复前:
48
+ ```javascript
49
+ const result = validate(schema, data);
50
+ // errors: [
51
+ // { message: "Credit price is required" },
52
+ // { message: "must match then schema" }, // 冗余
53
+ // { message: "must match then schema" } // 冗余
54
+ // ]
55
+ ```
56
+
57
+ 修复后:
58
+ ```javascript
59
+ const result = validate(schema, data);
60
+ // errors: [
61
+ // { message: "Credit price is required" } // 只显示具体错误
62
+ // ]
63
+ ```
64
+
65
+ **技术细节**:
66
+ - 修改文件:[lib/core/ErrorFormatter.js](lib/core/ErrorFormatter.js#L84-L101)
67
+ - 过滤逻辑:检测到具体错误(type, required, pattern等)时,自动过滤包装错误(if, anyOf, oneOf)
68
+ - 适用场景:所有使用 `dsl.if()`、`dsl.match()` 以及 JSON Schema 的 anyOf/oneOf 的验证
69
+
70
+ **测试覆盖**:
71
+ - 测试文件:[test/unit/error-message-filter.test.js](test/unit/error-message-filter.test.js)
72
+ - 新增测试:5个错误过滤场景
73
+ - 测试总数:725 (+5)
74
+ - 所有测试通过 ✅
75
+
76
+ ---
77
+
78
+ ## [v1.0.7] - 2026-01-04
79
+
80
+ ### Fixed (修复)
81
+
82
+ #### 全面支持 dsl.match/dsl.if 所有嵌套组合 ⭐⭐⭐
83
+
84
+ **问题描述**:
85
+ 在 v1.0.6 中,TypeScript 用户被要求使用 `dsl()` 包裹字符串,但在 `dsl.match()` 和 `dsl.if()` 中使用嵌套结构时会失败。用户报告了复杂嵌套场景无法正常工作。
86
+
87
+ **修复内容**:
88
+ - ✅ **Match 嵌套 Match** - 多级条件分支
89
+ - ✅ **Match 嵌套 If** - 在分支中使用条件
90
+ - ✅ **If 嵌套 Match** - 条件中使用多分支(用户报告的场景)
91
+ - ✅ **If 嵌套 If** - 多层条件嵌套
92
+ - ✅ **_default 中嵌套** - 默认规则支持 Match/If
93
+ - ✅ **三层嵌套** - 支持任意深度嵌套
94
+
95
+ **修复前问题**:
96
+ ```javascript
97
+ // ❌ v1.0.6 中所有嵌套都会失败
98
+ credit_price: dsl.if('enabled',
99
+ dsl.match('payment_type', {
100
+ 'credit': dsl('integer:1-10000!').label('价格'),
101
+ '_default': 'integer:1-10000'
102
+ }),
103
+ 'integer:1-10000'
104
+ )
105
+ ```
106
+
107
+ **修复后**:
108
+ ```javascript
109
+ // ✅ v1.0.7 完全支持所有嵌套组合
110
+
111
+ // 1. If 嵌套 Match(用户场景)
112
+ credit_price: dsl.if('enabled',
113
+ dsl.match('payment_type', {
114
+ 'credit': dsl('integer:1-10000!').label('credit_price'),
115
+ '_default': 'integer:1-10000'
116
+ }),
117
+ 'integer:1-10000'
118
+ )
119
+
120
+ // 2. Match 嵌套 Match
121
+ value: dsl.match('category', {
122
+ 'contact': dsl.match('type', {
123
+ 'email': dsl('email!').label('邮箱'),
124
+ 'phone': dsl('string:11!').label('手机号')
125
+ }),
126
+ 'payment': dsl.match('type', {
127
+ 'credit': dsl('integer:1-10000!'),
128
+ 'cash': dsl('number:0.01-10000!')
129
+ })
130
+ })
131
+
132
+ // 3. Match 嵌套 If
133
+ discount: dsl.match('user_type', {
134
+ 'member': dsl.if('is_vip',
135
+ dsl('number:10-50!').label('VIP会员折扣'),
136
+ dsl('number:5-20!').label('普通会员折扣')
137
+ ),
138
+ 'guest': dsl('number:0-10').label('访客折扣')
139
+ })
140
+
141
+ // 4. If 嵌套 If
142
+ price: dsl.if('is_member',
143
+ dsl.if('is_premium',
144
+ dsl('number:100-500!').label('高级会员价'),
145
+ dsl('number:200-800!').label('普通会员价')
146
+ ),
147
+ dsl('number:500-1000!').label('非会员价')
148
+ )
149
+
150
+ // 5. 三层嵌套
151
+ value: dsl.match('level1', {
152
+ 'A': dsl.match('level2', {
153
+ 'A1': dsl.if('level3',
154
+ dsl('integer:1-100!'),
155
+ dsl('integer:1-50!')
156
+ )
157
+ })
158
+ })
159
+ ```
160
+
161
+ **技术细节**:
162
+ - 修复了 `DslAdapter._buildMatchSchema()` 方法(第476-489行)
163
+ - 修复了 `DslAdapter._buildIfSchema()` 方法
164
+ - 递归处理所有 `_isMatch` 和 `_isIf` 结构
165
+ - 新增 null/undefined 分支值处理逻辑
166
+ - 支持任意深度嵌套(已测试至5层)
167
+
168
+ **已知限制**:
169
+ 1. **自定义验证器传递限制** - `.custom()` 验证器在嵌套 Match/If 中可能不会完全传递
170
+ ```javascript
171
+ // .custom() 的自定义消息可能在深层嵌套中丢失
172
+ value: dsl.match('type', {
173
+ 'email': dsl('string!').custom((v) => v.includes('@'))
174
+ })
175
+ // 基础验证(必填、类型)会生效,但 custom 验证可能不完全传递
176
+ ```
177
+
178
+ 2. **嵌套字段路径限制** - `dsl.match(field)` 的 field 参数不支持嵌套路径
179
+ ```javascript
180
+ // ❌ 不支持
181
+ dsl.match('config.engine', {...})
182
+
183
+ // ✅ 支持 - 使用扁平化字段
184
+ dsl.match('config_engine', {...})
185
+ ```
186
+
187
+ 3. **自定义消息传递** - 在多层嵌套中,自定义错误消息可能不会完全保留
188
+
189
+ **测试覆盖**:
190
+ - 测试总数:**686 → 720**(**+34 个全面测试**)
191
+ - **v1.0.7 新增测试场景**:
192
+
193
+ **基础嵌套组合**(6个测试):
194
+ - Match 嵌套 Match - 多级条件分支
195
+ - Match 嵌套 If - 在分支中使用条件
196
+ - If 嵌套 Match - 条件中使用多分支
197
+ - If 嵌套 If - 多层条件嵌套
198
+ - _default 中使用嵌套 - 默认规则支持 Match/If
199
+ - 三层嵌套 - 任意深度嵌套验证
200
+
201
+ **参数验证和错误处理**(7个测试):
202
+ - 空 map 处理 - Match 中空映射表的行为
203
+ - null/undefined 分支值 - 空值分支的处理
204
+ - 参数验证 - match/if 参数的有效性检查
205
+ - 对象/数组作为条件值 - 复杂类型作为条件的验证
206
+ - 循环依赖检测 - 防止无限递归
207
+ - 同一字段多规则引用 - 字段重复使用场景
208
+ - undefined else 分支 - If 中省略 else 的行为
209
+
210
+ **深度嵌套和高级场景**(6个测试):
211
+ - 4层嵌套 - Match-Match-Match-If 四层组合
212
+ - 大量分支(10+)- 单个 Match 包含15个分支
213
+ - .custom() 验证器在嵌套中 - 自定义验证器的传递(已知限制)
214
+ - 复杂对象规则 - 嵌套中包含复杂对象 schema
215
+ - 混合嵌套 - Match 中 If,If 中 Match,再嵌套对象
216
+ - 5层超深嵌套 - 极端深度测试
217
+
218
+ **覆盖率提升测试**(14个测试):
219
+ - 错误处理和边界情况(6个)
220
+ - 特殊DSL语法覆盖(4个)
221
+ - 极端和性能测试(4个)
222
+
223
+ **代码修复**:
224
+ - 修复 `lib/adapters/DslAdapter.js` 中 null/undefined 分支处理
225
+ - 新增代码行:476-489(处理空值分支)
226
+
227
+ - **测试覆盖率**:73.12% → 73.17%(语句覆盖率)
228
+ - **Match/If核心功能覆盖率**:~100%(所有嵌套组合和边界情况)
229
+ - 所有测试通过 ✅
230
+
231
+ **迁移指南**:
232
+ 无需任何代码修改,所有嵌套场景现在都自动支持。
22
233
 
23
234
  ---
24
235
 
@@ -454,25 +454,42 @@ class DslAdapter {
454
454
  const build = (index) => {
455
455
  if (index >= entries.length) {
456
456
  if (defaultDsl) {
457
- const s = this._resolveDsl(defaultDsl);
458
- const isRequired = s._required;
459
- // 清理标记
460
- this._cleanRequiredMarks(s);
461
-
462
- const result = { properties: { [targetField]: s } };
463
- if (isRequired) result.required = [targetField];
464
- return result;
457
+ // 处理 _default 值(可能是 Match、If 或普通 DSL)
458
+ if (defaultDsl._isMatch) {
459
+ return this._buildMatchSchema(defaultDsl.field, targetField, defaultDsl.map);
460
+ } else if (defaultDsl._isIf) {
461
+ return this._buildIfSchema(defaultDsl.condition, targetField, defaultDsl.then, defaultDsl.else);
462
+ } else {
463
+ const s = this._resolveDsl(defaultDsl);
464
+ const isRequired = s._required;
465
+ this._cleanRequiredMarks(s);
466
+ const result = { properties: { [targetField]: s } };
467
+ if (isRequired) result.required = [targetField];
468
+ return result;
469
+ }
465
470
  }
466
471
  return {};
467
472
  }
468
473
 
469
474
  const [val, dsl] = entries[index];
470
- const branchSchema = this._resolveDsl(dsl);
471
- const isRequired = branchSchema._required;
472
- this._cleanRequiredMarks(branchSchema);
473
475
 
474
- const thenSchema = { properties: { [targetField]: branchSchema } };
475
- if (isRequired) thenSchema.required = [targetField];
476
+ // 处理分支值(可能是 Match、If 或普通 DSL)
477
+ let thenSchema;
478
+ // 先检查 null/undefined
479
+ if (dsl === null || dsl === undefined) {
480
+ // 跳过 null/undefined 分支,继续下一个
481
+ return build(index + 1);
482
+ } else if (dsl._isMatch) {
483
+ thenSchema = this._buildMatchSchema(dsl.field, targetField, dsl.map);
484
+ } else if (dsl._isIf) {
485
+ thenSchema = this._buildIfSchema(dsl.condition, targetField, dsl.then, dsl.else);
486
+ } else {
487
+ const branchSchema = this._resolveDsl(dsl);
488
+ const isRequired = branchSchema._required;
489
+ this._cleanRequiredMarks(branchSchema);
490
+ thenSchema = { properties: { [targetField]: branchSchema } };
491
+ if (isRequired) thenSchema.required = [targetField];
492
+ }
476
493
 
477
494
  return {
478
495
  if: { properties: { [conditionField]: { const: val } } },
@@ -490,31 +507,41 @@ class DslAdapter {
490
507
  * @static
491
508
  */
492
509
  static _buildIfSchema(conditionField, targetField, thenDsl, elseDsl) {
493
- const thenSchema = this._resolveDsl(thenDsl);
494
- const elseSchema = elseDsl ? this._resolveDsl(elseDsl) : null;
495
-
496
- const thenRequired = thenSchema._required;
497
- this._cleanRequiredMarks(thenSchema);
498
-
499
- const thenResult = { properties: { [targetField]: thenSchema } };
500
- if (thenRequired) thenResult.required = [targetField];
510
+ // 处理 then 分支
511
+ let thenResult;
512
+ if (thenDsl && thenDsl._isMatch) {
513
+ // then 是一个 Match 结构,需要递归构建
514
+ thenResult = this._buildMatchSchema(thenDsl.field, targetField, thenDsl.map);
515
+ } else if (thenDsl && thenDsl._isIf) {
516
+ // then 是一个 If 结构,需要递归构建
517
+ thenResult = this._buildIfSchema(thenDsl.condition, targetField, thenDsl.then, thenDsl.else);
518
+ } else {
519
+ const thenSchema = this._resolveDsl(thenDsl);
520
+ const thenRequired = thenSchema._required;
521
+ this._cleanRequiredMarks(thenSchema);
522
+ thenResult = { properties: { [targetField]: thenSchema } };
523
+ if (thenRequired) thenResult.required = [targetField];
524
+ }
501
525
 
526
+ // 处理 else 分支
502
527
  let elseResult = {};
503
- if (elseSchema) {
504
- const elseRequired = elseSchema._required;
505
- this._cleanRequiredMarks(elseSchema);
506
- elseResult = { properties: { [targetField]: elseSchema } };
507
- if (elseRequired) elseResult.required = [targetField];
528
+ if (elseDsl) {
529
+ if (elseDsl._isMatch) {
530
+ // else 也是一个 Match 结构
531
+ elseResult = this._buildMatchSchema(elseDsl.field, targetField, elseDsl.map);
532
+ } else if (elseDsl._isIf) {
533
+ // else 也是一个 If 结构
534
+ elseResult = this._buildIfSchema(elseDsl.condition, targetField, elseDsl.then, elseDsl.else);
535
+ } else {
536
+ const elseSchema = this._resolveDsl(elseDsl);
537
+ const elseRequired = elseSchema._required;
538
+ this._cleanRequiredMarks(elseSchema);
539
+ elseResult = { properties: { [targetField]: elseSchema } };
540
+ if (elseRequired) elseResult.required = [targetField];
541
+ }
508
542
  }
509
543
 
510
- // 简单假设 condition 是字段存在性或布尔值 true
511
- // 如果需要更复杂的条件,可能需要解析 condition 字符串
512
- // 这里假设 conditionField 是一个布尔字段,检查它是否为 true
513
- // 或者检查它是否存在 (如果它是一个 required 字段)
514
-
515
544
  // 默认行为:检查字段值为 true (适用于 isVip: boolean)
516
- // 如果需要检查存在性,JSON Schema 比较复杂
517
-
518
545
  return {
519
546
  if: { properties: { [conditionField]: { const: true } } },
520
547
  then: thenResult,
@@ -80,6 +80,27 @@ class ErrorFormatter {
80
80
  errors = [errors];
81
81
  }
82
82
 
83
+ // 过滤冗余的包装错误(v1.0.7.1 增强)
84
+ // 当存在具体字段错误时,移除通用的包装错误
85
+ const hasConcreteErrors = errors.some(err => {
86
+ const keyword = err.keyword;
87
+ // 包装错误关键字:if, anyOf, oneOf, allOf
88
+ return keyword !== 'if' &&
89
+ keyword !== 'anyOf' &&
90
+ keyword !== 'oneOf' &&
91
+ keyword !== 'error';
92
+ });
93
+
94
+ if (hasConcreteErrors) {
95
+ // 过滤掉包装错误(这些是通用的组合验证错误)
96
+ errors = errors.filter(err => {
97
+ const keyword = err.keyword;
98
+ return keyword !== 'if' &&
99
+ keyword !== 'anyOf' &&
100
+ keyword !== 'oneOf';
101
+ });
102
+ }
103
+
83
104
  // 获取当前使用的消息模板
84
105
  const messages = locale ? this._loadMessages(locale) : this.messages;
85
106
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schema-dsl",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "简洁强大的JSON Schema验证库 - DSL语法 + String扩展 + 便捷validate",
5
5
  "main": "index.js",
6
6
  "exports": {