Skip to content

Commit

Permalink
fix: pass context to ValidationError for async validations (#533)
Browse files Browse the repository at this point in the history
  • Loading branch information
Larsrdev committed Mar 26, 2020
1 parent 2012d72 commit 4eb1216
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 17 deletions.
24 changes: 18 additions & 6 deletions src/validation/ValidationExecutor.ts
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
}

Expand All @@ -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);
}
}
});

Expand All @@ -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;
}
});
});
Expand Down
55 changes: 44 additions & 11 deletions test/functional/custom-decorators.spec.ts
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -118,11 +151,11 @@ describe("custom decorators", function() {
});
};
}

class SecondClass {
@IsLonger("lastName")
firstName: string;

lastName: string;
}

Expand Down Expand Up @@ -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() {
Expand All @@ -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;
Expand Down

0 comments on commit 4eb1216

Please sign in to comment.