var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; import { ValidationError } from './ValidationError'; import { ValidationTypes } from './ValidationTypes'; import { ValidationUtils } from './ValidationUtils'; import { isPromise, convertToArray } from '../utils'; import { getMetadataStorage } from '../metadata/MetadataStorage'; /** * Executes validation over given object. */ var ValidationExecutor = /** @class */ (function () { // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- function ValidationExecutor(validator, validatorOptions) { this.validator = validator; this.validatorOptions = validatorOptions; // ------------------------------------------------------------------------- // Properties // ------------------------------------------------------------------------- this.awaitingPromises = []; this.ignoreAsyncValidations = false; // ------------------------------------------------------------------------- // Private Properties // ------------------------------------------------------------------------- this.metadataStorage = getMetadataStorage(); } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- ValidationExecutor.prototype.execute = function (object, targetSchema, validationErrors) { var _this = this; var _a, _b; /** * If there is no metadata registered it means possibly the dependencies are not flatterned and * more than one instance is used. * * TODO: This needs proper handling, forcing to use the same container or some other proper solution. */ if (!this.metadataStorage.hasValidationMetaData && ((_a = this.validatorOptions) === null || _a === void 0 ? void 0 : _a.enableDebugMessages) === true) { console.warn("No validation metadata found. No validation will be performed. There are multiple possible reasons:\n" + " - There may be multiple class-validator versions installed. You will need to flatten your dependencies to fix the issue.\n" + " - This validation runs before any file with validation decorator was parsed by NodeJS."); } var groups = this.validatorOptions ? this.validatorOptions.groups : undefined; var strictGroups = (this.validatorOptions && this.validatorOptions.strictGroups) || false; var always = (this.validatorOptions && this.validatorOptions.always) || false; /** Forbid unknown values are turned on by default and any other value than false will enable it. */ var forbidUnknownValues = ((_b = this.validatorOptions) === null || _b === void 0 ? void 0 : _b.forbidUnknownValues) === undefined || this.validatorOptions.forbidUnknownValues !== false; var targetMetadatas = this.metadataStorage.getTargetValidationMetadatas(object.constructor, targetSchema, always, strictGroups, groups); var groupedMetadatas = this.metadataStorage.groupByPropertyName(targetMetadatas); if (this.validatorOptions && forbidUnknownValues && !targetMetadatas.length) { var validationError = new ValidationError(); if (!this.validatorOptions || !this.validatorOptions.validationError || this.validatorOptions.validationError.target === undefined || this.validatorOptions.validationError.target === true) validationError.target = object; validationError.value = undefined; validationError.property = undefined; validationError.children = []; validationError.constraints = { unknownValue: 'an unknown value was passed to the validate function' }; validationErrors.push(validationError); return; } if (this.validatorOptions && this.validatorOptions.whitelist) this.whitelist(object, groupedMetadatas, validationErrors); // General validation Object.keys(groupedMetadatas).forEach(function (propertyName) { var value = object[propertyName]; var definedMetadatas = groupedMetadatas[propertyName].filter(function (metadata) { return metadata.type === ValidationTypes.IS_DEFINED; }); var metadatas = groupedMetadatas[propertyName].filter(function (metadata) { return metadata.type !== ValidationTypes.IS_DEFINED && metadata.type !== ValidationTypes.WHITELIST; }); if (value instanceof Promise && metadatas.find(function (metadata) { return metadata.type === ValidationTypes.PROMISE_VALIDATION; })) { _this.awaitingPromises.push(value.then(function (resolvedValue) { _this.performValidations(object, resolvedValue, propertyName, definedMetadatas, metadatas, validationErrors); })); } else { _this.performValidations(object, value, propertyName, definedMetadatas, metadatas, validationErrors); } }); }; ValidationExecutor.prototype.whitelist = function (object, groupedMetadatas, validationErrors) { var _this = this; var notAllowedProperties = []; Object.keys(object).forEach(function (propertyName) { // does this property have no metadata? if (!groupedMetadatas[propertyName] || groupedMetadatas[propertyName].length === 0) notAllowedProperties.push(propertyName); }); if (notAllowedProperties.length > 0) { if (this.validatorOptions && this.validatorOptions.forbidNonWhitelisted) { // throw errors notAllowedProperties.forEach(function (property) { var _a; var validationError = _this.generateValidationError(object, object[property], property); validationError.constraints = (_a = {}, _a[ValidationTypes.WHITELIST] = "property ".concat(property, " should not exist"), _a); validationError.children = undefined; validationErrors.push(validationError); }); } else { // strip non allowed properties notAllowedProperties.forEach(function (property) { return delete object[property]; }); } } }; ValidationExecutor.prototype.stripEmptyErrors = function (errors) { var _this = this; return errors.filter(function (error) { if (error.children) { error.children = _this.stripEmptyErrors(error.children); } if (Object.keys(error.constraints).length === 0) { if (error.children.length === 0) { return false; } else { delete error.constraints; } } return true; }); }; // ------------------------------------------------------------------------- // Private Methods // ------------------------------------------------------------------------- ValidationExecutor.prototype.performValidations = function (object, value, propertyName, definedMetadatas, metadatas, validationErrors) { var customValidationMetadatas = metadatas.filter(function (metadata) { return metadata.type === ValidationTypes.CUSTOM_VALIDATION; }); var nestedValidationMetadatas = metadatas.filter(function (metadata) { return metadata.type === ValidationTypes.NESTED_VALIDATION; }); var conditionalValidationMetadatas = metadatas.filter(function (metadata) { return metadata.type === ValidationTypes.CONDITIONAL_VALIDATION; }); var validationError = this.generateValidationError(object, value, propertyName); validationErrors.push(validationError); var canValidate = this.conditionalValidations(object, value, conditionalValidationMetadatas); if (!canValidate) { return; } // handle IS_DEFINED validation type the special way - it should work no matter skipUndefinedProperties/skipMissingProperties is set or not this.customValidations(object, value, definedMetadatas, validationError); this.mapContexts(object, value, definedMetadatas, validationError); if (value === undefined && this.validatorOptions && this.validatorOptions.skipUndefinedProperties === true) { return; } if (value === null && this.validatorOptions && this.validatorOptions.skipNullProperties === true) { return; } if ((value === null || value === undefined) && this.validatorOptions && this.validatorOptions.skipMissingProperties === true) { return; } this.customValidations(object, value, customValidationMetadatas, validationError); this.nestedValidations(value, nestedValidationMetadatas, validationError); this.mapContexts(object, value, metadatas, validationError); this.mapContexts(object, value, customValidationMetadatas, validationError); }; ValidationExecutor.prototype.generateValidationError = function (object, value, propertyName) { var validationError = new ValidationError(); if (!this.validatorOptions || !this.validatorOptions.validationError || this.validatorOptions.validationError.target === undefined || this.validatorOptions.validationError.target === true) validationError.target = object; if (!this.validatorOptions || !this.validatorOptions.validationError || this.validatorOptions.validationError.value === undefined || this.validatorOptions.validationError.value === true) validationError.value = value; validationError.property = propertyName; validationError.children = []; validationError.constraints = {}; return validationError; }; ValidationExecutor.prototype.conditionalValidations = function (object, value, metadatas) { return metadatas .map(function (metadata) { return metadata.constraints[0](object, value); }) .reduce(function (resultA, resultB) { return resultA && resultB; }, true); }; ValidationExecutor.prototype.customValidations = function (object, value, metadatas, error) { var _this = this; metadatas.forEach(function (metadata) { _this.metadataStorage.getTargetValidatorConstraints(metadata.constraintCls).forEach(function (customConstraintMetadata) { if (customConstraintMetadata.async && _this.ignoreAsyncValidations) return; if (_this.validatorOptions && _this.validatorOptions.stopAtFirstError && Object.keys(error.constraints || {}).length > 0) return; var validationArguments = { targetName: object.constructor ? object.constructor.name : undefined, property: metadata.propertyName, object: object, value: value, constraints: metadata.constraints, }; if (!metadata.each || !(Array.isArray(value) || value instanceof Set || value instanceof Map)) { var validatedValue = customConstraintMetadata.instance.validate(value, validationArguments); if (isPromise(validatedValue)) { var promise = validatedValue.then(function (isValid) { if (!isValid) { var _a = __read(_this.createValidationError(object, value, metadata, customConstraintMetadata), 2), type = _a[0], message = _a[1]; error.constraints[type] = message; if (metadata.context) { if (!error.contexts) { error.contexts = {}; } error.contexts[type] = Object.assign(error.contexts[type] || {}, metadata.context); } } }); _this.awaitingPromises.push(promise); } else { if (!validatedValue) { var _a = __read(_this.createValidationError(object, value, metadata, customConstraintMetadata), 2), type = _a[0], message = _a[1]; error.constraints[type] = message; } } return; } // convert set and map into array var arrayValue = convertToArray(value); // Validation needs to be applied to each array item var validatedSubValues = arrayValue.map(function (subValue) { return customConstraintMetadata.instance.validate(subValue, validationArguments); }); var validationIsAsync = validatedSubValues.some(function (validatedSubValue) { return isPromise(validatedSubValue); }); if (validationIsAsync) { // Wrap plain values (if any) in promises, so that all are async var asyncValidatedSubValues = validatedSubValues.map(function (validatedSubValue) { return isPromise(validatedSubValue) ? validatedSubValue : Promise.resolve(validatedSubValue); }); var asyncValidationIsFinishedPromise = Promise.all(asyncValidatedSubValues).then(function (flatValidatedValues) { var validationResult = flatValidatedValues.every(function (isValid) { return isValid; }); if (!validationResult) { var _a = __read(_this.createValidationError(object, value, metadata, customConstraintMetadata), 2), type = _a[0], message = _a[1]; error.constraints[type] = message; if (metadata.context) { if (!error.contexts) { error.contexts = {}; } error.contexts[type] = Object.assign(error.contexts[type] || {}, metadata.context); } } }); _this.awaitingPromises.push(asyncValidationIsFinishedPromise); return; } var validationResult = validatedSubValues.every(function (isValid) { return isValid; }); if (!validationResult) { var _b = __read(_this.createValidationError(object, value, metadata, customConstraintMetadata), 2), type = _b[0], message = _b[1]; error.constraints[type] = message; } }); }); }; ValidationExecutor.prototype.nestedValidations = function (value, metadatas, error) { var _this = this; if (value === void 0) { return; } metadatas.forEach(function (metadata) { if (metadata.type !== ValidationTypes.NESTED_VALIDATION && metadata.type !== ValidationTypes.PROMISE_VALIDATION) { return; } else if (_this.validatorOptions && _this.validatorOptions.stopAtFirstError && Object.keys(error.constraints || {}).length > 0) { return; } if (Array.isArray(value) || value instanceof Set || value instanceof Map) { // Treats Set as an array - as index of Set value is value itself and it is common case to have Object as value var arrayLikeValue = value instanceof Set ? Array.from(value) : value; arrayLikeValue.forEach(function (subValue, index) { _this.performValidations(value, subValue, index.toString(), [], metadatas, error.children); }); } else if (value instanceof Object) { var targetSchema = typeof metadata.target === 'string' ? metadata.target : metadata.target.name; _this.execute(value, targetSchema, error.children); } else { var _a = __read(_this.createValidationError(metadata.target, value, metadata), 2), type = _a[0], message = _a[1]; error.constraints[type] = message; } }); }; ValidationExecutor.prototype.mapContexts = function (object, value, metadatas, error) { var _this = this; return metadatas.forEach(function (metadata) { if (metadata.context) { var customConstraint = void 0; if (metadata.type === ValidationTypes.CUSTOM_VALIDATION) { var customConstraints = _this.metadataStorage.getTargetValidatorConstraints(metadata.constraintCls); customConstraint = customConstraints[0]; } var type = _this.getConstraintType(metadata, customConstraint); if (error.constraints[type]) { if (!error.contexts) { error.contexts = {}; } error.contexts[type] = Object.assign(error.contexts[type] || {}, metadata.context); } } }); }; ValidationExecutor.prototype.createValidationError = function (object, value, metadata, customValidatorMetadata) { var targetName = object.constructor ? object.constructor.name : undefined; var type = this.getConstraintType(metadata, customValidatorMetadata); var validationArguments = { targetName: targetName, property: metadata.propertyName, object: object, value: value, constraints: metadata.constraints, }; var message = metadata.message || ''; if (!metadata.message && (!this.validatorOptions || (this.validatorOptions && !this.validatorOptions.dismissDefaultMessages))) { if (customValidatorMetadata && customValidatorMetadata.instance.defaultMessage instanceof Function) { message = customValidatorMetadata.instance.defaultMessage(validationArguments); } } var messageString = ValidationUtils.replaceMessageSpecialTokens(message, validationArguments); return [type, messageString]; }; ValidationExecutor.prototype.getConstraintType = function (metadata, customValidatorMetadata) { var type = customValidatorMetadata && customValidatorMetadata.name ? customValidatorMetadata.name : metadata.type; return type; }; return ValidationExecutor; }()); export { ValidationExecutor }; //# sourceMappingURL=ValidationExecutor.js.map