ValidationExecutor.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. var __read = (this && this.__read) || function (o, n) {
  2. var m = typeof Symbol === "function" && o[Symbol.iterator];
  3. if (!m) return o;
  4. var i = m.call(o), r, ar = [], e;
  5. try {
  6. while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
  7. }
  8. catch (error) { e = { error: error }; }
  9. finally {
  10. try {
  11. if (r && !r.done && (m = i["return"])) m.call(i);
  12. }
  13. finally { if (e) throw e.error; }
  14. }
  15. return ar;
  16. };
  17. import { ValidationError } from './ValidationError';
  18. import { ValidationTypes } from './ValidationTypes';
  19. import { ValidationUtils } from './ValidationUtils';
  20. import { isPromise, convertToArray } from '../utils';
  21. import { getMetadataStorage } from '../metadata/MetadataStorage';
  22. /**
  23. * Executes validation over given object.
  24. */
  25. var ValidationExecutor = /** @class */ (function () {
  26. // -------------------------------------------------------------------------
  27. // Constructor
  28. // -------------------------------------------------------------------------
  29. function ValidationExecutor(validator, validatorOptions) {
  30. this.validator = validator;
  31. this.validatorOptions = validatorOptions;
  32. // -------------------------------------------------------------------------
  33. // Properties
  34. // -------------------------------------------------------------------------
  35. this.awaitingPromises = [];
  36. this.ignoreAsyncValidations = false;
  37. // -------------------------------------------------------------------------
  38. // Private Properties
  39. // -------------------------------------------------------------------------
  40. this.metadataStorage = getMetadataStorage();
  41. }
  42. // -------------------------------------------------------------------------
  43. // Public Methods
  44. // -------------------------------------------------------------------------
  45. ValidationExecutor.prototype.execute = function (object, targetSchema, validationErrors) {
  46. var _this = this;
  47. var _a, _b;
  48. /**
  49. * If there is no metadata registered it means possibly the dependencies are not flatterned and
  50. * more than one instance is used.
  51. *
  52. * TODO: This needs proper handling, forcing to use the same container or some other proper solution.
  53. */
  54. if (!this.metadataStorage.hasValidationMetaData && ((_a = this.validatorOptions) === null || _a === void 0 ? void 0 : _a.enableDebugMessages) === true) {
  55. console.warn("No validation metadata found. No validation will be performed. There are multiple possible reasons:\n" +
  56. " - There may be multiple class-validator versions installed. You will need to flatten your dependencies to fix the issue.\n" +
  57. " - This validation runs before any file with validation decorator was parsed by NodeJS.");
  58. }
  59. var groups = this.validatorOptions ? this.validatorOptions.groups : undefined;
  60. var strictGroups = (this.validatorOptions && this.validatorOptions.strictGroups) || false;
  61. var always = (this.validatorOptions && this.validatorOptions.always) || false;
  62. /** Forbid unknown values are turned on by default and any other value than false will enable it. */
  63. var forbidUnknownValues = ((_b = this.validatorOptions) === null || _b === void 0 ? void 0 : _b.forbidUnknownValues) === undefined || this.validatorOptions.forbidUnknownValues !== false;
  64. var targetMetadatas = this.metadataStorage.getTargetValidationMetadatas(object.constructor, targetSchema, always, strictGroups, groups);
  65. var groupedMetadatas = this.metadataStorage.groupByPropertyName(targetMetadatas);
  66. if (this.validatorOptions && forbidUnknownValues && !targetMetadatas.length) {
  67. var validationError = new ValidationError();
  68. if (!this.validatorOptions ||
  69. !this.validatorOptions.validationError ||
  70. this.validatorOptions.validationError.target === undefined ||
  71. this.validatorOptions.validationError.target === true)
  72. validationError.target = object;
  73. validationError.value = undefined;
  74. validationError.property = undefined;
  75. validationError.children = [];
  76. validationError.constraints = { unknownValue: 'an unknown value was passed to the validate function' };
  77. validationErrors.push(validationError);
  78. return;
  79. }
  80. if (this.validatorOptions && this.validatorOptions.whitelist)
  81. this.whitelist(object, groupedMetadatas, validationErrors);
  82. // General validation
  83. Object.keys(groupedMetadatas).forEach(function (propertyName) {
  84. var value = object[propertyName];
  85. var definedMetadatas = groupedMetadatas[propertyName].filter(function (metadata) { return metadata.type === ValidationTypes.IS_DEFINED; });
  86. var metadatas = groupedMetadatas[propertyName].filter(function (metadata) { return metadata.type !== ValidationTypes.IS_DEFINED && metadata.type !== ValidationTypes.WHITELIST; });
  87. if (value instanceof Promise &&
  88. metadatas.find(function (metadata) { return metadata.type === ValidationTypes.PROMISE_VALIDATION; })) {
  89. _this.awaitingPromises.push(value.then(function (resolvedValue) {
  90. _this.performValidations(object, resolvedValue, propertyName, definedMetadatas, metadatas, validationErrors);
  91. }));
  92. }
  93. else {
  94. _this.performValidations(object, value, propertyName, definedMetadatas, metadatas, validationErrors);
  95. }
  96. });
  97. };
  98. ValidationExecutor.prototype.whitelist = function (object, groupedMetadatas, validationErrors) {
  99. var _this = this;
  100. var notAllowedProperties = [];
  101. Object.keys(object).forEach(function (propertyName) {
  102. // does this property have no metadata?
  103. if (!groupedMetadatas[propertyName] || groupedMetadatas[propertyName].length === 0)
  104. notAllowedProperties.push(propertyName);
  105. });
  106. if (notAllowedProperties.length > 0) {
  107. if (this.validatorOptions && this.validatorOptions.forbidNonWhitelisted) {
  108. // throw errors
  109. notAllowedProperties.forEach(function (property) {
  110. var _a;
  111. var validationError = _this.generateValidationError(object, object[property], property);
  112. validationError.constraints = (_a = {}, _a[ValidationTypes.WHITELIST] = "property ".concat(property, " should not exist"), _a);
  113. validationError.children = undefined;
  114. validationErrors.push(validationError);
  115. });
  116. }
  117. else {
  118. // strip non allowed properties
  119. notAllowedProperties.forEach(function (property) { return delete object[property]; });
  120. }
  121. }
  122. };
  123. ValidationExecutor.prototype.stripEmptyErrors = function (errors) {
  124. var _this = this;
  125. return errors.filter(function (error) {
  126. if (error.children) {
  127. error.children = _this.stripEmptyErrors(error.children);
  128. }
  129. if (Object.keys(error.constraints).length === 0) {
  130. if (error.children.length === 0) {
  131. return false;
  132. }
  133. else {
  134. delete error.constraints;
  135. }
  136. }
  137. return true;
  138. });
  139. };
  140. // -------------------------------------------------------------------------
  141. // Private Methods
  142. // -------------------------------------------------------------------------
  143. ValidationExecutor.prototype.performValidations = function (object, value, propertyName, definedMetadatas, metadatas, validationErrors) {
  144. var customValidationMetadatas = metadatas.filter(function (metadata) { return metadata.type === ValidationTypes.CUSTOM_VALIDATION; });
  145. var nestedValidationMetadatas = metadatas.filter(function (metadata) { return metadata.type === ValidationTypes.NESTED_VALIDATION; });
  146. var conditionalValidationMetadatas = metadatas.filter(function (metadata) { return metadata.type === ValidationTypes.CONDITIONAL_VALIDATION; });
  147. var validationError = this.generateValidationError(object, value, propertyName);
  148. validationErrors.push(validationError);
  149. var canValidate = this.conditionalValidations(object, value, conditionalValidationMetadatas);
  150. if (!canValidate) {
  151. return;
  152. }
  153. // handle IS_DEFINED validation type the special way - it should work no matter skipUndefinedProperties/skipMissingProperties is set or not
  154. this.customValidations(object, value, definedMetadatas, validationError);
  155. this.mapContexts(object, value, definedMetadatas, validationError);
  156. if (value === undefined && this.validatorOptions && this.validatorOptions.skipUndefinedProperties === true) {
  157. return;
  158. }
  159. if (value === null && this.validatorOptions && this.validatorOptions.skipNullProperties === true) {
  160. return;
  161. }
  162. if ((value === null || value === undefined) &&
  163. this.validatorOptions &&
  164. this.validatorOptions.skipMissingProperties === true) {
  165. return;
  166. }
  167. this.customValidations(object, value, customValidationMetadatas, validationError);
  168. this.nestedValidations(value, nestedValidationMetadatas, validationError);
  169. this.mapContexts(object, value, metadatas, validationError);
  170. this.mapContexts(object, value, customValidationMetadatas, validationError);
  171. };
  172. ValidationExecutor.prototype.generateValidationError = function (object, value, propertyName) {
  173. var validationError = new ValidationError();
  174. if (!this.validatorOptions ||
  175. !this.validatorOptions.validationError ||
  176. this.validatorOptions.validationError.target === undefined ||
  177. this.validatorOptions.validationError.target === true)
  178. validationError.target = object;
  179. if (!this.validatorOptions ||
  180. !this.validatorOptions.validationError ||
  181. this.validatorOptions.validationError.value === undefined ||
  182. this.validatorOptions.validationError.value === true)
  183. validationError.value = value;
  184. validationError.property = propertyName;
  185. validationError.children = [];
  186. validationError.constraints = {};
  187. return validationError;
  188. };
  189. ValidationExecutor.prototype.conditionalValidations = function (object, value, metadatas) {
  190. return metadatas
  191. .map(function (metadata) { return metadata.constraints[0](object, value); })
  192. .reduce(function (resultA, resultB) { return resultA && resultB; }, true);
  193. };
  194. ValidationExecutor.prototype.customValidations = function (object, value, metadatas, error) {
  195. var _this = this;
  196. metadatas.forEach(function (metadata) {
  197. _this.metadataStorage.getTargetValidatorConstraints(metadata.constraintCls).forEach(function (customConstraintMetadata) {
  198. if (customConstraintMetadata.async && _this.ignoreAsyncValidations)
  199. return;
  200. if (_this.validatorOptions &&
  201. _this.validatorOptions.stopAtFirstError &&
  202. Object.keys(error.constraints || {}).length > 0)
  203. return;
  204. var validationArguments = {
  205. targetName: object.constructor ? object.constructor.name : undefined,
  206. property: metadata.propertyName,
  207. object: object,
  208. value: value,
  209. constraints: metadata.constraints,
  210. };
  211. if (!metadata.each || !(Array.isArray(value) || value instanceof Set || value instanceof Map)) {
  212. var validatedValue = customConstraintMetadata.instance.validate(value, validationArguments);
  213. if (isPromise(validatedValue)) {
  214. var promise = validatedValue.then(function (isValid) {
  215. if (!isValid) {
  216. var _a = __read(_this.createValidationError(object, value, metadata, customConstraintMetadata), 2), type = _a[0], message = _a[1];
  217. error.constraints[type] = message;
  218. if (metadata.context) {
  219. if (!error.contexts) {
  220. error.contexts = {};
  221. }
  222. error.contexts[type] = Object.assign(error.contexts[type] || {}, metadata.context);
  223. }
  224. }
  225. });
  226. _this.awaitingPromises.push(promise);
  227. }
  228. else {
  229. if (!validatedValue) {
  230. var _a = __read(_this.createValidationError(object, value, metadata, customConstraintMetadata), 2), type = _a[0], message = _a[1];
  231. error.constraints[type] = message;
  232. }
  233. }
  234. return;
  235. }
  236. // convert set and map into array
  237. var arrayValue = convertToArray(value);
  238. // Validation needs to be applied to each array item
  239. var validatedSubValues = arrayValue.map(function (subValue) {
  240. return customConstraintMetadata.instance.validate(subValue, validationArguments);
  241. });
  242. var validationIsAsync = validatedSubValues.some(function (validatedSubValue) {
  243. return isPromise(validatedSubValue);
  244. });
  245. if (validationIsAsync) {
  246. // Wrap plain values (if any) in promises, so that all are async
  247. var asyncValidatedSubValues = validatedSubValues.map(function (validatedSubValue) {
  248. return isPromise(validatedSubValue) ? validatedSubValue : Promise.resolve(validatedSubValue);
  249. });
  250. var asyncValidationIsFinishedPromise = Promise.all(asyncValidatedSubValues).then(function (flatValidatedValues) {
  251. var validationResult = flatValidatedValues.every(function (isValid) { return isValid; });
  252. if (!validationResult) {
  253. var _a = __read(_this.createValidationError(object, value, metadata, customConstraintMetadata), 2), type = _a[0], message = _a[1];
  254. error.constraints[type] = message;
  255. if (metadata.context) {
  256. if (!error.contexts) {
  257. error.contexts = {};
  258. }
  259. error.contexts[type] = Object.assign(error.contexts[type] || {}, metadata.context);
  260. }
  261. }
  262. });
  263. _this.awaitingPromises.push(asyncValidationIsFinishedPromise);
  264. return;
  265. }
  266. var validationResult = validatedSubValues.every(function (isValid) { return isValid; });
  267. if (!validationResult) {
  268. var _b = __read(_this.createValidationError(object, value, metadata, customConstraintMetadata), 2), type = _b[0], message = _b[1];
  269. error.constraints[type] = message;
  270. }
  271. });
  272. });
  273. };
  274. ValidationExecutor.prototype.nestedValidations = function (value, metadatas, error) {
  275. var _this = this;
  276. if (value === void 0) {
  277. return;
  278. }
  279. metadatas.forEach(function (metadata) {
  280. if (metadata.type !== ValidationTypes.NESTED_VALIDATION && metadata.type !== ValidationTypes.PROMISE_VALIDATION) {
  281. return;
  282. }
  283. else if (_this.validatorOptions &&
  284. _this.validatorOptions.stopAtFirstError &&
  285. Object.keys(error.constraints || {}).length > 0) {
  286. return;
  287. }
  288. if (Array.isArray(value) || value instanceof Set || value instanceof Map) {
  289. // Treats Set as an array - as index of Set value is value itself and it is common case to have Object as value
  290. var arrayLikeValue = value instanceof Set ? Array.from(value) : value;
  291. arrayLikeValue.forEach(function (subValue, index) {
  292. _this.performValidations(value, subValue, index.toString(), [], metadatas, error.children);
  293. });
  294. }
  295. else if (value instanceof Object) {
  296. var targetSchema = typeof metadata.target === 'string' ? metadata.target : metadata.target.name;
  297. _this.execute(value, targetSchema, error.children);
  298. }
  299. else {
  300. var _a = __read(_this.createValidationError(metadata.target, value, metadata), 2), type = _a[0], message = _a[1];
  301. error.constraints[type] = message;
  302. }
  303. });
  304. };
  305. ValidationExecutor.prototype.mapContexts = function (object, value, metadatas, error) {
  306. var _this = this;
  307. return metadatas.forEach(function (metadata) {
  308. if (metadata.context) {
  309. var customConstraint = void 0;
  310. if (metadata.type === ValidationTypes.CUSTOM_VALIDATION) {
  311. var customConstraints = _this.metadataStorage.getTargetValidatorConstraints(metadata.constraintCls);
  312. customConstraint = customConstraints[0];
  313. }
  314. var type = _this.getConstraintType(metadata, customConstraint);
  315. if (error.constraints[type]) {
  316. if (!error.contexts) {
  317. error.contexts = {};
  318. }
  319. error.contexts[type] = Object.assign(error.contexts[type] || {}, metadata.context);
  320. }
  321. }
  322. });
  323. };
  324. ValidationExecutor.prototype.createValidationError = function (object, value, metadata, customValidatorMetadata) {
  325. var targetName = object.constructor ? object.constructor.name : undefined;
  326. var type = this.getConstraintType(metadata, customValidatorMetadata);
  327. var validationArguments = {
  328. targetName: targetName,
  329. property: metadata.propertyName,
  330. object: object,
  331. value: value,
  332. constraints: metadata.constraints,
  333. };
  334. var message = metadata.message || '';
  335. if (!metadata.message &&
  336. (!this.validatorOptions || (this.validatorOptions && !this.validatorOptions.dismissDefaultMessages))) {
  337. if (customValidatorMetadata && customValidatorMetadata.instance.defaultMessage instanceof Function) {
  338. message = customValidatorMetadata.instance.defaultMessage(validationArguments);
  339. }
  340. }
  341. var messageString = ValidationUtils.replaceMessageSpecialTokens(message, validationArguments);
  342. return [type, messageString];
  343. };
  344. ValidationExecutor.prototype.getConstraintType = function (metadata, customValidatorMetadata) {
  345. var type = customValidatorMetadata && customValidatorMetadata.name ? customValidatorMetadata.name : metadata.type;
  346. return type;
  347. };
  348. return ValidationExecutor;
  349. }());
  350. export { ValidationExecutor };
  351. //# sourceMappingURL=ValidationExecutor.js.map