schema-dsl 1.2.4 → 2.0.0
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 +87 -210
- package/README.md +391 -2249
- package/dist/DslBuilder-DQDN0ZxZ.d.cts +341 -0
- package/dist/DslBuilder-DkLaOo9Q.d.ts +341 -0
- package/dist/Validator-C7GsVQOH.d.cts +192 -0
- package/dist/Validator-hFWKGxir.d.ts +192 -0
- package/dist/index.cjs +6594 -0
- package/dist/index.d.cts +1145 -0
- package/dist/index.d.ts +1145 -0
- package/dist/index.js +6528 -0
- package/dist/plugin-CIKtTMtS.d.cts +246 -0
- package/dist/plugin-CIKtTMtS.d.ts +246 -0
- package/dist/plugins/custom-format.cjs +3802 -0
- package/dist/plugins/custom-format.d.cts +12 -0
- package/dist/plugins/custom-format.d.ts +12 -0
- package/dist/plugins/custom-format.js +3772 -0
- package/dist/plugins/custom-type-example.cjs +3795 -0
- package/dist/plugins/custom-type-example.d.cts +8 -0
- package/dist/plugins/custom-type-example.d.ts +8 -0
- package/dist/plugins/custom-type-example.js +3765 -0
- package/dist/plugins/custom-validator.cjs +146 -0
- package/dist/plugins/custom-validator.d.cts +10 -0
- package/dist/plugins/custom-validator.d.ts +10 -0
- package/dist/plugins/custom-validator.js +121 -0
- package/docs/FEATURE-INDEX.md +102 -68
- package/docs/add-custom-locale.md +48 -35
- package/docs/add-keyword.md +24 -0
- package/docs/api-reference.md +396 -154
- package/docs/api.md +13 -0
- package/docs/best-practices-project-structure.md +19 -10
- package/docs/best-practices.md +93 -53
- package/docs/cache-manager.md +23 -15
- package/docs/compile.md +45 -0
- package/docs/conditional-api.md +40 -11
- package/docs/custom-extensions-guide.md +80 -152
- package/docs/design-philosophy.md +76 -71
- package/docs/doc-index.md +324 -0
- package/docs/dsl-syntax.md +69 -19
- package/docs/dynamic-locale.md +24 -14
- package/docs/enum.md +12 -5
- package/docs/error-handling.md +53 -44
- package/docs/export-guide.md +47 -8
- package/docs/export-limitations.md +27 -11
- package/docs/faq.md +86 -67
- package/docs/frontend-i18n-guide.md +26 -12
- package/docs/i18n-user-guide.md +60 -47
- package/docs/i18n.md +51 -32
- package/docs/index.md +48 -0
- package/docs/json-schema-basics.md +40 -0
- package/docs/label-vs-description.md +12 -3
- package/docs/markdown-exporter.md +15 -6
- package/docs/mongodb-exporter.md +11 -4
- package/docs/multi-language.md +26 -0
- package/docs/multi-type-support.md +26 -33
- package/docs/mysql-exporter.md +9 -2
- package/docs/number-operators.md +12 -5
- package/docs/optional-marker-guide.md +28 -23
- package/docs/performance-guide.md +49 -0
- package/docs/plugin-system.md +205 -366
- package/docs/plugin-type-registration.md +34 -0
- package/docs/postgresql-exporter.md +9 -2
- package/docs/public/favicon.svg +5 -0
- package/docs/quick-start.md +37 -363
- package/docs/runtime-locale-support.md +20 -9
- package/docs/schema-helper.md +10 -5
- package/docs/schema-utils-advanced-issues.md +23 -0
- package/docs/schema-utils-best-practices.md +20 -0
- package/docs/schema-utils-chaining.md +7 -0
- package/docs/schema-utils.md +76 -42
- package/docs/security-checklist.md +20 -0
- package/docs/string-extensions.md +17 -9
- package/docs/troubleshooting.md +36 -21
- package/docs/type-converter.md +41 -50
- package/docs/type-reference.md +38 -15
- package/docs/typescript-guide.md +53 -42
- package/docs/union-type-guide.md +11 -1
- package/docs/union-types.md +10 -3
- package/docs/validate-async.md +36 -25
- package/docs/validate-batch.md +49 -0
- package/docs/validate-dsl-object-support.md +33 -28
- package/docs/validate.md +36 -16
- package/docs/validation-guide.md +25 -7
- package/docs/validator.md +39 -0
- package/package.json +85 -27
- package/plugins/custom-format.cjs +8 -0
- package/plugins/custom-type-example.cjs +8 -0
- package/plugins/custom-validator.cjs +8 -0
- package/src/adapters/DslAdapter.ts +111 -0
- package/src/adapters/index.ts +1 -0
- package/src/config/constants.ts +83 -0
- package/src/config/index.ts +2 -0
- package/src/config/patterns.ts +77 -0
- package/src/core/CacheManager.ts +159 -0
- package/src/core/ConditionalBuilder.ts +382 -0
- package/src/core/ConditionalRuntime.ts +28 -0
- package/src/core/ConditionalValidator.ts +255 -0
- package/src/core/DslBuilder.ts +677 -0
- package/src/core/ErrorCodes.ts +38 -0
- package/src/core/ErrorFormatter.ts +271 -0
- package/src/core/JSONSchemaCore.ts +65 -0
- package/src/core/Locale.ts +187 -0
- package/src/core/MessageTemplate.ts +42 -0
- package/src/core/ObjectDslBuilder.ts +64 -0
- package/src/core/PluginManager.ts +326 -0
- package/src/core/StringExtensions.ts +140 -0
- package/src/core/TemplateEngine.ts +44 -0
- package/src/core/Validator.ts +448 -0
- package/src/errors/I18nError.ts +159 -0
- package/src/errors/ValidationError.ts +105 -0
- package/src/exporters/BaseExporter.ts +60 -0
- package/src/exporters/MarkdownExporter.ts +305 -0
- package/src/exporters/MongoDBExporter.ts +126 -0
- package/src/exporters/MySQLExporter.ts +155 -0
- package/src/exporters/PostgreSQLExporter.ts +222 -0
- package/src/exporters/index.ts +18 -0
- package/src/index.ts +633 -0
- package/{lib/locales/en-US.js → src/locales/en-US.ts} +21 -37
- package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +63 -16
- package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +74 -27
- package/src/locales/index.ts +103 -0
- package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +59 -17
- package/src/locales/types.ts +156 -0
- package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +21 -38
- package/src/parser/ConstraintParser.ts +101 -0
- package/src/parser/DslParser.ts +470 -0
- package/src/parser/SchemaCompiler.ts +66 -0
- package/src/parser/TypeRegistry.ts +250 -0
- package/src/parser/index.ts +6 -0
- package/src/plugins/custom-format.ts +126 -0
- package/src/plugins/custom-type-example.ts +108 -0
- package/src/plugins/custom-validator.ts +140 -0
- package/src/types/conditional.ts +28 -0
- package/src/types/config.ts +59 -0
- package/src/types/dsl.ts +131 -0
- package/src/types/error.ts +60 -0
- package/src/types/index.ts +17 -0
- package/src/types/infer.ts +128 -0
- package/src/types/plugin.ts +58 -0
- package/src/types/safe-regex.d.ts +9 -0
- package/src/types/schema.ts +66 -0
- package/src/types/validate.ts +71 -0
- package/src/utils/SchemaHelper.ts +196 -0
- package/src/utils/SchemaUtils.ts +346 -0
- package/src/utils/TypeConverter.ts +215 -0
- package/src/utils/index.ts +10 -0
- package/src/validators/CustomKeywords.ts +477 -0
- package/.eslintignore +0 -11
- package/.eslintrc.json +0 -27
- package/CONTRIBUTING.md +0 -368
- package/STATUS.md +0 -491
- package/changelogs/v1.0.0.md +0 -328
- package/changelogs/v1.0.9.md +0 -367
- package/changelogs/v1.1.0.md +0 -389
- package/changelogs/v1.1.1.md +0 -308
- package/changelogs/v1.1.2.md +0 -183
- package/changelogs/v1.1.3.md +0 -161
- package/changelogs/v1.1.4.md +0 -432
- package/changelogs/v1.1.5.md +0 -493
- package/changelogs/v1.1.6.md +0 -211
- package/changelogs/v1.1.8.md +0 -376
- package/changelogs/v1.2.3.md +0 -124
- package/docs/INDEX.md +0 -252
- package/docs/issues-resolved-summary.md +0 -196
- package/docs/performance-benchmark-report.md +0 -179
- package/docs/performance-quick-reference.md +0 -123
- package/docs/user-questions-answered.md +0 -353
- package/docs/validation-rules-v1.0.2.md +0 -1608
- package/examples/README.md +0 -81
- package/examples/array-dsl-example.js +0 -227
- package/examples/conditional-example.js +0 -288
- package/examples/conditional-non-object.js +0 -129
- package/examples/conditional-validate-example.js +0 -321
- package/examples/custom-extension.js +0 -85
- package/examples/dsl-match-example.js +0 -74
- package/examples/dsl-style.js +0 -118
- package/examples/dynamic-locale-configuration.js +0 -348
- package/examples/dynamic-locale-example.js +0 -287
- package/examples/enum.examples.js +0 -324
- package/examples/export-demo.js +0 -130
- package/examples/express-integration.js +0 -376
- package/examples/i18n-error-handling-complete.js +0 -381
- package/examples/i18n-error-handling-quickstart.md +0 -0
- package/examples/i18n-error.examples.js +0 -181
- package/examples/i18n-full-demo.js +0 -301
- package/examples/i18n-memory-safety.examples.js +0 -268
- package/examples/markdown-export.js +0 -71
- package/examples/middleware-usage.js +0 -93
- package/examples/new-features-comparison.js +0 -315
- package/examples/password-reset/README.md +0 -153
- package/examples/password-reset/schema.js +0 -26
- package/examples/password-reset/test.js +0 -101
- package/examples/plugin-system.examples.js +0 -205
- package/examples/schema-utils-chaining.examples.js +0 -250
- package/examples/simple-example.js +0 -122
- package/examples/slug.examples.js +0 -179
- package/examples/string-extensions.js +0 -297
- package/examples/union-type-example.js +0 -127
- package/examples/union-types-example.js +0 -77
- package/examples/user-registration/README.md +0 -156
- package/examples/user-registration/routes.js +0 -92
- package/examples/user-registration/schema.js +0 -150
- package/examples/user-registration/server.js +0 -74
- package/index.d.ts +0 -3540
- package/index.js +0 -457
- package/index.mjs +0 -60
- package/lib/adapters/DslAdapter.js +0 -871
- package/lib/adapters/index.js +0 -20
- package/lib/config/constants.js +0 -286
- package/lib/config/patterns/common.js +0 -47
- package/lib/config/patterns/creditCard.js +0 -9
- package/lib/config/patterns/idCard.js +0 -9
- package/lib/config/patterns/index.js +0 -9
- package/lib/config/patterns/licensePlate.js +0 -4
- package/lib/config/patterns/passport.js +0 -4
- package/lib/config/patterns/phone.js +0 -9
- package/lib/config/patterns/postalCode.js +0 -5
- package/lib/core/CacheManager.js +0 -376
- package/lib/core/ConditionalBuilder.js +0 -503
- package/lib/core/DslBuilder.js +0 -1400
- package/lib/core/ErrorCodes.js +0 -233
- package/lib/core/ErrorFormatter.js +0 -445
- package/lib/core/JSONSchemaCore.js +0 -347
- package/lib/core/Locale.js +0 -130
- package/lib/core/MessageTemplate.js +0 -98
- package/lib/core/PluginManager.js +0 -448
- package/lib/core/StringExtensions.js +0 -240
- package/lib/core/Validator.js +0 -654
- package/lib/errors/I18nError.js +0 -328
- package/lib/errors/ValidationError.js +0 -191
- package/lib/exporters/MarkdownExporter.js +0 -420
- package/lib/exporters/MongoDBExporter.js +0 -162
- package/lib/exporters/MySQLExporter.js +0 -212
- package/lib/exporters/PostgreSQLExporter.js +0 -289
- package/lib/exporters/index.js +0 -24
- package/lib/locales/index.js +0 -8
- package/lib/utils/LRUCache.js +0 -174
- package/lib/utils/SchemaHelper.js +0 -240
- package/lib/utils/SchemaUtils.js +0 -445
- package/lib/utils/TypeConverter.js +0 -245
- package/lib/utils/index.js +0 -13
- package/lib/validators/CustomKeywords.js +0 -616
- package/lib/validators/index.js +0 -11
package/lib/core/Validator.js
DELETED
|
@@ -1,654 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validator - JSON Schema验证器
|
|
3
|
-
*
|
|
4
|
-
* 基于ajv实现,支持JSON Schema Draft 7标准
|
|
5
|
-
*
|
|
6
|
-
* @module lib/core/Validator
|
|
7
|
-
* @version 1.0.0
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const Ajv = require('ajv');
|
|
11
|
-
const addFormats = require('ajv-formats');
|
|
12
|
-
const CacheManager = require('./CacheManager');
|
|
13
|
-
const ErrorFormatter = require('./ErrorFormatter');
|
|
14
|
-
const CustomKeywords = require('../validators/CustomKeywords');
|
|
15
|
-
const Locale = require('./Locale');
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 验证器类
|
|
19
|
-
*
|
|
20
|
-
* @class Validator
|
|
21
|
-
* @description 封装ajv,提供统一的验证接口
|
|
22
|
-
*/
|
|
23
|
-
class Validator {
|
|
24
|
-
/**
|
|
25
|
-
* 构造函数
|
|
26
|
-
* @param {Object} options - ajv配置选项
|
|
27
|
-
* @param {boolean} options.allErrors - 是否返回所有错误(默认true)
|
|
28
|
-
* @param {boolean} options.useDefaults - 是否使用默认值(默认true)
|
|
29
|
-
* @param {boolean} options.coerceTypes - 是否自动类型转换(默认false)
|
|
30
|
-
* @param {boolean} options.removeAdditional - 是否移除额外属性(默认false)
|
|
31
|
-
* @param {Object} options.cache - 缓存配置选项 (v1.0.4+)
|
|
32
|
-
* @param {number} options.cache.maxSize - 最大缓存条目数(默认100)
|
|
33
|
-
* @param {number} options.cache.ttl - 缓存过期时间(毫秒,默认3600000)
|
|
34
|
-
*/
|
|
35
|
-
constructor(options = {}) {
|
|
36
|
-
// ajv配置
|
|
37
|
-
this.ajvOptions = {
|
|
38
|
-
allErrors: options.allErrors !== false,
|
|
39
|
-
useDefaults: options.useDefaults !== false,
|
|
40
|
-
coerceTypes: options.coerceTypes || false,
|
|
41
|
-
removeAdditional: options.removeAdditional || false,
|
|
42
|
-
verbose: true, // 启用详细模式,以便访问 parentSchema
|
|
43
|
-
...options
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// 创建ajv实例
|
|
47
|
-
this.ajv = new Ajv(this.ajvOptions);
|
|
48
|
-
|
|
49
|
-
// 添加格式支持(email、uri、date-time等)
|
|
50
|
-
addFormats(this.ajv);
|
|
51
|
-
|
|
52
|
-
// 注册自定义关键字
|
|
53
|
-
CustomKeywords.registerAll(this.ajv);
|
|
54
|
-
|
|
55
|
-
// 编译缓存(支持自定义配置)
|
|
56
|
-
const cacheOptions = {
|
|
57
|
-
maxSize: options.cache?.maxSize ?? 100,
|
|
58
|
-
ttl: options.cache?.ttl ?? 3600000, // 1小时
|
|
59
|
-
enabled: options.cache?.enabled,
|
|
60
|
-
statsEnabled: options.cache?.statsEnabled
|
|
61
|
-
};
|
|
62
|
-
this.cache = new CacheManager(cacheOptions);
|
|
63
|
-
|
|
64
|
-
// 错误格式化器
|
|
65
|
-
this.errorFormatter = new ErrorFormatter();
|
|
66
|
-
|
|
67
|
-
// ✅ 性能优化:WeakMap 缓存键(避免 JSON.stringify)
|
|
68
|
-
this.schemaMap = new WeakMap();
|
|
69
|
-
this.schemaKeyCounter = 0;
|
|
70
|
-
|
|
71
|
-
// ✅ 性能优化:缓存 DslBuilder 转换结果
|
|
72
|
-
this.dslSchemaCache = new WeakMap();
|
|
73
|
-
|
|
74
|
-
// 自定义关键字注册表
|
|
75
|
-
this.customKeywords = new Map();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 编译Schema
|
|
80
|
-
* @param {Object} schema - JSON Schema对象
|
|
81
|
-
* @param {string} cacheKey - 缓存键(可选)
|
|
82
|
-
* @returns {Function} ajv验证函数
|
|
83
|
-
*/
|
|
84
|
-
compile(schema, cacheKey = null) {
|
|
85
|
-
// 尝试从缓存获取
|
|
86
|
-
if (cacheKey) {
|
|
87
|
-
const cached = this.cache.get(cacheKey);
|
|
88
|
-
if (cached) {
|
|
89
|
-
return cached;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
// 编译Schema
|
|
95
|
-
const validate = this.ajv.compile(schema);
|
|
96
|
-
|
|
97
|
-
// 缓存编译结果
|
|
98
|
-
if (cacheKey) {
|
|
99
|
-
this.cache.set(cacheKey, validate);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return validate;
|
|
103
|
-
} catch (error) {
|
|
104
|
-
throw new Error(`Schema compilation failed: ${error.message}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 验证数据
|
|
110
|
-
* @param {Object} schema - JSON Schema对象或已编译的验证函数
|
|
111
|
-
* @param {*} data - 待验证的数据
|
|
112
|
-
* @param {Object} options - 验证选项
|
|
113
|
-
* @param {boolean} options.format - 是否格式化错误(默认true)
|
|
114
|
-
* @param {string} options.locale - 动态指定语言(如 'zh-CN', 'en-US')
|
|
115
|
-
* @returns {Object} 验证结果 { valid: boolean, errors: Array, data: * }
|
|
116
|
-
*/
|
|
117
|
-
validate(schema, data, options = {}) {
|
|
118
|
-
// ✅ 优化:直接传递 locale 到内部方法,不再切换全局状态
|
|
119
|
-
return this._validateInternal(schema, data, options);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* 内部验证方法
|
|
124
|
-
* @private
|
|
125
|
-
*/
|
|
126
|
-
_validateInternal(schema, data, options = {}) {
|
|
127
|
-
const shouldFormat = options.format !== false;
|
|
128
|
-
const locale = options.locale || Locale.getLocale();
|
|
129
|
-
|
|
130
|
-
// ✅ 性能优化:缓存 DslBuilder 转换结果
|
|
131
|
-
if (schema && typeof schema.toSchema === 'function') {
|
|
132
|
-
if (!this.dslSchemaCache.has(schema)) {
|
|
133
|
-
this.dslSchemaCache.set(schema, schema.toSchema());
|
|
134
|
-
}
|
|
135
|
-
schema = this.dslSchemaCache.get(schema);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ✅ 处理 ConditionalBuilder 条件链(运行时条件判断)
|
|
139
|
-
if (schema && schema._isConditional) {
|
|
140
|
-
// 顶层 ConditionalBuilder(直接作为 Schema 使用)
|
|
141
|
-
// 此时验证的是整个数据对象
|
|
142
|
-
return this._validateConditional(schema, data, null, data, options);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ✅ 预处理包含 ConditionalBuilder 的 Schema
|
|
146
|
-
if (schema && schema.properties) {
|
|
147
|
-
const hasConditional = Object.values(schema.properties).some(
|
|
148
|
-
fieldSchema => fieldSchema && fieldSchema._isConditional
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
if (hasConditional) {
|
|
152
|
-
return this._validateWithConditionals(schema, data, options);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// 检查是否需要移除额外字段 (clean 模式)
|
|
157
|
-
if (schema && schema._removeAdditional) {
|
|
158
|
-
// 创建新的 Validator 实例,启用 removeAdditional
|
|
159
|
-
const tempValidator = new Validator({
|
|
160
|
-
...this.ajvOptions,
|
|
161
|
-
removeAdditional: true
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// 移除标记字段并深拷贝,避免修改原 schema
|
|
165
|
-
const cleanSchema = JSON.parse(JSON.stringify(schema));
|
|
166
|
-
delete cleanSchema._removeAdditional;
|
|
167
|
-
|
|
168
|
-
return tempValidator.validate(cleanSchema, data, options);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
// 如果schema是函数,说明已编译
|
|
173
|
-
let validate;
|
|
174
|
-
if (typeof schema === 'function') {
|
|
175
|
-
validate = schema;
|
|
176
|
-
} else {
|
|
177
|
-
// 生成缓存键
|
|
178
|
-
const cacheKey = this._generateCacheKey(schema);
|
|
179
|
-
validate = this.compile(schema, cacheKey);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// 执行验证
|
|
183
|
-
const valid = validate(data);
|
|
184
|
-
|
|
185
|
-
// ✅ 优化:直接传递 locale 和 messages 到格式化器,不修改全局状态
|
|
186
|
-
const errors = valid ? [] : (
|
|
187
|
-
shouldFormat
|
|
188
|
-
? this.errorFormatter.format(validate.errors, {
|
|
189
|
-
locale,
|
|
190
|
-
messages: options.messages
|
|
191
|
-
})
|
|
192
|
-
: validate.errors
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
valid,
|
|
197
|
-
errors,
|
|
198
|
-
data // 可能被useDefaults修改过
|
|
199
|
-
};
|
|
200
|
-
} catch (error) {
|
|
201
|
-
return {
|
|
202
|
-
valid: false,
|
|
203
|
-
errors: [{
|
|
204
|
-
message: `Validation error: ${error.message}`,
|
|
205
|
-
path: '',
|
|
206
|
-
keyword: 'error'
|
|
207
|
-
}],
|
|
208
|
-
data
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* 快速验证(不缓存)
|
|
215
|
-
* @param {Object} schema - JSON Schema对象
|
|
216
|
-
* @param {*} data - 待验证的数据
|
|
217
|
-
* @returns {boolean} 是否有效
|
|
218
|
-
*/
|
|
219
|
-
quickValidate(schema, data) {
|
|
220
|
-
try {
|
|
221
|
-
return this.ajv.validate(schema, data);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* 异步验证方法(验证失败自动抛出异常)
|
|
229
|
-
*
|
|
230
|
-
* @param {Object|Function} schema - JSON Schema对象或DslBuilder实例
|
|
231
|
-
* @param {*} data - 待验证的数据
|
|
232
|
-
* @param {Object} options - 验证选项(可选)
|
|
233
|
-
* @param {boolean} options.format - 是否格式化错误(默认true)
|
|
234
|
-
* @param {string} options.locale - 语言环境(如 'zh-CN', 'en-US')
|
|
235
|
-
* @returns {Promise<*>} 验证通过返回处理后的数据
|
|
236
|
-
* @throws {ValidationError} 验证失败抛出异常
|
|
237
|
-
*
|
|
238
|
-
* @example
|
|
239
|
-
* // 基础使用
|
|
240
|
-
* try {
|
|
241
|
-
* const data = await validator.validateAsync(userSchema, inputData);
|
|
242
|
-
* console.log('验证通过:', data);
|
|
243
|
-
* } catch (error) {
|
|
244
|
-
* if (error instanceof ValidationError) {
|
|
245
|
-
* console.error('验证失败:', error.errors);
|
|
246
|
-
* }
|
|
247
|
-
* }
|
|
248
|
-
*
|
|
249
|
-
* @example
|
|
250
|
-
* // Express 中使用
|
|
251
|
-
* app.post('/users', async (req, res, next) => {
|
|
252
|
-
* try {
|
|
253
|
-
* const data = await validator.validateAsync(userSchema, req.body);
|
|
254
|
-
* const user = await db.users.insert(data);
|
|
255
|
-
* res.json(user);
|
|
256
|
-
* } catch (error) {
|
|
257
|
-
* next(error);
|
|
258
|
-
* }
|
|
259
|
-
* });
|
|
260
|
-
*/
|
|
261
|
-
async validateAsync(schema, data, options = {}) {
|
|
262
|
-
const result = this.validate(schema, data, options);
|
|
263
|
-
|
|
264
|
-
if (!result.valid) {
|
|
265
|
-
const ValidationError = require('../errors/ValidationError');
|
|
266
|
-
throw new ValidationError(result.errors, data);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return result.data;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* 批量验证
|
|
274
|
-
* @param {Object} schema - JSON Schema对象
|
|
275
|
-
* @param {Array} dataArray - 数据数组
|
|
276
|
-
* @returns {Array} 验证结果数组
|
|
277
|
-
*/
|
|
278
|
-
validateBatch(schema, dataArray) {
|
|
279
|
-
if (!Array.isArray(dataArray)) {
|
|
280
|
-
throw new Error('Data must be an array');
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// 编译一次,复用验证函数
|
|
284
|
-
const cacheKey = this._generateCacheKey(schema);
|
|
285
|
-
const validate = this.compile(schema, cacheKey);
|
|
286
|
-
|
|
287
|
-
return dataArray.map(data => this.validate(validate, data));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* 添加自定义关键字
|
|
292
|
-
* @param {string} keyword - 关键字名称
|
|
293
|
-
* @param {Object} definition - 关键字定义
|
|
294
|
-
* @returns {Validator} 返回this支持链式调用
|
|
295
|
-
*/
|
|
296
|
-
addKeyword(keyword, definition) {
|
|
297
|
-
try {
|
|
298
|
-
this.ajv.addKeyword(keyword, definition);
|
|
299
|
-
this.customKeywords.set(keyword, definition);
|
|
300
|
-
return this;
|
|
301
|
-
} catch (error) {
|
|
302
|
-
throw new Error(`Failed to add keyword '${keyword}': ${error.message}`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* 添加自定义格式
|
|
308
|
-
* @param {string} name - 格式名称
|
|
309
|
-
* @param {Function|RegExp} validator - 验证函数或正则表达式
|
|
310
|
-
* @returns {Validator} 返回this支持链式调用
|
|
311
|
-
*/
|
|
312
|
-
addFormat(name, validator) {
|
|
313
|
-
try {
|
|
314
|
-
this.ajv.addFormat(name, validator);
|
|
315
|
-
return this;
|
|
316
|
-
} catch (error) {
|
|
317
|
-
throw new Error(`Failed to add format '${name}': ${error.message}`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* 添加Schema引用
|
|
323
|
-
* @param {string} uri - Schema URI
|
|
324
|
-
* @param {Object} schema - JSON Schema对象
|
|
325
|
-
* @returns {Validator} 返回this支持链式调用
|
|
326
|
-
*/
|
|
327
|
-
addSchema(uri, schema) {
|
|
328
|
-
try {
|
|
329
|
-
this.ajv.addSchema(schema, uri);
|
|
330
|
-
return this;
|
|
331
|
-
} catch (error) {
|
|
332
|
-
throw new Error(`Failed to add schema '${uri}': ${error.message}`);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* 移除Schema引用
|
|
338
|
-
* @param {string} uri - Schema URI
|
|
339
|
-
* @returns {Validator} 返回this支持链式调用
|
|
340
|
-
*/
|
|
341
|
-
removeSchema(uri) {
|
|
342
|
-
this.ajv.removeSchema(uri);
|
|
343
|
-
return this;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* 获取ajv实例(高级用法)
|
|
348
|
-
* @returns {Ajv} ajv实例
|
|
349
|
-
*/
|
|
350
|
-
getAjv() {
|
|
351
|
-
return this.ajv;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* 清空缓存
|
|
356
|
-
*/
|
|
357
|
-
clearCache() {
|
|
358
|
-
this.cache.clear();
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* 获取缓存统计
|
|
363
|
-
* @returns {Object} 缓存统计信息
|
|
364
|
-
*/
|
|
365
|
-
getCacheStats() {
|
|
366
|
-
return this.cache.stats();
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* 生成Schema的缓存键
|
|
371
|
-
* @private
|
|
372
|
-
* @param {Object} schema - JSON Schema对象
|
|
373
|
-
* @returns {string} 缓存键
|
|
374
|
-
*/
|
|
375
|
-
_generateCacheKey(schema) {
|
|
376
|
-
// ✅ 性能优化:使用 WeakMap 避免昂贵的 JSON.stringify
|
|
377
|
-
// 对于对象类型的 schema,使用 WeakMap 存储唯一标识符
|
|
378
|
-
if (typeof schema === 'object' && schema !== null) {
|
|
379
|
-
if (!this.schemaMap.has(schema)) {
|
|
380
|
-
this.schemaMap.set(schema, `schema_${++this.schemaKeyCounter}`);
|
|
381
|
-
}
|
|
382
|
-
return this.schemaMap.get(schema);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// 对于原始类型或 null,降级到字符串化
|
|
386
|
-
return JSON.stringify(schema);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* 验证包含 ConditionalBuilder 字段的 Schema
|
|
391
|
-
* @private
|
|
392
|
-
* @param {Object} schema - Schema 对象
|
|
393
|
-
* @param {*} data - 待验证的数据
|
|
394
|
-
* @param {Object} options - 验证选项
|
|
395
|
-
* @returns {Object} 验证结果
|
|
396
|
-
*/
|
|
397
|
-
_validateWithConditionals(schema, data, options = {}) {
|
|
398
|
-
const errors = [];
|
|
399
|
-
|
|
400
|
-
// 深拷贝 schema,避免修改原始对象
|
|
401
|
-
const cleanSchema = JSON.parse(JSON.stringify(schema));
|
|
402
|
-
const conditionalFields = {};
|
|
403
|
-
|
|
404
|
-
// 提取所有条件字段
|
|
405
|
-
for (const [fieldName, fieldSchema] of Object.entries(schema.properties || {})) {
|
|
406
|
-
if (fieldSchema && fieldSchema._isConditional) {
|
|
407
|
-
conditionalFields[fieldName] = fieldSchema;
|
|
408
|
-
// 从 cleanSchema 中删除条件字段
|
|
409
|
-
delete cleanSchema.properties[fieldName];
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// 先验证非条件字段
|
|
414
|
-
const baseResult = this._validateInternal(cleanSchema, data, options);
|
|
415
|
-
if (!baseResult.valid) {
|
|
416
|
-
errors.push(...baseResult.errors);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// 然后验证每个条件字段
|
|
420
|
-
for (const [fieldName, conditionalSchema] of Object.entries(conditionalFields)) {
|
|
421
|
-
// ✅ 传递字段名和完整数据对象
|
|
422
|
-
const fieldResult = this._validateConditional(
|
|
423
|
-
conditionalSchema,
|
|
424
|
-
data,
|
|
425
|
-
fieldName,
|
|
426
|
-
data[fieldName],
|
|
427
|
-
options
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
if (!fieldResult.valid) {
|
|
431
|
-
// 添加字段路径信息
|
|
432
|
-
fieldResult.errors.forEach(error => {
|
|
433
|
-
if (!error.path || error.path === '') {
|
|
434
|
-
error.path = fieldName;
|
|
435
|
-
}
|
|
436
|
-
errors.push(error);
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return {
|
|
442
|
-
valid: errors.length === 0,
|
|
443
|
-
errors,
|
|
444
|
-
data
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* 验证条件链(ConditionalBuilder)
|
|
450
|
-
* @private
|
|
451
|
-
* @param {Object} conditionalSchema - 条件 Schema 对象
|
|
452
|
-
* @param {*} data - 完整数据对象(用于条件判断)
|
|
453
|
-
* @param {string} fieldName - 字段名
|
|
454
|
-
* @param {*} fieldValue - 字段值(用于验证)
|
|
455
|
-
* @param {Object} options - 验证选项
|
|
456
|
-
* @returns {Object} 验证结果
|
|
457
|
-
*/
|
|
458
|
-
_validateConditional(conditionalSchema, data, fieldName, fieldValue, options = {}) {
|
|
459
|
-
const locale = options.locale || Locale.getLocale();
|
|
460
|
-
|
|
461
|
-
try {
|
|
462
|
-
// ✅ 遍历条件链,执行第一个匹配的条件
|
|
463
|
-
// 对于 if/elseIf 链,只执行第一个满足条件的分支
|
|
464
|
-
for (let i = 0; i < conditionalSchema.conditions.length; i++) {
|
|
465
|
-
const cond = conditionalSchema.conditions[i];
|
|
466
|
-
|
|
467
|
-
// 执行组合条件(支持 and/or),获取结果和失败消息
|
|
468
|
-
const evaluation = conditionalSchema._evaluateCondition(cond, data);
|
|
469
|
-
const matched = evaluation.result;
|
|
470
|
-
const failedMessage = evaluation.failedMessage;
|
|
471
|
-
|
|
472
|
-
if (cond.action === 'throw') {
|
|
473
|
-
// ✅ message 模式:条件为 true 时抛错,条件为 false 时通过
|
|
474
|
-
if (matched) {
|
|
475
|
-
// ✅ 条件满足(true),抛出错误
|
|
476
|
-
// 优先使用具体的失败消息,如果没有则使用整体消息
|
|
477
|
-
const errorMsg = failedMessage || cond.message;
|
|
478
|
-
// 支持多语言:如果 message 是 key(如 'conditional.underAge'),从语言包获取翻译
|
|
479
|
-
// 传递 locale 参数以支持动态语言切换
|
|
480
|
-
const messageConfig = Locale.getMessage(errorMsg, options.messages || {}, locale);
|
|
481
|
-
|
|
482
|
-
// v1.1.5: Locale.getMessage 返回对象 { code, message },需要提取 message
|
|
483
|
-
const errorMessage = typeof messageConfig === 'object' && messageConfig.message
|
|
484
|
-
? messageConfig.message
|
|
485
|
-
: messageConfig;
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
valid: false,
|
|
489
|
-
errors: [{
|
|
490
|
-
message: errorMessage,
|
|
491
|
-
path: '',
|
|
492
|
-
keyword: 'conditional',
|
|
493
|
-
params: { condition: cond.type }
|
|
494
|
-
}],
|
|
495
|
-
data
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
// 条件不满足(false),继续验证
|
|
499
|
-
continue;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// ✅ then/else 模式:找到第一个满足的条件就执行并返回
|
|
503
|
-
if (matched) {
|
|
504
|
-
// 条件满足,执行 then Schema
|
|
505
|
-
if (cond.then !== undefined && cond.then !== null) {
|
|
506
|
-
return this._executeThenBranch(cond.then, data, fieldValue, fieldName, options);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// 条件满足但没有 then,表示验证通过
|
|
510
|
-
return {
|
|
511
|
-
valid: true,
|
|
512
|
-
errors: [],
|
|
513
|
-
data
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// ✅ 如果是 if/elseIf 条件不满足,继续检查下一个 elseIf
|
|
518
|
-
// 这样就支持了 if...elseIf...elseIf...else 链
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// ✅ 所有条件都不满足,执行 else
|
|
522
|
-
if (conditionalSchema.else !== undefined) {
|
|
523
|
-
if (conditionalSchema.else === null) {
|
|
524
|
-
// else 为 null,表示跳过验证
|
|
525
|
-
return {
|
|
526
|
-
valid: true,
|
|
527
|
-
errors: [],
|
|
528
|
-
data
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// 执行 else Schema
|
|
533
|
-
return this._executeThenBranch(conditionalSchema.else, data, fieldValue, fieldName, options);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// 没有 else,表示不验证
|
|
537
|
-
return {
|
|
538
|
-
valid: true,
|
|
539
|
-
errors: [],
|
|
540
|
-
data
|
|
541
|
-
};
|
|
542
|
-
} catch (error) {
|
|
543
|
-
return {
|
|
544
|
-
valid: false,
|
|
545
|
-
errors: [{
|
|
546
|
-
message: `Conditional validation error: ${error.message}`,
|
|
547
|
-
path: '',
|
|
548
|
-
keyword: 'conditional'
|
|
549
|
-
}],
|
|
550
|
-
data
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* 执行 then 分支(提取公共逻辑)
|
|
557
|
-
* @private
|
|
558
|
-
* @param {*} thenSchema - then 分支的 Schema
|
|
559
|
-
* @param {*} data - 完整数据对象(用于嵌套条件判断)
|
|
560
|
-
* @param {*} fieldValue - 字段值(用于验证)
|
|
561
|
-
* @param {string} fieldName - 字段名
|
|
562
|
-
* @param {Object} options - 验证选项
|
|
563
|
-
*/
|
|
564
|
-
_executeThenBranch(thenSchema, data, fieldValue, fieldName, options) {
|
|
565
|
-
const DslBuilder = require('./DslBuilder');
|
|
566
|
-
|
|
567
|
-
// ✅ 如果是 ConditionalBuilder 实例(未调用 toSchema),先转换
|
|
568
|
-
if (thenSchema && typeof thenSchema.toSchema === 'function') {
|
|
569
|
-
thenSchema = thenSchema.toSchema();
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// ✅ 处理嵌套的 ConditionalBuilder(已转换为 Schema 对象)
|
|
573
|
-
if (thenSchema && thenSchema._isConditional) {
|
|
574
|
-
// 嵌套的条件构建器,递归处理
|
|
575
|
-
// 传递完整数据对象用于条件判断,传递字段值用于验证
|
|
576
|
-
return this._validateConditional(thenSchema, data, fieldName, fieldValue, options);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// 如果是字符串,解析为 Schema
|
|
580
|
-
if (typeof thenSchema === 'string') {
|
|
581
|
-
const builder = new DslBuilder(thenSchema);
|
|
582
|
-
thenSchema = builder.toSchema();
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// ✅ 验证字段值
|
|
586
|
-
return this._validateFieldValue(thenSchema, fieldValue, fieldName, options);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* 验证字段值(处理空字符串和 undefined)
|
|
591
|
-
* @private
|
|
592
|
-
* @param {Object} schema - Schema 对象
|
|
593
|
-
* @param {*} fieldValue - 字段值
|
|
594
|
-
* @param {string} fieldName - 字段名
|
|
595
|
-
* @param {Object} options - 验证选项
|
|
596
|
-
* @returns {Object} 验证结果
|
|
597
|
-
*/
|
|
598
|
-
_validateFieldValue(schema, fieldValue, fieldName, options = {}) {
|
|
599
|
-
// ✅ 检查字段是否必填
|
|
600
|
-
const isRequired = schema && schema._required === true;
|
|
601
|
-
|
|
602
|
-
// ✅ 处理 undefined:可选字段缺失时跳过验证
|
|
603
|
-
if (!isRequired && fieldValue === undefined) {
|
|
604
|
-
return {
|
|
605
|
-
valid: true,
|
|
606
|
-
errors: [],
|
|
607
|
-
data: fieldValue
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// ✅ 处理空字符串:可选字段的空字符串视为未提供
|
|
612
|
-
if (!isRequired && fieldValue === '') {
|
|
613
|
-
return {
|
|
614
|
-
valid: true,
|
|
615
|
-
errors: [],
|
|
616
|
-
data: fieldValue
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// 正常验证
|
|
621
|
-
return this._validateInternal(schema, fieldValue, options);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* 静态工厂方法
|
|
626
|
-
* @static
|
|
627
|
-
* @param {Object} options - ajv配置选项
|
|
628
|
-
* @returns {Validator} Validator实例
|
|
629
|
-
*/
|
|
630
|
-
static create(options) {
|
|
631
|
-
return new Validator(options);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* 静态方法:快速验证(不创建实例)
|
|
636
|
-
* @static
|
|
637
|
-
* @param {Object} schema - JSON Schema对象
|
|
638
|
-
* @param {*} data - 待验证的数据
|
|
639
|
-
* @returns {boolean} 是否有效
|
|
640
|
-
*/
|
|
641
|
-
static quickValidate(schema, data) {
|
|
642
|
-
const ajv = new Ajv();
|
|
643
|
-
return ajv.validate(schema, data);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// Support calling without new
|
|
648
|
-
const ValidatorProxy = new Proxy(Validator, {
|
|
649
|
-
apply: function (target, thisArg, argumentsList) {
|
|
650
|
-
return new target(...argumentsList);
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
module.exports = ValidatorProxy;
|