Skip to content

Commit bcb8bad

Browse files
author
Umed Khudoiberdiev
committed
added validation of nested objects
1 parent 770554f commit bcb8bad

File tree

10 files changed

+109
-8
lines changed

10 files changed

+109
-8
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "validator.ts",
3-
"version": "0.0.5",
3+
"version": "0.0.6",
44
"description": "Wrapper over validator.js library that allows to use validation decorators in your Typescript classes",
55
"license": "Apache-2.0",
66
"readmeFilename": "README.md",

sample/sample1-simple-validation/app.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ post1.rating = 10; // should pass
1212
post1.email = 'info@google.com'; // should pass
1313
post1.site = 'google.com'; // should pass
1414
post1.createDate = new Date(); // should pass
15+
post1.tags = ['abcd1', 'abcd2', 'abcd3', 'abcd4', 'abcd4']; // should pass
1516

1617
console.log('should pass: ', validator.validate(Post, post1)); // should pass completely, e.g. return empty array
1718

@@ -65,7 +66,7 @@ post6.rating = 10; // should pass
6566
post6.email = 'info@google.com'; // should pass
6667
post6.site = 'google.com'; // should pass
6768
post6.createDate = new Date(); // should pass
68-
post6.tags = [];
69+
post6.tags = ['abcd1', 'abcd2', 'abcd3', 'abcd4', 'abcd4'];
6970

7071
console.log('should pass: ', validator.validate(Post, post6)); // should pass completely, e.g. return empty array
7172

sample/sample4-nested-objects/Post.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Contains, IsInt, IsLength, IsEmail, IsFQDN, IsDate, ValidateNested} from "../../src/annotation/ValidationAnnotations";
2+
import {Tag} from "./Tag";
3+
4+
export class Post {
5+
6+
@IsLength(10, 20, {
7+
message: 'Incorrect length!'
8+
})
9+
title: string;
10+
11+
@ValidateNested(type => Tag)
12+
tags: Tag[];
13+
14+
}

sample/sample4-nested-objects/Tag.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {Contains, IsInt, IsLength, IsEmail, IsFQDN, IsDate} from "../../src/annotation/ValidationAnnotations";
2+
3+
export class Tag {
4+
5+
@IsLength(10, 20, {
6+
message: 'Tag is too short or long'
7+
})
8+
name: string;
9+
10+
}

sample/sample4-nested-objects/app.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {Validator} from "../../src/Validator";
2+
import {Post} from "./Post";
3+
import {Tag} from "./Tag";
4+
5+
let validator = new Validator();
6+
7+
let tag1 = new Tag();
8+
tag1.name = 'ja';
9+
10+
let tag2 = new Tag();
11+
tag2.name = 'node.js';
12+
13+
let post1 = new Post();
14+
post1.title = 'Hello world';
15+
post1.tags = [tag1, tag2];
16+
17+
console.log('should not pass: ', validator.validate(Post, post1));

src/ValidationError.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export interface ValidationError {
2+
objectClass: Function;
23
property: string;
34
errorCode: number;
45
errorName: string;

src/Validator.ts

+40-6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ export class Validator {
2626
// Public Methods
2727
// -------------------------------------------------------------------------
2828

29+
validateAsync<T>(objectClass: Function, object: T, validationOptions?: ValidationOptions): Promise<T> {
30+
return new Promise<T>((ok, fail) => { // todo install promise
31+
let errors = this.validate(objectClass, object, validationOptions);
32+
if (errors.length > 0) {
33+
fail(errors);
34+
} else {
35+
ok(object);
36+
}
37+
});
38+
}
39+
40+
validateOrThrow(objectClass: Function, object: any, validationOptions?: ValidationOptions) {
41+
const errors = this.validate(objectClass, object, validationOptions);
42+
if (errors.length > 0)
43+
throw new Error('Validation failed: ' + JSON.stringify(errors)); // todo
44+
}
45+
2946
validate(objectClass: Function, object: any, validationOptions?: ValidationOptions): ValidationError[] {
3047
let groups = validationOptions ? validationOptions.groups : undefined;
3148
let metadatas = this.metadataStorage.getValidationMetadatasForObject(objectClass, groups);
@@ -38,16 +55,16 @@ export class Validator {
3855
let errors = duplicateMetadatas.map(metadata => {
3956
let isValid = true;
4057
if (metadata.each) {
41-
if (value instanceof Array) {
42-
58+
if (value instanceof Array)
4359
isValid = value.every((v: any) => this.performValidation(v, metadata));
44-
}
60+
4561
} else {
4662
isValid = this.performValidation(value, metadata);
4763
}
4864
if (isValid) return null;
4965

5066
return <ValidationError> {
67+
objectClass: objectClass,
5168
property: metadata.propertyName,
5269
errorCode: metadata.type,
5370
errorName: ValidationTypesUtils.getCodeName(metadata.type),
@@ -57,12 +74,29 @@ export class Validator {
5774
};
5875
});
5976

60-
if (value instanceof Array) {
77+
let nestedValidation = duplicateMetadatas.reduce((found, metadata) => {
78+
return metadata.type === ValidationTypes.NESTED_VALIDATION ? metadata : found;
79+
}, undefined);
80+
if (nestedValidation) {
81+
if (value instanceof Array) {
82+
value.map((v: any) => {
83+
const nestedErrors = this.validate(metadata.value1(), v, validationOptions);
84+
if (nestedErrors && nestedErrors.length)
85+
errors = errors.concat(nestedErrors);
86+
});
87+
88+
} else if (value instanceof Object) {
89+
const nestedErrors = this.validate(metadata.value1, value, validationOptions);
90+
if (nestedErrors && nestedErrors.length)
91+
errors = errors.concat(nestedErrors);
6192

93+
} else {
94+
throw new Error('Only objects and arrays are supported to nested validation');
95+
}
6296
}
6397

64-
if (errors.length > 0 && errors.indexOf(null) !== -1)
65-
return null;
98+
//if (errors.length > 0 && errors.indexOf(null) !== -1)
99+
// return null;
66100

67101
return errors.reduceRight((found, err) => err !== null ? err : found, null);
68102

src/annotation/ValidationAnnotations.ts

+19
Original file line numberDiff line numberDiff line change
@@ -994,4 +994,23 @@ export function MaxElements(max: number, annotationOptions?: ValidationAnnotatio
994994
each: annotationOptions && annotationOptions.each ? annotationOptions.each : undefined
995995
});
996996
}
997+
}
998+
999+
/**
1000+
* Checks if array's length is as maximal this number.
1001+
*/
1002+
export function ValidateNested(type: (_?: any) => Function, annotationOptions?: ValidationAnnotationOptions) {
1003+
return function (object: Object, propertyName: string) {
1004+
defaultMetadataStorage.addValidationMetadata({
1005+
type: ValidationTypes.NESTED_VALIDATION,
1006+
sanitize: false,
1007+
object: object,
1008+
propertyName: propertyName,
1009+
value1: type,
1010+
groups: annotationOptions && annotationOptions.groups ? annotationOptions.groups : undefined,
1011+
message: annotationOptions && annotationOptions.message ? annotationOptions.message : undefined,
1012+
always: annotationOptions && annotationOptions.always ? annotationOptions.always : undefined,
1013+
each: annotationOptions && annotationOptions.each ? annotationOptions.each : undefined
1014+
});
1015+
}
9971016
}

src/types/ValidationTypes.ts

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export enum ValidationTypes {
5252
NOT_EMPTY_ARRAY = 1002,
5353
MIN_ELEMENTS = 1003,
5454
MAX_ELEMENTS = 1004,
55+
56+
NESTED_VALIDATION = 0
5557
}
5658

5759
export class ValidationTypesUtils {

tsd.json

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
},
2121
"sinon/sinon.d.ts": {
2222
"commit": "7a3ca1f0b8a0960af9fc1838f3234cc9d6ce0645"
23+
},
24+
"es6-promise/es6-promise.d.ts": {
25+
"commit": "b70895565dd5bd5de71c31e573d781409554bbf1"
2326
}
2427
}
2528
}

0 commit comments

Comments
 (0)