From 62678e1d1b44f81a1ad6ba287ee8be1f72aad4ac Mon Sep 17 00:00:00 2001 From: Julien Bouyoud Date: Sat, 28 Mar 2020 22:01:15 +0100 Subject: [PATCH] fix: ValidateNested support multi-dimensional arrays (#539) --- README.md | 13 ++++++ src/validation/ValidationExecutor.ts | 25 +++-------- test/functional/nested-validation.spec.ts | 55 ++++++++++++++++++++++- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 1941665ed..951f31ca8 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/src/validation/ValidationExecutor.ts b/src/validation/ValidationExecutor.ts index e61f32f50..181fc4b6d 100644 --- a/src/validation/ValidationExecutor.ts +++ b/src/validation/ValidationExecutor.ts @@ -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); @@ -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; @@ -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 { diff --git a/test/functional/nested-validation.spec.ts b/test/functional/nested-validation.spec.ts index b1a25e2d6..e3b0cec7a 100644 --- a/test/functional/nested-validation.spec.ts +++ b/test/functional/nested-validation.spec.ts @@ -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"; @@ -67,6 +66,12 @@ describe("nested validation", function () { @ValidateNested() mySubClasses: MySubClass[]; + + @ValidateNested() + mySubSubClasses: MySubClass[][]; + + @ValidateNested() + mySubSubSubClasses: MySubClass[][][]; } const model = new MyClass(); @@ -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"); @@ -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"); }); });