Skip to content

Commit 25224c1

Browse files
author
Umed Khudoiberdiev
committed
added sync validation support, refactored custom decorators signature and other small refactorings
1 parent 1fc9589 commit 25224c1

12 files changed

+338
-189
lines changed

README.md

+63-20
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,45 @@
66
[![Dependency Status](https://david-dm.org/pleerock/class-validator.svg)](https://david-dm.org/pleerock/class-validator)
77
[![devDependency Status](https://david-dm.org/pleerock/class-validator/dev-status.svg)](https://david-dm.org/pleerock/class-validator#info=devDependencies)
88

9-
Allows use of decorator and non-decorator based validation. Internally uses [validator.js][1] to perform validation.
9+
Allows use of decorator and non-decorator based validation.
10+
Internally uses [validator.js][1] to perform validation.
11+
Class-validator works on both browser and node.js platforms.
1012

1113
## Installation
1214

13-
1. Install module:
15+
Install module:
1416

15-
`npm install class-validator --save`
17+
`npm install class-validator --save`
1618

17-
2. ES6 features are used, so you may want to install [es6-shim](https://github.com/paulmillr/es6-shim) too:
19+
#### Old versions of node.js/browser
1820

19-
`npm install es6-shim --save`
21+
ES6 features are used, if you are using old versions of node (or browser) you may want to install [es6-shim](https://github.com/paulmillr/es6-shim) too:
2022

21-
and use it somewhere in the global place of your app:
23+
`npm install es6-shim --save`
2224

23-
* for nodejs: `require("es6-shim")` (or `import "es6-shim";`) in your app's entry point (for example in `app.ts`)
24-
* for browser: `<script src="node_modules/es6-shim/es6-shim.js">` in your `index.html`
25+
and use it somewhere in the global place of your app:
2526

26-
For node.js users this step is only required if you are using old versions of node.
27+
* for nodejs: `require("es6-shim")` (or `import "es6-shim";`) in your app's entry point (for example in `app.ts`)
28+
* for browser: `<script src="node_modules/es6-shim/es6-shim.js">` in your `index.html`
29+
30+
This step is only required if you are using old versions of node/browser.
31+
32+
#### Using in browser
33+
34+
If you are using class-validator with system.js in browser then use following configuration:
35+
36+
```javascript
37+
System.config({
38+
map: {
39+
'class-validator': 'vendor/class-validator',
40+
'validator': 'vendor/validator'
41+
},
42+
packages: {
43+
'class-validator': { 'defaultExtension': 'js', 'main': 'index.js' },
44+
'validator': { 'defaultExtension': 'js', 'main': 'validator.js' },
45+
}
46+
};
47+
```
2748
2849
## Usage
2950
@@ -295,7 +316,7 @@ If you have custom validation logic you can create a *Constraint class*:
295316
```typescript
296317
import {ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator";
297318

298-
@ValidatorConstraint()
319+
@ValidatorConstraint({ name: "customText", async: false })
299320
export class CustomTextLength implements ValidatorConstraintInterface {
300321

301322
validate(text: string, args: ValidationArguments) {
@@ -389,17 +410,25 @@ Lets create a decorator called `@IsLongerThan`:
389410
1. Create a decorator itself:
390411
391412
```typescript
392-
import {registerDecorator, ValidationOptions} from "class-validator";
413+
import {registerDecorator, ValidationOptions, ValidationArguments} from "class-validator";
393414

394415
export function IsLongerThan(property: string, validationOptions?: ValidationOptions) {
395416
return function (object: Object, propertyName: string) {
396-
registerDecorator(object, propertyName, validationOptions, [property], "is_longer_than", (value, args) => {
397-
const [relatedPropertyName] = args.constraints;
398-
const relatedValue = (args.object as any)[relatedPropertyName];
399-
return typeof value === "string" &&
400-
typeof relatedValue === "string" &&
401-
value.length > relatedValue.length; // you can return a Promise<boolean> here as well, if you want to make async validation
402-
});
417+
registerDecorator({
418+
name: "isLongerThan",
419+
target: object.constructor,
420+
propertyName: propertyName,
421+
options: validationOptions,
422+
validator: {
423+
validate(value: any, args: ValidationArguments) {
424+
const [relatedPropertyName] = args.constraints;
425+
const relatedValue = (args.object as any)[relatedPropertyName];
426+
return typeof value === "string" &&
427+
typeof relatedValue === "string" &&
428+
value.length > relatedValue.length; // you can return a Promise<boolean> here as well, if you want to make async validation
429+
}
430+
}
431+
});
403432
};
404433
}
405434
```
@@ -430,7 +459,7 @@ Lets create another custom validation decorator called `IsUserAlreadyExist`:
430459
```typescript
431460
import {registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator";
432461

433-
@ValidatorConstraint()
462+
@ValidatorConstraint({ async: true })
434463
export class IsUserAlreadyExistConstraint implements ValidatorConstraintInterface {
435464

436465
validate(userName: any, args: ValidationArguments) {
@@ -444,11 +473,19 @@ Lets create another custom validation decorator called `IsUserAlreadyExist`:
444473

445474
export function IsUserAlreadyExist(validationOptions?: ValidationOptions) {
446475
return function (object: Object, propertyName: string) {
447-
registerDecorator(object, propertyName, validationOptions, [], IsUserAlreadyExistConstraint);
476+
registerDecorator({
477+
target: object.constructor,
478+
propertyName: propertyName,
479+
options: validationOptions,
480+
constraints: [],
481+
validator: IsUserAlreadyExistConstraint
482+
});
448483
};
449484
}
450485
```
451486
487+
note that we marked our constraint that it will by async by adding `{ async: true }` in validation options.
488+
452489
2. And put it to use:
453490
454491
```typescript
@@ -481,6 +518,12 @@ let validator = Container.get(Validator);
481518
// also you can inject classes using constructor injection into your custom ValidatorConstraint-s
482519
```
483520
521+
## Synchronous validation
522+
523+
If you want to perform a simple non async validation you can use `validateSync` method instead of regular `validate`
524+
method. It has the same arguments as `validate` method. But note, this method **ignores** all async validations
525+
you have.
526+
484527
## Manual validation
485528
486529
There are several method exist in the Validator that allows to perform non-decorator based validation:

doc/release-notes.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
# Release notes
22

3+
**0.5.0**
4+
5+
* async validations must be marked with `{ async: true }` option now.
6+
This is optional, but it helps to determine which decorators are async to prevent their execution in `validateSync` method.
7+
* added `validateSync` method that performs non asynchronous validation and ignores validations that marked with `async: true`.
8+
* there is a breaking change in `registerDecorator` method. Now it accepts options object.
9+
* breaking change with `@ValidatorConstraint` decorator. Now it accepts option object instead of single name.
10+
311
**0.4.1**
412

513
* fixed issue with wrong source maps packaged
614

7-
**0.4.0** *[BREAKING CHANGES]*
15+
**0.4.0**
816

917
* everything should be imported from "class-validator" main entry point now
1018
* `ValidatorInterface` has been renamed to `ValidatorConstraintInterface`

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "class-validator",
33
"private": true,
4-
"version": "0.4.1",
5-
"description": "Class-based validation in Typescript using decorators",
4+
"version": "0.5.0-alpha.1",
5+
"description": "Class-based validation with Typescript / ES6 / ES5 using decorators or validation schemas. Supports both node.js and browser",
66
"license": "MIT",
77
"readmeFilename": "README.md",
88
"author": {

sample/sample6-custom-decorator/IsLongerThan.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ import {ValidationArguments} from "../../src/validation/ValidationArguments";
66

77
export function IsLongerThan(property: string, validationOptions?: ValidationOptions) {
88
return function (object: Object, propertyName: string) {
9-
registerDecorator(object, propertyName, validationOptions, [property], IsLongerThanConstraint);
9+
registerDecorator({
10+
target: object.constructor,
11+
propertyName: propertyName,
12+
options: validationOptions,
13+
constraints: [property],
14+
validator: IsLongerThanConstraint
15+
});
1016
};
1117
}
1218

13-
@ValidatorConstraint("is_longer_than")
19+
@ValidatorConstraint({ name: "isLongerThan" })
1420
export class IsLongerThanConstraint implements ValidatorConstraintInterface {
1521

1622
validate(value: any, args: ValidationArguments) {

sample/sample6-custom-decorator/IsUserAlreadyExist.ts

+16-8
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@ import {ValidationArguments} from "../../src/validation/ValidationArguments";
44

55
export function IsUserAlreadyExist(validationOptions?: ValidationOptions) {
66
return function (object: Object, propertyName: string) {
7-
const constraints: any[] = [/*constraints your decorator can have*/];
8-
registerDecorator(object, propertyName, validationOptions, constraints, "user_exists", (userName, args) => {
9-
return new Promise(ok => {
10-
if (userName !== "admin" && userName !== "user") {
11-
ok(true);
12-
} else {
13-
ok(false);
7+
registerDecorator({
8+
name: "isUserAlreadyExist",
9+
async: true,
10+
target: object.constructor,
11+
propertyName: propertyName,
12+
options: validationOptions,
13+
validator: {
14+
validate(value: any, args: ValidationArguments) {
15+
return new Promise(ok => {
16+
if (value !== "admin" && value !== "user") {
17+
ok(true);
18+
} else {
19+
ok(false);
20+
}
21+
});
1422
}
15-
});
23+
}
1624
});
1725
};
1826
}

src/container.ts

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
/**
3+
* Container options.
4+
*/
5+
export interface UseContainerOptions {
6+
7+
/**
8+
* If set to true, then default container will be used in the case if given container haven't returned anything.
9+
*/
10+
fallback?: boolean;
11+
12+
/**
13+
* If set to true, then default container will be used in the case if given container thrown an exception.
14+
*/
15+
fallbackOnErrors?: boolean;
16+
17+
}
18+
19+
/**
20+
* Container to be used by this library for inversion control. If container was not implicitly set then by default
21+
* container simply creates a new instance of the given class.
22+
*/
23+
const defaultContainer: { get<T>(someClass: { new (...args: any[]): T }|Function): T } = new (class {
24+
private instances: any[] = [];
25+
get<T>(someClass: { new (...args: any[]): T }): T {
26+
if (!this.instances[someClass as any])
27+
this.instances[someClass as any] = new someClass();
28+
29+
return this.instances[<any>someClass];
30+
}
31+
})();
32+
33+
let userContainer: { get<T>(someClass: { new (...args: any[]): T }|Function): T };
34+
let userContainerOptions: UseContainerOptions;
35+
36+
/**
37+
* Sets container to be used by this library.
38+
*/
39+
export function useContainer(iocContainer: { get(someClass: any): any }, options?: UseContainerOptions) {
40+
userContainer = iocContainer;
41+
userContainerOptions = options;
42+
}
43+
44+
/**
45+
* Gets the IOC container used by this library.
46+
*/
47+
export function getFromContainer<T>(someClass: { new (...args: any[]): T }|Function): T {
48+
if (userContainer) {
49+
try {
50+
const instance = userContainer.get(someClass);
51+
if (instance)
52+
return instance;
53+
54+
if (!userContainerOptions || !userContainerOptions.fallback)
55+
return instance;
56+
57+
} catch (error) {
58+
if (!userContainerOptions || !userContainerOptions.fallbackOnErrors)
59+
throw error;
60+
}
61+
}
62+
return defaultContainer.get<T>(someClass);
63+
}

src/decorator/decorators.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ import {MetadataStorage} from "../metadata/MetadataStorage";
1414
/**
1515
* Registers custom validator class.
1616
*/
17-
export function ValidatorConstraint(name?: string) {
17+
export function ValidatorConstraint(options?: { name?: string, async?: boolean }) {
1818
return function(target: Function) {
1919
if (!name) {
2020
name = (target as any).name;
2121
if (!name) // generate name if it was not given
2222
name = name.replace(/\.?([A-Z]+)/g, (x, y) => "_" + y.toLowerCase()).replace(/^_/, "");
2323
}
24-
getFromContainer(MetadataStorage).addConstraintMetadata(new ConstraintMetadata(target, name));
24+
const metadata = new ConstraintMetadata(target, name, options && options.async ? true : false);
25+
getFromContainer(MetadataStorage).addConstraintMetadata(metadata);
2526
};
2627
}
2728

0 commit comments

Comments
 (0)