Skip to content

Commit d9ce1be

Browse files
author
Umed Khudoiberdiev
committed
added validation schemas support
1 parent a736187 commit d9ce1be

14 files changed

+397
-20
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ post.rating = 11; // should not pass
5656
post.email = "google.com"; // should not pass
5757
post.site = "googlecom"; // should not pass
5858

59-
validate(post).then(errors => {
59+
validate(post).then(errors => { // errors is an array of validation errors
6060
if (errors.length > 0) {
6161
console.log("validation failed. errors: ", errors);
6262
} else {
6363
console.log("validation succeed");
6464
}
65-
}); // returns you array of errors
65+
});
6666
```
6767

6868
## Validation messages

sample/sample5-schemas/Post.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export class Post {
2+
3+
title: string;
4+
text: string;
5+
rating: number;
6+
email: string;
7+
site: string;
8+
createDate: Date;
9+
tags: string[];
10+
11+
}

sample/sample5-schemas/app.ts

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import {validate, validateBySchema, registerSchema} from "../../src/index";
2+
import {Post} from "./Post";
3+
4+
// load schema. we load it a bit tricky way because we output source code into separate directory, so our json resource left in another directory
5+
const postSchema = require(__dirname + "/../../../../sample/sample5-schemas/post.json");
6+
7+
// register this schema
8+
registerSchema(postSchema);
9+
10+
// Sample1. simple validation
11+
12+
let post1 = new Post();
13+
post1.title = "Hello world"; // should pass
14+
post1.text = "this is a great post about hello world"; // should pass
15+
post1.rating = 10; // should pass
16+
post1.email = "info@google.com"; // should pass
17+
post1.site = "google.com"; // should pass
18+
post1.createDate = new Date(); // should pass
19+
post1.tags = ["abcd1", "abcd2", "abcd3", "abcd4", "abcd4"]; // should pass
20+
21+
validateBySchema("post", post1).then(result => {
22+
console.log("1. should pass: ", result); // should pass completely, e.g. return empty array
23+
});
24+
25+
let post2 = new Post();
26+
post2.title = "Hello"; // should not pass
27+
post2.text = "this is a great post about hell world"; // should not pass
28+
post2.rating = 11; // should not pass
29+
post2.email = "google.com"; // should not pass
30+
post2.site = "googlecom"; // should not pass
31+
// should not pass because date property is missing
32+
33+
validateBySchema("post", post2).then(result => {
34+
console.log("2. should not pass: ", result); // should not pass completely, must return array of ValidationError-s
35+
});
36+
37+
// Sample2. using validation options to skip properties that are not defined
38+
39+
let post3 = new Post();
40+
post3.title = "Hello"; // should not pass
41+
post3.text = "this is a great post about hell world"; // should not pass
42+
post3.rating = 11; // should not pass
43+
post3.email = "google.com"; // should not pass
44+
post3.site = "googlecom"; // should not pass
45+
46+
validateBySchema("post", post3, { skipMissingProperties: true }).then(result => {
47+
console.log("3. should not pass: ", result); // should not pass, but returned ValidationError-s should not have error about date field
48+
});
49+
50+
let post4 = new Post();
51+
post4.title = "Hello world"; // should pass
52+
post4.text = "this is a great post about hello world"; // should pass
53+
post4.rating = 10; // should pass
54+
post4.email = "info@google.com"; // should pass
55+
post4.site = "google.com"; // should pass
56+
57+
validateBySchema("post", post4, { skipMissingProperties: true }).then(result => {
58+
console.log("4. should pass: ", result); // should pass even if date is not set
59+
});
60+
61+
// Sample3. using validation groups
62+
63+
let post5 = new Post();
64+
post5.title = "Hello world"; // should pass
65+
post5.text = "this is a great post about hello world"; // should pass
66+
post5.rating = 10; // should pass
67+
post5.email = "info@google.com"; // should pass
68+
post5.site = "google.com"; // should pass
69+
70+
validateBySchema("post", post5, { skipMissingProperties: true }).then(result => {
71+
console.log("5. should pass: ", result); // should pass even if date is not set
72+
});
73+
74+
// Sample4. array validation
75+
76+
let post6 = new Post();
77+
post6.title = "Hello world"; // should pass
78+
post6.text = "this is a great post about hello world"; // should pass
79+
post6.rating = 10; // should pass
80+
post6.email = "info@google.com"; // should pass
81+
post6.site = "google.com"; // should pass
82+
post6.createDate = new Date(); // should pass
83+
post6.tags = ["abcd1", "abcd2", "abcd3", "abcd4", "abcd4"];
84+
85+
validateBySchema("post", post6).then(result => {
86+
console.log("6. should pass: ", result); // should pass completely, e.g. return empty array
87+
});
88+
89+
let post7 = new Post();
90+
post7.title = "Hello world"; // should pass
91+
post7.text = "this is a great post about hello world"; // should pass
92+
post7.rating = 10; // should pass
93+
post7.email = "info@google.com"; // should pass
94+
post7.site = "google.com"; // should pass
95+
post7.createDate = new Date(); // should pass
96+
post7.tags = ["news", "a"];
97+
98+
validateBySchema("post", post7).then(result => {
99+
console.log("7. should not pass: ", result); // should not pass
100+
});
101+
102+
let post8 = new Post();
103+
post8.title = "Hello world"; // should pass
104+
post8.text = "this is a great post about hello world"; // should pass
105+
post8.rating = 10; // should pass
106+
post8.email = "info@google.com"; // should pass
107+
post8.site = "google.com"; // should pass
108+
post8.createDate = new Date(); // should pass
109+
post8.tags = [];
110+
111+
validateBySchema("post", post8).then(result => {
112+
console.log("8. should not pass: ", result); // should not pass
113+
});
114+
115+
let post9 = new Post();
116+
post9.title = "Hello world"; // should pass
117+
post9.text = "this is a great post about hello world"; // should pass
118+
post9.rating = 10; // should pass
119+
post9.email = "info@google.com"; // should pass
120+
post9.site = "google.com"; // should pass
121+
post9.createDate = new Date(); // should pass
122+
post9.tags = ["abcd1", "abcd2", "abcd3", "abcd4", "abcd4", "abcd4"];
123+
124+
validateBySchema("post", post9).then(result => {
125+
console.log("9. should not pass: ", result); // should not pass
126+
});
127+
128+
let post10 = new Post();
129+
post10.title = "Hello world"; // should pass
130+
post10.text = "this is a great post about hello world"; // should pass
131+
post10.rating = 10; // should pass
132+
post10.email = "info@google.com"; // should pass
133+
post10.site = "google.com"; // should pass
134+
post10.createDate = new Date(); // should pass
135+
post10.tags = ["abcd1", "abcd2", "abcd3", "abcd4", "abcd4"];
136+
137+
validateBySchema("post", post10).then(result => {
138+
console.log("10. should pass: ", result); // should pass
139+
});
140+
141+
let post11 = new Post();
142+
post11.title = "Hello world"; // should pass
143+
post11.text = "this is a great post about hello world"; // should pass
144+
post11.rating = 10; // should pass
145+
post11.email = "info@google.com"; // should pass
146+
post11.site = "google.com"; // should pass
147+
post11.createDate = new Date(); // should pass
148+
post11.tags = null;
149+
150+
validateBySchema("post", post11).then(result => {
151+
console.log("11. should not pass: ", result); // should not pass
152+
});

sample/sample5-schemas/post.json

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"name": "post",
3+
"properties": {
4+
"title": [
5+
{
6+
"type": "min_length",
7+
"value1": 10
8+
},
9+
{
10+
"type": "max_length",
11+
"value1": 20
12+
}
13+
],
14+
"text": [
15+
{
16+
"type": "contains",
17+
"value1": "hello"
18+
}
19+
],
20+
"rating": [
21+
{
22+
"type": "is_int"
23+
}
24+
],
25+
"email": [
26+
{
27+
"type": "is_email"
28+
}
29+
],
30+
"site": [
31+
{
32+
"type": "is_fqdn"
33+
}
34+
],
35+
"createDate": [
36+
{
37+
"type": "is_date"
38+
}
39+
],
40+
"tags": [
41+
{
42+
"type": "array_not_empty"
43+
},
44+
{
45+
"type": "array_min_size",
46+
"value1": 2
47+
},
48+
{
49+
"type": "array_max_size",
50+
"value1": 5
51+
},
52+
{
53+
"type": "min_length",
54+
"each": true,
55+
"value1": 3,
56+
"message": "Tag is too short. Minimal length is $value characters"
57+
},
58+
{
59+
"type": "max_length",
60+
"each": true,
61+
"value1": 50,
62+
"message": "Tag is too long. Maximal length is $value characters"
63+
}
64+
]
65+
}
66+
}

src/decorator/ValidationOptions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface ValidationOptions {
99
each?: boolean;
1010

1111
/**
12-
* Message used to be shown on validation fail.
12+
* Error message used to be used on validation fail.
1313
* You can use "$value" to use value that was failed by validation.
1414
* You can use "$constraint1" and "$constraint2" keys in the message string,
1515
* and they will be replaced with constraint values if they exist.

src/index.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {Validator} from "./validation/Validator";
22
import {ValidationError} from "./validation/ValidationError";
33
import {ValidatorOptions} from "./validation/ValidatorOptions";
4+
import {ValidationSchema} from "./validation-schema/ValidationSchema";
5+
import {MetadataStorage} from "./metadata/MetadataStorage";
46

57
// -------------------------------------------------------------------------
68
// Global Container
@@ -13,8 +15,8 @@ import {ValidatorOptions} from "./validation/ValidatorOptions";
1315
let container: { get<T>(someClass: { new (...args: any[]): T }|Function): T } = new (class {
1416
private instances: any[] = [];
1517
get<T>(someClass: { new (...args: any[]): T }): T {
16-
if (!this.instances[<any>someClass])
17-
this.instances[<any>someClass] = new someClass();
18+
if (!this.instances[someClass as any])
19+
this.instances[someClass as any] = new someClass();
1820

1921
return this.instances[<any>someClass];
2022
}
@@ -46,12 +48,30 @@ export * from "./validation/ValidatorConstraintInterface";
4648
export * from "./validation/ValidationError";
4749
export * from "./validation/ValidationTypeOptions";
4850
export * from "./validation/ValidatorOptions";
51+
export * from "./validation-schema/ValidationSchema";
4952
export * from "./validation/Validator";
5053

5154
// -------------------------------------------------------------------------
5255
// Shortcut methods for api users
5356
// -------------------------------------------------------------------------
5457

58+
/**
59+
* Validates given object.
60+
*/
5561
export function validate(object: any, validatorOptions?: ValidatorOptions): Promise<ValidationError[]> {
5662
return getFromContainer(Validator).validate(object, validatorOptions);
63+
}
64+
65+
/**
66+
* Validates given object by a given validation schema.
67+
*/
68+
export function validateBySchema(schemaName: string, object: any, validatorOptions?: ValidatorOptions): Promise<ValidationError[]> {
69+
return getFromContainer(Validator).validateBySchema(schemaName, object, validatorOptions);
70+
}
71+
72+
/**
73+
* Registers a new validation schema.
74+
*/
75+
export function registerSchema(schema: ValidationSchema) {
76+
return getFromContainer(MetadataStorage).addValidationSchema(schema);
5777
}

src/metadata/MetadataStorage.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {ValidationMetadata} from "./ValidationMetadata";
22
import {ConstraintMetadata} from "./ConstraintMetadata";
3+
import {ValidationSchema} from "../validation-schema/ValidationSchema";
4+
import {ValidationSchemaToMetadataTransformer} from "../validation-schema/ValidationSchemaToMetadataTransformer";
35

46
/**
57
* Storage all metadatas.
@@ -17,6 +19,14 @@ export class MetadataStorage {
1719
// Public Methods
1820
// -------------------------------------------------------------------------
1921

22+
/**
23+
* Adds a new validation metadata.
24+
*/
25+
addValidationSchema(schema: ValidationSchema) {
26+
const validationMetadatas = new ValidationSchemaToMetadataTransformer().transform(schema);
27+
validationMetadatas.forEach(validationMetadata => this.addValidationMetadata(validationMetadata));
28+
}
29+
2030
/**
2131
* Adds a new validation metadata.
2232
*/
@@ -47,11 +57,11 @@ export class MetadataStorage {
4757
/**
4858
* Gets all validation metadatas for the given object with the given groups.
4959
*/
50-
getTargetValidationMetadatas(target: Function, groups?: string[]): ValidationMetadata[] {
60+
getTargetValidationMetadatas(targetConstructor: Function, targetSchema: string, groups?: string[]): ValidationMetadata[] {
5161

5262
// get directly related to a target metadatas
5363
const originalMetadatas = this.validationMetadatas.filter(metadata => {
54-
if (metadata.target !== target)
64+
if (metadata.target !== targetConstructor && metadata.target !== targetSchema)
5565
return false;
5666
if (metadata.always)
5767
return true;
@@ -63,7 +73,8 @@ export class MetadataStorage {
6373

6474
// get metadatas for inherited classes
6575
const inheritedMetadatas = this.validationMetadatas.filter(metadata => {
66-
if (!(target.prototype instanceof metadata.target))
76+
if (metadata.target instanceof Function &&
77+
!(targetConstructor instanceof (metadata.target as Function)))
6778
return false;
6879
if (metadata.always)
6980
return true;

src/metadata/ValidationMetadata.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class ValidationMetadata {
1717
/**
1818
* Target class to which this validation is applied.
1919
*/
20-
target: Function;
20+
target: Function|string;
2121

2222
/**
2323
* Property of the object to be validated.

src/metadata/ValidationMetadataArgs.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface ValidationMetadataArgs {
1313
/**
1414
* Object that is used to be validated.
1515
*/
16-
target: Function;
16+
target: Function|string;
1717

1818
/**
1919
* Property of the object to be validated.

0 commit comments

Comments
 (0)