schema-dsl 1.1.0 → 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 +131 -11
- package/README.md +244 -271
- package/STATUS.md +65 -3
- package/docs/conditional-api.md +257 -11
- package/examples/i18n-error.examples.js +181 -0
- package/index.d.ts +324 -1
- package/index.js +35 -2
- package/lib/core/ConditionalBuilder.js +115 -13
- 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
package/index.d.ts
CHANGED
|
@@ -1249,6 +1249,86 @@ declare module 'schema-dsl' {
|
|
|
1249
1249
|
*
|
|
1250
1250
|
* JavaScript 用户请直接使用 `dsl.if()`
|
|
1251
1251
|
* TypeScript 用户可以使用 `dsl['if']()` 或 `dsl._if()`
|
|
1252
|
+
*
|
|
1253
|
+
* @alias _if
|
|
1254
|
+
*/
|
|
1255
|
+
export const _if: (condition: any, thenSchema: any, elseSchema?: any) => any;
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* 多语言错误快捷方法 (v1.1.1+)
|
|
1259
|
+
*
|
|
1260
|
+
* @description 统一的多语言错误抛出机制
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* ```typescript
|
|
1264
|
+
* import { dsl } from 'schema-dsl';
|
|
1265
|
+
*
|
|
1266
|
+
* // 创建错误
|
|
1267
|
+
* const error = dsl.error.create('account.notFound');
|
|
1268
|
+
*
|
|
1269
|
+
* // 直接抛出
|
|
1270
|
+
* dsl.error.throw('account.notFound');
|
|
1271
|
+
*
|
|
1272
|
+
* // 断言风格
|
|
1273
|
+
* dsl.error.assert(account, 'account.notFound');
|
|
1274
|
+
* dsl.error.assert(
|
|
1275
|
+
* account.balance >= 100,
|
|
1276
|
+
* 'account.insufficientBalance',
|
|
1277
|
+
* { balance: account.balance, required: 100 }
|
|
1278
|
+
* );
|
|
1279
|
+
* ```
|
|
1280
|
+
*/
|
|
1281
|
+
export const error: {
|
|
1282
|
+
/**
|
|
1283
|
+
* 创建多语言错误(不抛出)
|
|
1284
|
+
* @param code - 错误代码(多语言 key)
|
|
1285
|
+
* @param params - 错误参数
|
|
1286
|
+
* @param statusCode - HTTP 状态码
|
|
1287
|
+
* @returns 错误实例
|
|
1288
|
+
*/
|
|
1289
|
+
create(
|
|
1290
|
+
code: string,
|
|
1291
|
+
params?: Record<string, any>,
|
|
1292
|
+
statusCode?: number
|
|
1293
|
+
): I18nError;
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* 抛出多语言错误
|
|
1297
|
+
* @param code - 错误代码(多语言 key)
|
|
1298
|
+
* @param params - 错误参数
|
|
1299
|
+
* @param statusCode - HTTP 状态码
|
|
1300
|
+
* @throws I18nError
|
|
1301
|
+
*/
|
|
1302
|
+
throw(
|
|
1303
|
+
code: string,
|
|
1304
|
+
params?: Record<string, any>,
|
|
1305
|
+
statusCode?: number
|
|
1306
|
+
): never;
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* 断言方法 - 条件不满足时抛错
|
|
1310
|
+
* @param condition - 条件表达式
|
|
1311
|
+
* @param code - 错误代码(多语言 key)
|
|
1312
|
+
* @param params - 错误参数
|
|
1313
|
+
* @param statusCode - HTTP 状态码
|
|
1314
|
+
* @throws I18nError 条件为 false 时抛出
|
|
1315
|
+
*/
|
|
1316
|
+
assert(
|
|
1317
|
+
condition: any,
|
|
1318
|
+
code: string,
|
|
1319
|
+
params?: Record<string, any>,
|
|
1320
|
+
statusCode?: number
|
|
1321
|
+
): asserts condition;
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* 条件规则的别名(用于 TypeScript)
|
|
1327
|
+
*
|
|
1328
|
+
* @description 因为 TypeScript 中 `if` 是保留字,提供 `_if` 作为别名
|
|
1329
|
+
*
|
|
1330
|
+
* JavaScript 用户请直接使用 `dsl.if()`
|
|
1331
|
+
* TypeScript 用户可以使用 `dsl['if']()` 或 `dsl._if()`
|
|
1252
1332
|
*/
|
|
1253
1333
|
export { _if as if };
|
|
1254
1334
|
|
|
@@ -1572,6 +1652,161 @@ declare module 'schema-dsl' {
|
|
|
1572
1652
|
getErrorCount(): number;
|
|
1573
1653
|
}
|
|
1574
1654
|
|
|
1655
|
+
/**
|
|
1656
|
+
* I18nError - 多语言错误类
|
|
1657
|
+
*
|
|
1658
|
+
* @version 1.1.1
|
|
1659
|
+
*
|
|
1660
|
+
* @description 统一的多语言错误抛出机制,支持:
|
|
1661
|
+
* - 多语言 key 自动翻译
|
|
1662
|
+
* - 参数插值(如 {{#balance}}, {{#required}})
|
|
1663
|
+
* - 自定义错误代码
|
|
1664
|
+
* - Express/Koa 集成
|
|
1665
|
+
*
|
|
1666
|
+
* @example 基础用法
|
|
1667
|
+
* ```typescript
|
|
1668
|
+
* import { I18nError } from 'schema-dsl';
|
|
1669
|
+
*
|
|
1670
|
+
* // 直接抛出
|
|
1671
|
+
* throw I18nError.create('account.notFound');
|
|
1672
|
+
* // 中文: "账户不存在"
|
|
1673
|
+
* // 英文: "Account not found"
|
|
1674
|
+
* ```
|
|
1675
|
+
*
|
|
1676
|
+
* @example 带参数
|
|
1677
|
+
* ```typescript
|
|
1678
|
+
* I18nError.throw('account.insufficientBalance', {
|
|
1679
|
+
* balance: 50,
|
|
1680
|
+
* required: 100
|
|
1681
|
+
* });
|
|
1682
|
+
* // 输出: "余额不足,当前余额50,需要100"
|
|
1683
|
+
* ```
|
|
1684
|
+
*
|
|
1685
|
+
* @example 断言风格
|
|
1686
|
+
* ```typescript
|
|
1687
|
+
* function getAccount(id: string) {
|
|
1688
|
+
* const account = db.findAccount(id);
|
|
1689
|
+
* I18nError.assert(account, 'account.notFound');
|
|
1690
|
+
* I18nError.assert(
|
|
1691
|
+
* account.balance >= 100,
|
|
1692
|
+
* 'account.insufficientBalance',
|
|
1693
|
+
* { balance: account.balance, required: 100 }
|
|
1694
|
+
* );
|
|
1695
|
+
* return account;
|
|
1696
|
+
* }
|
|
1697
|
+
* ```
|
|
1698
|
+
*
|
|
1699
|
+
* @example Express 集成
|
|
1700
|
+
* ```typescript
|
|
1701
|
+
* app.use((error, req, res, next) => {
|
|
1702
|
+
* if (error instanceof I18nError) {
|
|
1703
|
+
* return res.status(error.statusCode).json(error.toJSON());
|
|
1704
|
+
* }
|
|
1705
|
+
* next(error);
|
|
1706
|
+
* });
|
|
1707
|
+
* ```
|
|
1708
|
+
*/
|
|
1709
|
+
export class I18nError extends Error {
|
|
1710
|
+
/** 错误名称(固定为 'I18nError') */
|
|
1711
|
+
readonly name: 'I18nError';
|
|
1712
|
+
|
|
1713
|
+
/** 错误消息(已翻译) */
|
|
1714
|
+
message: string;
|
|
1715
|
+
|
|
1716
|
+
/** 错误代码(多语言 key) */
|
|
1717
|
+
code: string;
|
|
1718
|
+
|
|
1719
|
+
/** 错误参数(用于插值) */
|
|
1720
|
+
params: Record<string, any>;
|
|
1721
|
+
|
|
1722
|
+
/** HTTP 状态码 */
|
|
1723
|
+
statusCode: number;
|
|
1724
|
+
|
|
1725
|
+
/** 使用的语言环境 */
|
|
1726
|
+
locale: string;
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* 构造函数
|
|
1730
|
+
* @param code - 错误代码(多语言 key)
|
|
1731
|
+
* @param params - 错误参数(用于插值)
|
|
1732
|
+
* @param statusCode - HTTP 状态码(默认 400)
|
|
1733
|
+
* @param locale - 语言环境(默认使用当前语言)
|
|
1734
|
+
*/
|
|
1735
|
+
constructor(
|
|
1736
|
+
code: string,
|
|
1737
|
+
params?: Record<string, any>,
|
|
1738
|
+
statusCode?: number,
|
|
1739
|
+
locale?: string
|
|
1740
|
+
);
|
|
1741
|
+
|
|
1742
|
+
/**
|
|
1743
|
+
* 静态工厂方法 - 创建错误(不抛出)
|
|
1744
|
+
* @param code - 错误代码
|
|
1745
|
+
* @param params - 错误参数
|
|
1746
|
+
* @param statusCode - HTTP 状态码
|
|
1747
|
+
* @returns 错误实例
|
|
1748
|
+
*/
|
|
1749
|
+
static create(
|
|
1750
|
+
code: string,
|
|
1751
|
+
params?: Record<string, any>,
|
|
1752
|
+
statusCode?: number
|
|
1753
|
+
): I18nError;
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* 静态工厂方法 - 直接抛出错误
|
|
1757
|
+
* @param code - 错误代码
|
|
1758
|
+
* @param params - 错误参数
|
|
1759
|
+
* @param statusCode - HTTP 状态码
|
|
1760
|
+
* @throws I18nError
|
|
1761
|
+
*/
|
|
1762
|
+
static throw(
|
|
1763
|
+
code: string,
|
|
1764
|
+
params?: Record<string, any>,
|
|
1765
|
+
statusCode?: number
|
|
1766
|
+
): never;
|
|
1767
|
+
|
|
1768
|
+
/**
|
|
1769
|
+
* 断言方法 - 条件不满足时抛错
|
|
1770
|
+
* @param condition - 条件表达式
|
|
1771
|
+
* @param code - 错误代码
|
|
1772
|
+
* @param params - 错误参数
|
|
1773
|
+
* @param statusCode - HTTP 状态码
|
|
1774
|
+
* @throws I18nError 条件为 false 时抛出
|
|
1775
|
+
*/
|
|
1776
|
+
static assert(
|
|
1777
|
+
condition: any,
|
|
1778
|
+
code: string,
|
|
1779
|
+
params?: Record<string, any>,
|
|
1780
|
+
statusCode?: number
|
|
1781
|
+
): asserts condition;
|
|
1782
|
+
|
|
1783
|
+
/**
|
|
1784
|
+
* 检查错误是否为指定代码
|
|
1785
|
+
* @param code - 错误代码
|
|
1786
|
+
* @returns 是否匹配
|
|
1787
|
+
*/
|
|
1788
|
+
is(code: string): boolean;
|
|
1789
|
+
|
|
1790
|
+
/**
|
|
1791
|
+
* 转为 JSON 格式(用于 API 响应)
|
|
1792
|
+
* @returns JSON 对象
|
|
1793
|
+
*/
|
|
1794
|
+
toJSON(): {
|
|
1795
|
+
error: string;
|
|
1796
|
+
code: string;
|
|
1797
|
+
message: string;
|
|
1798
|
+
params: Record<string, any>;
|
|
1799
|
+
statusCode: number;
|
|
1800
|
+
locale: string;
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* 转为字符串
|
|
1805
|
+
* @returns 格式化的错误信息
|
|
1806
|
+
*/
|
|
1807
|
+
toString(): string;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1575
1810
|
/**
|
|
1576
1811
|
* 获取默认Validator实例(单例)
|
|
1577
1812
|
*
|
|
@@ -2893,15 +3128,74 @@ declare module 'schema-dsl' {
|
|
|
2893
3128
|
|
|
2894
3129
|
/**
|
|
2895
3130
|
* 添加 AND 条件(与前一个条件组合)
|
|
3131
|
+
*
|
|
3132
|
+
* @version 1.1.1 支持为每个 .and() 条件设置独立的错误消息
|
|
3133
|
+
*
|
|
2896
3134
|
* @param condition - 条件函数
|
|
2897
3135
|
* @returns 当前实例(支持链式调用)
|
|
3136
|
+
*
|
|
3137
|
+
* @example 基础用法(传统 AND 逻辑)
|
|
3138
|
+
* ```typescript
|
|
3139
|
+
* // 所有条件都为 true 才失败
|
|
3140
|
+
* dsl.if(d => d.age >= 18)
|
|
3141
|
+
* .and(d => d.userType === 'admin')
|
|
3142
|
+
* .then('email!')
|
|
3143
|
+
* ```
|
|
3144
|
+
*
|
|
3145
|
+
* @example v1.1.0+ 独立消息(推荐)
|
|
3146
|
+
* ```typescript
|
|
3147
|
+
* // 每个条件都有自己的错误消息
|
|
3148
|
+
* dsl.if(d => !d)
|
|
3149
|
+
* .message('ACCOUNT_NOT_FOUND')
|
|
3150
|
+
* .and(d => d.balance < 100)
|
|
3151
|
+
* .message('INSUFFICIENT_BALANCE')
|
|
3152
|
+
* .assert(account);
|
|
3153
|
+
*
|
|
3154
|
+
* // 工作原理:链式检查模式
|
|
3155
|
+
* // - 第一个条件失败 → 返回 'ACCOUNT_NOT_FOUND'
|
|
3156
|
+
* // - 第二个条件失败 → 返回 'INSUFFICIENT_BALANCE'
|
|
3157
|
+
* // - 所有条件通过 → 验证成功
|
|
3158
|
+
* ```
|
|
3159
|
+
*
|
|
3160
|
+
* @example 多个 .and() 条件
|
|
3161
|
+
* ```typescript
|
|
3162
|
+
* dsl.if(d => !d)
|
|
3163
|
+
* .message('NOT_FOUND')
|
|
3164
|
+
* .and(d => d.status !== 'active')
|
|
3165
|
+
* .message('INACTIVE')
|
|
3166
|
+
* .and(d => d.balance < 100)
|
|
3167
|
+
* .message('INSUFFICIENT')
|
|
3168
|
+
* .assert(account);
|
|
3169
|
+
* // 依次检查,第一个失败的返回其消息
|
|
3170
|
+
* ```
|
|
2898
3171
|
*/
|
|
2899
3172
|
and(condition: (data: any) => boolean): this;
|
|
2900
3173
|
|
|
2901
3174
|
/**
|
|
2902
3175
|
* 添加 OR 条件(与前一个条件组合)
|
|
3176
|
+
*
|
|
3177
|
+
* @version 1.1.1 支持为 .or() 条件设置独立的错误消息
|
|
3178
|
+
*
|
|
2903
3179
|
* @param condition - 条件函数
|
|
2904
3180
|
* @returns 当前实例(支持链式调用)
|
|
3181
|
+
*
|
|
3182
|
+
* @example 基础用法
|
|
3183
|
+
* ```typescript
|
|
3184
|
+
* // 任一条件为 true 就失败
|
|
3185
|
+
* dsl.if((data) => data.age < 18)
|
|
3186
|
+
* .or((data) => data.isBlocked)
|
|
3187
|
+
* .message('不允许注册')
|
|
3188
|
+
* ```
|
|
3189
|
+
*
|
|
3190
|
+
* @example v1.1.0+ 独立消息
|
|
3191
|
+
* ```typescript
|
|
3192
|
+
* dsl.if(d => d.age < 18)
|
|
3193
|
+
* .message('未成年用户不能注册')
|
|
3194
|
+
* .or(d => d.isBlocked)
|
|
3195
|
+
* .message('账户已被封禁')
|
|
3196
|
+
* .assert(data);
|
|
3197
|
+
* // 哪个条件为 true 就返回哪个消息
|
|
3198
|
+
* ```
|
|
2905
3199
|
*/
|
|
2906
3200
|
or(condition: (data: any) => boolean): this;
|
|
2907
3201
|
|
|
@@ -2914,16 +3208,45 @@ declare module 'schema-dsl' {
|
|
|
2914
3208
|
|
|
2915
3209
|
/**
|
|
2916
3210
|
* 设置错误消息(支持多语言 key)
|
|
3211
|
+
*
|
|
3212
|
+
* @version 1.1.1 支持为 .and() 和 .or() 条件设置独立消息
|
|
3213
|
+
*
|
|
2917
3214
|
* 条件为 true 时自动抛出此错误
|
|
3215
|
+
*
|
|
2918
3216
|
* @param msg - 错误消息或多语言 key
|
|
2919
3217
|
* @returns 当前实例(支持链式调用)
|
|
2920
3218
|
*
|
|
2921
|
-
* @example
|
|
3219
|
+
* @example 基础用法
|
|
2922
3220
|
* ```typescript
|
|
2923
3221
|
* // 如果是未成年人(条件为true),抛出错误
|
|
2924
3222
|
* dsl.if((data) => data.age < 18)
|
|
2925
3223
|
* .message('未成年用户不能注册')
|
|
2926
3224
|
* ```
|
|
3225
|
+
*
|
|
3226
|
+
* @example v1.1.0+ 为 .and() 设置独立消息
|
|
3227
|
+
* ```typescript
|
|
3228
|
+
* dsl.if((data) => !data)
|
|
3229
|
+
* .message('账户不存在')
|
|
3230
|
+
* .and((data) => data.balance < 100)
|
|
3231
|
+
* .message('余额不足')
|
|
3232
|
+
* .assert(account);
|
|
3233
|
+
* // 每个条件都有自己的错误消息
|
|
3234
|
+
* ```
|
|
3235
|
+
*
|
|
3236
|
+
* @example 链式检查模式说明
|
|
3237
|
+
* ```typescript
|
|
3238
|
+
* // 启用条件:
|
|
3239
|
+
* // 1. 使用 .message() 模式(不是 .then()/.else())
|
|
3240
|
+
* // 2. root 条件有 .message()
|
|
3241
|
+
* // 3. 有 .and() 条件
|
|
3242
|
+
* // 4. 没有 .or() 条件
|
|
3243
|
+
*
|
|
3244
|
+
* // ✅ 启用链式检查
|
|
3245
|
+
* dsl.if(d => !d).message('A').and(d => d < 100).message('B')
|
|
3246
|
+
*
|
|
3247
|
+
* // ❌ 不启用(有 .or())
|
|
3248
|
+
* dsl.if(d => !d).message('A').and(d => d < 100).or(d => d > 200).message('B')
|
|
3249
|
+
* ```
|
|
2927
3250
|
*/
|
|
2928
3251
|
message(msg: string): this;
|
|
2929
3252
|
|
package/index.js
CHANGED
|
@@ -22,6 +22,10 @@ const ErrorCodes = require('./lib/core/ErrorCodes');
|
|
|
22
22
|
const MessageTemplate = require('./lib/core/MessageTemplate');
|
|
23
23
|
const Locale = require('./lib/core/Locale');
|
|
24
24
|
|
|
25
|
+
// ========== 错误类 ==========
|
|
26
|
+
const ValidationError = require('./lib/errors/ValidationError');
|
|
27
|
+
const I18nError = require('./lib/errors/I18nError');
|
|
28
|
+
|
|
25
29
|
// ========== String 扩展 ==========
|
|
26
30
|
const { installStringExtensions, uninstallStringExtensions } = require('./lib/core/StringExtensions');
|
|
27
31
|
|
|
@@ -48,6 +52,36 @@ dsl.if = function(...args) {
|
|
|
48
52
|
return ConditionalBuilder.start(args[0]);
|
|
49
53
|
};
|
|
50
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
|
+
};
|
|
84
|
+
|
|
51
85
|
/**
|
|
52
86
|
* 全局配置
|
|
53
87
|
* @param {Object} options - 配置选项
|
|
@@ -182,8 +216,6 @@ installStringExtensions(dsl);
|
|
|
182
216
|
|
|
183
217
|
// ========== 导出 ==========
|
|
184
218
|
|
|
185
|
-
// 导入 ValidationError
|
|
186
|
-
const ValidationError = require('./lib/errors/ValidationError');
|
|
187
219
|
|
|
188
220
|
// 导入 validateAsync
|
|
189
221
|
const { validateAsync } = require('./lib/adapters/DslAdapter');
|
|
@@ -214,6 +246,7 @@ module.exports = {
|
|
|
214
246
|
|
|
215
247
|
// 错误类 (v2.1.0 新增)
|
|
216
248
|
ValidationError,
|
|
249
|
+
I18nError, // v1.1.1 新增:多语言错误类
|
|
217
250
|
|
|
218
251
|
// 错误消息系统
|
|
219
252
|
ErrorCodes,
|
|
@@ -59,7 +59,7 @@ class ConditionalBuilder {
|
|
|
59
59
|
this._conditions.push({
|
|
60
60
|
type: 'if',
|
|
61
61
|
condition: conditionFn,
|
|
62
|
-
combinedConditions: [{ op: 'root', fn: conditionFn }]
|
|
62
|
+
combinedConditions: [{ op: 'root', fn: conditionFn, message: null }]
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
return this;
|
|
@@ -73,6 +73,7 @@ class ConditionalBuilder {
|
|
|
73
73
|
* @example
|
|
74
74
|
* dsl.if((data) => data.age >= 18)
|
|
75
75
|
* .and((data) => data.userType === 'admin')
|
|
76
|
+
* .message('必须是管理员')
|
|
76
77
|
* .then('email!')
|
|
77
78
|
*/
|
|
78
79
|
and(conditionFn) {
|
|
@@ -85,7 +86,7 @@ class ConditionalBuilder {
|
|
|
85
86
|
throw new Error('.and() must follow .if() or .elseIf()');
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
last.combinedConditions.push({ op: 'and', fn: conditionFn });
|
|
89
|
+
last.combinedConditions.push({ op: 'and', fn: conditionFn, message: null });
|
|
89
90
|
return this;
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -109,7 +110,7 @@ class ConditionalBuilder {
|
|
|
109
110
|
throw new Error('.or() must follow .if() or .elseIf()');
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
last.combinedConditions.push({ op: 'or', fn: conditionFn });
|
|
113
|
+
last.combinedConditions.push({ op: 'or', fn: conditionFn, message: null });
|
|
113
114
|
return this;
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -136,7 +137,7 @@ class ConditionalBuilder {
|
|
|
136
137
|
this._conditions.push({
|
|
137
138
|
type: 'elseIf',
|
|
138
139
|
condition: conditionFn,
|
|
139
|
-
combinedConditions: [{ op: 'root', fn: conditionFn }]
|
|
140
|
+
combinedConditions: [{ op: 'root', fn: conditionFn, message: null }]
|
|
140
141
|
});
|
|
141
142
|
|
|
142
143
|
return this;
|
|
@@ -144,7 +145,7 @@ class ConditionalBuilder {
|
|
|
144
145
|
|
|
145
146
|
/**
|
|
146
147
|
* 设置错误消息(支持多语言 key)
|
|
147
|
-
|
|
148
|
+
* 条件为 true 时自动抛出此错误,条件为 false 时通过验证
|
|
148
149
|
*
|
|
149
150
|
* @param {string} msg - 错误消息或多语言 key
|
|
150
151
|
* @returns {ConditionalBuilder} 当前实例(支持链式调用)
|
|
@@ -153,6 +154,13 @@ t * 条件为 true 时自动抛出此错误,条件为 false 时通过验证
|
|
|
153
154
|
* // 如果是未成年人,抛出错误
|
|
154
155
|
* dsl.if((data) => data.age < 18)
|
|
155
156
|
* .message('未成年用户不能注册')
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* // 为 and 条件设置独立消息
|
|
160
|
+
* dsl.if((data) => !data)
|
|
161
|
+
* .message('账户不存在')
|
|
162
|
+
* .and((data) => data.balance < 100)
|
|
163
|
+
* .message('余额不足')
|
|
156
164
|
*/
|
|
157
165
|
message(msg) {
|
|
158
166
|
if (typeof msg !== 'string') {
|
|
@@ -164,6 +172,14 @@ t * 条件为 true 时自动抛出此错误,条件为 false 时通过验证
|
|
|
164
172
|
throw new Error('.message() must follow .if() or .elseIf()');
|
|
165
173
|
}
|
|
166
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
|
+
// 同时设置整体消息(作为后备)
|
|
167
183
|
last.message = msg;
|
|
168
184
|
last.action = 'throw'; // 有 message 就自动 throw
|
|
169
185
|
return this;
|
|
@@ -216,31 +232,117 @@ t * 条件为 true 时自动抛出此错误,条件为 false 时通过验证
|
|
|
216
232
|
* @private
|
|
217
233
|
* @param {Object} conditionObj - 条件对象
|
|
218
234
|
* @param {*} data - 待验证数据对象
|
|
219
|
-
* @returns {boolean}
|
|
235
|
+
* @returns {Object} { result: boolean, failedMessage: string|null } 条件结果和失败消息
|
|
236
|
+
*
|
|
237
|
+
* 语义说明:
|
|
238
|
+
* - 传统 AND 模式:所有条件都为 true 才失败
|
|
239
|
+
* - 链式检查模式:root 有 message,且任何 .and() 也有独立 message 时
|
|
240
|
+
* → 依次检查,第一个为 true 的失败
|
|
220
241
|
*/
|
|
221
242
|
_evaluateCondition(conditionObj, data) {
|
|
222
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
|
+
|
|
223
255
|
let result = false;
|
|
256
|
+
let failedMessage = null;
|
|
224
257
|
|
|
225
258
|
for (let i = 0; i < conditionObj.combinedConditions.length; i++) {
|
|
226
259
|
const combined = conditionObj.combinedConditions[i];
|
|
260
|
+
let conditionResult = false;
|
|
227
261
|
|
|
228
262
|
if (combined.op === 'root') {
|
|
229
263
|
// 第一个条件
|
|
230
|
-
|
|
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
|
+
}
|
|
231
287
|
} else if (combined.op === 'and') {
|
|
232
|
-
|
|
233
|
-
|
|
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
|
+
}
|
|
234
315
|
} else if (combined.op === 'or') {
|
|
235
|
-
// OR
|
|
236
|
-
|
|
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
|
+
}
|
|
237
332
|
}
|
|
238
333
|
}
|
|
239
334
|
|
|
240
|
-
|
|
335
|
+
// 返回最终结果
|
|
336
|
+
if (isChainCheckMode) {
|
|
337
|
+
// 链式检查模式:所有条件都为 false,验证通过
|
|
338
|
+
return { result: false, failedMessage: null };
|
|
339
|
+
} else {
|
|
340
|
+
// 传统模式:返回累积结果
|
|
341
|
+
return { result, failedMessage };
|
|
342
|
+
}
|
|
241
343
|
} catch (error) {
|
|
242
344
|
// 条件函数执行出错,视为不满足
|
|
243
|
-
return false;
|
|
345
|
+
return { result: false, failedMessage: null };
|
|
244
346
|
}
|
|
245
347
|
}
|
|
246
348
|
|
package/lib/core/Validator.js
CHANGED
|
@@ -464,16 +464,20 @@ class Validator {
|
|
|
464
464
|
for (let i = 0; i < conditionalSchema.conditions.length; i++) {
|
|
465
465
|
const cond = conditionalSchema.conditions[i];
|
|
466
466
|
|
|
467
|
-
// 执行组合条件(支持 and/or
|
|
468
|
-
const
|
|
467
|
+
// 执行组合条件(支持 and/or),获取结果和失败消息
|
|
468
|
+
const evaluation = conditionalSchema._evaluateCondition(cond, data);
|
|
469
|
+
const matched = evaluation.result;
|
|
470
|
+
const failedMessage = evaluation.failedMessage;
|
|
469
471
|
|
|
470
472
|
if (cond.action === 'throw') {
|
|
471
473
|
// ✅ message 模式:条件为 true 时抛错,条件为 false 时通过
|
|
472
474
|
if (matched) {
|
|
473
475
|
// ✅ 条件满足(true),抛出错误
|
|
476
|
+
// 优先使用具体的失败消息,如果没有则使用整体消息
|
|
477
|
+
const errorMsg = failedMessage || cond.message;
|
|
474
478
|
// 支持多语言:如果 message 是 key(如 'conditional.underAge'),从语言包获取翻译
|
|
475
479
|
// 传递 locale 参数以支持动态语言切换
|
|
476
|
-
const errorMessage = Locale.getMessage(
|
|
480
|
+
const errorMessage = Locale.getMessage(errorMsg, options.messages || {}, locale);
|
|
477
481
|
|
|
478
482
|
return {
|
|
479
483
|
valid: false,
|