Skip to content

sync validation and api for es6/es5 usage #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ node_js:
- 0.12
- 0.11

before_script:
- typings install

after_success:
- bash <(curl -s https://codecov.io/bash)
83 changes: 63 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,45 @@
[![Dependency Status](https://david-dm.org/pleerock/class-validator.svg)](https://david-dm.org/pleerock/class-validator)
[![devDependency Status](https://david-dm.org/pleerock/class-validator/dev-status.svg)](https://david-dm.org/pleerock/class-validator#info=devDependencies)

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

## Installation

1. Install module:
Install module:

`npm install class-validator --save`
`npm install class-validator --save`

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

`npm install es6-shim --save`
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:

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

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

For node.js users this step is only required if you are using old versions of node.
* for nodejs: `require("es6-shim")` (or `import "es6-shim";`) in your app's entry point (for example in `app.ts`)
* for browser: `<script src="node_modules/es6-shim/es6-shim.js">` in your `index.html`

This step is only required if you are using old versions of node/browser.

#### Using in browser

If you are using class-validator with system.js in browser then use following configuration:

```javascript
System.config({
map: {
'class-validator': 'vendor/class-validator',
'validator': 'vendor/validator'
},
packages: {
'class-validator': { 'defaultExtension': 'js', 'main': 'index.js' },
'validator': { 'defaultExtension': 'js', 'main': 'validator.js' },
}
};
```

## Usage

Expand Down Expand Up @@ -295,7 +316,7 @@ If you have custom validation logic you can create a *Constraint class*:
```typescript
import {ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator";

@ValidatorConstraint()
@ValidatorConstraint({ name: "customText", async: false })
export class CustomTextLength implements ValidatorConstraintInterface {

validate(text: string, args: ValidationArguments) {
Expand Down Expand Up @@ -389,17 +410,25 @@ Lets create a decorator called `@IsLongerThan`:
1. Create a decorator itself:

```typescript
import {registerDecorator, ValidationOptions} from "class-validator";
import {registerDecorator, ValidationOptions, ValidationArguments} from "class-validator";

export function IsLongerThan(property: string, validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator(object, propertyName, validationOptions, [property], "is_longer_than", (value, args) => {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return typeof value === "string" &&
typeof relatedValue === "string" &&
value.length > relatedValue.length; // you can return a Promise<boolean> here as well, if you want to make async validation
});
registerDecorator({
name: "isLongerThan",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return typeof value === "string" &&
typeof relatedValue === "string" &&
value.length > relatedValue.length; // you can return a Promise<boolean> here as well, if you want to make async validation
}
}
});
};
}
```
Expand Down Expand Up @@ -430,7 +459,7 @@ Lets create another custom validation decorator called `IsUserAlreadyExist`:
```typescript
import {registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator";

@ValidatorConstraint()
@ValidatorConstraint({ async: true })
export class IsUserAlreadyExistConstraint implements ValidatorConstraintInterface {

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

export function IsUserAlreadyExist(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator(object, propertyName, validationOptions, [], IsUserAlreadyExistConstraint);
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: IsUserAlreadyExistConstraint
});
};
}
```

note that we marked our constraint that it will by async by adding `{ async: true }` in validation options.

2. And put it to use:

```typescript
Expand Down Expand Up @@ -481,6 +518,12 @@ let validator = Container.get(Validator);
// also you can inject classes using constructor injection into your custom ValidatorConstraint-s
```

## Synchronous validation

If you want to perform a simple non async validation you can use `validateSync` method instead of regular `validate`
method. It has the same arguments as `validate` method. But note, this method **ignores** all async validations
you have.

## Manual validation

There are several method exist in the Validator that allows to perform non-decorator based validation:
Expand Down
10 changes: 9 additions & 1 deletion doc/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# Release notes

**0.5.0**

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

**0.4.1**

* fixed issue with wrong source maps packaged

**0.4.0** *[BREAKING CHANGES]*
**0.4.0**

* everything should be imported from "class-validator" main entry point now
* `ValidatorInterface` has been renamed to `ValidatorConstraintInterface`
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "class-validator",
"private": true,
"version": "0.4.1",
"description": "Class-based validation in Typescript using decorators",
"version": "0.5.0-alpha.3",
"description": "Class-based validation with Typescript / ES6 / ES5 using decorators or validation schemas. Supports both node.js and browser",
"license": "MIT",
"readmeFilename": "README.md",
"author": {
Expand Down Expand Up @@ -50,7 +50,6 @@
"typings": "^1.3.1"
},
"scripts": {
"postinstall": "typings install",
"test": "gulp tests"
}
}
10 changes: 8 additions & 2 deletions sample/sample6-custom-decorator/IsLongerThan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import {ValidationArguments} from "../../src/validation/ValidationArguments";

export function IsLongerThan(property: string, validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator(object, propertyName, validationOptions, [property], IsLongerThanConstraint);
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [property],
validator: IsLongerThanConstraint
});
};
}

@ValidatorConstraint("is_longer_than")
@ValidatorConstraint({ name: "isLongerThan" })
export class IsLongerThanConstraint implements ValidatorConstraintInterface {

validate(value: any, args: ValidationArguments) {
Expand Down
24 changes: 16 additions & 8 deletions sample/sample6-custom-decorator/IsUserAlreadyExist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import {ValidationArguments} from "../../src/validation/ValidationArguments";

export function IsUserAlreadyExist(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
const constraints: any[] = [/*constraints your decorator can have*/];
registerDecorator(object, propertyName, validationOptions, constraints, "user_exists", (userName, args) => {
return new Promise(ok => {
if (userName !== "admin" && userName !== "user") {
ok(true);
} else {
ok(false);
registerDecorator({
name: "isUserAlreadyExist",
async: true,
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
return new Promise(ok => {
if (value !== "admin" && value !== "user") {
ok(true);
} else {
ok(false);
}
});
}
});
}
});
};
}
66 changes: 66 additions & 0 deletions src/container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

/**
* Container options.
*/
export interface UseContainerOptions {

/**
* If set to true, then default container will be used in the case if given container haven't returned anything.
*/
fallback?: boolean;

/**
* If set to true, then default container will be used in the case if given container thrown an exception.
*/
fallbackOnErrors?: boolean;

}

/**
* Container to be used by this library for inversion control. If container was not implicitly set then by default
* container simply creates a new instance of the given class.
*/
const defaultContainer: { get<T>(someClass: { new (...args: any[]): T }|Function): T } = new (class {
private instances: { type: Function, object: any }[] = [];
get<T>(someClass: { new (...args: any[]): T }): T {
let instance = this.instances.find(instance => instance.type === someClass);
if (!instance) {
instance = { type: someClass, object: new someClass() };
this.instances.push(instance);
}

return instance.object;
}
})();

let userContainer: { get<T>(someClass: { new (...args: any[]): T }|Function): T };
let userContainerOptions: UseContainerOptions;

/**
* Sets container to be used by this library.
*/
export function useContainer(iocContainer: { get(someClass: any): any }, options?: UseContainerOptions) {
userContainer = iocContainer;
userContainerOptions = options;
}

/**
* Gets the IOC container used by this library.
*/
export function getFromContainer<T>(someClass: { new (...args: any[]): T }|Function): T {
if (userContainer) {
try {
const instance = userContainer.get(someClass);
if (instance)
return instance;

if (!userContainerOptions || !userContainerOptions.fallback)
return instance;

} catch (error) {
if (!userContainerOptions || !userContainerOptions.fallbackOnErrors)
throw error;
}
}
return defaultContainer.get<T>(someClass);
}
7 changes: 5 additions & 2 deletions src/decorator/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import {MetadataStorage} from "../metadata/MetadataStorage";
/**
* Registers custom validator class.
*/
export function ValidatorConstraint(name?: string) {
export function ValidatorConstraint(options?: { name?: string, async?: boolean }) {
return function(target: Function) {
const isAsync = options && options.async ? true : false;
let name = options && options.name ? options.name : "";
if (!name) {
name = (target as any).name;
if (!name) // generate name if it was not given
name = name.replace(/\.?([A-Z]+)/g, (x, y) => "_" + y.toLowerCase()).replace(/^_/, "");
}
getFromContainer(MetadataStorage).addConstraintMetadata(new ConstraintMetadata(target, name));
const metadata = new ConstraintMetadata(target, name, isAsync);
getFromContainer(MetadataStorage).addConstraintMetadata(metadata);
};
}

Expand Down
Loading