UNPKG

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