schema-dsl 1.1.3 → 1.1.5
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 +86 -1271
- package/README.md +366 -2
- package/STATUS.md +69 -3
- package/changelogs/v1.0.0.md +328 -0
- package/changelogs/v1.0.9.md +367 -0
- package/changelogs/v1.1.0.md +389 -0
- package/changelogs/v1.1.1.md +308 -0
- package/changelogs/v1.1.2.md +183 -0
- package/changelogs/v1.1.3.md +161 -0
- package/changelogs/v1.1.4.md +432 -0
- package/changelogs/v1.1.5.md +493 -0
- package/docs/dsl-syntax.md +14 -3
- package/docs/error-handling.md +247 -2
- package/docs/optional-marker-guide.md +321 -0
- package/docs/runtime-locale-support.md +470 -0
- package/index.d.ts +203 -12
- package/index.js +6 -3
- package/index.mjs +2 -2
- package/lib/core/DslBuilder.js +11 -2
- package/lib/core/Locale.js +28 -22
- package/lib/core/MessageTemplate.js +30 -21
- package/lib/core/Validator.js +6 -1
- package/lib/errors/I18nError.js +59 -15
- package/lib/locales/en-US.js +13 -3
- package/lib/locales/zh-CN.js +14 -3
- package/lib/validators/CustomKeywords.js +10 -2
- package/package.json +1 -1
package/lib/errors/I18nError.js
CHANGED
|
@@ -61,15 +61,34 @@ const MessageTemplate = require('../core/MessageTemplate');
|
|
|
61
61
|
class I18nError extends Error {
|
|
62
62
|
/**
|
|
63
63
|
* 构造函数
|
|
64
|
-
* @param {string}
|
|
64
|
+
* @param {string} key - 错误代码(多语言 key)
|
|
65
65
|
* @param {Object} params - 错误参数(用于插值)
|
|
66
66
|
* @param {number} statusCode - HTTP 状态码(默认 400)
|
|
67
67
|
* @param {string} locale - 语言环境(默认使用当前语言)
|
|
68
|
+
* @version 1.1.5 - 支持对象格式配置
|
|
68
69
|
*/
|
|
69
|
-
constructor(
|
|
70
|
-
//
|
|
70
|
+
constructor(key, params = {}, statusCode = 400, locale = null) {
|
|
71
|
+
// 获取语言环境
|
|
71
72
|
const actualLocale = locale || Locale.getLocale();
|
|
72
|
-
|
|
73
|
+
|
|
74
|
+
// 获取消息配置(v1.1.5: 返回对象 { code, message })
|
|
75
|
+
const messageConfig = Locale.getMessage(key, {}, actualLocale);
|
|
76
|
+
|
|
77
|
+
// 判断返回类型(向后兼容)
|
|
78
|
+
let errorCode, template;
|
|
79
|
+
if (typeof messageConfig === 'object' && messageConfig.code && messageConfig.message) {
|
|
80
|
+
// 对象格式:提取 code 和 message
|
|
81
|
+
errorCode = messageConfig.code;
|
|
82
|
+
template = messageConfig.message;
|
|
83
|
+
} else if (typeof messageConfig === 'string') {
|
|
84
|
+
// 字符串格式(向后兼容)
|
|
85
|
+
errorCode = key;
|
|
86
|
+
template = messageConfig;
|
|
87
|
+
} else {
|
|
88
|
+
// 降级处理
|
|
89
|
+
errorCode = key;
|
|
90
|
+
template = key;
|
|
91
|
+
}
|
|
73
92
|
|
|
74
93
|
// 使用 MessageTemplate 进行参数插值
|
|
75
94
|
const messageTemplate = new MessageTemplate(template);
|
|
@@ -78,7 +97,8 @@ class I18nError extends Error {
|
|
|
78
97
|
super(message);
|
|
79
98
|
|
|
80
99
|
this.name = 'I18nError';
|
|
81
|
-
this.
|
|
100
|
+
this.originalKey = key; // v1.1.5 新增:保留原始 key
|
|
101
|
+
this.code = errorCode; // v1.1.5 修改:从对象提取或使用 key
|
|
82
102
|
this.params = params || {};
|
|
83
103
|
this.statusCode = statusCode;
|
|
84
104
|
this.locale = actualLocale;
|
|
@@ -95,6 +115,7 @@ class I18nError extends Error {
|
|
|
95
115
|
* @param {string} code - 错误代码(多语言 key)
|
|
96
116
|
* @param {Object} params - 错误参数
|
|
97
117
|
* @param {number} statusCode - HTTP 状态码
|
|
118
|
+
* @param {string} locale - 语言环境(可选,不传则使用全局语言)
|
|
98
119
|
* @returns {I18nError} 错误实例
|
|
99
120
|
*
|
|
100
121
|
* @example
|
|
@@ -104,9 +125,13 @@ class I18nError extends Error {
|
|
|
104
125
|
* @example
|
|
105
126
|
* // 直接抛出
|
|
106
127
|
* throw I18nError.create('error.notFound', { resource: '用户' });
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* // 运行时指定语言
|
|
131
|
+
* const error = I18nError.create('error.notFound', {}, 404, 'en-US');
|
|
107
132
|
*/
|
|
108
|
-
static create(code, params = {}, statusCode = 400) {
|
|
109
|
-
return new I18nError(code, params, statusCode);
|
|
133
|
+
static create(code, params = {}, statusCode = 400, locale = null) {
|
|
134
|
+
return new I18nError(code, params, statusCode, locale);
|
|
110
135
|
}
|
|
111
136
|
|
|
112
137
|
/**
|
|
@@ -115,14 +140,19 @@ class I18nError extends Error {
|
|
|
115
140
|
* @param {string} code - 错误代码(多语言 key)
|
|
116
141
|
* @param {Object} params - 错误参数
|
|
117
142
|
* @param {number} statusCode - HTTP 状态码
|
|
143
|
+
* @param {string} locale - 语言环境(可选,不传则使用全局语言)
|
|
118
144
|
* @throws {I18nError} 直接抛出错误
|
|
119
145
|
*
|
|
120
146
|
* @example
|
|
121
147
|
* I18nError.throw('error.notFound', { resource: '用户' });
|
|
122
148
|
* // 等同于:throw I18nError.create('error.notFound', { resource: '用户' });
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* // 运行时指定语言
|
|
152
|
+
* I18nError.throw('error.notFound', {}, 404, 'en-US');
|
|
123
153
|
*/
|
|
124
|
-
static throw(code, params = {}, statusCode = 400) {
|
|
125
|
-
throw new I18nError(code, params, statusCode);
|
|
154
|
+
static throw(code, params = {}, statusCode = 400, locale = null) {
|
|
155
|
+
throw new I18nError(code, params, statusCode, locale);
|
|
126
156
|
}
|
|
127
157
|
|
|
128
158
|
/**
|
|
@@ -132,6 +162,7 @@ class I18nError extends Error {
|
|
|
132
162
|
* @param {string} code - 错误代码(多语言 key)
|
|
133
163
|
* @param {Object} params - 错误参数
|
|
134
164
|
* @param {number} statusCode - HTTP 状态码
|
|
165
|
+
* @param {string} locale - 语言环境(可选,不传则使用全局语言)
|
|
135
166
|
* @throws {I18nError} 条件为 false 时抛出错误
|
|
136
167
|
*
|
|
137
168
|
* @example
|
|
@@ -144,17 +175,21 @@ class I18nError extends Error {
|
|
|
144
175
|
* 'account.insufficientBalance',
|
|
145
176
|
* { balance: account.balance, required: 100 }
|
|
146
177
|
* );
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* // 运行时指定语言
|
|
181
|
+
* I18nError.assert(account, 'account.notFound', {}, 404, 'en-US');
|
|
147
182
|
*/
|
|
148
|
-
static assert(condition, code, params = {}, statusCode = 400) {
|
|
183
|
+
static assert(condition, code, params = {}, statusCode = 400, locale = null) {
|
|
149
184
|
if (!condition) {
|
|
150
|
-
throw new I18nError(code, params, statusCode);
|
|
185
|
+
throw new I18nError(code, params, statusCode, locale);
|
|
151
186
|
}
|
|
152
187
|
}
|
|
153
188
|
|
|
154
189
|
/**
|
|
155
190
|
* 检查错误是否为指定代码
|
|
156
191
|
*
|
|
157
|
-
* @param {string}
|
|
192
|
+
* @param {string} codeOrKey - 错误代码或原始 key
|
|
158
193
|
* @returns {boolean} 是否匹配
|
|
159
194
|
*
|
|
160
195
|
* @example
|
|
@@ -164,10 +199,16 @@ class I18nError extends Error {
|
|
|
164
199
|
* if (error instanceof I18nError && error.is('account.notFound')) {
|
|
165
200
|
* // 处理账户不存在的情况
|
|
166
201
|
* }
|
|
202
|
+
*
|
|
203
|
+
* // v1.1.5: 也可以用 code 判断
|
|
204
|
+
* if (error instanceof I18nError && error.is('ACCOUNT_NOT_FOUND')) {
|
|
205
|
+
* // 也能匹配
|
|
206
|
+
* }
|
|
167
207
|
* }
|
|
168
208
|
*/
|
|
169
|
-
is(
|
|
170
|
-
|
|
209
|
+
is(codeOrKey) {
|
|
210
|
+
// v1.1.5: 同时比较 code 和 originalKey(向后兼容)
|
|
211
|
+
return this.code === codeOrKey || this.originalKey === codeOrKey;
|
|
171
212
|
}
|
|
172
213
|
|
|
173
214
|
/**
|
|
@@ -175,6 +216,7 @@ class I18nError extends Error {
|
|
|
175
216
|
*
|
|
176
217
|
* @returns {Object} JSON 对象
|
|
177
218
|
* @returns {string} return.error - 错误名称
|
|
219
|
+
* @returns {string} return.originalKey - 原始 key(v1.1.5 新增)
|
|
178
220
|
* @returns {string} return.code - 错误代码
|
|
179
221
|
* @returns {string} return.message - 错误消息(已翻译)
|
|
180
222
|
* @returns {Object} return.params - 错误参数
|
|
@@ -186,7 +228,8 @@ class I18nError extends Error {
|
|
|
186
228
|
* res.status(error.statusCode).json(json);
|
|
187
229
|
* // {
|
|
188
230
|
* // error: 'I18nError',
|
|
189
|
-
* //
|
|
231
|
+
* // originalKey: 'account.notFound', // v1.1.5 新增
|
|
232
|
+
* // code: 'ACCOUNT_NOT_FOUND',
|
|
190
233
|
* // message: '找不到账户',
|
|
191
234
|
* // params: { accountId: '123' },
|
|
192
235
|
* // statusCode: 404,
|
|
@@ -196,6 +239,7 @@ class I18nError extends Error {
|
|
|
196
239
|
toJSON() {
|
|
197
240
|
return {
|
|
198
241
|
error: this.name,
|
|
242
|
+
originalKey: this.originalKey, // v1.1.5 新增
|
|
199
243
|
code: this.code,
|
|
200
244
|
message: this.message,
|
|
201
245
|
params: this.params,
|
package/lib/locales/en-US.js
CHANGED
|
@@ -26,10 +26,17 @@ module.exports = {
|
|
|
26
26
|
'error.conflict': 'Operation conflict: {{#reason}}',
|
|
27
27
|
|
|
28
28
|
// I18nError - Account related (v1.1.1)
|
|
29
|
-
|
|
29
|
+
// v1.1.5: Using object format for some examples
|
|
30
|
+
'account.notFound': {
|
|
31
|
+
code: 'ACCOUNT_NOT_FOUND',
|
|
32
|
+
message: 'Account not found'
|
|
33
|
+
},
|
|
30
34
|
'account.inactive': 'Account is inactive',
|
|
31
35
|
'account.banned': 'Account has been banned',
|
|
32
|
-
'account.insufficientBalance':
|
|
36
|
+
'account.insufficientBalance': {
|
|
37
|
+
code: 'INSUFFICIENT_BALANCE',
|
|
38
|
+
message: 'Insufficient balance, current: {{#balance}}, required: {{#required}}'
|
|
39
|
+
},
|
|
33
40
|
'account.insufficientCredits': 'Insufficient credits, current: {{#credits}}, required: {{#required}}',
|
|
34
41
|
|
|
35
42
|
// I18nError - User related (v1.1.1)
|
|
@@ -38,7 +45,10 @@ module.exports = {
|
|
|
38
45
|
'user.noPermission': 'No admin permission',
|
|
39
46
|
|
|
40
47
|
// I18nError - Order related (v1.1.1)
|
|
41
|
-
'order.notPaid':
|
|
48
|
+
'order.notPaid': {
|
|
49
|
+
code: 'ORDER_NOT_PAID',
|
|
50
|
+
message: 'Order not paid'
|
|
51
|
+
},
|
|
42
52
|
'order.paymentMissing': 'Payment information missing',
|
|
43
53
|
'order.addressMissing': 'Shipping address missing',
|
|
44
54
|
|
package/lib/locales/zh-CN.js
CHANGED
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
'conditional.blocked': '账号已被封禁',
|
|
18
18
|
'conditional.notAllowed': '不允许注册',
|
|
19
19
|
|
|
20
|
+
// I18nError - 通用错误消息 (v1.1.1)
|
|
20
21
|
// I18nError - 通用错误消息 (v1.1.1)
|
|
21
22
|
'error.notFound': '找不到{{#resource}}',
|
|
22
23
|
'error.forbidden': '没有权限访问{{#resource}}',
|
|
@@ -26,10 +27,17 @@ module.exports = {
|
|
|
26
27
|
'error.conflict': '操作冲突: {{#reason}}',
|
|
27
28
|
|
|
28
29
|
// I18nError - 账户相关 (v1.1.1)
|
|
29
|
-
|
|
30
|
+
// v1.1.5: 部分使用对象格式示例
|
|
31
|
+
'account.notFound': {
|
|
32
|
+
code: 'ACCOUNT_NOT_FOUND',
|
|
33
|
+
message: '账户不存在'
|
|
34
|
+
},
|
|
30
35
|
'account.inactive': '账户未激活',
|
|
31
36
|
'account.banned': '账户已被封禁',
|
|
32
|
-
'account.insufficientBalance':
|
|
37
|
+
'account.insufficientBalance': {
|
|
38
|
+
code: 'INSUFFICIENT_BALANCE',
|
|
39
|
+
message: '余额不足,当前余额{{#balance}},需要{{#required}}'
|
|
40
|
+
},
|
|
33
41
|
'account.insufficientCredits': '积分不足,当前积分{{#credits}},需要{{#required}}',
|
|
34
42
|
|
|
35
43
|
// I18nError - 用户相关 (v1.1.1)
|
|
@@ -38,7 +46,10 @@ module.exports = {
|
|
|
38
46
|
'user.noPermission': '没有管理员权限',
|
|
39
47
|
|
|
40
48
|
// I18nError - 订单相关 (v1.1.1)
|
|
41
|
-
'order.notPaid':
|
|
49
|
+
'order.notPaid': {
|
|
50
|
+
code: 'ORDER_NOT_PAID',
|
|
51
|
+
message: '订单未支付'
|
|
52
|
+
},
|
|
42
53
|
'order.paymentMissing': '缺少支付信息',
|
|
43
54
|
'order.addressMissing': '缺少收货地址',
|
|
44
55
|
|
|
@@ -90,12 +90,20 @@ class CustomKeywords {
|
|
|
90
90
|
// ajv 默认不支持同步验证中的异步操作
|
|
91
91
|
// 这里我们只能抛出错误提示
|
|
92
92
|
// 真正的异步支持需要 Validator.validateAsync
|
|
93
|
-
|
|
93
|
+
const messageConfig = Locale.getMessage('ASYNC_VALIDATION_NOT_SUPPORTED');
|
|
94
|
+
const errorMessage = typeof messageConfig === 'object' && messageConfig.message
|
|
95
|
+
? messageConfig.message
|
|
96
|
+
: messageConfig;
|
|
97
|
+
throw new Error(errorMessage);
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
// 处理返回值
|
|
97
101
|
if (result === false) {
|
|
98
|
-
|
|
102
|
+
const messageConfig = Locale.getMessage('CUSTOM_VALIDATION_FAILED');
|
|
103
|
+
const errorMessage = typeof messageConfig === 'object' && messageConfig.message
|
|
104
|
+
? messageConfig.message
|
|
105
|
+
: messageConfig;
|
|
106
|
+
validate.errors = [{ message: errorMessage }];
|
|
99
107
|
return false;
|
|
100
108
|
}
|
|
101
109
|
if (typeof result === 'string') {
|