diff --git a/src/validation/ValidationExecutor.ts b/src/validation/ValidationExecutor.ts index 273506851..e61f32f50 100644 --- a/src/validation/ValidationExecutor.ts +++ b/src/validation/ValidationExecutor.ts @@ -182,7 +182,7 @@ export class ValidationExecutor { } this.defaultValidations(object, value, metadatas, validationError.constraints); - this.customValidations(object, value, customValidationMetadatas, validationError.constraints); + this.customValidations(object, value, customValidationMetadatas, validationError); this.nestedValidations(value, nestedValidationMetadatas, validationError.children); this.mapContexts(object, value, metadatas, validationError); @@ -244,7 +244,7 @@ export class ValidationExecutor { private customValidations(object: Object, value: any, metadatas: ValidationMetadata[], - errorMap: { [key: string]: string }) { + error: ValidationError) { metadatas.forEach(metadata => { this.metadataStorage @@ -267,14 +267,20 @@ export class ValidationExecutor { const promise = validatedValue.then(isValid => { if (!isValid) { const [type, message] = this.createValidationError(object, value, metadata, customConstraintMetadata); - errorMap[type] = message; + 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) { const [type, message] = this.createValidationError(object, value, metadata, customConstraintMetadata); - errorMap[type] = message; + error.constraints[type] = message; } } @@ -297,7 +303,13 @@ export class ValidationExecutor { const validationResult = flatValidatedValues.every((isValid: boolean) => isValid); if (!validationResult) { const [type, message] = this.createValidationError(object, value, metadata, customConstraintMetadata); - errorMap[type] = message; + error.constraints[type] = message; + if (metadata.context) { + if (!error.contexts) { + error.contexts = {}; + } + error.contexts[type] = Object.assign((error.contexts[type] || {}), metadata.context); + } } }); @@ -309,7 +321,7 @@ export class ValidationExecutor { const validationResult = validatedSubValues.every((isValid: boolean) => isValid); if (!validationResult) { const [type, message] = this.createValidationError(object, value, metadata, customConstraintMetadata); - errorMap[type] = message; + error.constraints[type] = message; } }); }); diff --git a/test/functional/custom-decorators.spec.ts b/test/functional/custom-decorators.spec.ts index 6bcdc77d9..4a4fb02fb 100644 --- a/test/functional/custom-decorators.spec.ts +++ b/test/functional/custom-decorators.spec.ts @@ -41,22 +41,39 @@ describe("custom decorators", function() { const relatedValue = (args.object as any)[relatedPropertyName]; if (relatedValue === undefined || relatedValue === null) return true; - - return typeof value === "string" && + + const result = typeof value === "string" && typeof relatedValue === "string" && value.length > relatedValue.length; + + const asPromise = validationOptions && + validationOptions.context && + validationOptions.context.promise; + + return asPromise ? Promise.resolve(result) : result; } } }); }; } - + class MyClass { @IsLongerThan("lastName", { + context: { foo: "bar"}, message: "$property must be longer then $constraint1. Given value: $value" }) firstName: string; - + + lastName: string; + } + + class MyClassWithAsyncValidator { + @IsLongerThan("lastName", { + context: { foo: "bar", promise: true}, + message: "$property must be longer then $constraint1. Given value: $value" + }) + firstName: string; + lastName: string; } @@ -87,9 +104,25 @@ describe("custom decorators", function() { errors[0].constraints.should.be.eql({ isLongerThan: "firstName must be longer then lastName. Given value: Li" }); }); }); - + + it("should include context", function() { + const model = new MyClass(); + const asyncModel = new MyClassWithAsyncValidator(); + model.firstName = asyncModel.firstName = "Paul"; + model.lastName = asyncModel.lastName = "Walker"; + + return validator.validate(model).then(errors => { + errors.length.should.be.equal(1); + errors[0].contexts.should.be.eql({ isLongerThan: { foo: "bar" } }); + + return validator.validate(asyncModel).then(errors => { + errors.length.should.be.equal(1); + errors[0].contexts.should.have.nested.property("isLongerThan.foo", "bar"); + }); + }); + }); }); - + describe("decorator with default message", function() { function IsLonger(property: string, validationOptions?: ValidationOptions) { @@ -106,7 +139,7 @@ describe("custom decorators", function() { const relatedValue = (args.object as any)[relatedPropertyName]; if (relatedValue === undefined || relatedValue === null) return true; - + return typeof value === "string" && typeof relatedValue === "string" && value.length > relatedValue.length; @@ -118,11 +151,11 @@ describe("custom decorators", function() { }); }; } - + class SecondClass { @IsLonger("lastName") firstName: string; - + lastName: string; } @@ -153,7 +186,7 @@ describe("custom decorators", function() { errors[0].constraints.should.be.eql({ isLonger: "firstName must be longer then lastName" }); }); }); - + }); describe("decorator with separate validation constraint class", function() { @@ -166,7 +199,7 @@ describe("custom decorators", function() { const relatedValue = (args.object as any)[relatedPropertyName]; if (value === null || value === undefined) return true; - + return typeof value === "string" && typeof relatedValue === "string" && value.length < relatedValue.length;