schema-dsl 1.0.9 → 1.1.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 +325 -2
- package/README.md +419 -189
- package/STATUS.md +65 -3
- package/docs/FEATURE-INDEX.md +1 -1
- package/docs/best-practices.md +3 -3
- package/docs/cache-manager.md +1 -1
- package/docs/conditional-api.md +1278 -0
- package/docs/dsl-syntax.md +1 -1
- package/docs/dynamic-locale.md +2 -2
- package/docs/error-handling.md +2 -2
- package/docs/export-guide.md +2 -2
- package/docs/export-limitations.md +3 -3
- package/docs/faq.md +6 -6
- package/docs/frontend-i18n-guide.md +1 -1
- package/docs/mongodb-exporter.md +3 -3
- package/docs/multi-type-support.md +12 -2
- package/docs/mysql-exporter.md +1 -1
- package/docs/plugin-system.md +4 -4
- package/docs/postgresql-exporter.md +1 -1
- package/docs/quick-start.md +4 -4
- package/docs/troubleshooting.md +2 -2
- package/docs/type-reference.md +5 -5
- package/docs/typescript-guide.md +5 -6
- package/docs/union-type-guide.md +147 -0
- package/docs/union-types.md +277 -0
- package/docs/validate-async.md +1 -1
- package/examples/array-dsl-example.js +1 -1
- package/examples/conditional-example.js +288 -0
- package/examples/conditional-non-object.js +129 -0
- package/examples/conditional-validate-example.js +321 -0
- package/examples/i18n-error.examples.js +181 -0
- package/examples/union-type-example.js +127 -0
- package/examples/union-types-example.js +77 -0
- package/index.d.ts +655 -7
- package/index.js +54 -3
- package/lib/adapters/DslAdapter.js +14 -5
- package/lib/core/ConditionalBuilder.js +503 -0
- package/lib/core/DslBuilder.js +113 -0
- package/lib/core/Locale.js +13 -8
- package/lib/core/Validator.js +250 -2
- package/lib/errors/I18nError.js +222 -0
- package/lib/locales/en-US.js +39 -0
- package/lib/locales/es-ES.js +4 -0
- package/lib/locales/fr-FR.js +4 -0
- package/lib/locales/ja-JP.js +9 -0
- package/lib/locales/zh-CN.js +39 -0
- package/package.json +3 -1
package/index.js
CHANGED
|
@@ -15,12 +15,17 @@ const ErrorFormatter = require('./lib/core/ErrorFormatter');
|
|
|
15
15
|
const CacheManager = require('./lib/core/CacheManager');
|
|
16
16
|
const DslBuilder = require('./lib/core/DslBuilder');
|
|
17
17
|
const PluginManager = require('./lib/core/PluginManager');
|
|
18
|
+
const ConditionalBuilder = require('./lib/core/ConditionalBuilder');
|
|
18
19
|
|
|
19
20
|
// ========== 错误消息系统 ==========
|
|
20
21
|
const ErrorCodes = require('./lib/core/ErrorCodes');
|
|
21
22
|
const MessageTemplate = require('./lib/core/MessageTemplate');
|
|
22
23
|
const Locale = require('./lib/core/Locale');
|
|
23
24
|
|
|
25
|
+
// ========== 错误类 ==========
|
|
26
|
+
const ValidationError = require('./lib/errors/ValidationError');
|
|
27
|
+
const I18nError = require('./lib/errors/I18nError');
|
|
28
|
+
|
|
24
29
|
// ========== String 扩展 ==========
|
|
25
30
|
const { installStringExtensions, uninstallStringExtensions } = require('./lib/core/StringExtensions');
|
|
26
31
|
|
|
@@ -29,7 +34,53 @@ const dsl = require('./lib/adapters/DslAdapter');
|
|
|
29
34
|
|
|
30
35
|
// 挂载静态方法 (v2.1.0)
|
|
31
36
|
dsl.match = dsl.DslAdapter.match;
|
|
32
|
-
|
|
37
|
+
|
|
38
|
+
// ✅ 智能 dsl.if:根据参数类型选择实现
|
|
39
|
+
dsl.if = function(...args) {
|
|
40
|
+
// 如果第一个参数是函数 → 使用新的 ConditionalBuilder(链式API)
|
|
41
|
+
if (typeof args[0] === 'function') {
|
|
42
|
+
return ConditionalBuilder.start(args[0]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 如果第一个参数是字符串且有3个参数 → 使用原有的字段条件实现
|
|
46
|
+
// dsl.if('fieldName', thenSchema, elseSchema)
|
|
47
|
+
if (typeof args[0] === 'string' && args.length >= 2) {
|
|
48
|
+
return dsl.DslAdapter.if(args[0], args[1], args[2]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 其他情况 → 调用 ConditionalBuilder 让它抛出正确的错误
|
|
52
|
+
return ConditionalBuilder.start(args[0]);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ✅ dsl.error:统一的多语言错误抛出(v1.1.1+)
|
|
56
|
+
dsl.error = {
|
|
57
|
+
/**
|
|
58
|
+
* 创建多语言错误(不抛出)
|
|
59
|
+
* @param {string} code - 错误代码(多语言 key)
|
|
60
|
+
* @param {Object} params - 错误参数
|
|
61
|
+
* @param {number} statusCode - HTTP 状态码
|
|
62
|
+
* @returns {I18nError} 错误实例
|
|
63
|
+
*/
|
|
64
|
+
create: (code, params, statusCode) => I18nError.create(code, params, statusCode),
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 抛出多语言错误
|
|
68
|
+
* @param {string} code - 错误代码(多语言 key)
|
|
69
|
+
* @param {Object} params - 错误参数
|
|
70
|
+
* @param {number} statusCode - HTTP 状态码
|
|
71
|
+
* @throws {I18nError} 直接抛出错误
|
|
72
|
+
*/
|
|
73
|
+
throw: (code, params, statusCode) => I18nError.throw(code, params, statusCode),
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 断言方法 - 条件不满足时抛错
|
|
77
|
+
* @param {boolean} condition - 条件表达式
|
|
78
|
+
* @param {string} code - 错误代码(多语言 key)
|
|
79
|
+
* @param {Object} params - 错误参数
|
|
80
|
+
* @param {number} statusCode - HTTP 状态码
|
|
81
|
+
*/
|
|
82
|
+
assert: (condition, code, params, statusCode) => I18nError.assert(condition, code, params, statusCode)
|
|
83
|
+
};
|
|
33
84
|
|
|
34
85
|
/**
|
|
35
86
|
* 全局配置
|
|
@@ -165,8 +216,6 @@ installStringExtensions(dsl);
|
|
|
165
216
|
|
|
166
217
|
// ========== 导出 ==========
|
|
167
218
|
|
|
168
|
-
// 导入 ValidationError
|
|
169
|
-
const ValidationError = require('./lib/errors/ValidationError');
|
|
170
219
|
|
|
171
220
|
// 导入 validateAsync
|
|
172
221
|
const { validateAsync } = require('./lib/adapters/DslAdapter');
|
|
@@ -186,6 +235,7 @@ module.exports = {
|
|
|
186
235
|
// 核心类
|
|
187
236
|
JSONSchemaCore,
|
|
188
237
|
Validator,
|
|
238
|
+
DslBuilder, // v1.1.0 新增:导出DslBuilder供插件使用
|
|
189
239
|
|
|
190
240
|
// 便捷方法(推荐)
|
|
191
241
|
validate, // 便捷验证(单例)
|
|
@@ -196,6 +246,7 @@ module.exports = {
|
|
|
196
246
|
|
|
197
247
|
// 错误类 (v2.1.0 新增)
|
|
198
248
|
ValidationError,
|
|
249
|
+
I18nError, // v1.1.1 新增:多语言错误类
|
|
199
250
|
|
|
200
251
|
// 错误消息系统
|
|
201
252
|
ErrorCodes,
|
|
@@ -349,7 +349,7 @@ class DslAdapter {
|
|
|
349
349
|
// 占位Schema,确保字段存在
|
|
350
350
|
fieldSchema = { description: `Depends on ${value.field}` };
|
|
351
351
|
}
|
|
352
|
-
// 2. If 结构 (dsl.if)
|
|
352
|
+
// 2. If 结构 (dsl.if - 旧版本)
|
|
353
353
|
else if (value && value._isIf) {
|
|
354
354
|
const ifSchema = this._buildIfSchema(value.condition, fieldKey, value.then, value.else);
|
|
355
355
|
if (ifSchema) {
|
|
@@ -358,20 +358,29 @@ class DslAdapter {
|
|
|
358
358
|
}
|
|
359
359
|
fieldSchema = { description: `Conditional field based on ${value.condition}` };
|
|
360
360
|
}
|
|
361
|
-
// 3.
|
|
361
|
+
// 3. ConditionalBuilder 结构 (dsl.if - 新版本)
|
|
362
|
+
else if (value && value._isConditional) {
|
|
363
|
+
// 调用 toSchema() 转换为 Schema 对象
|
|
364
|
+
const conditionalSchema = typeof value.toSchema === 'function'
|
|
365
|
+
? value.toSchema()
|
|
366
|
+
: value;
|
|
367
|
+
// 保存条件链,留待 Validator 执行
|
|
368
|
+
fieldSchema = conditionalSchema;
|
|
369
|
+
}
|
|
370
|
+
// 4. DslBuilder 实例(链式调用结果)
|
|
362
371
|
else if (value instanceof DslBuilder) {
|
|
363
372
|
fieldSchema = value.toSchema();
|
|
364
373
|
}
|
|
365
|
-
//
|
|
374
|
+
// 5. 纯字符串 DSL
|
|
366
375
|
else if (typeof value === 'string') {
|
|
367
376
|
const builder = new DslBuilder(value);
|
|
368
377
|
fieldSchema = builder.toSchema();
|
|
369
378
|
}
|
|
370
|
-
//
|
|
379
|
+
// 6. 嵌套对象
|
|
371
380
|
else if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
|
|
372
381
|
fieldSchema = this.parseObject(value);
|
|
373
382
|
}
|
|
374
|
-
//
|
|
383
|
+
// 7. 其他类型(保留原样)
|
|
375
384
|
else {
|
|
376
385
|
fieldSchema = value;
|
|
377
386
|
}
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConditionalBuilder - 链式条件构建器
|
|
3
|
+
*
|
|
4
|
+
* 提供流畅的条件判断 API,类似 JavaScript if-else 语句
|
|
5
|
+
*
|
|
6
|
+
* @module lib/core/ConditionalBuilder
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // 简单条件 + 错误消息
|
|
11
|
+
* dsl.if((data) => data.age >= 18)
|
|
12
|
+
* .message('未成年用户不能注册')
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // 多条件 and
|
|
16
|
+
* dsl.if((data) => data.age >= 18)
|
|
17
|
+
* .and((data) => data.userType === 'admin')
|
|
18
|
+
* .then('email!')
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // 多条件 or
|
|
22
|
+
* dsl.if((data) => data.age < 18)
|
|
23
|
+
* .or((data) => data.isBlocked)
|
|
24
|
+
* .message('不允许注册')
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // elseIf 和 else
|
|
28
|
+
* dsl.if((data) => data.userType === 'admin')
|
|
29
|
+
* .then('email!')
|
|
30
|
+
* .elseIf((data) => data.userType === 'vip')
|
|
31
|
+
* .then('email')
|
|
32
|
+
* .else(null)
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
class ConditionalBuilder {
|
|
36
|
+
/**
|
|
37
|
+
* 创建条件构建器实例
|
|
38
|
+
* @private - 不直接调用,使用 dsl.if() 入口
|
|
39
|
+
*/
|
|
40
|
+
constructor() {
|
|
41
|
+
this._conditions = [];
|
|
42
|
+
this._elseSchema = undefined;
|
|
43
|
+
this._isConditional = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 开始条件判断
|
|
48
|
+
* @param {Function} conditionFn - 条件函数,接收完整数据对象
|
|
49
|
+
* @returns {ConditionalBuilder} 当前实例(支持链式调用)
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* dsl.if((data) => data.age >= 18)
|
|
53
|
+
*/
|
|
54
|
+
if(conditionFn) {
|
|
55
|
+
if (typeof conditionFn !== 'function') {
|
|
56
|
+
throw new Error('Condition must be a function');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this._conditions.push({
|
|
60
|
+
type: 'if',
|
|
61
|
+
condition: conditionFn,
|
|
62
|
+
combinedConditions: [{ op: 'root', fn: conditionFn, message: null }]
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 添加 AND 条件(与前一个条件组合)
|
|
70
|
+
* @param {Function} conditionFn - 条件函数
|
|
71
|
+
* @returns {ConditionalBuilder} 当前实例(支持链式调用)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* dsl.if((data) => data.age >= 18)
|
|
75
|
+
* .and((data) => data.userType === 'admin')
|
|
76
|
+
* .message('必须是管理员')
|
|
77
|
+
* .then('email!')
|
|
78
|
+
*/
|
|
79
|
+
and(conditionFn) {
|
|
80
|
+
if (typeof conditionFn !== 'function') {
|
|
81
|
+
throw new Error('Condition must be a function');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const last = this._conditions[this._conditions.length - 1];
|
|
85
|
+
if (!last) {
|
|
86
|
+
throw new Error('.and() must follow .if() or .elseIf()');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
last.combinedConditions.push({ op: 'and', fn: conditionFn, message: null });
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 添加 OR 条件(与前一个条件组合)
|
|
95
|
+
* @param {Function} conditionFn - 条件函数
|
|
96
|
+
* @returns {ConditionalBuilder} 当前实例(支持链式调用)
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* dsl.if((data) => data.age < 18)
|
|
100
|
+
* .or((data) => data.isBlocked)
|
|
101
|
+
* .message('不允许注册')
|
|
102
|
+
*/
|
|
103
|
+
or(conditionFn) {
|
|
104
|
+
if (typeof conditionFn !== 'function') {
|
|
105
|
+
throw new Error('Condition must be a function');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const last = this._conditions[this._conditions.length - 1];
|
|
109
|
+
if (!last) {
|
|
110
|
+
throw new Error('.or() must follow .if() or .elseIf()');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
last.combinedConditions.push({ op: 'or', fn: conditionFn, message: null });
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 添加 else-if 分支
|
|
119
|
+
* @param {Function} conditionFn - 条件函数
|
|
120
|
+
* @returns {ConditionalBuilder} 当前实例(支持链式调用)
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* dsl.if((data) => data.userType === 'admin')
|
|
124
|
+
* .then('email!')
|
|
125
|
+
* .elseIf((data) => data.userType === 'vip')
|
|
126
|
+
* .then('email')
|
|
127
|
+
*/
|
|
128
|
+
elseIf(conditionFn) {
|
|
129
|
+
if (typeof conditionFn !== 'function') {
|
|
130
|
+
throw new Error('Condition must be a function');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (this._conditions.length === 0) {
|
|
134
|
+
throw new Error('.elseIf() must follow .if()');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this._conditions.push({
|
|
138
|
+
type: 'elseIf',
|
|
139
|
+
condition: conditionFn,
|
|
140
|
+
combinedConditions: [{ op: 'root', fn: conditionFn, message: null }]
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 设置错误消息(支持多语言 key)
|
|
148
|
+
* 条件为 true 时自动抛出此错误,条件为 false 时通过验证
|
|
149
|
+
*
|
|
150
|
+
* @param {string} msg - 错误消息或多语言 key
|
|
151
|
+
* @returns {ConditionalBuilder} 当前实例(支持链式调用)
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* // 如果是未成年人,抛出错误
|
|
155
|
+
* dsl.if((data) => data.age < 18)
|
|
156
|
+
* .message('未成年用户不能注册')
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* // 为 and 条件设置独立消息
|
|
160
|
+
* dsl.if((data) => !data)
|
|
161
|
+
* .message('账户不存在')
|
|
162
|
+
* .and((data) => data.balance < 100)
|
|
163
|
+
* .message('余额不足')
|
|
164
|
+
*/
|
|
165
|
+
message(msg) {
|
|
166
|
+
if (typeof msg !== 'string') {
|
|
167
|
+
throw new Error('Message must be a string');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const last = this._conditions[this._conditions.length - 1];
|
|
171
|
+
if (!last) {
|
|
172
|
+
throw new Error('.message() must follow .if() or .elseIf()');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 找到最后一个添加的条件(可能是 root、and 或 or)
|
|
176
|
+
const lastCombined = last.combinedConditions[last.combinedConditions.length - 1];
|
|
177
|
+
if (lastCombined) {
|
|
178
|
+
// 为最后一个组合条件设置消息
|
|
179
|
+
lastCombined.message = msg;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 同时设置整体消息(作为后备)
|
|
183
|
+
last.message = msg;
|
|
184
|
+
last.action = 'throw'; // 有 message 就自动 throw
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 设置满足条件时的 Schema
|
|
190
|
+
* @param {string|Object} schema - DSL 字符串或 Schema 对象
|
|
191
|
+
* @returns {ConditionalBuilder} 当前实例(支持链式调用)
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* dsl.if((data) => data.userType === 'admin')
|
|
195
|
+
* .then('email!')
|
|
196
|
+
*/
|
|
197
|
+
then(schema) {
|
|
198
|
+
const last = this._conditions[this._conditions.length - 1];
|
|
199
|
+
if (!last) {
|
|
200
|
+
throw new Error('.then() must follow .if() or .elseIf()');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
last.then = schema;
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 设置默认 Schema(所有条件都不满足时)
|
|
209
|
+
* 可选:不写 else 就是不验证
|
|
210
|
+
*
|
|
211
|
+
* @param {string|Object|null} schema - DSL 字符串、Schema 对象或 null
|
|
212
|
+
* @returns {ConditionalBuilder} 当前实例(支持链式调用)
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* // else 可选
|
|
216
|
+
* dsl.if((data) => data.userType === 'admin')
|
|
217
|
+
* .then('email!') // 不写 else
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* // 显式指定 else
|
|
221
|
+
* dsl.if((data) => data.userType === 'admin')
|
|
222
|
+
* .then('email!')
|
|
223
|
+
* .else('email')
|
|
224
|
+
*/
|
|
225
|
+
else(schema) {
|
|
226
|
+
this._elseSchema = schema;
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 执行组合条件(内部方法)
|
|
232
|
+
* @private
|
|
233
|
+
* @param {Object} conditionObj - 条件对象
|
|
234
|
+
* @param {*} data - 待验证数据对象
|
|
235
|
+
* @returns {Object} { result: boolean, failedMessage: string|null } 条件结果和失败消息
|
|
236
|
+
*
|
|
237
|
+
* 语义说明:
|
|
238
|
+
* - 传统 AND 模式:所有条件都为 true 才失败
|
|
239
|
+
* - 链式检查模式:root 有 message,且任何 .and() 也有独立 message 时
|
|
240
|
+
* → 依次检查,第一个为 true 的失败
|
|
241
|
+
*/
|
|
242
|
+
_evaluateCondition(conditionObj, data) {
|
|
243
|
+
try {
|
|
244
|
+
// 检查是否是链式检查模式:
|
|
245
|
+
// 1. 必须是 message 模式(action='throw')
|
|
246
|
+
// 2. root 条件有 message
|
|
247
|
+
// 3. 有 .and() 条件(不管是否有独立 message)
|
|
248
|
+
// 4. 没有 .or() 条件(有 OR 就使用传统逻辑)
|
|
249
|
+
const isMessageMode = conditionObj.action === 'throw';
|
|
250
|
+
const rootHasMessage = conditionObj.combinedConditions[0]?.message != null;
|
|
251
|
+
const hasAndConditions = conditionObj.combinedConditions.some(c => c.op === 'and');
|
|
252
|
+
const hasOrConditions = conditionObj.combinedConditions.some(c => c.op === 'or');
|
|
253
|
+
const isChainCheckMode = isMessageMode && rootHasMessage && hasAndConditions && !hasOrConditions;
|
|
254
|
+
|
|
255
|
+
let result = false;
|
|
256
|
+
let failedMessage = null;
|
|
257
|
+
|
|
258
|
+
for (let i = 0; i < conditionObj.combinedConditions.length; i++) {
|
|
259
|
+
const combined = conditionObj.combinedConditions[i];
|
|
260
|
+
let conditionResult = false;
|
|
261
|
+
|
|
262
|
+
if (combined.op === 'root') {
|
|
263
|
+
// 第一个条件
|
|
264
|
+
conditionResult = combined.fn(data);
|
|
265
|
+
result = conditionResult;
|
|
266
|
+
|
|
267
|
+
if (isChainCheckMode) {
|
|
268
|
+
// 链式检查模式:第一个条件为 true 就失败
|
|
269
|
+
if (result) {
|
|
270
|
+
failedMessage = combined.message || conditionObj.message;
|
|
271
|
+
return { result: true, failedMessage };
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
// 传统模式
|
|
275
|
+
failedMessage = combined.message || conditionObj.message;
|
|
276
|
+
|
|
277
|
+
// 如果第一个条件为 false,检查是否有 OR 条件
|
|
278
|
+
if (!result) {
|
|
279
|
+
const hasOrConditions = conditionObj.combinedConditions.some(c => c.op === 'or');
|
|
280
|
+
if (!hasOrConditions) {
|
|
281
|
+
// 没有 OR 条件,直接通过
|
|
282
|
+
return { result: false, failedMessage: null };
|
|
283
|
+
}
|
|
284
|
+
// 有 OR 条件,继续检查
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} else if (combined.op === 'and') {
|
|
288
|
+
conditionResult = combined.fn(data);
|
|
289
|
+
|
|
290
|
+
if (isChainCheckMode) {
|
|
291
|
+
// 链式检查模式:任一条件为 true 就失败
|
|
292
|
+
if (conditionResult) {
|
|
293
|
+
// 使用独立消息,如果没有则使用整体消息
|
|
294
|
+
failedMessage = combined.message || conditionObj.message;
|
|
295
|
+
return { result: true, failedMessage };
|
|
296
|
+
}
|
|
297
|
+
// 条件为 false,继续检查下一个条件
|
|
298
|
+
} else {
|
|
299
|
+
// 传统 AND 模式:所有条件都必须为 true 才失败
|
|
300
|
+
if (!conditionResult) {
|
|
301
|
+
// AND 条件为 false
|
|
302
|
+
// 检查是否有 OR 条件
|
|
303
|
+
if (hasOrConditions) {
|
|
304
|
+
// 有 OR 条件,将 result 设为 false,继续检查 OR
|
|
305
|
+
result = false;
|
|
306
|
+
} else {
|
|
307
|
+
// 没有 OR 条件,任一 AND 条件为 false 就验证通过
|
|
308
|
+
return { result: false, failedMessage: null };
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
// AND 条件为 true,继续累积
|
|
312
|
+
result = true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} else if (combined.op === 'or') {
|
|
316
|
+
// OR 逻辑:任一条件为 true 就失败
|
|
317
|
+
if (!result) {
|
|
318
|
+
conditionResult = combined.fn(data);
|
|
319
|
+
|
|
320
|
+
if (conditionResult) {
|
|
321
|
+
// OR 条件为 true
|
|
322
|
+
result = true; // 更新 result
|
|
323
|
+
|
|
324
|
+
if (isMessageMode) {
|
|
325
|
+
// message 模式:立即返回失败
|
|
326
|
+
failedMessage = combined.message || conditionObj.message;
|
|
327
|
+
return { result: true, failedMessage };
|
|
328
|
+
}
|
|
329
|
+
// then/else 模式:继续累积 result(已经设置为true)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 返回最终结果
|
|
336
|
+
if (isChainCheckMode) {
|
|
337
|
+
// 链式检查模式:所有条件都为 false,验证通过
|
|
338
|
+
return { result: false, failedMessage: null };
|
|
339
|
+
} else {
|
|
340
|
+
// 传统模式:返回累积结果
|
|
341
|
+
return { result, failedMessage };
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
// 条件函数执行出错,视为不满足
|
|
345
|
+
return { result: false, failedMessage: null };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 转换为 Schema 对象(内部方法)
|
|
351
|
+
* @private
|
|
352
|
+
* @returns {Object} Schema 对象
|
|
353
|
+
*/
|
|
354
|
+
toSchema() {
|
|
355
|
+
return {
|
|
356
|
+
_isConditional: true,
|
|
357
|
+
conditions: this._conditions,
|
|
358
|
+
else: this._elseSchema,
|
|
359
|
+
// 保存 _evaluateCondition 方法供 Validator 使用
|
|
360
|
+
_evaluateCondition: this._evaluateCondition.bind(this)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 快捷验证方法 - 返回完整验证结果
|
|
366
|
+
* @param {*} data - 待验证的数据(任意类型)
|
|
367
|
+
* @param {Object} options - 验证选项(可选)
|
|
368
|
+
* @returns {Object} 验证结果 { valid, errors, data }
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* // 一行代码验证
|
|
372
|
+
* const result = dsl.if(d => d.age < 18)
|
|
373
|
+
* .message('未成年')
|
|
374
|
+
* .validate({ age: 16 });
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* // 复用验证器
|
|
378
|
+
* const validator = dsl.if(d => d.age < 18).message('未成年');
|
|
379
|
+
* const r1 = validator.validate({ age: 16 });
|
|
380
|
+
* const r2 = validator.validate({ age: 20 });
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* // 非对象类型
|
|
384
|
+
* const result = dsl.if(d => d.includes('@'))
|
|
385
|
+
* .then('email!')
|
|
386
|
+
* .validate('test@example.com');
|
|
387
|
+
*/
|
|
388
|
+
validate(data, options = {}) {
|
|
389
|
+
const Validator = require('./Validator');
|
|
390
|
+
const validator = new Validator(options);
|
|
391
|
+
return validator.validate(this.toSchema(), data, options);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 异步验证方法 - 失败自动抛出异常
|
|
396
|
+
* @param {*} data - 待验证的数据
|
|
397
|
+
* @param {Object} options - 验证选项(可选)
|
|
398
|
+
* @returns {Promise<*>} 验证通过返回数据,失败抛出异常
|
|
399
|
+
* @throws {ValidationError} 验证失败抛出异常
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* // 异步验证,失败自动抛错
|
|
403
|
+
* try {
|
|
404
|
+
* const data = await dsl.if(d => d.age < 18)
|
|
405
|
+
* .message('未成年')
|
|
406
|
+
* .validateAsync({ age: 16 });
|
|
407
|
+
* } catch (error) {
|
|
408
|
+
* console.log(error.message); // "未成年"
|
|
409
|
+
* }
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* // Express 中间件
|
|
413
|
+
* app.post('/register', async (req, res, next) => {
|
|
414
|
+
* try {
|
|
415
|
+
* await dsl.if(d => d.age < 18)
|
|
416
|
+
* .message('未成年用户不能注册')
|
|
417
|
+
* .validateAsync(req.body);
|
|
418
|
+
* // 验证通过,继续处理...
|
|
419
|
+
* } catch (error) {
|
|
420
|
+
* next(error);
|
|
421
|
+
* }
|
|
422
|
+
* });
|
|
423
|
+
*/
|
|
424
|
+
async validateAsync(data, options = {}) {
|
|
425
|
+
const Validator = require('./Validator');
|
|
426
|
+
const validator = new Validator(options);
|
|
427
|
+
return validator.validateAsync(this.toSchema(), data, options);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* 断言方法 - 同步验证,失败直接抛错
|
|
432
|
+
* @param {*} data - 待验证的数据
|
|
433
|
+
* @param {Object} options - 验证选项(可选)
|
|
434
|
+
* @returns {*} 验证通过返回数据
|
|
435
|
+
* @throws {Error} 验证失败抛出错误
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* // 断言验证,失败直接抛错
|
|
439
|
+
* try {
|
|
440
|
+
* dsl.if(d => d.age < 18)
|
|
441
|
+
* .message('未成年')
|
|
442
|
+
* .assert({ age: 16 });
|
|
443
|
+
* } catch (error) {
|
|
444
|
+
* console.log(error.message); // "未成年"
|
|
445
|
+
* }
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* // 函数中快速断言
|
|
449
|
+
* function registerUser(userData) {
|
|
450
|
+
* dsl.if(d => d.age < 18)
|
|
451
|
+
* .message('未成年用户不能注册')
|
|
452
|
+
* .assert(userData);
|
|
453
|
+
*
|
|
454
|
+
* // 验证通过,继续处理...
|
|
455
|
+
* return createUser(userData);
|
|
456
|
+
* }
|
|
457
|
+
*/
|
|
458
|
+
assert(data, options = {}) {
|
|
459
|
+
const result = this.validate(data, options);
|
|
460
|
+
if (!result.valid) {
|
|
461
|
+
const error = new Error(result.errors[0].message);
|
|
462
|
+
error.errors = result.errors;
|
|
463
|
+
error.name = 'ValidationError';
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
466
|
+
return data;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* 快捷检查方法 - 只返回 boolean
|
|
471
|
+
* @param {*} data - 待验证的数据
|
|
472
|
+
* @returns {boolean} 验证是否通过
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* // 快速判断
|
|
476
|
+
* const isValid = dsl.if(d => d.age < 18)
|
|
477
|
+
* .message('未成年')
|
|
478
|
+
* .check({ age: 16 });
|
|
479
|
+
* // => false
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* // 断言场景
|
|
483
|
+
* if (!validator.check(userData)) {
|
|
484
|
+
* console.log('验证失败');
|
|
485
|
+
* }
|
|
486
|
+
*/
|
|
487
|
+
check(data) {
|
|
488
|
+
return this.validate(data).valid;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* 静态工厂方法 - dsl.if() 入口
|
|
493
|
+
* @static
|
|
494
|
+
* @param {Function} conditionFn - 条件函数
|
|
495
|
+
* @returns {ConditionalBuilder} 新的构建器实例
|
|
496
|
+
*/
|
|
497
|
+
static start(conditionFn) {
|
|
498
|
+
return new ConditionalBuilder().if(conditionFn);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
module.exports = ConditionalBuilder;
|
|
503
|
+
|