Skip to content

Commit

Permalink
fix: ValidateNested support multi-dimensional arrays (#539)
Browse files Browse the repository at this point in the history
  • Loading branch information
jBouyoud committed Mar 28, 2020
1 parent 6457326 commit 62678e1
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 22 deletions.
13 changes: 13 additions & 0 deletions README.md
Expand Up @@ -318,6 +318,19 @@ export class Post {
}
```

It also works with multi-dimensional array, like :

```typescript
import {ValidateNested} from "class-validator";

export class Plan2D {

@ValidateNested()
matrix: Point[][];

}
```

## Validating promises

If your object contains property with `Promise`-returned value that should be validated, then you need to use the `@ValidatePromise()` decorator:
Expand Down
25 changes: 5 additions & 20 deletions src/validation/ValidationExecutor.ts
Expand Up @@ -183,7 +183,7 @@ export class ValidationExecutor {

this.defaultValidations(object, value, metadatas, validationError.constraints);
this.customValidations(object, value, customValidationMetadatas, validationError);
this.nestedValidations(value, nestedValidationMetadatas, validationError.children);
this.nestedValidations(value, nestedValidationMetadatas, validationError.children, definedMetadatas, metadatas);

this.mapContexts(object, value, metadatas, validationError);
this.mapContexts(object, value, customValidationMetadatas, validationError);
Expand Down Expand Up @@ -327,18 +327,8 @@ export class ValidationExecutor {
});
}

private nestedPromiseValidations(value: any, metadatas: ValidationMetadata[], errors: ValidationError[]) {

if (!(value instanceof Promise)) {
return;
}

this.awaitingPromises.push(
value.then(resolvedValue => this.nestedValidations(resolvedValue, metadatas, errors))
);
}

private nestedValidations(value: any, metadatas: ValidationMetadata[], errors: ValidationError[]) {
private nestedValidations(value: any, metadatas: ValidationMetadata[], errors: ValidationError[],
definedMetadatas: ValidationMetadata[], allMetadatas: ValidationMetadata[]) {

if (value === void 0) {
return;
Expand All @@ -352,19 +342,14 @@ export class ValidationExecutor {
return;
}

const targetSchema = typeof metadata.target === "string" ? metadata.target as string : undefined;

if (value instanceof Array || value instanceof Set || value instanceof Map) {
// Treats Set as an array - as index of Set value is value itself and it is common case to have Object as value
const arrayLikeValue = value instanceof Set ? Array.from(value) : value;
arrayLikeValue.forEach((subValue: any, index: any) => {
const validationError = this.generateValidationError(value, subValue, index.toString());
errors.push(validationError);

this.execute(subValue, targetSchema, validationError.children);
this.performValidations(value, subValue, index.toString(), definedMetadatas, allMetadatas, errors);
});

} else if (value instanceof Object) {
const targetSchema = typeof metadata.target === "string" ? metadata.target as string : metadata.target.name;
this.execute(value, targetSchema, errors);

} else {
Expand Down
55 changes: 53 additions & 2 deletions test/functional/nested-validation.spec.ts
Expand Up @@ -2,7 +2,6 @@ import "es6-shim";
import {Contains, IsDefined, MinLength, ValidateNested} from "../../src/decorator/decorators";
import {Validator} from "../../src/validation/Validator";
import {expect} from "chai";
import {inspect} from "util";
import {ValidationTypes} from "../../src/validation/ValidationTypes";

import {should, use } from "chai";
Expand Down Expand Up @@ -67,6 +66,12 @@ describe("nested validation", function () {

@ValidateNested()
mySubClasses: MySubClass[];

@ValidateNested()
mySubSubClasses: MySubClass[][];

@ValidateNested()
mySubSubSubClasses: MySubClass[][][];
}

const model = new MyClass();
Expand All @@ -76,8 +81,13 @@ describe("nested validation", function () {
model.mySubClasses = [new MySubClass(), new MySubClass()];
model.mySubClasses[0].name = "my";
model.mySubClasses[1].name = "not-short";
model.mySubSubClasses = [[new MySubClass()]];
model.mySubSubClasses[0][0].name = "sub";
model.mySubSubSubClasses = [[[new MySubClass()]]];
model.mySubSubSubClasses[0][0][0].name = "sub";

return validator.validate(model).then(errors => {
errors.length.should.be.equal(3);
errors.length.should.be.equal(5);

errors[0].target.should.be.equal(model);
errors[0].property.should.be.equal("title");
Expand Down Expand Up @@ -107,6 +117,47 @@ describe("nested validation", function () {
subSubError.property.should.be.equal("name");
subSubError.constraints.should.be.eql({minLength: "name must be longer than or equal to 5 characters"});
subSubError.value.should.be.equal("my");

errors[3].target.should.be.equal(model);
errors[3].property.should.be.equal("mySubSubClasses");
errors[3].value.should.be.equal(model.mySubSubClasses);
expect(errors[3].constraints).to.be.undefined;
const subError3 = errors[3].children[0];
subError3.target.should.be.equal(model.mySubSubClasses);
subError3.value.should.be.equal(model.mySubSubClasses[0]);
subError3.property.should.be.equal("0");
const subSubError3 = subError3.children[0];
subSubError3.target.should.be.equal(model.mySubSubClasses[0]);
subSubError3.value.should.be.equal(model.mySubSubClasses[0][0]);
subSubError3.property.should.be.equal("0");
const subSubSubError3 = subSubError3.children[0];
subSubSubError3.target.should.be.equal(model.mySubSubClasses[0][0]);
subSubSubError3.property.should.be.equal("name");
subSubSubError3.constraints.should.be.eql({minLength: "name must be longer than or equal to 5 characters"});
subSubSubError3.value.should.be.equal("sub");


errors[4].target.should.be.equal(model);
errors[4].property.should.be.equal("mySubSubSubClasses");
errors[4].value.should.be.equal(model.mySubSubSubClasses);
expect(errors[4].constraints).to.be.undefined;
const subError4 = errors[4].children[0];
subError4.target.should.be.equal(model.mySubSubSubClasses);
subError4.value.should.be.equal(model.mySubSubSubClasses[0]);
subError4.property.should.be.equal("0");
const subSubError4 = subError4.children[0];
subSubError4.target.should.be.equal(model.mySubSubSubClasses[0]);
subSubError4.value.should.be.equal(model.mySubSubSubClasses[0][0]);
subSubError4.property.should.be.equal("0");
const subSubSubError4 = subSubError4.children[0];
subSubSubError4.target.should.be.equal(model.mySubSubSubClasses[0][0]);
subSubSubError4.value.should.be.equal(model.mySubSubSubClasses[0][0][0]);
subSubSubError4.property.should.be.equal("0");
const subSubSubSubError4 = subSubSubError4.children[0];
subSubSubSubError4.target.should.be.equal(model.mySubSubSubClasses[0][0][0]);
subSubSubSubError4.property.should.be.equal("name");
subSubSubSubError4.constraints.should.be.eql({minLength: "name must be longer than or equal to 5 characters"});
subSubSubSubError4.value.should.be.equal("sub");
});
});

Expand Down

0 comments on commit 62678e1

Please sign in to comment.