UNPKG

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