koatty_validation 1.6.0 → 1.6.3

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/dist/index.mjs CHANGED
@@ -1,33 +1,33 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-10-23 01:25:03
3
+ * @Date: 2026-01-28 12:40:10
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
7
7
  */
8
8
  import * as helper from 'koatty_lib';
9
9
  import 'reflect-metadata';
10
- import { getOriginMetadata } from 'koatty_container';
10
+ import { getOriginMetadata, IOCContainer } from 'koatty_container';
11
11
  import { LRUCache } from 'lru-cache';
12
12
  import { validate, isNotIn, isIn, contains, notEquals, equals, isHash, isURL, isPhoneNumber, isIP, isEmail, registerDecorator } from 'class-validator';
13
13
 
14
14
  /**
15
- * koatty_validation 类型定义
15
+ * koatty_validation type definitions
16
16
  * @author richen
17
17
  * @copyright Copyright (c) - <richenlin(at)gmail.com>
18
18
  * @license MIT
19
19
  */
20
20
  /**
21
- * 参数类型键常量
21
+ * Parameter type key constant
22
22
  */
23
23
  const PARAM_TYPE_KEY = 'PARAM_TYPE_KEY';
24
24
 
25
25
  /**
26
- * 性能缓存模块 - 提供多层次缓存和性能监控
26
+ * Performance cache module - Provides multi-level caching and performance monitoring
27
27
  * @author richen
28
28
  */
29
29
  /**
30
- * 元数据缓存
30
+ * Metadata cache
31
31
  */
32
32
  class MetadataCache {
33
33
  constructor() {
@@ -40,7 +40,7 @@ class MetadataCache {
40
40
  return MetadataCache.instance;
41
41
  }
42
42
  /**
43
- * 获取类的元数据缓存
43
+ * Get metadata cache for a class
44
44
  */
45
45
  getClassCache(target) {
46
46
  if (!this.cache.has(target)) {
@@ -49,28 +49,28 @@ class MetadataCache {
49
49
  return this.cache.get(target);
50
50
  }
51
51
  /**
52
- * 缓存元数据
52
+ * Cache metadata
53
53
  */
54
54
  setMetadata(target, key, value) {
55
55
  const classCache = this.getClassCache(target);
56
56
  classCache.set(key, value);
57
57
  }
58
58
  /**
59
- * 获取缓存的元数据
59
+ * Get cached metadata
60
60
  */
61
61
  getMetadata(target, key) {
62
62
  const classCache = this.getClassCache(target);
63
63
  return classCache.get(key);
64
64
  }
65
65
  /**
66
- * 检查是否已缓存
66
+ * Check if metadata is cached
67
67
  */
68
68
  hasMetadata(target, key) {
69
69
  const classCache = this.getClassCache(target);
70
70
  return classCache.has(key);
71
71
  }
72
72
  /**
73
- * 清空指定类的缓存
73
+ * Clear cache for a specific class
74
74
  */
75
75
  clearClassCache(target) {
76
76
  if (this.cache.has(target)) {
@@ -79,7 +79,7 @@ class MetadataCache {
79
79
  }
80
80
  }
81
81
  /**
82
- * 验证结果缓存
82
+ * Validation result cache
83
83
  */
84
84
  class ValidationCache {
85
85
  constructor(options) {
@@ -87,7 +87,7 @@ class ValidationCache {
87
87
  this.misses = 0;
88
88
  this.cache = new LRUCache({
89
89
  max: (options === null || options === void 0 ? void 0 : options.max) || 5000,
90
- ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 10, // 10分钟
90
+ ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 10, // 10 minutes
91
91
  allowStale: (options === null || options === void 0 ? void 0 : options.allowStale) || false,
92
92
  updateAgeOnGet: (options === null || options === void 0 ? void 0 : options.updateAgeOnGet) || true,
93
93
  });
@@ -205,7 +205,7 @@ class RegexCache {
205
205
  constructor(options) {
206
206
  this.cache = new LRUCache({
207
207
  max: (options === null || options === void 0 ? void 0 : options.max) || 200,
208
- ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 30, // 30分钟
208
+ ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 30, // 30 minutes
209
209
  allowStale: (options === null || options === void 0 ? void 0 : options.allowStale) || false,
210
210
  updateAgeOnGet: (options === null || options === void 0 ? void 0 : options.updateAgeOnGet) || true,
211
211
  });
@@ -389,7 +389,7 @@ function cached(validator, ttl) {
389
389
  try {
390
390
  const result = originalMethod.apply(this, args);
391
391
  validationCache.set(validator, value, result, ...additionalArgs);
392
- // 如果指定了TTL,设置过期时间
392
+ // If TTL is specified, set expiration time
393
393
  if (ttl && ttl > 0) {
394
394
  validationCache.setTTL(validator, value, ttl, ...additionalArgs);
395
395
  }
@@ -799,6 +799,209 @@ function plateNumber(value) {
799
799
  }
800
800
  }
801
801
 
802
+ /**
803
+ * Improved error handling mechanism
804
+ * @author richen
805
+ */
806
+ /**
807
+ * Error message internationalization
808
+ */
809
+ const ERROR_MESSAGES = {
810
+ zh: {
811
+ // Chinese localization validation
812
+ IsCnName: '必须是有效的中文姓名',
813
+ IsIdNumber: '必须是有效的身份证号码',
814
+ IsZipCode: '必须是有效的邮政编码',
815
+ IsMobile: '必须是有效的手机号码',
816
+ IsPlateNumber: '必须是有效的车牌号码',
817
+ // Basic validation
818
+ IsNotEmpty: '不能为空',
819
+ IsDate: '必须是有效的日期',
820
+ IsEmail: '必须是有效的邮箱地址',
821
+ IsIP: '必须是有效的IP地址',
822
+ IsPhoneNumber: '必须是有效的电话号码',
823
+ IsUrl: '必须是有效的URL地址',
824
+ IsHash: '必须是有效的哈希值',
825
+ // Comparison validation
826
+ Equals: '必须等于 {comparison}',
827
+ NotEquals: '不能等于 {comparison}',
828
+ Contains: '必须包含 {seed}',
829
+ IsIn: '必须是以下值之一: {possibleValues}',
830
+ IsNotIn: '不能是以下值之一: {possibleValues}',
831
+ Gt: '必须大于 {min}',
832
+ Gte: '必须大于或等于 {min}',
833
+ Lt: '必须小于 {max}',
834
+ Lte: '必须小于或等于 {max}',
835
+ // Common errors
836
+ invalidParameter: '参数 {field} 无效',
837
+ validationFailed: '验证失败',
838
+ },
839
+ en: {
840
+ // Chinese localization validators
841
+ IsCnName: 'must be a valid Chinese name',
842
+ IsIdNumber: 'must be a valid ID number',
843
+ IsZipCode: 'must be a valid zip code',
844
+ IsMobile: 'must be a valid mobile number',
845
+ IsPlateNumber: 'must be a valid plate number',
846
+ // Basic validators
847
+ IsNotEmpty: 'should not be empty',
848
+ IsDate: 'must be a valid date',
849
+ IsEmail: 'must be a valid email',
850
+ IsIP: 'must be a valid IP address',
851
+ IsPhoneNumber: 'must be a valid phone number',
852
+ IsUrl: 'must be a valid URL',
853
+ IsHash: 'must be a valid hash',
854
+ // Comparison validators
855
+ Equals: 'must equal to {comparison}',
856
+ NotEquals: 'should not equal to {comparison}',
857
+ Contains: 'must contain {seed}',
858
+ IsIn: 'must be one of the following values: {possibleValues}',
859
+ IsNotIn: 'should not be one of the following values: {possibleValues}',
860
+ Gt: 'must be greater than {min}',
861
+ Gte: 'must be greater than or equal to {min}',
862
+ Lt: 'must be less than {max}',
863
+ Lte: 'must be less than or equal to {max}',
864
+ // Common errors
865
+ invalidParameter: 'invalid parameter {field}',
866
+ validationFailed: 'validation failed',
867
+ }
868
+ };
869
+ /**
870
+ * Enhanced validation error class
871
+ */
872
+ class KoattyValidationError extends Error {
873
+ constructor(errors, message) {
874
+ const errorMessage = message || 'Validation failed';
875
+ super(errorMessage);
876
+ this.name = 'KoattyValidationError';
877
+ this.errors = errors;
878
+ this.statusCode = 400;
879
+ this.timestamp = new Date();
880
+ // Ensure correct prototype chain
881
+ Object.setPrototypeOf(this, KoattyValidationError.prototype);
882
+ }
883
+ /**
884
+ * Get the first error message
885
+ */
886
+ getFirstError() {
887
+ return this.errors[0];
888
+ }
889
+ /**
890
+ * Get errors for a specific field
891
+ */
892
+ getFieldErrors(field) {
893
+ return this.errors.filter(error => error.field === field);
894
+ }
895
+ /**
896
+ * Convert to JSON format
897
+ */
898
+ toJSON() {
899
+ return {
900
+ name: this.name,
901
+ message: this.message,
902
+ statusCode: this.statusCode,
903
+ timestamp: this.timestamp,
904
+ errors: this.errors
905
+ };
906
+ }
907
+ }
908
+ /**
909
+ * Error message formatter
910
+ */
911
+ class ErrorMessageFormatter {
912
+ constructor(language = 'zh') {
913
+ this.language = 'zh';
914
+ this.language = language;
915
+ }
916
+ /**
917
+ * Set language
918
+ */
919
+ setLanguage(language) {
920
+ this.language = language;
921
+ }
922
+ /**
923
+ * Format error message
924
+ */
925
+ formatMessage(constraint, field, value, context) {
926
+ const messages = ERROR_MESSAGES[this.language];
927
+ let template = messages[constraint] || messages.invalidParameter;
928
+ // Replace placeholders
929
+ template = template.replace('{field}', field);
930
+ // Prioritize values from context, then use passed value
931
+ if (context) {
932
+ Object.entries(context).forEach(([key, val]) => {
933
+ template = template.replace(`{${key}}`, this.formatValue(val));
934
+ });
935
+ }
936
+ // If there's still a {value} placeholder and value was passed, replace it
937
+ if (value !== undefined && template.includes('{value}')) {
938
+ template = template.replace('{value}', this.formatValue(value));
939
+ }
940
+ return template;
941
+ }
942
+ /**
943
+ * Format value for message display
944
+ * @private
945
+ */
946
+ formatValue(value) {
947
+ if (value === null)
948
+ return 'null';
949
+ if (value === undefined)
950
+ return 'undefined';
951
+ if (typeof value === 'number')
952
+ return String(value);
953
+ if (typeof value === 'string')
954
+ return `"${value}"`;
955
+ if (Array.isArray(value))
956
+ return `[${value.map(v => this.formatValue(v)).join(', ')}]`;
957
+ if (typeof value === 'object') {
958
+ try {
959
+ return JSON.stringify(value);
960
+ }
961
+ catch {
962
+ // Handle circular references
963
+ return '[Circular Reference]';
964
+ }
965
+ }
966
+ return String(value);
967
+ }
968
+ }
969
+ /**
970
+ * Global error message formatter instance
971
+ */
972
+ const errorFormatter = new ErrorMessageFormatter();
973
+ /**
974
+ * Set global language
975
+ */
976
+ function setValidationLanguage(language) {
977
+ errorFormatter.setLanguage(language);
978
+ }
979
+ /**
980
+ * Create validation error
981
+ */
982
+ function createValidationError(field, value, constraint, customMessage, context) {
983
+ const message = customMessage || errorFormatter.formatMessage(constraint, field, value, context);
984
+ return {
985
+ field,
986
+ value,
987
+ constraint,
988
+ message,
989
+ context
990
+ };
991
+ }
992
+ /**
993
+ * Create validation errors in batch
994
+ */
995
+ function createValidationErrors(errors, separator = '; ') {
996
+ const validationErrors = errors.map(error => createValidationError(error.field, error.value, error.constraint, error.message, error.context));
997
+ // Generate combined message from all errors
998
+ const combinedMessage = validationErrors
999
+ .map(err => err.message)
1000
+ .filter(msg => msg && msg.trim())
1001
+ .join(separator) || 'Validation failed';
1002
+ return new KoattyValidationError(validationErrors, combinedMessage);
1003
+ }
1004
+
802
1005
  /*
803
1006
  * @Description:
804
1007
  * @Usage:
@@ -858,10 +1061,11 @@ class ValidateClass {
858
1061
  * @param {*} Clazz
859
1062
  * @param {*} data
860
1063
  * @param {boolean} [convert=false] auto convert parameters type
1064
+ * @param {ValidationOptions} [options] validation options (returnAllErrors, errorSeparator)
861
1065
  * @returns {Promise<any>}
862
1066
  * @memberof ValidateClass
863
1067
  */
864
- async valid(Clazz, data, convert = false) {
1068
+ async valid(Clazz, data, convert = false, options) {
865
1069
  let obj = {};
866
1070
  if (data instanceof Clazz) {
867
1071
  obj = data;
@@ -877,7 +1081,21 @@ class ValidateClass {
877
1081
  errors = await validate(obj, { skipMissingProperties: true });
878
1082
  }
879
1083
  if (errors.length > 0) {
880
- throw new Error(Object.values(errors[0].constraints)[0]);
1084
+ // Check if user wants all errors or just the first one
1085
+ if (options === null || options === void 0 ? void 0 : options.returnAllErrors) {
1086
+ // Throw KoattyValidationError with all error details
1087
+ throw createValidationErrors(errors.map(e => ({
1088
+ field: e.property,
1089
+ value: e.value,
1090
+ constraint: Object.keys(e.constraints || {})[0] || 'unknown',
1091
+ message: Object.values(e.constraints || {})[0] || 'Validation failed',
1092
+ context: e.constraints
1093
+ })), options.errorSeparator || '; ');
1094
+ }
1095
+ else {
1096
+ // Default behavior (backward compatible): return only first error
1097
+ throw new Error(Object.values(errors[0].constraints)[0]);
1098
+ }
881
1099
  }
882
1100
  return obj;
883
1101
  }
@@ -1114,24 +1332,24 @@ const FunctionValidator = {
1114
1332
  };
1115
1333
 
1116
1334
  /**
1117
- * 装饰器工厂 - 消除装饰器代码重复
1335
+ * Decorator Factory - Eliminate decorator code duplication
1118
1336
  * @author richen
1119
1337
  */
1120
1338
  /**
1121
- * 创建验证装饰器的工厂函数
1122
- * @param options 装饰器配置选项
1123
- * @returns 装饰器工厂函数
1339
+ * Factory function to create validation decorators
1340
+ * @param options Decorator configuration options
1341
+ * @returns Decorator factory function
1124
1342
  */
1125
1343
  function createValidationDecorator(options) {
1126
1344
  const { name, validator, defaultMessage, requiresValue = false } = options;
1127
1345
  return function decoratorFactory(...args) {
1128
- // 处理参数:最后一个参数是ValidationOptions,前面是验证函数的参数
1346
+ // Handle parameters: last parameter is ValidationOptions, previous ones are validator function parameters
1129
1347
  const validationOptions = args[args.length - 1];
1130
1348
  const validatorArgs = requiresValue ? args.slice(0, -1) : [];
1131
1349
  return function propertyDecorator(object, propertyName) {
1132
- // 设置属性为可导出
1350
+ // Set property as exportable
1133
1351
  setExpose(object, propertyName);
1134
- // 注册验证装饰器
1352
+ // Register validation decorator
1135
1353
  registerDecorator({
1136
1354
  name,
1137
1355
  target: object.constructor,
@@ -1149,8 +1367,9 @@ function createValidationDecorator(options) {
1149
1367
  },
1150
1368
  defaultMessage(validationArguments) {
1151
1369
  const property = validationArguments.property;
1152
- return defaultMessage
1153
- ? defaultMessage.replace('$property', property)
1370
+ const customMessage = (validationOptions === null || validationOptions === void 0 ? void 0 : validationOptions.message) || defaultMessage;
1371
+ return customMessage
1372
+ ? customMessage.replace('$property', property)
1154
1373
  : `Invalid value for ${property}`;
1155
1374
  }
1156
1375
  }
@@ -1159,11 +1378,11 @@ function createValidationDecorator(options) {
1159
1378
  };
1160
1379
  }
1161
1380
  /**
1162
- * 创建简单验证装饰器(不需要额外参数)
1163
- * @param name 装饰器名称
1164
- * @param validator 验证函数
1165
- * @param defaultMessage 默认错误信息
1166
- * @returns 装饰器函数
1381
+ * Create simple validation decorator (no additional parameters required)
1382
+ * @param name Decorator name
1383
+ * @param validator Validation function
1384
+ * @param defaultMessage Default error message
1385
+ * @returns Decorator function
1167
1386
  */
1168
1387
  function createSimpleDecorator(name, validator, defaultMessage) {
1169
1388
  return createValidationDecorator({
@@ -1174,11 +1393,11 @@ function createSimpleDecorator(name, validator, defaultMessage) {
1174
1393
  });
1175
1394
  }
1176
1395
  /**
1177
- * 创建带参数的验证装饰器
1178
- * @param name 装饰器名称
1179
- * @param validator 验证函数
1180
- * @param defaultMessage 默认错误信息
1181
- * @returns 装饰器工厂函数
1396
+ * Create parameterized validation decorator
1397
+ * @param name Decorator name
1398
+ * @param validator Validation function
1399
+ * @param defaultMessage Default error message
1400
+ * @returns Decorator factory function
1182
1401
  */
1183
1402
  function createParameterizedDecorator(name, validator, defaultMessage) {
1184
1403
  return createValidationDecorator({
@@ -1190,217 +1409,19 @@ function createParameterizedDecorator(name, validator, defaultMessage) {
1190
1409
  }
1191
1410
 
1192
1411
  /**
1193
- * 改进的错误处理机制
1194
- * @author richen
1195
- */
1196
- /**
1197
- * 错误信息国际化
1198
- */
1199
- const ERROR_MESSAGES = {
1200
- zh: {
1201
- // 中国本土化验证
1202
- IsCnName: '必须是有效的中文姓名',
1203
- IsIdNumber: '必须是有效的身份证号码',
1204
- IsZipCode: '必须是有效的邮政编码',
1205
- IsMobile: '必须是有效的手机号码',
1206
- IsPlateNumber: '必须是有效的车牌号码',
1207
- // 基础验证
1208
- IsNotEmpty: '不能为空',
1209
- IsDate: '必须是有效的日期',
1210
- IsEmail: '必须是有效的邮箱地址',
1211
- IsIP: '必须是有效的IP地址',
1212
- IsPhoneNumber: '必须是有效的电话号码',
1213
- IsUrl: '必须是有效的URL地址',
1214
- IsHash: '必须是有效的哈希值',
1215
- // 比较验证
1216
- Equals: '必须等于 {comparison}',
1217
- NotEquals: '不能等于 {comparison}',
1218
- Contains: '必须包含 {seed}',
1219
- IsIn: '必须是以下值之一: {possibleValues}',
1220
- IsNotIn: '不能是以下值之一: {possibleValues}',
1221
- Gt: '必须大于 {min}',
1222
- Gte: '必须大于或等于 {min}',
1223
- Lt: '必须小于 {max}',
1224
- Lte: '必须小于或等于 {max}',
1225
- // 通用错误
1226
- invalidParameter: '参数 {field} 无效',
1227
- validationFailed: '验证失败',
1228
- },
1229
- en: {
1230
- // Chinese localization validators
1231
- IsCnName: 'must be a valid Chinese name',
1232
- IsIdNumber: 'must be a valid ID number',
1233
- IsZipCode: 'must be a valid zip code',
1234
- IsMobile: 'must be a valid mobile number',
1235
- IsPlateNumber: 'must be a valid plate number',
1236
- // Basic validators
1237
- IsNotEmpty: 'should not be empty',
1238
- IsDate: 'must be a valid date',
1239
- IsEmail: 'must be a valid email',
1240
- IsIP: 'must be a valid IP address',
1241
- IsPhoneNumber: 'must be a valid phone number',
1242
- IsUrl: 'must be a valid URL',
1243
- IsHash: 'must be a valid hash',
1244
- // Comparison validators
1245
- Equals: 'must equal to {comparison}',
1246
- NotEquals: 'should not equal to {comparison}',
1247
- Contains: 'must contain {seed}',
1248
- IsIn: 'must be one of the following values: {possibleValues}',
1249
- IsNotIn: 'should not be one of the following values: {possibleValues}',
1250
- Gt: 'must be greater than {min}',
1251
- Gte: 'must be greater than or equal to {min}',
1252
- Lt: 'must be less than {max}',
1253
- Lte: 'must be less than or equal to {max}',
1254
- // Common errors
1255
- invalidParameter: 'invalid parameter {field}',
1256
- validationFailed: 'validation failed',
1257
- }
1258
- };
1259
- /**
1260
- * 增强的验证错误类
1261
- */
1262
- class KoattyValidationError extends Error {
1263
- constructor(errors, message) {
1264
- const errorMessage = message || 'Validation failed';
1265
- super(errorMessage);
1266
- this.name = 'KoattyValidationError';
1267
- this.errors = errors;
1268
- this.statusCode = 400;
1269
- this.timestamp = new Date();
1270
- // 确保正确的原型链
1271
- Object.setPrototypeOf(this, KoattyValidationError.prototype);
1272
- }
1273
- /**
1274
- * 获取第一个错误信息
1275
- */
1276
- getFirstError() {
1277
- return this.errors[0];
1278
- }
1279
- /**
1280
- * 获取指定字段的错误
1281
- */
1282
- getFieldErrors(field) {
1283
- return this.errors.filter(error => error.field === field);
1284
- }
1285
- /**
1286
- * 转换为JSON格式
1287
- */
1288
- toJSON() {
1289
- return {
1290
- name: this.name,
1291
- message: this.message,
1292
- statusCode: this.statusCode,
1293
- timestamp: this.timestamp,
1294
- errors: this.errors
1295
- };
1296
- }
1297
- }
1298
- /**
1299
- * 错误信息格式化器
1300
- */
1301
- class ErrorMessageFormatter {
1302
- constructor(language = 'zh') {
1303
- this.language = 'zh';
1304
- this.language = language;
1305
- }
1306
- /**
1307
- * 设置语言
1308
- */
1309
- setLanguage(language) {
1310
- this.language = language;
1311
- }
1312
- /**
1313
- * 格式化错误消息
1314
- */
1315
- formatMessage(constraint, field, value, context) {
1316
- const messages = ERROR_MESSAGES[this.language];
1317
- let template = messages[constraint] || messages.invalidParameter;
1318
- // 替换占位符
1319
- template = template.replace('{field}', field);
1320
- // 优先使用上下文中的值,然后是传入的value
1321
- if (context) {
1322
- Object.entries(context).forEach(([key, val]) => {
1323
- template = template.replace(`{${key}}`, this.formatValue(val));
1324
- });
1325
- }
1326
- // 如果还有{value}占位符且传入了value,则替换
1327
- if (value !== undefined && template.includes('{value}')) {
1328
- template = template.replace('{value}', this.formatValue(value));
1329
- }
1330
- return template;
1331
- }
1332
- /**
1333
- * 格式化值用于消息显示
1334
- * @private
1335
- */
1336
- formatValue(value) {
1337
- if (value === null)
1338
- return 'null';
1339
- if (value === undefined)
1340
- return 'undefined';
1341
- if (typeof value === 'number')
1342
- return String(value);
1343
- if (typeof value === 'string')
1344
- return `"${value}"`;
1345
- if (Array.isArray(value))
1346
- return `[${value.map(v => this.formatValue(v)).join(', ')}]`;
1347
- if (typeof value === 'object') {
1348
- try {
1349
- return JSON.stringify(value);
1350
- }
1351
- catch {
1352
- // 处理循环引用
1353
- return '[Circular Reference]';
1354
- }
1355
- }
1356
- return String(value);
1357
- }
1358
- }
1359
- /**
1360
- * 全局错误信息格式化器实例
1361
- */
1362
- const errorFormatter = new ErrorMessageFormatter();
1363
- /**
1364
- * 设置全局语言
1365
- */
1366
- function setValidationLanguage(language) {
1367
- errorFormatter.setLanguage(language);
1368
- }
1369
- /**
1370
- * 创建验证错误
1371
- */
1372
- function createValidationError(field, value, constraint, customMessage, context) {
1373
- const message = customMessage || errorFormatter.formatMessage(constraint, field, value, context);
1374
- return {
1375
- field,
1376
- value,
1377
- constraint,
1378
- message,
1379
- context
1380
- };
1381
- }
1382
- /**
1383
- * 批量创建验证错误
1384
- */
1385
- function createValidationErrors(errors) {
1386
- const validationErrors = errors.map(error => createValidationError(error.field, error.value, error.constraint, error.message, error.context));
1387
- return new KoattyValidationError(validationErrors);
1388
- }
1389
-
1390
- /**
1391
- * 重构后的装饰器定义 - 使用工厂函数消除重复
1412
+ * Refactored decorator definitions - Using factory functions to eliminate duplication
1392
1413
  * @author richen
1393
1414
  */
1394
- // 中国本土化验证装饰器
1415
+ // Chinese localization validation decorators
1395
1416
  const IsCnName = createSimpleDecorator('IsCnName', (value) => helper.isString(value) && cnName(value), 'must be a valid Chinese name');
1396
1417
  const IsIdNumber = createSimpleDecorator('IsIdNumber', (value) => helper.isString(value) && idNumber(value), 'must be a valid ID number');
1397
1418
  const IsZipCode = createSimpleDecorator('IsZipCode', (value) => helper.isString(value) && zipCode(value), 'must be a valid zip code');
1398
1419
  const IsMobile = createSimpleDecorator('IsMobile', (value) => helper.isString(value) && mobile(value), 'must be a valid mobile number');
1399
1420
  const IsPlateNumber = createSimpleDecorator('IsPlateNumber', (value) => helper.isString(value) && plateNumber(value), 'must be a valid plate number');
1400
- // 基础验证装饰器
1421
+ // Basic validation decorators
1401
1422
  const IsNotEmpty = createSimpleDecorator('IsNotEmpty', (value) => !helper.isEmpty(value), 'should not be empty');
1402
1423
  const IsDate = createSimpleDecorator('IsDate', (value) => helper.isDate(value), 'must be a valid date');
1403
- // 带参数的验证装饰器
1424
+ // Parameterized validation decorators
1404
1425
  const Equals = createParameterizedDecorator('Equals', (value, comparison) => value === comparison, 'must equal to $constraint1');
1405
1426
  const NotEquals = createParameterizedDecorator('NotEquals', (value, comparison) => value !== comparison, 'should not equal to $constraint1');
1406
1427
  const Contains = createParameterizedDecorator('Contains', (value, seed) => helper.isString(value) && value.includes(seed), 'must contain $constraint1');
@@ -1413,7 +1434,10 @@ const Lt = createParameterizedDecorator('Lt', (value, max) => helper.toNumber(va
1413
1434
  const Lte = createParameterizedDecorator('Lte', (value, max) => helper.toNumber(value) <= max, 'must be less than or equal to $constraint1');
1414
1435
  // 复杂验证装饰器(需要特殊处理)
1415
1436
  function IsEmail(options, validationOptions) {
1416
- return createParameterizedDecorator('IsEmail', (value) => isEmail(value, options), 'must be a valid email')(validationOptions);
1437
+ // Handle case where options is actually ValidationOptions (message only)
1438
+ const actualOptions = (options === null || options === void 0 ? void 0 : options.message) ? {} : options;
1439
+ const actualValidationOptions = (options === null || options === void 0 ? void 0 : options.message) ? options : validationOptions;
1440
+ return createParameterizedDecorator('IsEmail', (value) => isEmail(value, actualOptions), 'must be a valid email')(actualValidationOptions);
1417
1441
  }
1418
1442
  function IsIP(version, validationOptions) {
1419
1443
  return createParameterizedDecorator('IsIP', (value) => isIP(value, version), 'must be a valid IP address')(validationOptions);
@@ -1427,9 +1451,9 @@ function IsUrl(options, validationOptions) {
1427
1451
  function IsHash(algorithm, validationOptions) {
1428
1452
  return createParameterizedDecorator('IsHash', (value) => isHash(value, algorithm), 'must be a valid hash')(validationOptions);
1429
1453
  }
1430
- // 基础工具装饰器(从原始decorator.ts移植)
1454
+ // Basic utility decorators (migrated from original decorator.ts)
1431
1455
  /**
1432
- * 标记属性为可导出
1456
+ * Mark property as exportable
1433
1457
  */
1434
1458
  function Expose() {
1435
1459
  return function (object, propertyName) {
@@ -1437,7 +1461,7 @@ function Expose() {
1437
1461
  };
1438
1462
  }
1439
1463
  /**
1440
- * Expose的别名
1464
+ * Alias for Expose
1441
1465
  */
1442
1466
  function IsDefined() {
1443
1467
  return function (object, propertyName) {
@@ -1445,63 +1469,91 @@ function IsDefined() {
1445
1469
  };
1446
1470
  }
1447
1471
  /**
1448
- * 参数验证装饰器
1472
+ * Parameter validation decorator
1449
1473
  */
1450
1474
  function Valid(rule, options) {
1451
1475
  return function (object, propertyName, parameterIndex) {
1452
- // 这里保持与原始实现一致
1476
+ // Keep consistent with original implementation
1453
1477
  const existingRules = Reflect.getOwnMetadata("validate", object, propertyName) || {};
1454
1478
  existingRules[parameterIndex] = { rule, options };
1455
1479
  Reflect.defineMetadata("validate", existingRules, object, propertyName);
1456
1480
  };
1457
1481
  }
1458
1482
  /**
1459
- * 方法验证装饰器
1460
- * 自动验证方法参数中的 DTO 对象
1483
+ * Synchronous validation function - Executes the actual validation logic
1484
+ * @param args Method parameters
1485
+ * @param paramTypes Parameter type metadata
1486
+ * @returns Validated parameters and validation targets
1461
1487
  */
1462
- function Validated() {
1463
- return function (object, propertyName, descriptor) {
1464
- const originalMethod = descriptor.value;
1465
- descriptor.value = async function (...args) {
1466
- // 获取参数类型元数据
1467
- const paramTypes = Reflect.getMetadata('design:paramtypes', object, propertyName) || [];
1468
- // 验证每个参数
1469
- for (let i = 0; i < args.length; i++) {
1470
- const arg = args[i];
1471
- const paramType = paramTypes[i];
1472
- // 如果是类类型且不是基础类型,执行验证
1473
- if (paramType && typeof paramType === 'function' &&
1474
- paramType !== String && paramType !== Number &&
1475
- paramType !== Boolean && paramType !== Array &&
1476
- paramType !== Object && paramType !== Date) {
1477
- try {
1478
- // 如果参数不是目标类型的实例,转换为实例
1479
- let validationTarget = arg;
1480
- if (!(arg instanceof paramType)) {
1481
- validationTarget = Object.assign(new paramType(), arg);
1482
- }
1483
- const errors = await validate(validationTarget);
1484
- if (errors.length > 0) {
1485
- throw createValidationErrors(errors.map(e => ({
1486
- field: e.property,
1487
- value: e.value,
1488
- constraint: Object.keys(e.constraints || {})[0] || 'unknown',
1489
- message: Object.values(e.constraints || {})[0] || 'Validation failed',
1490
- context: e.constraints
1491
- })));
1492
- }
1493
- }
1494
- catch (error) {
1495
- // 如果验证失败,重新抛出错误
1496
- throw error;
1497
- }
1488
+ async function checkValidated(args, paramTypes) {
1489
+ const validationTargets = [];
1490
+ // Validate each parameter
1491
+ for (let i = 0; i < args.length; i++) {
1492
+ const arg = args[i];
1493
+ const paramType = paramTypes[i];
1494
+ // If it's a class type and not a basic type, perform validation
1495
+ if (paramType && typeof paramType === 'function' &&
1496
+ paramType !== String && paramType !== Number &&
1497
+ paramType !== Boolean && paramType !== Array &&
1498
+ paramType !== Object && paramType !== Date) {
1499
+ try {
1500
+ // If parameter is not an instance of the target type, convert it to an instance
1501
+ let validationTarget = arg;
1502
+ if (!(arg instanceof paramType)) {
1503
+ validationTarget = Object.assign(new paramType(), arg);
1504
+ }
1505
+ const errors = await validate(validationTarget);
1506
+ if (errors.length > 0) {
1507
+ throw createValidationErrors(errors.map(e => ({
1508
+ field: e.property,
1509
+ value: e.value,
1510
+ constraint: Object.keys(e.constraints || {})[0] || 'unknown',
1511
+ message: Object.values(e.constraints || {})[0] || 'Validation failed',
1512
+ context: e.constraints
1513
+ })));
1498
1514
  }
1515
+ validationTargets.push(validationTarget);
1499
1516
  }
1500
- // 执行原始方法
1501
- return originalMethod.apply(this, args);
1502
- };
1517
+ catch (error) {
1518
+ // If validation fails, rethrow the error
1519
+ throw error;
1520
+ }
1521
+ }
1522
+ else {
1523
+ validationTargets.push(arg);
1524
+ }
1525
+ }
1526
+ return { validatedArgs: args, validationTargets };
1527
+ }
1528
+ /**
1529
+ * Method validation decorator
1530
+ * Automatically validates DTO objects in method parameters
1531
+ * @param isAsync Whether to use async validation mode, default is true
1532
+ * - true: Async mode, validation is handled by IOC container in the framework (suitable for scenarios where parameter values need to be obtained asynchronously)
1533
+ * - false: Sync mode, validation is performed immediately when the method is called (suitable for scenarios where parameter values are already prepared)
1534
+ */
1535
+ function Validated(isAsync = true) {
1536
+ return function (target, propertyKey, descriptor) {
1537
+ if (isAsync) {
1538
+ // Async mode: Save metadata, validation will be performed by the framework after async parameter retrieval
1539
+ IOCContainer.savePropertyData(PARAM_CHECK_KEY, {
1540
+ dtoCheck: 1
1541
+ }, target, propertyKey);
1542
+ }
1543
+ else {
1544
+ // Sync mode: Perform validation immediately when the method is called
1545
+ const originalMethod = descriptor.value;
1546
+ descriptor.value = async function (...args) {
1547
+ // Get parameter type metadata
1548
+ const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey) || [];
1549
+ // Execute validation
1550
+ await checkValidated(args, paramTypes);
1551
+ // Execute original method
1552
+ return originalMethod.apply(this, args);
1553
+ };
1554
+ }
1503
1555
  return descriptor;
1504
1556
  };
1505
1557
  }
1506
1558
 
1507
- export { ClassValidator, Contains, ENABLE_VALIDATED, ERROR_MESSAGES, Equals, ErrorMessageFormatter, Expose, FunctionValidator, Gt, Gte, IsCnName, IsDate, IsDefined, IsEmail, IsHash, IsIP, IsIdNumber, IsIn, IsMobile, IsNotEmpty, IsNotIn, IsPhoneNumber, IsPlateNumber, IsUrl, IsZipCode, KoattyValidationError, Lt, Lte, NotEquals, PARAM_CHECK_KEY, PARAM_RULE_KEY, PARAM_TYPE_KEY, Valid, ValidFuncs, Validated, cached, checkParamsType, clearAllCaches, configureCaches, convertDtoParamsType, convertParamsType, createParameterizedDecorator, createSimpleDecorator, createValidationDecorator, createValidationError, createValidationErrors, errorFormatter, getAllCacheStats, metadataCache, paramterTypes, performanceMonitor, plainToClass, regexCache, setValidationLanguage, validationCache, warmupCaches };
1559
+ export { ClassValidator, Contains, ENABLE_VALIDATED, ERROR_MESSAGES, Equals, ErrorMessageFormatter, Expose, FunctionValidator, Gt, Gte, IsCnName, IsDate, IsDefined, IsEmail, IsHash, IsIP, IsIdNumber, IsIn, IsMobile, IsNotEmpty, IsNotIn, IsPhoneNumber, IsPlateNumber, IsUrl, IsZipCode, KoattyValidationError, Lt, Lte, NotEquals, PARAM_CHECK_KEY, PARAM_RULE_KEY, PARAM_TYPE_KEY, Valid, ValidFuncs, Validated, cached, checkParamsType, checkValidated, clearAllCaches, configureCaches, convertDtoParamsType, convertParamsType, createParameterizedDecorator, createSimpleDecorator, createValidationDecorator, createValidationError, createValidationErrors, errorFormatter, getAllCacheStats, metadataCache, paramterTypes, performanceMonitor, plainToClass, regexCache, setValidationLanguage, validationCache, warmupCaches };