UNPKG

19.8 kBJavaScriptView Raw
1var __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};
17import { ValidationError } from './ValidationError';
18import { ValidationTypes } from './ValidationTypes';
19import { ValidationUtils } from './ValidationUtils';
20import { isPromise, convertToArray } from '../utils';
21import { getMetadataStorage } from '../metadata/MetadataStorage';
22/**
23 * Executes validation over given object.
24 */
25var 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}());
350export { ValidationExecutor };
351//# sourceMappingURL=ValidationExecutor.js.map
\No newline at end of file