ValidationExecutor.js 18 KB

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