diff --git a/README.md b/README.md index f9a33bf30c..11446b8ea8 100644 --- a/README.md +++ b/README.md @@ -1,960 +1,993 @@ -# class-validator - -[![Build Status](https://travis-ci.org/typestack/class-validator.svg?branch=master)](https://travis-ci.org/typestack/class-validator) -[![npm version](https://badge.fury.io/js/class-validator.svg)](https://badge.fury.io/js/class-validator) -[![install size](https://packagephobia.now.sh/badge?p=class-validator)](https://packagephobia.now.sh/result?p=class-validator) -[![Join the chat at https://gitter.im/typestack/class-validator](https://badges.gitter.im/typestack/class-validator.svg)](https://gitter.im/typestack/class-validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -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. - -## Table of Contents - - * [Installation](#installation) - - [Old versions of node.js/browser](#old-versions-of-nodejsbrowser) - - [Using in browser](#using-in-browser) - * [Usage](#usage) - + [Validation errors](#validation-errors) - + [Validation messages](#validation-messages) - + [Validating arrays](#validating-arrays) - + [Validating nested objects](#validating-nested-objects) - + [Inheriting Validation decorators](#inheriting-validation-decorators) - + [Conditional validation](#conditional-validation) - + [Whitelisting](#whitelisting) - + [Passing context to decorators](#passing-context-to-decorators) - + [Skipping missing properties](#skipping-missing-properties) - + [Validation groups](#validation-groups) - + [Custom validation classes](#custom-validation-classes) - + [Custom validation decorators](#custom-validation-decorators) - + [Using service container](#using-service-container) - + [Synchronous validation](#synchronous-validation) - + [Manual validation](#manual-validation) - + [Validation decorators](#validation-decorators) - + [Defining validation schema without decorators](#defining-validation-schema-without-decorators) - + [Validating plain objects](#validating-plain-objects) - * [Samples](#samples) - * [Extensions](#extensions) - * [Release notes](#release-notes) - -## Installation - -``` -npm install class-validator --save -``` - -> Note: Please use at least npm@6 when using class-validator as from npm@6 the dependency tree is flatterned what is good for us. - -## Usage - -Create your class and put some validation decorators on the properties you want to validate: - -```typescript -import {validate, Contains, IsInt, Length, IsEmail, IsFQDN, IsDate, Min, Max} from "class-validator"; - -export class Post { - - @Length(10, 20) - title: string; - - @Contains("hello") - text: string; - - @IsInt() - @Min(0) - @Max(10) - rating: number; - - @IsEmail() - email: string; - - @IsFQDN() - site: string; - - @IsDate() - createDate: Date; - -} - -let post = new Post(); -post.title = "Hello"; // should not pass -post.text = "this is a great post about hell world"; // should not pass -post.rating = 11; // should not pass -post.email = "google.com"; // should not pass -post.site = "googlecom"; // should not pass - -validate(post).then(errors => { // errors is an array of validation errors - if (errors.length > 0) { - console.log("validation failed. errors: ", errors); - } else { - console.log("validation succeed"); - } -}); -``` - -### Passing options - -The `validate` function optionally expects a `ValidatorOptions` object as a second parameter. - -```ts -export interface ValidatorOptions { - - skipMissingProperties?: boolean; - whitelist?: boolean; - forbidNonWhitelisted?: boolean; - groups?: string[]; - dismissDefaultMessages?: boolean; - validationError?: { - target?: boolean; - value?: boolean; - }; - - forbidUnknownValues?: boolean; -} -``` - -> It's highly advised to enable on `forbidUnknownValues` what prevent unknown objects to pass validation. - -## Validation errors - -`validate` method returns you an array of `ValidationError` objects. Each `ValidationError` is: - -```typescript -{ - target: Object; // Object that was validated. - property: string; // Object's property that haven't pass validation. - value: any; // Value that haven't pass a validation. - constraints?: { // Constraints that failed validation with error messages. - [type: string]: string; - }; - children?: ValidationError[]; // Contains all nested validation errors of the property -} -``` - -In our case, when we validated a Post object, we have such array of ValidationErrors: - -```typescript -[{ - target: /* post object */, - property: "title", - value: "Hello", - constraints: { - length: "$property must be longer than or equal to 10 characters" - } -}, { - target: /* post object */, - property: "text", - value: "this is a great post about hell world", - constraints: { - contains: "text must contain a hello string" - } -}, -// and other errors -] -``` - -If you don't want a `target` to be exposed in validation errors, there is a special option when you use validator: - -```typescript -validator.validate(post, { validationError: { target: false } }); -``` - -This is especially useful when you send errors back over http, and you most probably don't want to expose -the whole target object. - -## Validation messages - -You can specify validation message in the decorator options and that message will be returned in `ValidationError` -returned by `validate` method in the case that validation for this field fails. - -```typescript -import {MinLength, MaxLength} from "class-validator"; - -export class Post { - - @MinLength(10, { - message: "Title is too short" - }) - @MaxLength(50, { - message: "Title is too long" - }) - title: string; -} -``` - -There are few special tokens you can use in your messages: -* `$value` - the value that is being validated -* `$property` - name of the object's property being validated -* `$target` - name of the object's class being validated -* `$constraint1`, `$constraint2`, ... `$constraintN` - constraints defined by specific validation type - -Example of usage: - -```typescript -import {MinLength, MaxLength} from "class-validator"; - -export class Post { - - @MinLength(10, { // here, $constraint1 will be replaced with "10", and $value with actual supplied value - message: "Title is too short. Minimal length is $constraint1 characters, but actual is $value" - }) - @MaxLength(50, { // here, $constraint1 will be replaced with "50", and $value with actual supplied value - message: "Title is too long. Maximal length is $constraint1 characters, but actual is $value" - }) - title: string; -} -``` - -Also you can provide a function, that returns a message. This way allows to create more granular messages: - -```typescript -import {MinLength, MaxLength, ValidationArguments} from "class-validator"; - -export class Post { - - @MinLength(10, { - message: (args: ValidationArguments) => { - if (args.value.length === 1) { - return "Too short, minimum length is 1 character"; - } else { - return "Too short, minimum length is " + args.constraints[0] + " characters"; - } - } - }) - title: string; -} -``` - -Message function accepts `ValidationArguments` which contains following information: -* `value` - the value that is being validated -* `constraints` - array of constraints defined by specific validation type -* `targetName` - name of the object's class being validated -* `object` - object that is being validated -* `property` - name of the object's property being validated - -## Validating arrays - -If your field is an array and you want to perform validation of each item in the array you must specify a -special `each: true` decorator option: - -```typescript -import {MinLength, MaxLength} from "class-validator"; - -export class Post { - - @MaxLength(20, { - each: true - }) - tags: string[]; -} -``` - -This will validate each item in `post.tags` array. - -## Validating nested objects - -If your object contains nested objects and you want the validator to perform their validation too, then you need to -use the `@ValidateNested()` decorator: - -```typescript -import {ValidateNested} from "class-validator"; - -export class Post { - - @ValidateNested() - user: User; - -} -``` - -## Inheriting Validation decorators - -When you define a subclass which extends from another one, the subclass will automatically inherit the parent's decorators. If a property is redefined in the descendant class decorators will be applied on it both from that and the base class. - -```typescript -import {validate} from "class-validator"; - -class BaseContent { - - @IsEmail() - email: string; - - @IsString() - password: string; -} - -class User extends BaseContent { - - @MinLength(10) - @MaxLength(20) - name: string; - - @Contains("hello") - welcome: string; - - @MinLength(20) - password: string; / -} - -let user = new User(); - -user.email = "invalid email"; // inherited property -user.password = "too short" // password wil be validated not only against IsString, but against MinLength as well -user.name = "not valid"; -user.welcome = "helo"; - -validate(user).then(errors => { - // ... -}); // it will return errors for email, title and text properties - -``` - -## Conditional validation - -The conditional validation decorator (`@ValidateIf`) can be used to ignore the validators on a property when the provided condition function returns false. The condition function takes the object being validated and must return a `boolean`. - -```typescript -import {ValidateIf, IsNotEmpty} from "class-validator"; - -export class Post { - otherProperty:string; - - @ValidateIf(o => o.otherProperty === "value") - @IsNotEmpty() - example:string; -} -``` - -In the example above, the validation rules applied to `example` won't be run unless the object's `otherProperty` is `"value"`. - -Note that when the condition is false all validation decorators are ignored, including `isDefined`. - -## Whitelisting - -Even if your object is an instance of a validation class it can contain additional properties that are not defined. -If you do not want to have such properties on your object, pass special flag to `validate` method: - -```typescript -import {validate} from "class-validator"; -// ... -validate(post, { whitelist: true }); -``` - -This will strip all properties that don't have any decorators. If no other decorator is suitable for your property, -you can use @Allow decorator: - -```typescript -import {validate, Allow, Min} from "class-validator"; - -export class Post { - - @Allow() - title: string; - - @Min(0) - views: number; - - nonWhitelistedProperty: number; -} - -let post = new Post(); -post.title = 'Hello world!'; -post.views = 420; - -post.nonWhitelistedProperty = 69; -(post as any).anotherNonWhitelistedProperty = "something"; - -validate(post).then(errors => { - // post.nonWhitelistedProperty is not defined - // (post as any).anotherNonWhitelistedProperty is not defined - ... -}); -```` - -If you would rather to have an error thrown when any non-whitelisted properties are present, pass another flag to -`validate` method: - -```typescript -import {validate} from "class-validator"; -// ... -validate(post, { whitelist: true, forbidNonWhitelisted: true }); -``` - -## Passing context to decorators - -It's possible to pass a custom object to decorators which will be accessible on the `ValidationError` instance of the property if validation failed. - -```ts -import { validate } from 'class-validator'; - -class MyClass { - @MinLength(32, { - message: "EIC code must be at least 32 charatcers", - context: { - errorCode: 1003, - developerNote: "The validated string must contain 32 or more characters." - } - }) - eicCode: string; -} - -const model = new MyClass(); - -validate(model).then(errors => { - //errors[0].contexts['minLength'].errorCode === 1003 -}); -``` - -## Skipping missing properties - -Sometimes you may want to skip validation of the properties that does not exist in the validating object. This is -usually desirable when you want to update some parts of the object, and want to validate only updated parts, -but skip everything else, e.g. skip missing properties. -In such situations you will need to pass a special flag to `validate` method: - -```typescript -import {validate} from "class-validator"; -// ... -validate(post, { skipMissingProperties: true }); -``` - -When skipping missing properties, sometimes you want not to skip all missing properties, some of them maybe required -for you, even if skipMissingProperties is set to true. For such cases you should use `@IsDefined()` decorator. -`@IsDefined()` is the only decorator that ignores `skipMissingProperties` option. - -## Validation groups - -In different situations you may want to use different validation schemas of the same object. - In such cases you can use validation groups. - -```typescript -import {validate, Min, Length} from "class-validator"; - -export class User { - - @Min(12, { - groups: ["registration"] - }) - age: number; - - @Length(2, 20, { - groups: ["registration", "admin"] - }) - name: string; -} - -let user = new User(); -user.age = 10; -user.name = "Alex"; - -validate(user, { - groups: ["registration"] -}); // this will not pass validation - -validate(user, { - groups: ["admin"] -}); // this will pass validation - -validate(user, { - groups: ["registration", "admin"] -}); // this will not pass validation - -validate(user, { - groups: undefined // the default -}); // this will not pass validation since all properties get validated regardless of their groups - -validate(user, { - groups: [] -}); // this will not pass validation, (equivalent to 'groups: undefined', see above) -``` - -There is also a special flag `always: true` in validation options that you can use. This flag says that this validation -must be applied always no matter which group is used. - -## Custom validation classes - -If you have custom validation logic you can create a *Constraint class*: - -1. First create a file, lets say `CustomTextLength.ts`, and define a new class: - - ```typescript - import {ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator"; - - @ValidatorConstraint({ name: "customText", async: false }) - export class CustomTextLength implements ValidatorConstraintInterface { - - validate(text: string, args: ValidationArguments) { - return text.length > 1 && text.length < 10; // for async validations you must return a Promise here - } - - defaultMessage(args: ValidationArguments) { // here you can provide default error message if validation failed - return "Text ($value) is too short or too long!"; - } - - } - ``` - - We marked our class with `@ValidatorConstraint` decorator. - You can also supply a validation constraint name - this name will be used as "error type" in ValidationError. - If you will not supply a constraint name - it will be auto-generated. - - Our class must implement `ValidatorConstraintInterface` interface and its `validate` method, - which defines validation logic. If validation succeeds, method returns true, otherwise false. - Custom validator can be asynchronous, if you want to perform validation after some asynchronous - operations, simply return a promise with boolean inside in `validate` method. - - Also we defined optional method `defaultMessage` which defines a default error message, - in the case that the decorator's implementation doesn't set an error message. - - -2. Then you can use your new validation constraint in your class: - - ```typescript - import {Validate} from "class-validator"; - import {CustomTextLength} from "./CustomTextLength"; - - export class Post { - - @Validate(CustomTextLength, { - message: "Title is too short or long!" - }) - title: string; - - } - ``` - - Here we set our newly created `CustomTextLength` validation constraint for `Post.title`. - -3. And use validator as usual: - - ```typescript - import {validate} from "class-validator"; - - validate(post).then(errors => { - // ... - }); - ``` - -You can also pass constraints to your validator, like this: - -```typescript -import {Validate} from "class-validator"; -import {CustomTextLength} from "./CustomTextLength"; - -export class Post { - - @Validate(CustomTextLength, [3, 20], { - message: "Wrong post title" - }) - title: string; - -} -``` - -And use them from `validationArguments` object: - -```typescript -import {ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface} from "class-validator"; - -@ValidatorConstraint() -export class CustomTextLength implements ValidatorConstraintInterface { - - validate(text: string, validationArguments: ValidationArguments) { - return text.length > validationArguments.constraints[0] && text.length < validationArguments.constraints[1]; - } - -} -``` - -## Custom validation decorators - -You can also create a custom decorators. Its the most elegant way of using a custom validations. -Lets create a decorator called `@IsLongerThan`: - -1. Create a decorator itself: - - ```typescript - import {registerDecorator, ValidationOptions, ValidationArguments} from "class-validator"; - - export function IsLongerThan(property: string, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - registerDecorator({ - name: "isLongerThan", - target: object.constructor, - propertyName: propertyName, - constraints: [property], - 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 here as well, if you want to make async validation - } - } - }); - }; - } - ``` - -2. Put it to use: - - ```typescript - import {IsLongerThan} from "./IsLongerThan"; - - export class Post { - - title: string; - - @IsLongerThan("title", { - /* you can also use additional validation options, like "groups" in your custom validation decorators. "each" is not supported */ - message: "Text must be longer than the title" - }) - text: string; - - } - ``` - -In your custom decorators you can also use `ValidationConstraint`. -Lets create another custom validation decorator called `IsUserAlreadyExist`: - -1. Create a ValidationConstraint and decorator: - - ```typescript - import {registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator"; - - @ValidatorConstraint({ async: true }) - export class IsUserAlreadyExistConstraint implements ValidatorConstraintInterface { - - validate(userName: any, args: ValidationArguments) { - return UserRepository.findOneByName(userName).then(user => { - if (user) return false; - return true; - }); - } - - } - - export function IsUserAlreadyExist(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - 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 - import {IsUserAlreadyExist} from "./IsUserAlreadyExist"; - - export class User { - - @IsUserAlreadyExist({ - message: "User $value already exists. Choose another name." - }) - name: string; - - } - ``` - -## Using service container - -Validator supports service container in the case if want to inject dependencies into your custom validator constraint -classes. Here is example how to integrate it with [typedi][2]: - -```typescript -import {Container} from "typedi"; -import {useContainer, Validator} from "class-validator"; - -// do this somewhere in the global application level: -useContainer(Container); -let validator = Container.get(Validator); - -// now everywhere you can inject Validator class which will go from the container -// 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: - -```typescript -import {Validator} from "class-validator"; - -// Validation methods -const validator = new Validator(); - -// common validation methods -validator.isDefined(value); // Checks if value is defined ("!==undefined"). -validator.equals(value, comparison); // Checks if value matches ("===") the comparison. -validator.notEquals(value, comparison); // Checks if value does not match ("!==") the comparison. -validator.isEmpty(value); // Checks if given value is empty (=== '', === null, === undefined). -validator.isNotEmpty(value); // Checks if given value is not empty (!== '', !== null, !== undefined). -validator.isIn(value, possibleValues); // Checks if given value is in a array of allowed values. -validator.isNotIn(value, possibleValues); // Checks if given value not in a array of allowed values. - -// type validation methods -validator.isBoolean(value); // Checks if a given value is a real boolean. -validator.isDate(value); // Checks if a given value is a real date. -validator.isString(value); // Checks if a given value is a real string. -validator.isArray(value); // Checks if a given value is an array. -validator.isNumber(value, options); // Checks if a given value is a real number. -validator.isInt(value); // Checks if value is an integer. -validator.isEnum(value, entity); // Checks if value is valid for a certain enum entity. - -// number validation methods -validator.isDivisibleBy(value, num); // Checks if value is a number that's divisible by another. -validator.isPositive(value); // Checks if the value is a positive number. -validator.isNegative(value); // Checks if the value is a negative number. -validator.min(num, min); // Checks if the first number is greater than or equal to the second. -validator.max(num, max); // Checks if the first number is less than or equal to the second. - -// date validation methods -validator.minDate(date, minDate); // Checks if the value is a date that's after the specified date. -validator.maxDate(date, minDate); // Checks if the value is a date that's before the specified date. - -// string-type validation methods -validator.isBooleanString(str); // Checks if a string is a boolean. -validator.isNumberString(str); // Checks if the string is numeric. - -// string validation methods -validator.contains(str, seed); // Checks if the string contains the seed. -validator.notContains(str, seed); // Checks if the string does not contain the seed. -validator.isAlpha(str); // Checks if the string contains only letters (a-zA-Z). -validator.isAlphanumeric(str); // Checks if the string contains only letters and numbers. -validator.isAscii(str); // Checks if the string contains ASCII chars only. -validator.isBase64(str); // Checks if a string is base64 encoded. -validator.isByteLength(str, min, max); // Checks if the string's length (in bytes) falls in a range. -validator.isCreditCard(str); // Checks if the string is a credit card. -validator.isCurrency(str, options); // Checks if the string is a valid currency amount. -validator.isEmail(str, options); // Checks if the string is an email. -validator.isFQDN(str, options); // Checks if the string is a fully qualified domain name (e.g. domain.com). -validator.isFullWidth(str); // Checks if the string contains any full-width chars. -validator.isHalfWidth(str); // Checks if the string contains any half-width chars. -validator.isVariableWidth(str); // Checks if the string contains variable-width chars. -validator.isHexColor(str); // Checks if the string is a hexadecimal color. -validator.isHexadecimal(str); // Checks if the string is a hexadecimal number. -validator.isIP(str, version); // Checks if the string is an IP (version 4 or 6). -validator.isISBN(str, version); // Checks if the string is an ISBN (version 10 or 13). -validator.isISIN(str); // Checks if the string is an ISIN (stock/security identifier). -validator.isISO8601(str); // Checks if the string is a valid ISO 8601 date. -validator.isJSON(str); // Checks if the string is valid JSON (note: uses JSON.parse). -validator.isLowercase(str); // Checks if the string is lowercase. -validator.isMobilePhone(str, locale); // Checks if the string is a mobile phone number. -validator.isPhoneNumber(str, region); // Checks if the string is a valid phone number. -validator.isMongoId(str); // Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. -validator.isMultibyte(str); // Checks if the string contains one or more multibyte chars. -validator.isSurrogatePair(str); // Checks if the string contains any surrogate pairs chars. -validator.isURL(str, options); // Checks if the string is an url. -validator.isUUID(str, version); // Checks if the string is a UUID (version 3, 4 or 5). -validator.isUppercase(str); // Checks if the string is uppercase. -validator.length(str, min, max); // Checks if the string's length falls in a range. -validator.minLength(str, min); // Checks if the string's length is not less than given number. -validator.maxLength(str, max); // Checks if the string's length is not more than given number. -validator.matches(str, pattern, modifiers); // Checks if string matches the pattern. Either matches('foo', /foo/i) or matches('foo', 'foo', 'i'). -validator.isMilitaryTime(str); // Checks if the string is a valid representation of military time in the format HH:MM. - -// array validation methods -validator.arrayContains(array, values); // Checks if array contains all values from the given array of values. -validator.arrayNotContains(array, values); // Checks if array does not contain any of the given values. -validator.arrayNotEmpty(array); // Checks if given array is not empty. -validator.arrayMinSize(array, min); // Checks if array's length is at least `min` number. -validator.arrayMaxSize(array, max); // Checks if array's length is as most `max` number. -validator.arrayUnique(array); // Checks if all array's values are unique. Comparison for objects is reference-based. - -// object validation methods -validator.isInstance(value, target); // Checks value is an instance of the target. -``` - -## Validation decorators - -| Decorator | Description | -|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| -| **Common validation decorators** | -| `@IsDefined(value: any)` | Checks if value is defined (!== undefined, !== null). This is the only decorator that ignores skipMissingProperties option. | -| `@IsOptional()` | Checks if given value is empty (=== null, === undefined) and if so, ignores all the validators on the property. | -| `@Equals(comparison: any)` | Checks if value equals ("===") comparison. | -| `@NotEquals(comparison: any)` | Checks if value not equal ("!==") comparison. | -| `@IsEmpty()` | Checks if given value is empty (=== '', === null, === undefined). | -| `@IsNotEmpty()` | Checks if given value is not empty (!== '', !== null, !== undefined). | -| `@IsIn(values: any[])` | Checks if value is in a array of allowed values. | -| `@IsNotIn(values: any[])` | Checks if value is not in a array of disallowed values. | -| **Type validation decorators** | -| `@IsBoolean()` | Checks if a value is a boolean. | -| `@IsDate()` | Checks if the value is a date. | -| `@IsString()` | Checks if the string is a string. | -| `@IsNumber(options: IsNumberOptions)` | Checks if the value is a number. | -| `@IsInt()` | Checks if the value is an integer number. | -| `@IsArray()` | Checks if the value is an array | -| `@IsEnum(entity: object)` | Checks if the value is an valid enum | -| **Number validation decorators** | -| `@IsDivisibleBy(num: number)` | Checks if the value is a number that's divisible by another. | -| `@IsPositive()` | Checks if the value is a positive number. | -| `@IsNegative()` | Checks if the value is a negative number. | -| `@Min(min: number)` | Checks if the given number is greater than or equal to given number. | -| `@Max(max: number)` | Checks if the given number is less than or equal to given number. | -| **Date validation decorators** | -| `@MinDate(date: Date)` | Checks if the value is a date that's after the specified date. | -| `@MaxDate(date: Date)` | Checks if the value is a date that's before the specified date. | | -| **String-type validation decorators** | -| `@IsBooleanString()` | Checks if a string is a boolean (e.g. is "true" or "false"). | -| `@IsDateString()` | Checks if a string is a complete representation of a date (e.g. "2017-06-07T14:34:08.700Z", "2017-06-07T14:34:08.700 or "2017-06-07T14:34:08+04:00"). | -| `@IsNumberString()` | Checks if a string is a number. | -| **String validation decorators** | -| `@Contains(seed: string)` | Checks if the string contains the seed. | -| `@NotContains(seed: string)` | Checks if the string not contains the seed. | -| `@IsAlpha()` | Checks if the string contains only letters (a-zA-Z). | -| `@IsAlphanumeric()` | Checks if the string contains only letters and numbers. | -| `@IsAscii()` | Checks if the string contains ASCII chars only. | -| `@IsBase64()` | Checks if a string is base64 encoded. | -| `@IsByteLength(min: number, max?: number)` | Checks if the string's length (in bytes) falls in a range. | -| `@IsCreditCard()` | Checks if the string is a credit card. | -| `@IsCurrency(options?: IsCurrencyOptions)` | Checks if the string is a valid currency amount. | -| `@IsEmail(options?: IsEmailOptions)` | Checks if the string is an email. | -| `@IsFQDN(options?: IsFQDNOptions)` | Checks if the string is a fully qualified domain name (e.g. domain.com). | -| `@IsFullWidth()` | Checks if the string contains any full-width chars. | -| `@IsHalfWidth()` | Checks if the string contains any half-width chars. | -| `@IsVariableWidth()` | Checks if the string contains a mixture of full and half-width chars. | -| `@IsHexColor()` | Checks if the string is a hexadecimal color. | -| `@IsHexadecimal()` | Checks if the string is a hexadecimal number. | -| `@IsIP(version?: "4"\|"6")` | Checks if the string is an IP (version 4 or 6). | -| `@IsISBN(version?: "10"\|"13")` | Checks if the string is an ISBN (version 10 or 13). | -| `@IsISIN()` | Checks if the string is an ISIN (stock/security identifier). | -| `@IsISO8601()` | Checks if the string is a valid ISO 8601 date. | -| `@IsJSON()` | Checks if the string is valid JSON. | -| `@IsLowercase()` | Checks if the string is lowercase. | -| `@IsMobilePhone(locale: string)` | Checks if the string is a mobile phone number. | -| `@IsPhoneNumber(region: string)` | Checks if the string is a valid phone number. "region" accepts 2 characters uppercase country code (e.g. DE, US, CH).If users must enter the intl. prefix (e.g. +41), then you may pass "ZZ" or null as region. See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github](https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33) | -| `@IsMongoId()` | Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. | -| `@IsMultibyte()` | Checks if the string contains one or more multibyte chars. | -| `@IsNumberString()` | Checks if the string is numeric. | -| `@IsSurrogatePair()` | Checks if the string contains any surrogate pairs chars. | -| `@IsUrl(options?: IsURLOptions)` | Checks if the string is an url. | -| `@IsUUID(version?: "3"\|"4"\|"5")` | Checks if the string is a UUID (version 3, 4 or 5). | -| `@IsUppercase()` | Checks if the string is uppercase. | -| `@Length(min: number, max?: number)` | Checks if the string's length falls in a range. | -| `@MinLength(min: number)` | Checks if the string's length is not less than given number. | -| `@MaxLength(max: number)` | Checks if the string's length is not more than given number. | -| `@Matches(pattern: RegExp, modifiers?: string)` | Checks if string matches the pattern. Either matches('foo', /foo/i) or matches('foo', 'foo', 'i'). -| `@IsMilitaryTime()` | Checks if the string is a valid representation of military time in the format HH:MM. | -| **Array validation decorators** | -| `@ArrayContains(values: any[])` | Checks if array contains all values from the given array of values. | -| `@ArrayNotContains(values: any[])` | Checks if array does not contain any of the given values. | -| `@ArrayNotEmpty()` | Checks if given array is not empty. | -| `@ArrayMinSize(min: number)` | Checks if array's length is as minimal this number. | -| `@ArrayMaxSize(max: number)` | Checks if array's length is as maximal this number. | -| `@ArrayUnique()` | Checks if all array's values are unique. Comparison for objects is reference-based. | -| **Object validation decorators** | -| `@IsInstance(value: any)` | Checks if the property is an instance of the passed value. | - **Other decorators** | -| `@Allow()` | Prevent stripping off the property when no other constraint is specified for it. | - -## Defining validation schema without decorators - -You can define your validation schemas without decorators: - -* you can define it in the separate object -* you can define it in the `.json` file - -This feature maybe useful in the cases if: - -* are using es5/es6 and don't have decorators available -* you don't have a classes, and instead using interfaces -* you don't want to use model at all -* you want to have a validation schema separate of your model -* you want beautiful json-schema based validation models -* you simply hate decorators - -Here is an example of using it: - -1. Create a schema object: - - ```typescript - import {ValidationSchema} from "class-validator"; - export let UserValidationSchema: ValidationSchema = { // using interface here is not required, its just for type-safety - name: "myUserSchema", // this is required, and must be unique - properties: { - firstName: [{ - type: "minLength", // validation type. All validation types are listed in ValidationTypes class. - constraints: [2] - }, { - type: "maxLength", - constraints: [20] - }], - lastName: [{ - type: "minLength", - constraints: [2] - }, { - type: "maxLength", - constraints: [20] - }], - email: [{ - type: "isEmail" - }] - } - }; - ``` - - Same schema can be provided in `.json` file, depend on your wish. - -2. Register your schema: - - ```typescript - import {registerSchema} from "class-validator"; - import {UserValidationSchema} from "./UserValidationSchema"; - registerSchema(schema); // if schema is in .json file, then you can simply do registerSchema(require("path-to-schema.json")); - ``` - - Better to put this code in a global place, maybe when you bootstrap your application, for example in `app.ts`. - -3. Validate your object using validation schema: - - ```typescript - import {validate} from "class-validator"; - const user = { firstName: "Johny", secondName: "Cage", email: "johny@cage.com" }; - validate("myUserSchema", user).then(errors => { - if (errors.length > 0) { - console.log("Validation failed: ", errors); - } else { - console.log("Validation succeed."); - } - }); - ``` - - That's it. Here `"myUserSchema"` is the name of our validation schema. - `validate` method will perform validation based on this schema - -## Validating plain objects -Due to nature of the decorators, the validated object has to be instantiated using `new Class()` syntax. If you have your class defined using class-validator decorators and you want to validate plain JS object (literal object or returned by JSON.parse), you need to transform it to the class instance (e.g. using [class-transformer](https://github.com/pleerock/class-transformer)) or just use the [class-transformer-validator](https://github.com/19majkel94/class-transformer-validator) extension which can do that for you. - -## Samples - -Take a look on samples in [./sample](https://github.com/pleerock/class-validator/tree/master/sample) for more examples of -usages. - -## Extensions -There are several extensions that simplify class-validator integration with other modules: -- [class-validator integration](https://github.com/19majkel94/class-transformer-validator) with [class-transformer](https://github.com/pleerock/class-transformer) - -## Release notes - -See information about breaking changes and release notes [here][3]. - -[1]: https://github.com/chriso/validator.js -[2]: https://github.com/pleerock/typedi -[3]: CHANGELOG.md +# class-validator + +[![Build Status](https://travis-ci.org/typestack/class-validator.svg?branch=master)](https://travis-ci.org/typestack/class-validator) +[![npm version](https://badge.fury.io/js/class-validator.svg)](https://badge.fury.io/js/class-validator) +[![install size](https://packagephobia.now.sh/badge?p=class-validator)](https://packagephobia.now.sh/result?p=class-validator) +[![Join the chat at https://gitter.im/typestack/class-validator](https://badges.gitter.im/typestack/class-validator.svg)](https://gitter.im/typestack/class-validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +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. + +## Table of Contents + + * [Installation](#installation) + - [Old versions of node.js/browser](#old-versions-of-nodejsbrowser) + - [Using in browser](#using-in-browser) + * [Usage](#usage) + + [Validation errors](#validation-errors) + + [Validation messages](#validation-messages) + + [Validating arrays](#validating-arrays) + + [Validating nested objects](#validating-nested-objects) + + [Inheriting Validation decorators](#inheriting-validation-decorators) + + [Conditional validation](#conditional-validation) + + [Whitelisting](#whitelisting) + + [Passing context to decorators](#passing-context-to-decorators) + + [Skipping missing properties](#skipping-missing-properties) + + [Validation groups](#validation-groups) + + [Custom validation classes](#custom-validation-classes) + + [Custom validation decorators](#custom-validation-decorators) + + [Using service container](#using-service-container) + + [Synchronous validation](#synchronous-validation) + + [Manual validation](#manual-validation) + + [Validation decorators](#validation-decorators) + + [Defining validation schema without decorators](#defining-validation-schema-without-decorators) + + [Validating plain objects](#validating-plain-objects) + * [Samples](#samples) + * [Extensions](#extensions) + * [Release notes](#release-notes) + +## Installation + +``` +npm install class-validator --save +``` + +> Note: Please use at least npm@6 when using class-validator as from npm@6 the dependency tree is flatterned what is good for us. + +## Usage + +Create your class and put some validation decorators on the properties you want to validate: + +```typescript +import {validate} from "class-validator"; +import {Length} from "class-validator/decorator/string/Length"; +import {Contains} from "class-validator/decorator/string/Contains"; +import {IsInt} from "class-validator/decorator/typechecker/IsInt"; +import {Min} from "class-validator/decorator/number/Min"; +import {Max} from "class-validator/decorator/number/Max"; +import {IsEmail} from "class-validator/decorator/string/IsEmail"; +import {IsFQDN} from "class-validator/decorator/string/IsFQDN"; +import {IsDate} from "class-validator/decorator/typechecker/IsDate"; + +export class Post { + + @Length(10, 20) + title: string; + + @Contains("hello") + text: string; + + @IsInt() + @Min(0) + @Max(10) + rating: number; + + @IsEmail() + email: string; + + @IsFQDN() + site: string; + + @IsDate() + createDate: Date; + +} + +let post = new Post(); +post.title = "Hello"; // should not pass +post.text = "this is a great post about hell world"; // should not pass +post.rating = 11; // should not pass +post.email = "google.com"; // should not pass +post.site = "googlecom"; // should not pass + +validate(post).then(errors => { // errors is an array of validation errors + if (errors.length > 0) { + console.log("validation failed. errors: ", errors); + } else { + console.log("validation succeed"); + } +}); +``` + +### Passing options + +The `validate` function optionally expects a `ValidatorOptions` object as a second parameter. + +```ts +export interface ValidatorOptions { + + skipMissingProperties?: boolean; + whitelist?: boolean; + forbidNonWhitelisted?: boolean; + groups?: string[]; + dismissDefaultMessages?: boolean; + validationError?: { + target?: boolean; + value?: boolean; + }; + + forbidUnknownValues?: boolean; +} +``` + +> It's highly advised to enable on `forbidUnknownValues` what prevent unknown objects to pass validation. + +## Validation errors + +`validate` method returns you an array of `ValidationError` objects. Each `ValidationError` is: + +```typescript +{ + target: Object; // Object that was validated. + property: string; // Object's property that haven't pass validation. + value: any; // Value that haven't pass a validation. + constraints?: { // Constraints that failed validation with error messages. + [type: string]: string; + }; + children?: ValidationError[]; // Contains all nested validation errors of the property +} +``` + +In our case, when we validated a Post object, we have such array of ValidationErrors: + +```typescript +[{ + target: /* post object */, + property: "title", + value: "Hello", + constraints: { + length: "$property must be longer than or equal to 10 characters" + } +}, { + target: /* post object */, + property: "text", + value: "this is a great post about hell world", + constraints: { + contains: "text must contain a hello string" + } +}, +// and other errors +] +``` + +If you don't want a `target` to be exposed in validation errors, there is a special option when you use validator: + +```typescript +validator.validate(post, { validationError: { target: false } }); +``` + +This is especially useful when you send errors back over http, and you most probably don't want to expose +the whole target object. + +## Validation messages + +You can specify validation message in the decorator options and that message will be returned in `ValidationError` +returned by `validate` method in the case that validation for this field fails. + +```typescript +import {MaxLength} from "class-validator/decorator/string/MaxLength"; +import {MinLength} from "class-validator/decorator/string/MinLength"; + +export class Post { + + @MinLength(10, { + message: "Title is too short" + }) + @MaxLength(50, { + message: "Title is too long" + }) + title: string; +} +``` + +There are few special tokens you can use in your messages: +* `$value` - the value that is being validated +* `$property` - name of the object's property being validated +* `$target` - name of the object's class being validated +* `$constraint1`, `$constraint2`, ... `$constraintN` - constraints defined by specific validation type + +Example of usage: + +```typescript +import {MaxLength} from "class-validator/decorator/string/MaxLength"; +import {MinLength} from "class-validator/decorator/string/MinLength"; + +export class Post { + + @MinLength(10, { // here, $constraint1 will be replaced with "10", and $value with actual supplied value + message: "Title is too short. Minimal length is $constraint1 characters, but actual is $value" + }) + @MaxLength(50, { // here, $constraint1 will be replaced with "50", and $value with actual supplied value + message: "Title is too long. Maximal length is $constraint1 characters, but actual is $value" + }) + title: string; +} +``` + +Also you can provide a function, that returns a message. This way allows to create more granular messages: + +```typescript +import {MaxLength} from "class-validator/decorator/string/MaxLength"; +import {MinLength} from "class-validator/decorator/string/MinLength"; +import {ValidationArguments} from "class-validator/validation/ValidationArguments"; + +export class Post { + + @MinLength(10, { + message: (args: ValidationArguments) => { + if (args.value.length === 1) { + return "Too short, minimum length is 1 character"; + } else { + return "Too short, minimum length is " + args.constraints[0] + " characters"; + } + } + }) + title: string; +} +``` + +Message function accepts `ValidationArguments` which contains following information: +* `value` - the value that is being validated +* `constraints` - array of constraints defined by specific validation type +* `targetName` - name of the object's class being validated +* `object` - object that is being validated +* `property` - name of the object's property being validated + +## Validating arrays + +If your field is an array and you want to perform validation of each item in the array you must specify a +special `each: true` decorator option: + +```typescript +import {MaxLength} from "class-validator/decorator/string/MaxLength"; +import {MinLength} from "class-validator/decorator/string/MinLength"; + +export class Post { + + @MaxLength(20, { + each: true + }) + tags: string[]; +} +``` + +This will validate each item in `post.tags` array. + +## Validating nested objects + +If your object contains nested objects and you want the validator to perform their validation too, then you need to +use the `@ValidateNested()` decorator: + +```typescript +import {ValidateNested} from "class-validator/decorator/system/ValidateNested"; + +export class Post { + + @ValidateNested() + user: User; + +} +``` + +## Inheriting Validation decorators + +When you define a subclass which extends from another one, the subclass will automatically inherit the parent's decorators. If a property is redefined in the descendant class decorators will be applied on it both from that and the base class. + +```typescript +import {validate} from "class-validator"; + +class BaseContent { + + @IsEmail() + email: string; + + @IsString() + password: string; +} + +class User extends BaseContent { + + @MinLength(10) + @MaxLength(20) + name: string; + + @Contains("hello") + welcome: string; + + @MinLength(20) + password: string; / +} + +let user = new User(); + +user.email = "invalid email"; // inherited property +user.password = "too short" // password wil be validated not only against IsString, but against MinLength as well +user.name = "not valid"; +user.welcome = "helo"; + +validate(user).then(errors => { + // ... +}); // it will return errors for email, title and text properties + +``` + +## Conditional validation + +The conditional validation decorator (`@ValidateIf`) can be used to ignore the validators on a property when the provided condition function returns false. The condition function takes the object being validated and must return a `boolean`. + +```typescript +import {ValidateIf} from "class-validator/decorator/system/ValidateIf"; +import {IsNotEmpty} from "class-validator/decorator/common/IsNotEmpty"; + +export class Post { + otherProperty:string; + + @ValidateIf(o => o.otherProperty === "value") + @IsNotEmpty() + example:string; +} +``` + +In the example above, the validation rules applied to `example` won't be run unless the object's `otherProperty` is `"value"`. + +Note that when the condition is false all validation decorators are ignored, including `isDefined`. + +## Whitelisting + +Even if your object is an instance of a validation class it can contain additional properties that are not defined. +If you do not want to have such properties on your object, pass special flag to `validate` method: + +```typescript +import {validate} from "class-validator"; +// ... +validate(post, { whitelist: true }); +``` + +This will strip all properties that don't have any decorators. If no other decorator is suitable for your property, +you can use @Allow decorator: + +```typescript +import {Allow} from "class-validator/decorator/system/Allow"; +import {Min} from "class-validator/decorator/number/Min"; +import {validate} from "class-validator"; + +export class Post { + + @Allow() + title: string; + + @Min(0) + views: number; + + nonWhitelistedProperty: number; +} + +let post = new Post(); +post.title = 'Hello world!'; +post.views = 420; + +post.nonWhitelistedProperty = 69; +(post as any).anotherNonWhitelistedProperty = "something"; + +validate(post).then(errors => { + // post.nonWhitelistedProperty is not defined + // (post as any).anotherNonWhitelistedProperty is not defined + ... +}); +```` + +If you would rather to have an error thrown when any non-whitelisted properties are present, pass another flag to +`validate` method: + +```typescript +import {validate} from "class-validator"; +// ... +validate(post, { whitelist: true, forbidNonWhitelisted: true }); +``` + +## Passing context to decorators + +It's possible to pass a custom object to decorators which will be accessible on the `ValidationError` instance of the property if validation failed. + +```ts +import { validate } from 'class-validator'; + +class MyClass { + @MinLength(32, { + message: "EIC code must be at least 32 charatcers", + context: { + errorCode: 1003, + developerNote: "The validated string must contain 32 or more characters." + } + }) + eicCode: string; +} + +const model = new MyClass(); + +validate(model).then(errors => { + //errors[0].contexts['minLength'].errorCode === 1003 +}); +``` + +## Skipping missing properties + +Sometimes you may want to skip validation of the properties that does not exist in the validating object. This is +usually desirable when you want to update some parts of the object, and want to validate only updated parts, +but skip everything else, e.g. skip missing properties. +In such situations you will need to pass a special flag to `validate` method: + +```typescript +import {validate} from "class-validator"; +// ... +validate(post, { skipMissingProperties: true }); +``` + +When skipping missing properties, sometimes you want not to skip all missing properties, some of them maybe required +for you, even if skipMissingProperties is set to true. For such cases you should use `@IsDefined()` decorator. +`@IsDefined()` is the only decorator that ignores `skipMissingProperties` option. + +## Validation groups + +In different situations you may want to use different validation schemas of the same object. + In such cases you can use validation groups. + +```typescript +import {validate} from "class-validator"; +import {Length} from "class-validator/decorator/string/Length"; +import {Min} from "class-validator/decorator/number/Min"; + +export class User { + + @Min(12, { + groups: ["registration"] + }) + age: number; + + @Length(2, 20, { + groups: ["registration", "admin"] + }) + name: string; +} + +let user = new User(); +user.age = 10; +user.name = "Alex"; + +validate(user, { + groups: ["registration"] +}); // this will not pass validation + +validate(user, { + groups: ["admin"] +}); // this will pass validation + +validate(user, { + groups: ["registration", "admin"] +}); // this will not pass validation + +validate(user, { + groups: undefined // the default +}); // this will not pass validation since all properties get validated regardless of their groups + +validate(user, { + groups: [] +}); // this will not pass validation, (equivalent to 'groups: undefined', see above) +``` + +There is also a special flag `always: true` in validation options that you can use. This flag says that this validation +must be applied always no matter which group is used. + +## Custom validation classes + +If you have custom validation logic you can create a *Constraint class*: + +1. First create a file, lets say `CustomTextLength.ts`, and define a new class: + + ```typescript + import {ValidatorConstraintInterface} from "class-validator/validation/ValidatorConstraintInterface"; + import {ValidationArguments} from "class-validator/validation/ValidationArguments"; + import {ValidatorConstraint} from "class-validator/decorator/ValidatorConstraint"; + + @ValidatorConstraint({ name: "customText", async: false }) + export class CustomTextLength implements ValidatorConstraintInterface { + + validate(text: string, args: ValidationArguments) { + return text.length > 1 && text.length < 10; // for async validations you must return a Promise here + } + + defaultMessage(args: ValidationArguments) { // here you can provide default error message if validation failed + return "Text ($value) is too short or too long!"; + } + + } + ``` + + We marked our class with `@ValidatorConstraint` decorator. + You can also supply a validation constraint name - this name will be used as "error type" in ValidationError. + If you will not supply a constraint name - it will be auto-generated. + + Our class must implement `ValidatorConstraintInterface` interface and its `validate` method, + which defines validation logic. If validation succeeds, method returns true, otherwise false. + Custom validator can be asynchronous, if you want to perform validation after some asynchronous + operations, simply return a promise with boolean inside in `validate` method. + + Also we defined optional method `defaultMessage` which defines a default error message, + in the case that the decorator's implementation doesn't set an error message. + + +2. Then you can use your new validation constraint in your class: + + ```typescript + import {Validate} from "class-validator"; + import {CustomTextLength} from "./CustomTextLength"; + + export class Post { + + @Validate(CustomTextLength, { + message: "Title is too short or long!" + }) + title: string; + + } + ``` + + Here we set our newly created `CustomTextLength` validation constraint for `Post.title`. + +3. And use validator as usual: + + ```typescript + import {validate} from "class-validator"; + + validate(post).then(errors => { + // ... + }); + ``` + +You can also pass constraints to your validator, like this: + +```typescript +import {Validate} from "class-validator"; +import {CustomTextLength} from "./CustomTextLength"; + +export class Post { + + @Validate(CustomTextLength, [3, 20], { + message: "Wrong post title" + }) + title: string; + +} +``` + +And use them from `validationArguments` object: + +```typescript +import {ValidatorConstraintInterface} from "class-validator/validation/ValidatorConstraintInterface"; +import {ValidationArguments} from "class-validator/validation/ValidationArguments"; +import {ValidatorConstraint} from "class-validator/decorator/ValidatorConstraint"; + +@ValidatorConstraint() +export class CustomTextLength implements ValidatorConstraintInterface { + + validate(text: string, validationArguments: ValidationArguments) { + return text.length > validationArguments.constraints[0] && text.length < validationArguments.constraints[1]; + } + +} +``` + +## Custom validation decorators + +You can also create a custom decorators. Its the most elegant way of using a custom validations. +Lets create a decorator called `@IsLongerThan`: + +1. Create a decorator itself: + + ```typescript + import {registerDecorator} from "class-validator/register-decorator"; + import {ValidationOptions} from "class-validator/decorator/ValidationOptions"; + import {ValidationArguments} from "class-validator/validation/ValidationArguments"; + + export function IsLongerThan(property: string, validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + registerDecorator({ + name: "isLongerThan", + target: object.constructor, + propertyName: propertyName, + constraints: [property], + 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 here as well, if you want to make async validation + } + } + }); + }; + } + ``` + +2. Put it to use: + + ```typescript + import {IsLongerThan} from "./IsLongerThan"; + + export class Post { + + title: string; + + @IsLongerThan("title", { + /* you can also use additional validation options, like "groups" in your custom validation decorators. "each" is not supported */ + message: "Text must be longer than the title" + }) + text: string; + + } + ``` + +In your custom decorators you can also use `ValidationConstraint`. +Lets create another custom validation decorator called `IsUserAlreadyExist`: + +1. Create a ValidationConstraint and decorator: + + ```typescript + import {registerDecorator} from "class-validator/register-decorator"; + import {ValidationOptions} from "class-validator/decorator/ValidationOptions"; + import {ValidatorConstraint} from "class-validator/decorator/ValidatorConstraint"; + import {ValidatorConstraintInterface} from "class-validator/validation/ValidatorConstraintInterface"; + import {ValidationArguments} from "class-validator/validation/ValidationArguments"; + + @ValidatorConstraint({ async: true }) + export class IsUserAlreadyExistConstraint implements ValidatorConstraintInterface { + + validate(userName: any, args: ValidationArguments) { + return UserRepository.findOneByName(userName).then(user => { + if (user) return false; + return true; + }); + } + + } + + export function IsUserAlreadyExist(validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + 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 + import {IsUserAlreadyExist} from "./IsUserAlreadyExist"; + + export class User { + + @IsUserAlreadyExist({ + message: "User $value already exists. Choose another name." + }) + name: string; + + } + ``` + +## Using service container + +Validator supports service container in the case if want to inject dependencies into your custom validator constraint +classes. Here is example how to integrate it with [typedi][2]: + +```typescript +import {Container} from "typedi"; +import {useContainer} from "class-validator/container"; +import {Validator} from "class-validator/validation/Validator"; + +// do this somewhere in the global application level: +useContainer(Container); +let validator = Container.get(Validator); + +// now everywhere you can inject Validator class which will go from the container +// 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 that is +compatible to the respective decorator. + +Each method is exported from the same module as the decorator. + +```typescript + +// common validation methods +// import {fooValidation} from "class-validator/decorator/common/FooValidation"; +isDefined(value); // Checks if value is defined ("!==undefined"). +equals(value, comparison); // Checks if value matches ("===") the comparison. +notEquals(value, comparison); // Checks if value does not match ("!==") the comparison. +isEmpty(value); // Checks if given value is empty (=== '', === null, === undefined). +isNotEmpty(value); // Checks if given value is not empty (!== '', !== null, !== undefined). +isIn(value, possibleValues); // Checks if given value is in a array of allowed values. +isNotIn(value, possibleValues); // Checks if given value not in a array of allowed values. + +// type validation methods +// import {fooValidation} from "class-validator/decorator/typechecker/FooValidation"; +isBoolean(value); // Checks if a given value is a real boolean. +isDate(value); // Checks if a given value is a real date. +isString(value); // Checks if a given value is a real string. +isArray(value); // Checks if a given value is an array. +isNumber(value, options); // Checks if a given value is a real number. +isInt(value); // Checks if value is an integer. +isEnum(value, entity); // Checks if value is valid for a certain enum entity. +isInstance(value, target); // Checks value is an instance of the target. + +// number validation methods +// import {fooValidation} from "class-validator/decorator/number/FooValidation"; +isDivisibleBy(value, num); // Checks if value is a number that's divisible by another. +isPositive(value); // Checks if the value is a positive number. +isNegative(value); // Checks if the value is a negative number. +min(num, min); // Checks if the first number is greater than or equal to the second. +max(num, max); // Checks if the first number is less than or equal to the second. + +// date validation methods +// import {fooValidation} from "class-validator/decorator/date/FooValidation"; +minDate(date, minDate); // Checks if the value is a date that's after the specified date. +maxDate(date, minDate); // Checks if the value is a date that's before the specified date. + +// string-type validation methods +// import {fooValidation} from "class-validator/decorator/string-as-type/FooValidation"; +isBooleanString(str); // Checks if a string is a boolean. +isNumberString(str); // Checks if the string is numeric. + +// string validation methods +// import {fooValidation} from "class-validator/decorator/string/FooValidation"; +contains(str, seed); // Checks if the string contains the seed. +notContains(str, seed); // Checks if the string does not contain the seed. +isAlpha(str); // Checks if the string contains only letters (a-zA-Z). +isAlphanumeric(str); // Checks if the string contains only letters and numbers. +isAscii(str); // Checks if the string contains ASCII chars only. +isBase64(str); // Checks if a string is base64 encoded. +isByteLength(str, min, max); // Checks if the string's length (in bytes) falls in a range. +isCreditCard(str); // Checks if the string is a credit card. +isCurrency(str, options); // Checks if the string is a valid currency amount. +isEmail(str, options); // Checks if the string is an email. +isFQDN(str, options); // Checks if the string is a fully qualified domain name (e.g. domain.com). +isFullWidth(str); // Checks if the string contains any full-width chars. +isHalfWidth(str); // Checks if the string contains any half-width chars. +isVariableWidth(str); // Checks if the string contains variable-width chars. +isHexColor(str); // Checks if the string is a hexadecimal color. +isHexadecimal(str); // Checks if the string is a hexadecimal number. +isIP(str, version); // Checks if the string is an IP (version 4 or 6). +isISBN(str, version); // Checks if the string is an ISBN (version 10 or 13). +isISIN(str); // Checks if the string is an ISIN (stock/security identifier). +isISO8601(str); // Checks if the string is a valid ISO 8601 date. +isJSON(str); // Checks if the string is valid JSON (note: uses JSON.parse). +isLowercase(str); // Checks if the string is lowercase. +isMobilePhone(str, locale); // Checks if the string is a mobile phone number. +isPhoneNumber(str, region); // Checks if the string is a valid phone number. +isMongoId(str); // Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. +isMultibyte(str); // Checks if the string contains one or more multibyte chars. +isSurrogatePair(str); // Checks if the string contains any surrogate pairs chars. +isURL(str, options); // Checks if the string is an url. +isUUID(str, version); // Checks if the string is a UUID (version 3, 4 or 5). +isUppercase(str); // Checks if the string is uppercase. +length(str, min, max); // Checks if the string's length falls in a range. +minLength(str, min); // Checks if the string's length is not less than given number. +maxLength(str, max); // Checks if the string's length is not more than given number. +matches(str, pattern, modifiers); // Checks if string matches the pattern. Either matches('foo', /foo/i) or matches('foo', 'foo', 'i'). +isMilitaryTime(str); // Checks if the string is a valid representation of military time in the format HH:MM. + +// array validation methods +// import {fooValidation} from "class-validator/decorator/array/FooValidation"; +arrayContains(array, values); // Checks if array contains all values from the given array of values. +arrayNotContains(array, values); // Checks if array does not contain any of the given values. +arrayNotEmpty(array); // Checks if given array is not empty. +arrayMinSize(array, min); // Checks if array's length is at least `min` number. +arrayMaxSize(array, max); // Checks if array's length is as most `max` number. +arrayUnique(array); // Checks if all array's values are unique. Comparison for objects is reference-based. +``` + +## Validation decorators + +| Decorator | Description | +|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| +| **Common validation decorators** | +| `@IsDefined(value: any)` | Checks if value is defined (!== undefined, !== null). This is the only decorator that ignores skipMissingProperties option. | +| `@IsOptional()` | Checks if given value is empty (=== null, === undefined) and if so, ignores all the validators on the property. | +| `@Equals(comparison: any)` | Checks if value equals ("===") comparison. | +| `@NotEquals(comparison: any)` | Checks if value not equal ("!==") comparison. | +| `@IsEmpty()` | Checks if given value is empty (=== '', === null, === undefined). | +| `@IsNotEmpty()` | Checks if given value is not empty (!== '', !== null, !== undefined). | +| `@IsIn(values: any[])` | Checks if value is in a array of allowed values. | +| `@IsNotIn(values: any[])` | Checks if value is not in a array of disallowed values. | +| **Type validation decorators** | +| `@IsBoolean()` | Checks if a value is a boolean. | +| `@IsDate()` | Checks if the value is a date. | +| `@IsString()` | Checks if the string is a string. | +| `@IsNumber(options: IsNumberOptions)` | Checks if the value is a number. | +| `@IsInt()` | Checks if the value is an integer number. | +| `@IsArray()` | Checks if the value is an array | +| `@IsEnum(entity: object)` | Checks if the value is an valid enum | +| **Number validation decorators** | +| `@IsDivisibleBy(num: number)` | Checks if the value is a number that's divisible by another. | +| `@IsPositive()` | Checks if the value is a positive number. | +| `@IsNegative()` | Checks if the value is a negative number. | +| `@Min(min: number)` | Checks if the given number is greater than or equal to given number. | +| `@Max(max: number)` | Checks if the given number is less than or equal to given number. | +| **Date validation decorators** | +| `@MinDate(date: Date)` | Checks if the value is a date that's after the specified date. | +| `@MaxDate(date: Date)` | Checks if the value is a date that's before the specified date. | | +| **String-type validation decorators** | +| `@IsBooleanString()` | Checks if a string is a boolean (e.g. is "true" or "false"). | +| `@IsDateString()` | Checks if a string is a complete representation of a date (e.g. "2017-06-07T14:34:08.700Z", "2017-06-07T14:34:08.700 or "2017-06-07T14:34:08+04:00"). | +| `@IsNumberString()` | Checks if a string is a number. | +| **String validation decorators** | +| `@Contains(seed: string)` | Checks if the string contains the seed. | +| `@NotContains(seed: string)` | Checks if the string not contains the seed. | +| `@IsAlpha()` | Checks if the string contains only letters (a-zA-Z). | +| `@IsAlphanumeric()` | Checks if the string contains only letters and numbers. | +| `@IsAscii()` | Checks if the string contains ASCII chars only. | +| `@IsBase64()` | Checks if a string is base64 encoded. | +| `@IsByteLength(min: number, max?: number)` | Checks if the string's length (in bytes) falls in a range. | +| `@IsCreditCard()` | Checks if the string is a credit card. | +| `@IsCurrency(options?: IsCurrencyOptions)` | Checks if the string is a valid currency amount. | +| `@IsEmail(options?: IsEmailOptions)` | Checks if the string is an email. | +| `@IsFQDN(options?: IsFQDNOptions)` | Checks if the string is a fully qualified domain name (e.g. domain.com). | +| `@IsFullWidth()` | Checks if the string contains any full-width chars. | +| `@IsHalfWidth()` | Checks if the string contains any half-width chars. | +| `@IsVariableWidth()` | Checks if the string contains a mixture of full and half-width chars. | +| `@IsHexColor()` | Checks if the string is a hexadecimal color. | +| `@IsHexadecimal()` | Checks if the string is a hexadecimal number. | +| `@IsIP(version?: "4"\|"6")` | Checks if the string is an IP (version 4 or 6). | +| `@IsISBN(version?: "10"\|"13")` | Checks if the string is an ISBN (version 10 or 13). | +| `@IsISIN()` | Checks if the string is an ISIN (stock/security identifier). | +| `@IsISO8601()` | Checks if the string is a valid ISO 8601 date. | +| `@IsJSON()` | Checks if the string is valid JSON. | +| `@IsLowercase()` | Checks if the string is lowercase. | +| `@IsMobilePhone(locale: string)` | Checks if the string is a mobile phone number. | +| `@IsPhoneNumber(region: string)` | Checks if the string is a valid phone number. "region" accepts 2 characters uppercase country code (e.g. DE, US, CH).If users must enter the intl. prefix (e.g. +41), then you may pass "ZZ" or null as region. See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github](https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33) | +| `@IsMongoId()` | Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. | +| `@IsMultibyte()` | Checks if the string contains one or more multibyte chars. | +| `@IsNumberString()` | Checks if the string is numeric. | +| `@IsSurrogatePair()` | Checks if the string contains any surrogate pairs chars. | +| `@IsUrl(options?: IsURLOptions)` | Checks if the string is an url. | +| `@IsUUID(version?: "3"\|"4"\|"5")` | Checks if the string is a UUID (version 3, 4 or 5). | +| `@IsUppercase()` | Checks if the string is uppercase. | +| `@Length(min: number, max?: number)` | Checks if the string's length falls in a range. | +| `@MinLength(min: number)` | Checks if the string's length is not less than given number. | +| `@MaxLength(max: number)` | Checks if the string's length is not more than given number. | +| `@Matches(pattern: RegExp, modifiers?: string)` | Checks if string matches the pattern. Either matches('foo', /foo/i) or matches('foo', 'foo', 'i'). +| `@IsMilitaryTime()` | Checks if the string is a valid representation of military time in the format HH:MM. | +| **Array validation decorators** | +| `@ArrayContains(values: any[])` | Checks if array contains all values from the given array of values. | +| `@ArrayNotContains(values: any[])` | Checks if array does not contain any of the given values. | +| `@ArrayNotEmpty()` | Checks if given array is not empty. | +| `@ArrayMinSize(min: number)` | Checks if array's length is as minimal this number. | +| `@ArrayMaxSize(max: number)` | Checks if array's length is as maximal this number. | +| `@ArrayUnique()` | Checks if all array's values are unique. Comparison for objects is reference-based. | +| **Object validation decorators** | +| `@IsInstance(value: any)` | Checks if the property is an instance of the passed value. | + **Other decorators** | +| `@Allow()` | Prevent stripping off the property when no other constraint is specified for it. | + +## Defining validation schema without decorators + +You can define your validation schemas without decorators: + +* you can define it in the separate object +* you can define it in the `.json` file + +This feature maybe useful in the cases if: + +* are using es5/es6 and don't have decorators available +* you don't have a classes, and instead using interfaces +* you don't want to use model at all +* you want to have a validation schema separate of your model +* you want beautiful json-schema based validation models +* you simply hate decorators + +Here is an example of using it: + +1. Create a schema object: + + ```typescript + import {ValidationSchema} from "class-validator/validation-schema/ValidationSchema"; + export let UserValidationSchema: ValidationSchema = { // using interface here is not required, its just for type-safety + name: "myUserSchema", // this is required, and must be unique + properties: { + firstName: [{ + type: "minLength", // validation type. All validation types are listed in ValidationTypes class. + constraints: [2] + }, { + type: "maxLength", + constraints: [20] + }], + lastName: [{ + type: "minLength", + constraints: [2] + }, { + type: "maxLength", + constraints: [20] + }], + email: [{ + type: "isEmail" + }] + } + }; + ``` + + Same schema can be provided in `.json` file, depend on your wish. + +2. Register your schema: + + ```typescript + import {registerSchema} from "class-validator"; + import {UserValidationSchema} from "./UserValidationSchema"; + registerSchema(schema); // if schema is in .json file, then you can simply do registerSchema(require("path-to-schema.json")); + ``` + + Better to put this code in a global place, maybe when you bootstrap your application, for example in `app.ts`. + +3. Validate your object using validation schema: + + ```typescript + import {validate} from "class-validator"; + const user = { firstName: "Johny", secondName: "Cage", email: "johny@cage.com" }; + validate("myUserSchema", user).then(errors => { + if (errors.length > 0) { + console.log("Validation failed: ", errors); + } else { + console.log("Validation succeed."); + } + }); + ``` + + That's it. Here `"myUserSchema"` is the name of our validation schema. + `validate` method will perform validation based on this schema + +## Validating plain objects +Due to nature of the decorators, the validated object has to be instantiated using `new Class()` syntax. If you have your class defined using class-validator decorators and you want to validate plain JS object (literal object or returned by JSON.parse), you need to transform it to the class instance (e.g. using [class-transformer](https://github.com/pleerock/class-transformer)) or just use the [class-transformer-validator](https://github.com/19majkel94/class-transformer-validator) extension which can do that for you. + +## Samples + +Take a look on samples in [./sample](https://github.com/pleerock/class-validator/tree/master/sample) for more examples of +usages. + +## Extensions +There are several extensions that simplify class-validator integration with other modules: +- [class-validator integration](https://github.com/19majkel94/class-transformer-validator) with [class-transformer](https://github.com/pleerock/class-transformer) + +## Release notes + +See information about breaking changes and release notes [here][3]. + +[1]: https://github.com/chriso/validator.js +[2]: https://github.com/pleerock/typedi +[3]: CHANGELOG.md diff --git a/package-lock.json b/package-lock.json index 12d4e658b6..2315a795ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "class-validator", - "version": "0.9.1", + "version": "1.0.0-rc1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -162,7 +162,8 @@ "@types/validator": { "version": "9.4.2", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-9.4.2.tgz", - "integrity": "sha512-v6H2QH+oXVdLKp9keOJi5LQSt6X5/XIOtK1YmbCzvkAT2kHW9WyQkixit9w1UgJpBGrDCqqCZlQ+Qucpmsf8hA==" + "integrity": "sha512-v6H2QH+oXVdLKp9keOJi5LQSt6X5/XIOtK1YmbCzvkAT2kHW9WyQkixit9w1UgJpBGrDCqqCZlQ+Qucpmsf8hA==", + "dev": true }, "@types/vinyl": { "version": "2.0.2", diff --git a/package.json b/package.json index 04ee8178d0..caa9b332a9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "class-validator", "private": true, - "version": "0.9.1", + "version": "1.0.0-rc1", "description": "Class-based validation with Typescript / ES6 / ES5 using decorators or validation schemas. Supports both node.js and browser", "license": "MIT", "readmeFilename": "README.md", @@ -23,7 +23,6 @@ "typescript-validator" ], "dependencies": { - "@types/validator": "9.4.2", "google-libphonenumber": "^3.1.6", "validator": "10.4.0" }, @@ -34,6 +33,7 @@ "@types/mocha": "^5.2.5", "@types/node": "^10.5.2", "@types/sinon": "^5.0.1", + "@types/validator": "^9.4.2", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "codecov": "^3.0.4", diff --git a/sample/sample1-simple-validation/Post.ts b/sample/sample1-simple-validation/Post.ts index 8a3e2c6f6a..a8619cccb3 100644 --- a/sample/sample1-simple-validation/Post.ts +++ b/sample/sample1-simple-validation/Post.ts @@ -1,4 +1,14 @@ -import {Contains, IsInt, MinLength, MaxLength, IsEmail, IsFQDN, IsDate, ArrayNotEmpty, ArrayMinSize, ArrayMaxSize, IsEnum} from "../../src/decorator/decorators"; +import {IsDate} from "../../src/decorator/typechecker/IsDate"; +import {IsInt} from "../../src/decorator/typechecker/IsInt"; +import {IsEnum} from "../../src/decorator/typechecker/IsEnum"; +import {Contains} from "../../src/decorator/string/Contains"; +import {IsEmail} from "../../src/decorator/string/IsEmail"; +import {IsFQDN} from "../../src/decorator/string/IsFQDN"; +import {MinLength} from "../../src/decorator/string/MinLength"; +import {MaxLength} from "../../src/decorator/string/MaxLength"; +import {ArrayNotEmpty} from "../../src/decorator/array/ArrayNotEmpty"; +import {ArrayMinSize} from "../../src/decorator/array/ArrayMinSize"; +import {ArrayMaxSize} from "../../src/decorator/array/ArrayMaxSize"; export enum PostType { Public, diff --git a/sample/sample2-using-groups/Post.ts b/sample/sample2-using-groups/Post.ts index 4ae81ce862..ddd1dd6931 100644 --- a/sample/sample2-using-groups/Post.ts +++ b/sample/sample2-using-groups/Post.ts @@ -1,4 +1,9 @@ -import {Contains, IsInt, Length, IsEmail, IsFQDN, IsDate} from "../../src/decorator/decorators"; +import {IsDate} from "../../src/decorator/typechecker/IsDate"; +import {IsInt} from "../../src/decorator/typechecker/IsInt"; +import {Contains} from "../../src/decorator/string/Contains"; +import {IsEmail} from "../../src/decorator/string/IsEmail"; +import {IsFQDN} from "../../src/decorator/string/IsFQDN"; +import {Length} from "../../src/decorator/string/Length"; export class Post { @@ -35,4 +40,4 @@ export class Post { @IsDate() createDate: Date; -} \ No newline at end of file +} diff --git a/sample/sample3-nested-objects/Post.ts b/sample/sample3-nested-objects/Post.ts index 85e794be87..4a74897bdd 100644 --- a/sample/sample3-nested-objects/Post.ts +++ b/sample/sample3-nested-objects/Post.ts @@ -1,5 +1,6 @@ -import {Contains, IsInt, Length, IsEmail, IsFQDN, IsDate, ValidateNested} from "../../src/decorator/decorators"; import {Tag} from "./Tag"; +import {Length} from "../../src/decorator/string/Length"; +import {ValidateNested} from "../../src/decorator/system/ValidateNested"; export class Post { @@ -11,4 +12,4 @@ export class Post { @ValidateNested() tags: Tag[]; -} \ No newline at end of file +} diff --git a/sample/sample3-nested-objects/Tag.ts b/sample/sample3-nested-objects/Tag.ts index c547f90ec3..bb7cf6ea37 100644 --- a/sample/sample3-nested-objects/Tag.ts +++ b/sample/sample3-nested-objects/Tag.ts @@ -1,4 +1,4 @@ -import {Contains, IsInt, Length, IsEmail, IsFQDN, IsDate} from "../../src/decorator/decorators"; +import {Length} from "../../src/decorator/string/Length"; export class Tag { @@ -7,4 +7,4 @@ export class Tag { }) name: string; -} \ No newline at end of file +} diff --git a/sample/sample4-custom-validator/CustomTextLength.ts b/sample/sample4-custom-validator/CustomTextLength.ts index 04e3746e83..b34140a0c2 100644 --- a/sample/sample4-custom-validator/CustomTextLength.ts +++ b/sample/sample4-custom-validator/CustomTextLength.ts @@ -1,5 +1,5 @@ +import {ValidatorConstraint} from "../../src/decorator/ValidatorConstraint"; import {ValidatorConstraintInterface} from "../../src/validation/ValidatorConstraintInterface"; -import {ValidatorConstraint} from "../../src/decorator/decorators"; @ValidatorConstraint() export class CustomTextLength implements ValidatorConstraintInterface { @@ -8,4 +8,4 @@ export class CustomTextLength implements ValidatorConstraintInterface { return text.length > 1 && text.length < 10; } -} \ No newline at end of file +} diff --git a/sample/sample4-custom-validator/Post.ts b/sample/sample4-custom-validator/Post.ts index 6a4d226c0a..f4d372c253 100644 --- a/sample/sample4-custom-validator/Post.ts +++ b/sample/sample4-custom-validator/Post.ts @@ -1,6 +1,5 @@ -import {Contains, IsInt, MinLength, MaxLength, IsEmail, IsFQDN, IsDate, IsNotEmpty, ArrayNotEmpty, ArrayMinSize, ArrayMaxSize} from "../../src/decorator/decorators"; -import {Validate} from "../../src/decorator/decorators"; import {CustomTextLength} from "./CustomTextLength"; +import {Validate} from "../../src/decorator/Validate"; export class Post { @@ -9,4 +8,4 @@ export class Post { }) title: string; -} \ No newline at end of file +} diff --git a/sample/sample6-custom-decorator/IsLongerThan.ts b/sample/sample6-custom-decorator/IsLongerThan.ts index 90bde6e092..22465c35b6 100644 --- a/sample/sample6-custom-decorator/IsLongerThan.ts +++ b/sample/sample6-custom-decorator/IsLongerThan.ts @@ -1,8 +1,8 @@ -import {registerDecorator} from "../../src/index"; import {ValidationOptions} from "../../src/decorator/ValidationOptions"; import {ValidatorConstraintInterface} from "../../src/validation/ValidatorConstraintInterface"; -import {ValidatorConstraint} from "../../src/decorator/decorators"; +import {ValidatorConstraint} from "../../src/decorator/ValidatorConstraint"; import {ValidationArguments} from "../../src/validation/ValidationArguments"; +import {registerDecorator} from "../../src/register-decorator"; export function IsLongerThan(property: string, validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { @@ -27,4 +27,4 @@ export class IsLongerThanConstraint implements ValidatorConstraintInterface { value.length > relatedValue.length; } -} \ No newline at end of file +} diff --git a/sample/sample6-custom-decorator/IsUserAlreadyExist.ts b/sample/sample6-custom-decorator/IsUserAlreadyExist.ts index 49474e9c61..e73380d819 100644 --- a/sample/sample6-custom-decorator/IsUserAlreadyExist.ts +++ b/sample/sample6-custom-decorator/IsUserAlreadyExist.ts @@ -1,6 +1,6 @@ -import {registerDecorator} from "../../src/index"; import {ValidationOptions} from "../../src/decorator/ValidationOptions"; import {ValidationArguments} from "../../src/validation/ValidationArguments"; +import {registerDecorator} from "../../src/register-decorator"; export function IsUserAlreadyExist(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { @@ -23,4 +23,4 @@ export function IsUserAlreadyExist(validationOptions?: ValidationOptions) { } }); }; -} \ No newline at end of file +} diff --git a/sample/sample7-inheritance-support/BaseContent.ts b/sample/sample7-inheritance-support/BaseContent.ts index e6b0171fbd..02db1bdf76 100644 --- a/sample/sample7-inheritance-support/BaseContent.ts +++ b/sample/sample7-inheritance-support/BaseContent.ts @@ -1,8 +1,8 @@ -import {IsEmail} from "../../src/decorator/decorators"; +import {IsEmail} from "../../src/decorator/string/IsEmail"; export class BaseContent { @IsEmail() email: string; -} \ No newline at end of file +} diff --git a/sample/sample7-inheritance-support/Post.ts b/sample/sample7-inheritance-support/Post.ts index 6d3a6d2325..bcb9981be0 100644 --- a/sample/sample7-inheritance-support/Post.ts +++ b/sample/sample7-inheritance-support/Post.ts @@ -1,5 +1,8 @@ -import {Contains, IsInt, MinLength, MaxLength} from "../../src/decorator/decorators"; import {BaseContent} from "./BaseContent"; +import {IsInt} from "../../src/decorator/typechecker/IsInt"; +import {Contains} from "../../src/decorator/string/Contains"; +import {MinLength} from "../../src/decorator/string/MinLength"; +import {MaxLength} from "../../src/decorator/string/MaxLength"; export class Post extends BaseContent { @@ -13,4 +16,4 @@ export class Post extends BaseContent { @IsInt() rating: number; -} \ No newline at end of file +} diff --git a/src/decorator/Validate.ts b/src/decorator/Validate.ts new file mode 100644 index 0000000000..b58c9806c0 --- /dev/null +++ b/src/decorator/Validate.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "./ValidationOptions"; +import {ValidationMetadataArgs} from "../metadata/ValidationMetadataArgs"; +import {ValidationMetadata} from "../metadata/ValidationMetadata"; +import {MetadataStorage} from "../metadata/MetadataStorage"; +import {ValidationTypes} from "../validation/ValidationTypes"; +import {getFromContainer} from "../container"; + +/** + * Performs validation based on the given custom validation class. + * Validation class must be decorated with ValidatorConstraint decorator. + */ +export function Validate(constraintClass: Function, validationOptions?: ValidationOptions): Function; +export function Validate(constraintClass: Function, constraints?: any[], validationOptions?: ValidationOptions): Function; +export function Validate(constraintClass: Function, constraintsOrValidationOptions?: any[] | ValidationOptions, maybeValidationOptions?: ValidationOptions): Function { + return function (object: Object, propertyName: string) { + const args: ValidationMetadataArgs = { + type: ValidationTypes.CUSTOM_VALIDATION, + target: object.constructor, + propertyName: propertyName, + constraintCls: constraintClass, + constraints: constraintsOrValidationOptions instanceof Array ? constraintsOrValidationOptions as any[] : undefined, + validationOptions: !(constraintsOrValidationOptions instanceof Array) ? constraintsOrValidationOptions as ValidationOptions : maybeValidationOptions + }; + getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); + }; +} diff --git a/src/decorator/ValidateBy.ts b/src/decorator/ValidateBy.ts new file mode 100644 index 0000000000..72c5e96a98 --- /dev/null +++ b/src/decorator/ValidateBy.ts @@ -0,0 +1,33 @@ +import {ValidationFunction} from "./ValidationFunction"; +import {ValidationOptions} from "./ValidationOptions"; +import {registerDecorator} from "../register-decorator"; +import {ValidationArguments} from "../validation/ValidationArguments"; +import {ValidatorConstraintInterface} from "../validation/ValidatorConstraintInterface"; + +export function buildMessage( + impl: (eachPrefix: string, args?: ValidationArguments) => string, + validationOptions?: ValidationOptions) + : (validationArguments?: ValidationArguments) => string { + return (validationArguments?: ValidationArguments) => { + const eachPrefix = validationOptions && validationOptions.each + ? "each value in " + : ""; + return impl(eachPrefix, validationArguments); + }; +} + +export function ValidateBy(validator: ValidationFunction, validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + registerDecorator({ + name: validator.name, + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: validator.constraints, + validator: { + validate: validator.validate, + defaultMessage: validator.defaultMessage + } + }); + }; +} diff --git a/src/decorator/ValidationFunction.ts b/src/decorator/ValidationFunction.ts new file mode 100644 index 0000000000..4e6495dac8 --- /dev/null +++ b/src/decorator/ValidationFunction.ts @@ -0,0 +1,9 @@ +import {ValidationArguments} from "../validation/ValidationArguments"; + +export interface ValidationFunction { + name: string; + constraints?: any[]; + validate: (value: any, validationArguments?: ValidationArguments) => Promise|boolean; + defaultMessage: (validationArguments?: ValidationArguments) => string; + async?: boolean; +} diff --git a/src/decorator/ValidatorConstraint.ts b/src/decorator/ValidatorConstraint.ts new file mode 100644 index 0000000000..035767b094 --- /dev/null +++ b/src/decorator/ValidatorConstraint.ts @@ -0,0 +1,20 @@ +import {ConstraintMetadata} from "../metadata/ConstraintMetadata"; +import {getFromContainer} from "../container"; +import {MetadataStorage} from "../metadata/MetadataStorage"; + +/** + * Registers custom validator class. + */ +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(/^_/, ""); + } + const metadata = new ConstraintMetadata(target, name, isAsync); + getFromContainer(MetadataStorage).addConstraintMetadata(metadata); + }; +} diff --git a/src/decorator/array/ArrayContains.ts b/src/decorator/array/ArrayContains.ts new file mode 100644 index 0000000000..13f9e1e4b2 --- /dev/null +++ b/src/decorator/array/ArrayContains.ts @@ -0,0 +1,30 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const ARRAY_CONTAINS = "arrayContains"; + +/** + * Checks if array contains all values from the given array of values. + * If null or undefined is given then this function returns false. + */ +export function arrayContains(array: any[], values: any[]) { + if (!(array instanceof Array)) { + return false; + } + + return !array || values.every(value => array.indexOf(value) !== -1); +} + +/** + * Checks if array contains all values from the given array of values. + */ +export function ArrayContains(values: any[], validationOptions?: ValidationOptions) { + return ValidateBy({ + name: ARRAY_CONTAINS, + validate: (value, args) => arrayContains(value, args.constraints[0]), + constraints: [values], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain $constraint1 values", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/array/ArrayMaxSize.ts b/src/decorator/array/ArrayMaxSize.ts new file mode 100644 index 0000000000..94c95f100a --- /dev/null +++ b/src/decorator/array/ArrayMaxSize.ts @@ -0,0 +1,30 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const ARRAY_MAX_SIZE = "arrayMaxSize"; + +/** + * Checks if array's length is as maximal this number. + * If null or undefined is given then this function returns false. + */ +export function arrayMaxSize(array: any[], max: number) { + if (!(array instanceof Array)) { + return false; + } + + return array instanceof Array && array.length <= max; +} + +/** + * Checks if array's length is as maximal this number. + */ +export function ArrayMaxSize(max: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: ARRAY_MAX_SIZE, + validate: (value, args) => arrayMaxSize(value, args.constraints[0]), + constraints: [max], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain not more than $constraint1 elements", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/array/ArrayMinSize.ts b/src/decorator/array/ArrayMinSize.ts new file mode 100644 index 0000000000..5d9579e6d2 --- /dev/null +++ b/src/decorator/array/ArrayMinSize.ts @@ -0,0 +1,30 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const ARRAY_MIN_SIZE = "arrayMinSize"; + +/** + * Checks if array's length is as minimal this number. + * If null or undefined is given then this function returns false. + */ +export function arrayMinSize(array: any[], min: number) { + if (!(array instanceof Array)) { + return false; + } + + return array instanceof Array && array.length >= min; +} + +/** + * Checks if array's length is as minimal this number. + */ +export function ArrayMinSize(min: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: ARRAY_MIN_SIZE, + validate: (value, args) => arrayMinSize(value, args.constraints[0]), + constraints: [min], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain at least $constraint1 elements", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/array/ArrayNotContains.ts b/src/decorator/array/ArrayNotContains.ts new file mode 100644 index 0000000000..afd99559ff --- /dev/null +++ b/src/decorator/array/ArrayNotContains.ts @@ -0,0 +1,30 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const ARRAY_NOT_CONTAINS = "arrayNotContains"; + +/** + * Checks if array does not contain any of the given values. + * If null or undefined is given then this function returns false. + */ +export function arrayNotContains(array: any[], values: any[]) { + if (!(array instanceof Array)) { + return false; + } + + return !array || values.every(value => array.indexOf(value) === -1); +} + +/** + * Checks if array does not contain any of the given values. + */ +export function ArrayNotContains(values: any[], validationOptions?: ValidationOptions) { + return ValidateBy({ + name: ARRAY_NOT_CONTAINS, + validate: (value, args) => arrayNotContains(value, args.constraints[0]), + constraints: [values], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not contain $constraint1 values", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/array/ArrayNotEmpty.ts b/src/decorator/array/ArrayNotEmpty.ts new file mode 100644 index 0000000000..d61949d5dc --- /dev/null +++ b/src/decorator/array/ArrayNotEmpty.ts @@ -0,0 +1,29 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const ARRAY_NOT_EMPTY = "arrayNotEmpty"; + +/** + * Checks if given array is not empty. + * If null or undefined is given then this function returns false. + */ +export function arrayNotEmpty(array: any[]) { + if (!(array instanceof Array)) { + return false; + } + + return array instanceof Array && array.length > 0; +} + +/** + * Checks if given array is not empty. + */ +export function ArrayNotEmpty(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: ARRAY_NOT_EMPTY, + validate: (value) => arrayNotEmpty(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be empty", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/array/ArrayUnique.ts b/src/decorator/array/ArrayUnique.ts new file mode 100644 index 0000000000..6cc940a1e4 --- /dev/null +++ b/src/decorator/array/ArrayUnique.ts @@ -0,0 +1,30 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const ARRAY_UNIQUE = "arrayUnique"; + +/** + * Checks if all array's values are unique. Comparison for objects is reference-based. + * If null or undefined is given then this function returns false. + */ +export function arrayUnique(array: any[]) { + if (!(array instanceof Array)) { + return false; + } + + const uniqueItems = array.filter((a, b, c) => c.indexOf(a) === b); + return array.length === uniqueItems.length; +} + +/** + * Checks if all array's values are unique. Comparison for objects is reference-based. + */ +export function ArrayUnique(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: ARRAY_UNIQUE, + validate: (value) => arrayUnique(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "All $property's elements must be unique", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/common/Equals.ts b/src/decorator/common/Equals.ts new file mode 100644 index 0000000000..6fc40ee0ab --- /dev/null +++ b/src/decorator/common/Equals.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const EQUALS = "equals"; + +/** + * Checks if value matches ("===") the comparison. + */ +export function equals(value: any, comparison: any): boolean { + return value === comparison; +} + +/** + * Checks if the value match ("===") the comparison. + */ +export function Equals(comparison: any, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: EQUALS, + validate: (value, args) => equals(value, args.constraints[0]), + constraints: [comparison], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be equal to $constraint1", validationOptions), + + }, + validationOptions + ); +} + diff --git a/src/decorator/common/IsEmpty.ts b/src/decorator/common/IsEmpty.ts new file mode 100644 index 0000000000..088cce7ede --- /dev/null +++ b/src/decorator/common/IsEmpty.ts @@ -0,0 +1,23 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_EMPTY = "isEmpty"; + +/** + * Checks if given value is empty (=== '', === null, === undefined). + */ +export function isEmpty(value: any): boolean { + return value === "" || value === null || value === undefined; +} + +/** + * Checks if given value is empty (=== '', === null, === undefined). + */ +export function IsEmpty(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_EMPTY, + validate: (value, args) => isEmpty(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be empty", validationOptions), + + }, validationOptions); +} diff --git a/src/decorator/common/IsIn.ts b/src/decorator/common/IsIn.ts new file mode 100644 index 0000000000..90f7e9fd2c --- /dev/null +++ b/src/decorator/common/IsIn.ts @@ -0,0 +1,28 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_IN = "isIn"; + +/** + * Checks if given value is in a array of allowed values. + */ +export function isIn(value: any, possibleValues: any[]): boolean { + return !(possibleValues instanceof Array) || possibleValues.some(possibleValue => possibleValue === value); +} + +/** + * Checks if value is in a array of allowed values. + */ +export function IsIn(values: any[], validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_IN, + constraints: [values], + validate: (value, args) => isIn(value, args.constraints[0]), + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + "$property must be one of the following values: $constraint1", + validationOptions), + + }, + validationOptions + ); +} diff --git a/src/decorator/common/IsNotEmpty.ts b/src/decorator/common/IsNotEmpty.ts new file mode 100644 index 0000000000..41896d67b8 --- /dev/null +++ b/src/decorator/common/IsNotEmpty.ts @@ -0,0 +1,23 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_NOT_EMPTY = "isNotEmpty"; + +/** + * Checks if given value is not empty (!== '', !== null, !== undefined). + */ +export function isNotEmpty(value: any): boolean { + return value !== "" && value !== null && value !== undefined; +} + +/** + * Checks if given value is not empty (!== '', !== null, !== undefined). + */ +export function IsNotEmpty(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_NOT_EMPTY, + validate: (value, args) => isNotEmpty(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be empty", validationOptions), + + }, validationOptions); +} diff --git a/src/decorator/common/IsNotIn.ts b/src/decorator/common/IsNotIn.ts new file mode 100644 index 0000000000..063acff930 --- /dev/null +++ b/src/decorator/common/IsNotIn.ts @@ -0,0 +1,28 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_NOT_IN = "isNotIn"; + +/** + * Checks if given value not in a array of allowed values. + */ +export function isNotIn(value: any, possibleValues: any[]): boolean { + return !(possibleValues instanceof Array) || !possibleValues.some(possibleValue => possibleValue === value); +} + +/** + * Checks if value is not in a array of disallowed values. + */ +export function IsNotIn(values: any[], validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_NOT_IN, + validate: (value, args) => isNotIn(value, args.constraints[0]), + constraints: [values], + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + "$property should not be one of the following values: $constraint1", + validationOptions), + + }, + validationOptions + ); +} diff --git a/src/decorator/common/NotEquals.ts b/src/decorator/common/NotEquals.ts new file mode 100644 index 0000000000..b02c4bdfca --- /dev/null +++ b/src/decorator/common/NotEquals.ts @@ -0,0 +1,24 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const NOT_EQUALS = "notEquals"; + +/** + * Checks if value does not match ("!==") the comparison. + */ +export function notEquals(value: any, comparison: any): boolean { + return value !== comparison; +} + +/** + * Checks if the value does not match ("!==") the comparison. + */ +export function NotEquals(comparison: any, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: NOT_EQUALS, + constraints: [comparison], + validate: (value, args) => notEquals(value, args.constraints[0]), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be equal to $constraint1", validationOptions) + }, + validationOptions); +} diff --git a/src/decorator/date/MaxDate.ts b/src/decorator/date/MaxDate.ts new file mode 100644 index 0000000000..79a552ecba --- /dev/null +++ b/src/decorator/date/MaxDate.ts @@ -0,0 +1,25 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const MAX_DATE = "maxDate"; + +/** + * Checks if the value is a date that's before the specified date. + */ +export function maxDate(date: Date, maxDate: Date): boolean { + return date && date.getTime() <= maxDate.getTime(); +} + +/** + * Checks if the value is a date that's before the specified date. + */ +export function MaxDate(date: Date, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: MAX_DATE, + validate: (value, args) => maxDate(value, args.constraints[0]), + constraints: [date], + defaultMessage: buildMessage((eachPrefix) => "maximal allowed date for " + eachPrefix + "$property is $constraint1", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/date/MinDate.ts b/src/decorator/date/MinDate.ts new file mode 100644 index 0000000000..d9803fb5b7 --- /dev/null +++ b/src/decorator/date/MinDate.ts @@ -0,0 +1,25 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const MIN_DATE = "minDate"; + +/** + * Checks if the value is a date that's after the specified date. + */ +export function minDate(date: Date, minDate: Date): boolean { + return date && date.getTime() >= minDate.getTime(); +} + +/** + * Checks if the value is a date that's after the specified date. + */ +export function MinDate(date: Date, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: MIN_DATE, + validate: (value, args) => minDate(value, args.constraints[0]), + constraints: [date], + defaultMessage: buildMessage((eachPrefix) => "minimal allowed date for " + eachPrefix + "$property is $constraint1", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/decorators.ts b/src/decorator/decorators.ts deleted file mode 100644 index 370bc7cd18..0000000000 --- a/src/decorator/decorators.ts +++ /dev/null @@ -1,1174 +0,0 @@ -import {ValidationTypes} from "../validation/ValidationTypes"; -import {IsNumberOptions} from "../validation/ValidationTypeOptions"; -import {ValidationOptions} from "./ValidationOptions"; -import {ValidationMetadata} from "../metadata/ValidationMetadata"; -import {ValidationMetadataArgs} from "../metadata/ValidationMetadataArgs"; -import {ConstraintMetadata} from "../metadata/ConstraintMetadata"; -import {getFromContainer} from "../container"; -import {MetadataStorage} from "../metadata/MetadataStorage"; - -// ------------------------------------------------------------------------- -// System -// ------------------------------------------------------------------------- - -/** - * Registers custom validator class. - */ -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(/^_/, ""); - } - const metadata = new ConstraintMetadata(target, name, isAsync); - getFromContainer(MetadataStorage).addConstraintMetadata(metadata); - }; -} - -/** - * Performs validation based on the given custom validation class. - * Validation class must be decorated with ValidatorConstraint decorator. - */ -export function Validate(constraintClass: Function, validationOptions?: ValidationOptions): Function; -export function Validate(constraintClass: Function, constraints?: any[], validationOptions?: ValidationOptions): Function; -export function Validate(constraintClass: Function, constraintsOrValidationOptions?: any[]|ValidationOptions, maybeValidationOptions?: ValidationOptions): Function { - return function(object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.CUSTOM_VALIDATION, - target: object.constructor, - propertyName: propertyName, - constraintCls: constraintClass, - constraints: constraintsOrValidationOptions instanceof Array ? constraintsOrValidationOptions as any[] : undefined, - validationOptions: !(constraintsOrValidationOptions instanceof Array) ? constraintsOrValidationOptions as ValidationOptions : maybeValidationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Objects / object arrays marked with this decorator will also be validated. - */ -export function ValidateNested(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.NESTED_VALIDATION, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * If object has both allowed and not allowed properties a validation error will be thrown. - */ -export function Allow(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.WHITELIST, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - - -/** - * Objects / object arrays marked with this decorator will also be validated. - */ -export function ValidateIf(condition: (object: any, value: any) => boolean, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.CONDITIONAL_VALIDATION, - target: object.constructor, - propertyName: propertyName, - constraints: [condition], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -// ------------------------------------------------------------------------- -// Common checkers -// ------------------------------------------------------------------------- - -/** - * Checks if given value is defined (!== undefined, !== null). - */ -export function IsDefined(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_DEFINED, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the value match ("===") the comparison. - */ -export function Equals(comparison: any, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.EQUALS, - target: object.constructor, - propertyName: propertyName, - constraints: [comparison], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the value does not match ("!==") the comparison. - */ -export function NotEquals(comparison: any, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.NOT_EQUALS, - target: object.constructor, - propertyName: propertyName, - constraints: [comparison], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if given value is empty (=== '', === null, === undefined). - */ -export function IsEmpty(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_EMPTY, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if given value is not empty (!== '', !== null, !== undefined). - */ -export function IsNotEmpty(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_NOT_EMPTY, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if value is in a array of allowed values. - */ -export function IsIn(values: any[], validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_IN, - target: object.constructor, - propertyName: propertyName, - constraints: [values], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if value is not in a array of disallowed values. - */ -export function IsNotIn(values: any[], validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_NOT_IN, - target: object.constructor, - propertyName: propertyName, - constraints: [values], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if value is missing and if so, ignores all validators. - */ -export function IsOptional(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.CONDITIONAL_VALIDATION, - target: object.constructor, - propertyName: propertyName, - constraints: [(object: any, value: any) => { - return object[propertyName] !== null && object[propertyName] !== undefined; - }], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -// ------------------------------------------------------------------------- -// Type checkers -// ------------------------------------------------------------------------- - -/** - * Checks if a value is a boolean. - */ -export function IsBoolean(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_BOOLEAN, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if a value is a date. - */ -export function IsDate(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_DATE, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if a value is a number. - */ -export function IsNumber(options: IsNumberOptions = {}, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_NUMBER, - target: object.constructor, - propertyName: propertyName, - constraints: [options], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the value is an integer number. - */ -export function IsInt(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_INT, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if a value is a string. - */ -export function IsString(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_STRING, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -export function IsDateString(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_DATE_STRING, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if a value is an array. - */ -export function IsArray(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_ARRAY, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if a value is a number enum. - */ -export function IsEnum(entity: Object, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_ENUM, - target: object.constructor, - propertyName: propertyName, - constraints: [entity], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - - -// ------------------------------------------------------------------------- -// Number checkers -// ------------------------------------------------------------------------- - -/** - * Checks if the value is a number that's divisible by another. - */ -export function IsDivisibleBy(num: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_DIVISIBLE_BY, - target: object.constructor, - propertyName: propertyName, - constraints: [num], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the value is a positive number. - */ -export function IsPositive(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_POSITIVE, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the value is a negative number. - */ -export function IsNegative(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_NEGATIVE, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} -/** - * Checks if the given number is greater than or equal to given number. - */ -export function Min(min: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.MIN, - target: object.constructor, - propertyName: propertyName, - constraints: [min], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the given number is less than or equal to given number. - */ -export function Max(max: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.MAX, - target: object.constructor, - propertyName: propertyName, - constraints: [max], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -// ------------------------------------------------------------------------- -// Date checkers -// ------------------------------------------------------------------------- - -/** - * Checks if the value is a date that's after the specified date. - */ -export function MinDate(date: Date, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.MIN_DATE, - target: object.constructor, - propertyName: propertyName, - constraints: [date], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the value is a date that's before the specified date. - */ -export function MaxDate(date: Date, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.MAX_DATE, - target: object.constructor, - propertyName: propertyName, - constraints: [date], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -// ------------------------------------------------------------------------- -// String-as-types checkers -// ------------------------------------------------------------------------- - -/** - * Checks if a string is a boolean. - */ -export function IsBooleanString(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_BOOLEAN_STRING, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a number. - */ -export function IsNumberString(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_NUMBER_STRING, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -// ------------------------------------------------------------------------- -// String checkers -// ------------------------------------------------------------------------- - -/** - * Checks if the string contains the seed. - */ -export function Contains(seed: string, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.CONTAINS, - target: object.constructor, - propertyName: propertyName, - constraints: [seed], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string does not contain the seed. - */ -export function NotContains(seed: string, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.NOT_CONTAINS, - target: object.constructor, - propertyName: propertyName, - constraints: [seed], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string contains only letters (a-zA-Z). - */ -export function IsAlpha(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_ALPHA, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string contains only letters and numbers. - */ -export function IsAlphanumeric(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_ALPHANUMERIC, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string contains ASCII chars only. - */ -export function IsAscii(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_ASCII, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if a string is base64 encoded. - */ -export function IsBase64(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_BASE64, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string's length (in bytes) falls in a range. - */ -export function IsByteLength(min: number, max?: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_BYTE_LENGTH, - target: object.constructor, - propertyName: propertyName, - constraints: [min, max], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a credit card. - */ -export function IsCreditCard(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_CREDIT_CARD, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a valid currency amount. - */ -export function IsCurrency(options?: ValidatorJS.IsCurrencyOptions, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_CURRENCY, - target: object.constructor, - propertyName: propertyName, - constraints: [options], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is an email. - */ -export function IsEmail(options?: ValidatorJS.IsEmailOptions, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_EMAIL, - target: object.constructor, - propertyName: propertyName, - constraints: [options], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a fully qualified domain name (e.g. domain.com). - */ -export function IsFQDN(options?: ValidatorJS.IsFQDNOptions, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_FQDN, - target: object.constructor, - propertyName: propertyName, - constraints: [options], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string contains any full-width chars. - */ -export function IsFullWidth(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_FULL_WIDTH, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string contains any half-width chars. - */ -export function IsHalfWidth(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_HALF_WIDTH, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string contains a mixture of full and half-width chars. - */ -export function IsVariableWidth(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_VARIABLE_WIDTH, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a hexadecimal color. - */ -export function IsHexColor(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_HEX_COLOR, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a hexadecimal number. - */ -export function IsHexadecimal(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_HEXADECIMAL, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is an IP (version 4 or 6). - */ -export function IsIP(version?: "4"|"6", validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_IP, - target: object.constructor, - propertyName: propertyName, - constraints: [version], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is an ISBN (version 10 or 13). - */ -export function IsISBN(version?: "10"|"13", validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_ISBN, - target: object.constructor, - propertyName: propertyName, - constraints: [version], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is an ISIN (stock/security identifier). - */ -export function IsISIN(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_ISIN, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a valid ISO 8601 date. - */ -export function IsISO8601(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_ISO8601, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is valid JSON (note: uses JSON.parse). - */ -export function IsJSON(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_JSON, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is lowercase. - */ -export function IsLowercase(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_LOWERCASE, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a mobile phone number (locale is one of ['zh-CN', 'zh-TW', 'en-ZA', 'en-AU', 'en-HK', - * 'pt-PT', 'fr-FR', 'el-GR', 'en-GB', 'en-US', 'en-ZM', 'ru-RU', 'nb-NO', 'nn-NO', 'vi-VN', 'en-NZ']). - */ -export function IsMobilePhone(locale: string, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_MOBILE_PHONE, - target: object.constructor, - propertyName: propertyName, - constraints: [locale], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a valid phone number. - * @param {string} region 2 characters uppercase country code (e.g. DE, US, CH). - * If users must enter the intl. prefix (e.g. +41), then you may pass "ZZ" or null as region. - * See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github]{@link https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33} - */ -export function IsPhoneNumber(region: string, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_PHONE_NUMBER, - target: object.constructor, - propertyName: propertyName, - constraints: [region], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. - */ -export function IsMongoId(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_MONGO_ID, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string contains one or more multibyte chars. - */ -export function IsMultibyte(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_MULTIBYTE, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string contains any surrogate pairs chars. - */ -export function IsSurrogatePair(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_SURROGATE_PAIR, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is an url. - */ -export function IsUrl(options?: ValidatorJS.IsURLOptions, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_URL, - target: object.constructor, - propertyName: propertyName, - constraints: [options], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is a UUID (version 3, 4 or 5). - */ -export function IsUUID(version?: "3"|"4"|"5", validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_UUID, - target: object.constructor, - propertyName: propertyName, - constraints: [version], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string is uppercase. - */ -export function IsUppercase(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_UPPERCASE, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string's length falls in a range. Note: this function takes into account surrogate pairs. - */ -export function Length(min: number, max?: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.LENGTH, - target: object.constructor, - propertyName: propertyName, - constraints: [min, max], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string's length is not less than given number. Note: this function takes into account surrogate pairs. - */ -export function MinLength(min: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.MIN_LENGTH, - target: object.constructor, - propertyName: propertyName, - constraints: [min], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string's length is not more than given number. Note: this function takes into account surrogate pairs. - */ -export function MaxLength(max: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.MAX_LENGTH, - target: object.constructor, - propertyName: propertyName, - constraints: [max], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if string matches the pattern. Either matches('foo', /foo/i) or matches('foo', 'foo', 'i'). - */ -export function Matches(pattern: RegExp, validationOptions?: ValidationOptions): Function; -export function Matches(pattern: RegExp, modifiers?: string, validationOptions?: ValidationOptions): Function; -export function Matches(pattern: RegExp, modifiersOrAnnotationOptions?: string|ValidationOptions, validationOptions?: ValidationOptions): Function { - let modifiers: string; - if (modifiersOrAnnotationOptions && modifiersOrAnnotationOptions instanceof Object && !validationOptions) { - validationOptions = modifiersOrAnnotationOptions as ValidationOptions; - } else { - modifiers = modifiersOrAnnotationOptions as string; - } - - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.MATCHES, - target: object.constructor, - propertyName: propertyName, - constraints: [pattern, modifiers], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if the string correctly represents a time in the format HH:MM - */ -export function IsMilitaryTime(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_MILITARY_TIME, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -// ------------------------------------------------------------------------- -// Array checkers -// ------------------------------------------------------------------------- - -/** - * Checks if array contains all values from the given array of values. - */ -export function ArrayContains(values: any[], validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.ARRAY_CONTAINS, - target: object.constructor, - propertyName: propertyName, - constraints: [values], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if array does not contain any of the given values. - */ -export function ArrayNotContains(values: any[], validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.ARRAY_NOT_CONTAINS, - target: object.constructor, - propertyName: propertyName, - constraints: [values], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if given array is not empty. - */ -export function ArrayNotEmpty(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.ARRAY_NOT_EMPTY, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if array's length is as minimal this number. - */ -export function ArrayMinSize(min: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.ARRAY_MIN_SIZE, - target: object.constructor, - propertyName: propertyName, - constraints: [min], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if array's length is as maximal this number. - */ -export function ArrayMaxSize(max: number, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.ARRAY_MAX_SIZE, - target: object.constructor, - propertyName: propertyName, - constraints: [max], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if all array's values are unique. Comparison for objects is reference-based. - */ -export function ArrayUnique(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.ARRAY_UNIQUE, - target: object.constructor, - propertyName: propertyName, - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} - -/** - * Checks if all array's values are unique. Comparison for objects is reference-based. - */ -export function IsInstance(targetType: new (...args: any[]) => any, validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - const args: ValidationMetadataArgs = { - type: ValidationTypes.IS_INSTANCE, - target: object.constructor, - propertyName: propertyName, - constraints: [targetType], - validationOptions: validationOptions - }; - getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); - }; -} diff --git a/src/decorator/number/IsDivisibleBy.ts b/src/decorator/number/IsDivisibleBy.ts new file mode 100644 index 0000000000..1cd41a439e --- /dev/null +++ b/src/decorator/number/IsDivisibleBy.ts @@ -0,0 +1,28 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import * as validatorIsDivisibleBy from "validator/lib/isDivisibleBy"; + +export const IS_DIVISIBLE_BY = "isDivisibleBy"; + +/** + * Checks if value is a number that's divisible by another. + */ +export function isDivisibleBy(value: number, num: number): boolean { + return typeof value === "number" && + typeof num === "number" && + validatorIsDivisibleBy(String(value), num); +} + +/** + * Checks if the value is a number that's divisible by another. + */ +export function IsDivisibleBy(num: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_DIVISIBLE_BY, + validate: (value, args) => isDivisibleBy(value, args.constraints[0]), + constraints: [num], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be divisible by $constraint1", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/number/IsNegative.ts b/src/decorator/number/IsNegative.ts new file mode 100644 index 0000000000..f31ef20530 --- /dev/null +++ b/src/decorator/number/IsNegative.ts @@ -0,0 +1,24 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_NEGATIVE = "isNegative"; + +/** + * Checks if the value is a negative number. + */ +export function isNegative(value: number): boolean { + return typeof value === "number" && value < 0; +} + +/** + * Checks if the value is a negative number. + */ +export function IsNegative(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_NEGATIVE, + validate: (value) => isNegative(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a negative number", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/number/IsPositive.ts b/src/decorator/number/IsPositive.ts new file mode 100644 index 0000000000..31ae1eeb48 --- /dev/null +++ b/src/decorator/number/IsPositive.ts @@ -0,0 +1,24 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_POSITIVE = "isPositive"; + +/** + * Checks if the value is a positive number. + */ +export function isPositive(value: number): boolean { + return typeof value === "number" && value > 0; +} + +/** + * Checks if the value is a positive number. + */ +export function IsPositive(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_POSITIVE, + validate: (value) => isPositive(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a positive number", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/number/Max.ts b/src/decorator/number/Max.ts new file mode 100644 index 0000000000..ff34bb6995 --- /dev/null +++ b/src/decorator/number/Max.ts @@ -0,0 +1,25 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const MAX = "max"; + +/** + * Checks if the first number is less than or equal to the second/reference. + */ +export function max(num: number, reference: number): boolean { + return typeof num === "number" && typeof reference === "number" && num <= reference; +} + +/** + * Checks if the given number is less than or equal to the reference number. + */ +export function Max(reference: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: MAX, + validate: (value, args) => max(value, args.constraints[0]), + constraints: [reference], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must not be greater than $constraint1", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/number/Min.ts b/src/decorator/number/Min.ts new file mode 100644 index 0000000000..2fd38e1e20 --- /dev/null +++ b/src/decorator/number/Min.ts @@ -0,0 +1,25 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const MIN = "min"; + +/** + * Checks if the first number is greater than or equal to the second/reference. + */ +export function min(num: number, reference: number): boolean { + return typeof num === "number" && typeof reference === "number" && num >= reference; +} + +/** + * Checks if the given number is greater than or equal to reference number. + */ +export function Min(reference: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: MIN, + validate: (value, args) => min(value, args.constraints[0]), + constraints: [reference], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must not be less than $constraint1", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string-as-type/IsBooleanString.ts b/src/decorator/string-as-type/IsBooleanString.ts new file mode 100644 index 0000000000..07433be51a --- /dev/null +++ b/src/decorator/string-as-type/IsBooleanString.ts @@ -0,0 +1,26 @@ +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {ValidationOptions} from "../ValidationOptions"; +import validatorJsIsBoolean = require("validator/lib/isBoolean"); + +export const IS_BOOLEAN_STRING = "isBooleanString"; + +/** + * Checks if a string is a boolean. + * If given value is not a string, then it returns false. + */ +export function isBooleanString(value: string): boolean { + return typeof value === "string" && validatorJsIsBoolean(value); +} + +/** + * Checks if a string is a boolean. + */ +export function IsBooleanString(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_BOOLEAN_STRING, + validate: (value) => isBooleanString(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + eachPrefix + "$property must be a boolean string", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string-as-type/IsDateString.ts b/src/decorator/string-as-type/IsDateString.ts new file mode 100644 index 0000000000..d62dd53cbe --- /dev/null +++ b/src/decorator/string-as-type/IsDateString.ts @@ -0,0 +1,23 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {isString} from "../typechecker/IsString"; + +export const IS_DATE_STRING = "isDateString"; + +/** + * Checks if a given value is a ISOString date. + */ +export function isDateString(value: any): boolean { + const regex = /^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|\+[0-2]\d(?:\:[0-5]\d)?)?$/g; + return isString(value) && regex.test(value); +} + +export function IsDateString(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_DATE_STRING, + validate: (value) => isDateString(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a ISOString", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string-as-type/IsNumberString.ts b/src/decorator/string-as-type/IsNumberString.ts new file mode 100644 index 0000000000..eef8cbed36 --- /dev/null +++ b/src/decorator/string-as-type/IsNumberString.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsNumeric = require("validator/lib/isNumeric"); + +export const IS_NUMBER_STRING = "isNumberString"; + +/** + * Checks if the string is numeric. + * If given value is not a string, then it returns false. + */ +export function isNumberString(value: string, options?: ValidatorJS.IsNumericOptions): boolean { + return typeof value === "string" && validatorJsIsNumeric(value, options); +} + +/** + * Checks if the string is a number. + */ +export function IsNumberString(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_NUMBER_STRING, + validate: (value) => isNumberString(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a number string", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/Contains.ts b/src/decorator/string/Contains.ts new file mode 100644 index 0000000000..ecb9483631 --- /dev/null +++ b/src/decorator/string/Contains.ts @@ -0,0 +1,28 @@ +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {ValidationOptions} from "../ValidationOptions"; +import validatorJsContains = require("validator/lib/contains"); + +export const CONTAINS = "contains"; + +/** + * Checks if the string contains the seed. + * If given value is not a string, then it returns false. + */ +export function contains(value: string, seed: string): boolean { + return typeof value === "string" && validatorJsContains(value, seed); +} + +/** + * Checks if the string contains the seed. + */ +export function Contains(seed: string, validationOptions?: ValidationOptions) { + + return ValidateBy({ + name: CONTAINS, + validate: (value, args) => contains(value, args.constraints[0]), + constraints: [seed], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain a $constraint1 string", validationOptions), + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsAlpha.ts b/src/decorator/string/IsAlpha.ts new file mode 100644 index 0000000000..6396a93cd6 --- /dev/null +++ b/src/decorator/string/IsAlpha.ts @@ -0,0 +1,28 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsAlpha = require("validator/lib/isAlpha"); + +export const IS_ALPHA = "isAlpha"; + +/** + * Checks if the string contains only letters (a-zA-Z). + * If given value is not a string, then it returns false. + */ +export function isAlpha(value: string): boolean { + return typeof value === "string" && validatorJsIsAlpha(value); +} + + +/** + * Checks if the string contains only letters (a-zA-Z). + */ +export function IsAlpha(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_ALPHA, + validate: (value) => isAlpha(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain only letters (a-zA-Z)", validationOptions) + }, + validationOptions + ); +} + diff --git a/src/decorator/string/IsAlphanumeric.ts b/src/decorator/string/IsAlphanumeric.ts new file mode 100644 index 0000000000..ba31d5844c --- /dev/null +++ b/src/decorator/string/IsAlphanumeric.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsAlphanumeric = require("validator/lib/isAlphanumeric"); + +export const IS_ALPHANUMERIC = "isAlphanumeric"; + +/** + * Checks if the string contains only letters and numbers. + * If given value is not a string, then it returns false. + */ +export function isAlphanumeric(value: string): boolean { + return typeof value === "string" && validatorJsIsAlphanumeric(value); +} + +/** + * Checks if the string contains only letters and numbers. + */ +export function IsAlphanumeric(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_ALPHANUMERIC, + validate: (value) => isAlphanumeric(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain only letters and numbers", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsAscii.ts b/src/decorator/string/IsAscii.ts new file mode 100644 index 0000000000..71f7c09ef5 --- /dev/null +++ b/src/decorator/string/IsAscii.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsAscii = require("validator/lib/isAscii"); + +export const IS_ASCII = "isAscii"; + +/** + * Checks if the string contains ASCII chars only. + * If given value is not a string, then it returns false. + */ +export function isAscii(value: string): boolean { + return typeof value === "string" && validatorJsIsAscii(value); +} + +/** + * Checks if the string contains ASCII chars only. + */ +export function IsAscii(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_ASCII, + validate: (value) => isAscii(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain only ASCII characters", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsBase64.ts b/src/decorator/string/IsBase64.ts new file mode 100644 index 0000000000..c184295c9c --- /dev/null +++ b/src/decorator/string/IsBase64.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsBase64 = require("validator/lib/isBase64"); + +export const IS_BASE64 = "isBase64"; + +/** + * Checks if a string is base64 encoded. + * If given value is not a string, then it returns false. + */ +export function isBase64(value: string): boolean { + return typeof value === "string" && validatorJsIsBase64(value); +} + +/** + * Checks if a string is base64 encoded. + */ +export function IsBase64(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_BASE64, + validate: (value) => isBase64(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be base64 encoded", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsByteLength.ts b/src/decorator/string/IsByteLength.ts new file mode 100644 index 0000000000..bacbd6b271 --- /dev/null +++ b/src/decorator/string/IsByteLength.ts @@ -0,0 +1,28 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsByteLength = require("validator/lib/isByteLength"); + +export const IS_BYTE_LENGTH = "isByteLength"; + +/** + * Checks if the string's length (in bytes) falls in a range. + * If given value is not a string, then it returns false. + */ +export function isByteLength(value: string, min: number, max?: number): boolean { + return typeof value === "string" && validatorJsIsByteLength(value, min, max); +} + + +/** + * Checks if the string's length (in bytes) falls in a range. + */ +export function IsByteLength(min: number, max?: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_BYTE_LENGTH, + validate: (value, args) => isByteLength(value, args.constraints[0], args.constraints[1]), + constraints: [min, max], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property's byte length must fall into ($constraint1, $constraint2) range", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsCreditCard.ts b/src/decorator/string/IsCreditCard.ts new file mode 100644 index 0000000000..69957722c9 --- /dev/null +++ b/src/decorator/string/IsCreditCard.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsCreditCard = require("validator/lib/isCreditCard"); + +export const IS_CREDIT_CARD = "isCreditCard"; + +/** + * Checks if the string is a credit card. + * If given value is not a string, then it returns false. + */ +export function isCreditCard(value: string): boolean { + return typeof value === "string" && validatorJsIsCreditCard(value); +} + + +/** + * Checks if the string is a credit card. + */ +export function IsCreditCard(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_CREDIT_CARD, + validate: (value) => isCreditCard(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a credit card", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsCurrency.ts b/src/decorator/string/IsCurrency.ts new file mode 100644 index 0000000000..be0d3b570d --- /dev/null +++ b/src/decorator/string/IsCurrency.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsCurrency = require("validator/lib/isCurrency"); + +export const IS_CURRENCY = "isCurrency"; + +/** + * Checks if the string is a valid currency amount. + * If given value is not a string, then it returns false. + */ +export function isCurrency(value: string, options?: ValidatorJS.IsCurrencyOptions): boolean { + return typeof value === "string" && validatorJsIsCurrency(value, options); +} + +/** + * Checks if the string is a valid currency amount. + */ +export function IsCurrency(options?: ValidatorJS.IsCurrencyOptions, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_CURRENCY, + validate: (value, args) => isCurrency(value, args.constraints[0]), + constraints: [options], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a currency", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsEmail.ts b/src/decorator/string/IsEmail.ts new file mode 100644 index 0000000000..470a547b7e --- /dev/null +++ b/src/decorator/string/IsEmail.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsEmail = require("validator/lib/isEmail"); + +export const IS_EMAIL = "isEmail"; + +/** + * Checks if the string is an email. + * If given value is not a string, then it returns false. + */ +export function isEmail(value: string, options?: ValidatorJS.IsEmailOptions): boolean { + return typeof value === "string" && validatorJsIsEmail(value, options); +} + +/** + * Checks if the string is an email. + */ +export function IsEmail(options?: ValidatorJS.IsEmailOptions, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_EMAIL, + validate: (value, args) => isEmail(value, args.constraints[0]), + constraints: [options], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an email", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsFQDN.ts b/src/decorator/string/IsFQDN.ts new file mode 100644 index 0000000000..0ae4b93d36 --- /dev/null +++ b/src/decorator/string/IsFQDN.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsFQDN = require("validator/lib/isFQDN"); + +export const IS_FQDN = "isFqdn"; + +/** + * Checks if the string is a fully qualified domain name (e.g. domain.com). + * If given value is not a string, then it returns false. + */ +export function isFQDN(value: string, options?: ValidatorJS.IsFQDNOptions): boolean { + return typeof value === "string" && validatorJsIsFQDN(value, options); +} + +/** + * Checks if the string is a fully qualified domain name (e.g. domain.com). + */ +export function IsFQDN(options?: ValidatorJS.IsFQDNOptions, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_FQDN, + validate: (value, args) => isFQDN(value, args.constraints[0]), + constraints: [options], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid domain name", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsFullWidth.ts b/src/decorator/string/IsFullWidth.ts new file mode 100644 index 0000000000..003127e96a --- /dev/null +++ b/src/decorator/string/IsFullWidth.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +// strange module export :( +const validatorJsIsFullWidth: (value: any) => boolean = require("validator/lib/isFullWidth").default; + +export const IS_FULL_WIDTH = "isFullWidth"; + +/** + * Checks if the string contains any full-width chars. + * If given value is not a string, then it returns false. + */ +export function isFullWidth(value: string): boolean { + return typeof value === "string" && validatorJsIsFullWidth(value); +} + +/** + * Checks if the string contains any full-width chars. + */ +export function IsFullWidth(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_FULL_WIDTH, + validate: (value) => isFullWidth(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain a full-width characters", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsHalfWidth.ts b/src/decorator/string/IsHalfWidth.ts new file mode 100644 index 0000000000..c4bc004d3b --- /dev/null +++ b/src/decorator/string/IsHalfWidth.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +// strange module export :( +const validatorJsIsHalfWidth: (value: any) => boolean = require("validator/lib/isHalfWidth").default; + +export const IS_HALF_WIDTH = "isHalfWidth"; + +/** + * Checks if the string contains any half-width chars. + * If given value is not a string, then it returns false. + */ +export function isHalfWidth(value: string): boolean { + return typeof value === "string" && validatorJsIsHalfWidth(value); +} + +/** + * Checks if the string contains any half-width chars. + */ +export function IsHalfWidth(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_HALF_WIDTH, + validate: (value) => isHalfWidth(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain a half-width characters", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsHexColor.ts b/src/decorator/string/IsHexColor.ts new file mode 100644 index 0000000000..64fc5a649b --- /dev/null +++ b/src/decorator/string/IsHexColor.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsHexColor = require("validator/lib/isHexColor"); + +export const IS_HEX_COLOR = "isHexColor"; + +/** + * Checks if the string is a hexadecimal color. + * If given value is not a string, then it returns false. + */ +export function isHexColor(value: string): boolean { + return typeof value === "string" && validatorJsIsHexColor(value); +} + +/** + * Checks if the string is a hexadecimal color. + */ +export function IsHexColor(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_HEX_COLOR, + validate: (value) => isHexColor(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a hexadecimal color", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsHexadecimal.ts b/src/decorator/string/IsHexadecimal.ts new file mode 100644 index 0000000000..75780a62b7 --- /dev/null +++ b/src/decorator/string/IsHexadecimal.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsHexadecimal = require("validator/lib/isHexadecimal"); + +export const IS_HEXADECIMAL = "isHexadecimal"; + +/** + * Checks if the string is a hexadecimal number. + * If given value is not a string, then it returns false. + */ +export function isHexadecimal(value: string): boolean { + return typeof value === "string" && validatorJsIsHexadecimal(value); +} + +/** + * Checks if the string is a hexadecimal number. + */ +export function IsHexadecimal(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_HEXADECIMAL, + validate: (value) => isHexadecimal(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a hexadecimal number", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsIP.ts b/src/decorator/string/IsIP.ts new file mode 100644 index 0000000000..99e513c9c8 --- /dev/null +++ b/src/decorator/string/IsIP.ts @@ -0,0 +1,29 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsIP = require("validator/lib/isIP"); + +export const IS_IP = "isIp"; + +/** + * Checks if the string is an IP (version 4 or 6). + * If given value is not a string, then it returns false. + */ +export function isIP(value: string, version?: "4" | "6"): boolean { + // typings for isIP are wrong: JS actually accepts strings and numbers + const versionNr = version ? Number(version) : undefined; + return typeof value === "string" && validatorJsIsIP(value, versionNr); +} + +/** + * Checks if the string is an IP (version 4 or 6). + */ +export function IsIP(version?: "4" | "6", validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_IP, + validate: (value, args) => isIP(value, args.constraints[0]), + constraints: [version], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an ip address", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsISBN.ts b/src/decorator/string/IsISBN.ts new file mode 100644 index 0000000000..5ccd322150 --- /dev/null +++ b/src/decorator/string/IsISBN.ts @@ -0,0 +1,29 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsISBN = require("validator/lib/isISBN"); + +export const IS_ISBN = "isIsbn"; + +/** + * Checks if the string is an ISBN (version 10 or 13). + * If given value is not a string, then it returns false. + */ +export function isISBN(value: string, version?: "10" | "13"): boolean { + // typings are wrong: JS actually allows string or number + const versionNr = version ? Number(version) : undefined; + return typeof value === "string" && validatorJsIsISBN(value, versionNr); +} + +/** + * Checks if the string is an ISBN (version 10 or 13). + */ +export function IsISBN(version?: "10" | "13", validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_ISBN, + validate: (value, args) => isISBN(value, args.constraints[0]), + constraints: [version], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an ISBN", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsISIN.ts b/src/decorator/string/IsISIN.ts new file mode 100644 index 0000000000..e9efa99df2 --- /dev/null +++ b/src/decorator/string/IsISIN.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsISIN = require("validator/lib/isISIN"); + +export const IS_ISIN = "isIsin"; + +/** + * Checks if the string is an ISIN (stock/security identifier). + * If given value is not a string, then it returns false. + */ +export function isISIN(value: string): boolean { + return typeof value === "string" && validatorJsIsISIN(value); +} + +/** + * Checks if the string is an ISIN (stock/security identifier). + */ +export function IsISIN(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_ISIN, + validate: (value) => isISIN(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an ISIN (stock/security identifier)", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsISO8601.ts b/src/decorator/string/IsISO8601.ts new file mode 100644 index 0000000000..1a3b878c74 --- /dev/null +++ b/src/decorator/string/IsISO8601.ts @@ -0,0 +1,28 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsISO8601 = require("validator/lib/isISO8601"); + +export const IS_ISO8601 = "isIso8601"; + + +/** + * Checks if the string is a valid ISO 8601 date. + * If given value is not a string, then it returns false. + */ +export function isISO8601(value: string): boolean { + return typeof value === "string" && validatorJsIsISO8601(value); +} + + +/** + * Checks if the string is a valid ISO 8601 date. + */ +export function IsISO8601(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_ISO8601, + validate: (value) => isISO8601(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid ISO 8601 date string", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsJSON.ts b/src/decorator/string/IsJSON.ts new file mode 100644 index 0000000000..878766ea37 --- /dev/null +++ b/src/decorator/string/IsJSON.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsJSON = require("validator/lib/isJSON"); + +export const IS_JSON = "isJson"; + +/** + * Checks if the string is valid JSON (note: uses JSON.parse). + * If given value is not a string, then it returns false. + */ +export function isJSON(value: string): boolean { + return typeof value === "string" && validatorJsIsJSON(value); +} + +/** + * Checks if the string is valid JSON (note: uses JSON.parse). + */ +export function IsJSON(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_JSON, + validate: (value) => isJSON(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a json string", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsLowercase.ts b/src/decorator/string/IsLowercase.ts new file mode 100644 index 0000000000..8164a84d42 --- /dev/null +++ b/src/decorator/string/IsLowercase.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsLowercase = require("validator/lib/isLowercase"); + +export const IS_LOWERCASE = "isLowercase"; + +/** + * Checks if the string is lowercase. + * If given value is not a string, then it returns false. + */ +export function isLowercase(value: string): boolean { + return typeof value === "string" && validatorJsIsLowercase(value); +} + +/** + * Checks if the string is lowercase. + */ +export function IsLowercase(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_LOWERCASE, + validate: (value) => isLowercase(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a lowercase string", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsMilitaryTime.ts b/src/decorator/string/IsMilitaryTime.ts new file mode 100644 index 0000000000..59fae5c590 --- /dev/null +++ b/src/decorator/string/IsMilitaryTime.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {matches} from "./Matches"; + +export const IS_MILITARY_TIME = "isMilitaryTime"; + +/** + * Checks if the string represents a time without a given timezone in the format HH:MM (military) + * If the given value does not match the pattern HH:MM, then it returns false. + */ +export function isMilitaryTime(value: string): boolean { + return matches(value, /^([01]\d|2[0-3]):?([0-5]\d)$/); +} + + +/** + * Checks if the string correctly represents a time in the format HH:MM + */ +export function IsMilitaryTime(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_MILITARY_TIME, + validate: (value) => isMilitaryTime(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a military time", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsMobilePhone.ts b/src/decorator/string/IsMobilePhone.ts new file mode 100644 index 0000000000..385e258ec1 --- /dev/null +++ b/src/decorator/string/IsMobilePhone.ts @@ -0,0 +1,29 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsMobilePhone = require("validator/lib/isMobilePhone"); + +export const IS_MOBILE_PHONE = "isMobilePhone"; + +/** + * Checks if the string is a mobile phone number. See ValidatorJS for a list of supported locales! + * If given value is not a string, then it returns false. + */ +export function isMobilePhone(value: string, locale: ValidatorJS.MobilePhoneLocale | ValidatorJS.MobilePhoneLocale[] | "any"): boolean { + // typings are wrong: current ValidatorJS.isMobilePhone supports both one locale and an array of locales! + return typeof value === "string" && validatorJsIsMobilePhone(value, locale as any); +} + +/** + * Checks if the string is a mobile phone number. See ValidatorJS for a list of supported locales! + */ +export function IsMobilePhone(locale: ValidatorJS.MobilePhoneLocale | ValidatorJS.MobilePhoneLocale[] | any, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_MOBILE_PHONE, + validate: (value, args) => isMobilePhone(value, args.constraints[0]), + constraints: [locale], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a phone number", validationOptions) + }, + validationOptions + ); +} + diff --git a/src/decorator/string/IsMongoId.ts b/src/decorator/string/IsMongoId.ts new file mode 100644 index 0000000000..885e29b54f --- /dev/null +++ b/src/decorator/string/IsMongoId.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsMongoId = require("validator/lib/isMongoId"); + +export const IS_MONGO_ID = "isMongoId"; + +/** + * Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. + * If given value is not a string, then it returns false. + */ +export function isMongoId(value: string): boolean { + return typeof value === "string" && validatorJsIsMongoId(value); +} + +/** + * Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. + */ +export function IsMongoId(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_MONGO_ID, + validate: (value) => isMongoId(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a mongodb id", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsMultibyte.ts b/src/decorator/string/IsMultibyte.ts new file mode 100644 index 0000000000..341d44d656 --- /dev/null +++ b/src/decorator/string/IsMultibyte.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsMultibyte = require("validator/lib/isMultibyte"); + +export const IS_MULTIBYTE = "isMultibyte"; + +/** + * Checks if the string contains one or more multibyte chars. + * If given value is not a string, then it returns false. + */ +export function isMultibyte(value: string): boolean { + return typeof value === "string" && validatorJsIsMultibyte(value); +} + + +/** + * Checks if the string contains one or more multibyte chars. + */ +export function IsMultibyte(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_MULTIBYTE, + validate: (value) => isMultibyte(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain one or more multibyte chars", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsPhoneNumber.ts b/src/decorator/string/IsPhoneNumber.ts new file mode 100644 index 0000000000..bfc2876481 --- /dev/null +++ b/src/decorator/string/IsPhoneNumber.ts @@ -0,0 +1,45 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import * as libphonenumber from "google-libphonenumber"; + +const phoneUtil = libphonenumber.PhoneNumberUtil.getInstance(); + +export const IS_PHONE_NUMBER = "isPhoneNumber"; + +/** + * Checks if the string is a valid phone number. + * @param value the potential phone number string to test + * @param {string} region 2 characters uppercase country code (e.g. DE, US, CH). + * If users must enter the intl. prefix (e.g. +41), then you may pass "ZZ" or null as region. + * See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github]{@link https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33} + */ +export function isPhoneNumber(value: string, region: string | null): boolean { + try { + const phoneNum = phoneUtil.parseAndKeepRawInput(value, region); + const result = phoneUtil.isValidNumber(phoneNum); + return result; + } catch (error) { + // logging? + return false; + } +} + +/** + * Checks if the string is a valid phone number. + * @param region 2 characters uppercase country code (e.g. DE, US, CH). + * If users must enter the intl. prefix (e.g. +41), then you may pass "ZZ" or null as region. + * See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github]{@link https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33} + */ +export function IsPhoneNumber(region: string | null, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_PHONE_NUMBER, + constraints: [region], + validate: (value, args) => isPhoneNumber(value, args.constraints[0]), + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + "$property must be a valid phone number", + validationOptions), + + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsSurrogatePair.ts b/src/decorator/string/IsSurrogatePair.ts new file mode 100644 index 0000000000..854bd7a4de --- /dev/null +++ b/src/decorator/string/IsSurrogatePair.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsSurrogatePair = require("validator/lib/isSurrogatePair"); + +export const IS_SURROGATE_PAIR = "isSurrogatePair"; + +/** + * Checks if the string contains any surrogate pairs chars. + * If given value is not a string, then it returns false. + */ +export function isSurrogatePair(value: string): boolean { + return typeof value === "string" && validatorJsIsSurrogatePair(value); +} + + +/** + * Checks if the string contains any surrogate pairs chars. + */ +export function IsSurrogatePair(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_SURROGATE_PAIR, + validate: (value) => isSurrogatePair(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain any surrogate pairs chars", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsUUID.ts b/src/decorator/string/IsUUID.ts new file mode 100644 index 0000000000..ff61f65ffe --- /dev/null +++ b/src/decorator/string/IsUUID.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsUUID = require("validator/lib/isUUID"); + +export const IS_UUID = "isUuid"; + +/** + * Checks if the string is a UUID (version 3, 4 or 5). + * If given value is not a string, then it returns false. + */ +export function isUUID(value: string, version?: 3|4|5|"3"|"4"|"5"|"all"): boolean { + return typeof value === "string" && validatorJsIsUUID(value, version); +} + +/** + * Checks if the string is a UUID (version 3, 4 or 5). + */ +export function IsUUID(version?: 3|4|5|"3"|"4"|"5"|"all", validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_UUID, + validate: (value, args) => isUUID(value, args.constraints[0]), + constraints: [version], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an UUID", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsUppercase.ts b/src/decorator/string/IsUppercase.ts new file mode 100644 index 0000000000..1cb83cb073 --- /dev/null +++ b/src/decorator/string/IsUppercase.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsUppercase = require("validator/lib/isUppercase"); + +export const IS_UPPERCASE = "isUppercase"; + +/** + * Checks if the string is uppercase. + * If given value is not a string, then it returns false. + */ +export function isUppercase(value: string): boolean { + return typeof value === "string" && validatorJsIsUppercase(value); +} + + +/** + * Checks if the string is uppercase. + */ +export function IsUppercase(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_UPPERCASE, + validate: (value) => isUppercase(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be uppercase", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsUrl.ts b/src/decorator/string/IsUrl.ts new file mode 100644 index 0000000000..26a4ac962f --- /dev/null +++ b/src/decorator/string/IsUrl.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsURL = require("validator/lib/isURL"); + +export const IS_URL = "isUrl"; + +/** + * Checks if the string is an url. + * If given value is not a string, then it returns false. + */ +export function isURL(value: string, options?: ValidatorJS.IsURLOptions): boolean { + return typeof value === "string" && validatorJsIsURL(value, options); +} + +/** + * Checks if the string is an url. + */ +export function IsUrl(options?: ValidatorJS.IsURLOptions, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_URL, + validate: (value, args) => isURL(value, args.constraints[0]), + constraints: [options], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an URL address", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/IsVariableWidth.ts b/src/decorator/string/IsVariableWidth.ts new file mode 100644 index 0000000000..7cee6a8f34 --- /dev/null +++ b/src/decorator/string/IsVariableWidth.ts @@ -0,0 +1,26 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsVariableWidth = require("validator/lib/isVariableWidth"); + +export const IS_VARIABLE_WIDTH = "isVariableWidth"; + +/** + * Checks if the string contains variable-width chars. + * If given value is not a string, then it returns false. + */ +export function isVariableWidth(value: string): boolean { + return typeof value === "string" && validatorJsIsVariableWidth(value); +} + +/** + * Checks if the string contains a mixture of full and half-width chars. + */ +export function IsVariableWidth(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_VARIABLE_WIDTH, + validate: (value) => isVariableWidth(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain a full-width and half-width characters", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/Length.ts b/src/decorator/string/Length.ts new file mode 100644 index 0000000000..b0b2a840d4 --- /dev/null +++ b/src/decorator/string/Length.ts @@ -0,0 +1,36 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsIsLength = require("validator/lib/isLength"); + +export const LENGTH = "length"; + +/** + * Checks if the string's length falls in a range. Note: this function takes into account surrogate pairs. + * If given value is not a string, then it returns false. + */ +export function length(value: string, min: number, max?: number): boolean { + return typeof value === "string" && validatorJsIsLength(value, min, max); +} + +/** + * Checks if the string's length falls in a range. Note: this function takes into account surrogate pairs. + */ +export function Length(min: number, max?: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: LENGTH, + validate: (value, args) => length(value, args.constraints[0], args.constraints[1]), + constraints: [min, max], + defaultMessage: buildMessage((eachPrefix, args) => { + const isMinLength = args.constraints[0] !== null && args.constraints[0] !== undefined; + const isMaxLength = args.constraints[1] !== null && args.constraints[1] !== undefined; + if (isMinLength && (!args.value || args.value.length < args.constraints[0])) { + return eachPrefix + "$property must be longer than or equal to $constraint1 characters"; + } else if (isMaxLength && (args.value.length > args.constraints[1])) { + return eachPrefix + "$property must be shorter than or equal to $constraint2 characters"; + } + return eachPrefix + "$property must be longer than or equal to $constraint1 and shorter than or equal to $constraint2 characters"; + }, validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/Matches.ts b/src/decorator/string/Matches.ts new file mode 100644 index 0000000000..5d040effc0 --- /dev/null +++ b/src/decorator/string/Matches.ts @@ -0,0 +1,35 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import validatorJsMatches = require("validator/lib/matches"); + +export const MATCHES = "matches"; + +/** + * Checks if string matches the pattern. Either matches('foo', /foo/i) or matches('foo', 'foo', 'i'). + * If given value is not a string, then it returns false. + */ +export function matches(value: string, pattern: RegExp, modifiers?: string): boolean { + return typeof value === "string" && validatorJsMatches(value, pattern, modifiers); +} + +/** + * Checks if string matches the pattern. Either matches('foo', /foo/i) or matches('foo', 'foo', 'i'). + */ +export function Matches(pattern: RegExp, validationOptions?: ValidationOptions): Function; +export function Matches(pattern: RegExp, modifiers?: string, validationOptions?: ValidationOptions): Function; +export function Matches(pattern: RegExp, modifiersOrAnnotationOptions?: string | ValidationOptions, validationOptions?: ValidationOptions): Function { + let modifiers: string; + if (modifiersOrAnnotationOptions && modifiersOrAnnotationOptions instanceof Object && !validationOptions) { + validationOptions = modifiersOrAnnotationOptions as ValidationOptions; + } else { + modifiers = modifiersOrAnnotationOptions as string; + } + return ValidateBy({ + name: MATCHES, + validate: (value, args) => matches(value, args.constraints[0]), + constraints: [pattern, modifiers], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must match $constraint1 regular expression", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/MaxLength.ts b/src/decorator/string/MaxLength.ts new file mode 100644 index 0000000000..08455012be --- /dev/null +++ b/src/decorator/string/MaxLength.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {length} from "./Length"; + +export const MAX_LENGTH = "maxLength"; + +/** + * Checks if the string's length is not more than given number. Note: this function takes into account surrogate pairs. + * If given value is not a string, then it returns false. + */ +export function maxLength(value: string, max: number) { + return typeof value === "string" && length(value, 0, max); +} + +/** + * Checks if the string's length is not more than given number. Note: this function takes into account surrogate pairs. + */ +export function MaxLength(max: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: MAX_LENGTH, + validate: (value, args) => maxLength(value, args.constraints[0]), + constraints: [max], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be shorter than or equal to $constraint1 characters", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/MinLength.ts b/src/decorator/string/MinLength.ts new file mode 100644 index 0000000000..3a74b9ed65 --- /dev/null +++ b/src/decorator/string/MinLength.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {length} from "./Length"; + +export const MIN_LENGTH = "minLength"; + +/** + * Checks if the string's length is not less than given number. Note: this function takes into account surrogate pairs. + * If given value is not a string, then it returns false. + */ +export function minLength(value: string, min: number) { + return typeof value === "string" && length(value, min); +} + +/** + * Checks if the string's length is not less than given number. Note: this function takes into account surrogate pairs. + */ +export function MinLength(min: number, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: MIN_LENGTH, + validate: (value, args) => minLength(value, args.constraints[0]), + constraints: [min], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be longer than or equal to $constraint1 characters", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/string/NotContains.ts b/src/decorator/string/NotContains.ts new file mode 100644 index 0000000000..2c1062b750 --- /dev/null +++ b/src/decorator/string/NotContains.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {contains} from "./Contains"; + +export const NOT_CONTAINS = "notContains"; + +/** + * Checks if the string does not contain the seed. + * If given value is not a string, then it returns false. + */ +export function notContains(value: string, seed: string): boolean { + return typeof value === "string" && !contains(value, seed); +} + +/** + * Checks if the string does not contain the seed. + */ +export function NotContains(seed: string, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: NOT_CONTAINS, + validate: (value, args) => notContains(value, args.constraints[0]), + constraints: [seed], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not contain a $constraint1 string", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/system/Allow.ts b/src/decorator/system/Allow.ts new file mode 100644 index 0000000000..1ffe231830 --- /dev/null +++ b/src/decorator/system/Allow.ts @@ -0,0 +1,22 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {ValidationMetadataArgs} from "../../metadata/ValidationMetadataArgs"; +import {ValidationTypes} from "../../validation/ValidationTypes"; +import {getFromContainer} from "../../container"; +import {MetadataStorage} from "../../metadata/MetadataStorage"; +import {ValidationMetadata} from "../../metadata/ValidationMetadata"; + + +/** + * If object has both allowed and not allowed properties a validation error will be thrown. + */ +export function Allow(validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + const args: ValidationMetadataArgs = { + type: ValidationTypes.WHITELIST, + target: object.constructor, + propertyName: propertyName, + validationOptions: validationOptions + }; + getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); + }; +} diff --git a/src/decorator/system/IsDefined.ts b/src/decorator/system/IsDefined.ts new file mode 100644 index 0000000000..0796b5d976 --- /dev/null +++ b/src/decorator/system/IsDefined.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {ValidationTypes} from "../../validation/ValidationTypes"; + +// isDefined is (yet) a special case +export const IS_DEFINED = ValidationTypes.IS_DEFINED; + +/** + * Checks if value is defined (!== undefined, !== null). + */ +export function isDefined(value: any): boolean { + return value !== undefined && value !== null; +} + + +/** + * Checks if given value is defined (!== undefined, !== null). + */ +export function IsDefined(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_DEFINED, + validate: (value) => isDefined(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be null or undefined", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/system/IsOptional.ts b/src/decorator/system/IsOptional.ts new file mode 100644 index 0000000000..84c2f539b7 --- /dev/null +++ b/src/decorator/system/IsOptional.ts @@ -0,0 +1,24 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {ValidationMetadataArgs} from "../../metadata/ValidationMetadataArgs"; +import {ValidationTypes} from "../../validation/ValidationTypes"; +import {getFromContainer} from "../../container"; +import {MetadataStorage} from "../../metadata/MetadataStorage"; +import {ValidationMetadata} from "../../metadata/ValidationMetadata"; + +/** + * Checks if value is missing and if so, ignores all validators. + */ +export function IsOptional(validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + const args: ValidationMetadataArgs = { + type: ValidationTypes.CONDITIONAL_VALIDATION, + target: object.constructor, + propertyName: propertyName, + constraints: [(object: any, value: any) => { + return object[propertyName] !== null && object[propertyName] !== undefined; + }], + validationOptions: validationOptions + }; + getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); + }; +} diff --git a/src/decorator/system/ValidateIf.ts b/src/decorator/system/ValidateIf.ts new file mode 100644 index 0000000000..c68b5e1545 --- /dev/null +++ b/src/decorator/system/ValidateIf.ts @@ -0,0 +1,22 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {ValidationMetadataArgs} from "../../metadata/ValidationMetadataArgs"; +import {ValidationTypes} from "../../validation/ValidationTypes"; +import {getFromContainer} from "../../container"; +import {MetadataStorage} from "../../metadata/MetadataStorage"; +import {ValidationMetadata} from "../../metadata/ValidationMetadata"; + +/** + * Objects / object arrays marked with this decorator will also be validated. + */ +export function ValidateIf(condition: (object: any, value: any) => boolean, validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + const args: ValidationMetadataArgs = { + type: ValidationTypes.CONDITIONAL_VALIDATION, + target: object.constructor, + propertyName: propertyName, + constraints: [condition], + validationOptions: validationOptions + }; + getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); + }; +} diff --git a/src/decorator/system/ValidateNested.ts b/src/decorator/system/ValidateNested.ts new file mode 100644 index 0000000000..3fd7d5dfba --- /dev/null +++ b/src/decorator/system/ValidateNested.ts @@ -0,0 +1,25 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {ValidationMetadataArgs} from "../../metadata/ValidationMetadataArgs"; +import {ValidationTypes} from "../../validation/ValidationTypes"; +import {getFromContainer} from "../../container"; +import {MetadataStorage} from "../../metadata/MetadataStorage"; +import {ValidationMetadata} from "../../metadata/ValidationMetadata"; + +/** + * Objects / object arrays marked with this decorator will also be validated. + */ +export function ValidateNested(validationOptions?: ValidationOptions) { + const opts: ValidationOptions = {...validationOptions}; + const eachPrefix = opts.each ? "each value in " : ""; + opts.message = opts.message || eachPrefix + "nested property $property must be either object or array"; + + return function (object: Object, propertyName: string) { + const args: ValidationMetadataArgs = { + type: ValidationTypes.NESTED_VALIDATION, + target: object.constructor, + propertyName: propertyName, + validationOptions: opts, + }; + getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(args)); + }; +} diff --git a/src/decorator/typechecker/IsArray.ts b/src/decorator/typechecker/IsArray.ts new file mode 100644 index 0000000000..f6e1d1641b --- /dev/null +++ b/src/decorator/typechecker/IsArray.ts @@ -0,0 +1,24 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_ARRAY = "isArray"; + +/** + * Checks if a given value is an array + */ +export function isArray(value: any): boolean { + return value instanceof Array; +} + +/** + * Checks if a value is an array. + */ +export function IsArray(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_ARRAY, + validate: (value) => isArray(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an array", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/typechecker/IsBoolean.ts b/src/decorator/typechecker/IsBoolean.ts new file mode 100644 index 0000000000..a0fd1e7a46 --- /dev/null +++ b/src/decorator/typechecker/IsBoolean.ts @@ -0,0 +1,24 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_BOOLEAN = "isBoolean"; + +/** + * Checks if a given value is a real boolean. + */ +export function isBoolean(value: any): boolean { + return value instanceof Boolean || typeof value === "boolean"; +} + +/** + * Checks if a value is a boolean. + */ +export function IsBoolean(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_BOOLEAN, + validate: (value) => isBoolean(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a boolean value", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/typechecker/IsDate.ts b/src/decorator/typechecker/IsDate.ts new file mode 100644 index 0000000000..8ff367f07e --- /dev/null +++ b/src/decorator/typechecker/IsDate.ts @@ -0,0 +1,24 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_DATE = "isDate"; + +/** + * Checks if a given value is a real date. + */ +export function isDate(value: any): boolean { + return value instanceof Date && !isNaN(value.getTime()); +} + +/** + * Checks if a value is a date. + */ +export function IsDate(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_DATE, + validate: (value) => isDate(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a Date instance", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/typechecker/IsEnum.ts b/src/decorator/typechecker/IsEnum.ts new file mode 100644 index 0000000000..5bf2ad26aa --- /dev/null +++ b/src/decorator/typechecker/IsEnum.ts @@ -0,0 +1,27 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_ENUM = "isEnum"; + +/** + * Checks if a given value is an enum + */ +export function isEnum(value: any, entity: any): boolean { + const enumValues = Object.keys(entity) + .map(k => entity[k]); + return enumValues.indexOf(value) >= 0; +} + +/** + * Checks if a value is a number enum. + */ +export function IsEnum(entity: Object, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_ENUM, + validate: (value, args) => isEnum(value, args.constraints[0]), + constraints: [entity], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid enum value", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/typechecker/IsInstance.ts b/src/decorator/typechecker/IsInstance.ts new file mode 100644 index 0000000000..9adf7ff935 --- /dev/null +++ b/src/decorator/typechecker/IsInstance.ts @@ -0,0 +1,33 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_INSTANCE = "isInstance"; + +/** + * Checks if the value is an instance of the specified object. + */ +export function isInstance(object: any, targetTypeConstructor: new (...args: any[]) => any) { + return targetTypeConstructor + && typeof targetTypeConstructor === "function" + && object instanceof targetTypeConstructor; +} + +/** + * Checks if all array's values are unique. Comparison for objects is reference-based. + */ +export function IsInstance(targetType: new (...args: any[]) => any, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_INSTANCE, + validate: (value, args) => isInstance(value, args.constraints[0]), + constraints: [targetType], + defaultMessage: buildMessage((eachPrefix, args) => { + if (args.constraints[0]) { + return eachPrefix + `$property must be an instance of ${args.constraints[0].name}`; + } else { + return eachPrefix + `${this.IS_INSTANCE} decorator expects and object as value, but got falsy value.`; + } + }, validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/typechecker/IsInt.ts b/src/decorator/typechecker/IsInt.ts new file mode 100644 index 0000000000..25bbb06612 --- /dev/null +++ b/src/decorator/typechecker/IsInt.ts @@ -0,0 +1,24 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_INT = "isInt"; + +/** + * Checks if value is an integer. + */ +export function isInt(val: number): boolean { + return Number.isInteger(val); +} + +/** + * Checks if the value is an integer number. + */ +export function IsInt(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_INT, + validate: (value) => isInt(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an integer number", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/typechecker/IsNumber.ts b/src/decorator/typechecker/IsNumber.ts new file mode 100644 index 0000000000..0a68ff8cf5 --- /dev/null +++ b/src/decorator/typechecker/IsNumber.ts @@ -0,0 +1,41 @@ +import {ValidationOptions} from "../ValidationOptions"; +import {buildMessage, ValidateBy} from "../ValidateBy"; + +export const IS_NUMBER = "isNumber"; + +/** + * Options to be passed to IsNumber decorator. + */ +export interface IsNumberOptions { + allowNaN?: boolean; + allowInfinity?: boolean; +} + +/** + * Checks if a given value is a number. + */ +export function isNumber(value: any, options: IsNumberOptions = {}): boolean { + if (value === Infinity || value === -Infinity) { + return options.allowInfinity; + } + + if (Number.isNaN(value)) { + return options.allowNaN; + } + + return Number.isFinite(value); +} + +/** + * Checks if a value is a number. + */ +export function IsNumber(options: IsNumberOptions = {}, validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_NUMBER, + validate: (value, args) => isNumber(value, args.constraints[0]), + constraints: [options], + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a number", validationOptions) + }, + validationOptions + ); +} diff --git a/src/decorator/typechecker/IsString.ts b/src/decorator/typechecker/IsString.ts new file mode 100644 index 0000000000..532c04c036 --- /dev/null +++ b/src/decorator/typechecker/IsString.ts @@ -0,0 +1,24 @@ +import {buildMessage, ValidateBy} from "../ValidateBy"; +import {ValidationOptions} from "../ValidationOptions"; + +export const IS_STRING = "isString"; + +/** + * Checks if a given value is a real string. + */ +export function isString(value: any): boolean { + return value instanceof String || typeof value === "string"; +} + +/** + * Checks if a value is a string. + */ +export function IsString(validationOptions?: ValidationOptions) { + return ValidateBy({ + name: IS_STRING, + validate: (value) => isString(value), + defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a string", validationOptions) + }, + validationOptions + ); +} diff --git a/src/index.ts b/src/index.ts index 07001fc2a1..d3ec4a10a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,19 +9,20 @@ import {getFromContainer} from "./container"; // Export everything api users needs // ------------------------------------------------------------------------- -export * from "./container"; -export * from "./decorator/decorators"; -export * from "./decorator/ValidationOptions"; -export * from "./validation/ValidatorConstraintInterface"; -export * from "./validation/ValidationError"; -export * from "./validation/ValidationTypeOptions"; -export * from "./validation/ValidatorOptions"; -export * from "./validation/ValidationArguments"; -export * from "./validation/ValidationTypes"; -export * from "./validation/Validator"; -export * from "./validation-schema/ValidationSchema"; -export * from "./register-decorator"; -export * from "./metadata/MetadataStorage"; +// export * from "./container"; +// export * from "./decorator/decorators"; +// export * from "./decorator/ValidationOptions"; +// export * from "./validation/ValidatorConstraintInterface"; +// export * from "./validation/ValidationError"; +// export * from "./validation/ValidationTypeOptions"; +// export * from "./validation/ValidatorOptions"; +// export * from "./validation/ValidationArguments"; +// export * from "./validation/ValidationTypes"; +// export * from "./validation/Validator"; +// export * from "./validation-schema/ValidationSchema"; +// export * from "./register-decorator"; +// export * from "./metadata/MetadataStorage"; +// FIXME: re-add exports above and all decorators here? // ------------------------------------------------------------------------- // Shortcut methods for api users diff --git a/src/metadata/ValidationMetadataArgs.ts b/src/metadata/ValidationMetadataArgs.ts index 8dcdb904ba..0278944349 100644 --- a/src/metadata/ValidationMetadataArgs.ts +++ b/src/metadata/ValidationMetadataArgs.ts @@ -39,4 +39,10 @@ export interface ValidationMetadataArgs { * Extra options specific to validation type. */ validationTypeOptions?: any; -} \ No newline at end of file + + /** + * A transient set of data passed through to the validation result for response mapping + */ + context?: any; + +} diff --git a/src/register-decorator.ts b/src/register-decorator.ts index cdf6ca38ff..c7a9186039 100644 --- a/src/register-decorator.ts +++ b/src/register-decorator.ts @@ -51,11 +51,18 @@ export interface ValidationDecoratorOptions { */ export function registerDecorator(options: ValidationDecoratorOptions): void { + let name; let constraintCls: Function; if (options.validator instanceof Function) { constraintCls = options.validator as Function; + const constraintClasses = getFromContainer(MetadataStorage).getTargetValidatorConstraints(options.validator); + if (constraintClasses.length !== 1) { + throw `More than one implementation of ValidatorConstraintInterface found for validator on: ${options.target}:${options.propertyName}`; + } + name = options.name || constraintClasses[0].name; } else { const validator = options.validator as ValidatorConstraintInterface; + name = options.name; constraintCls = class CustomConstraint implements ValidatorConstraintInterface { validate(value: any, validationArguments?: ValidationArguments): Promise|boolean { return validator.validate(value, validationArguments); @@ -73,12 +80,13 @@ export function registerDecorator(options: ValidationDecoratorOptions): void { } const validationMetadataArgs: ValidationMetadataArgs = { - type: ValidationTypes.CUSTOM_VALIDATION, + type: name || ValidationTypes.CUSTOM_VALIDATION, target: options.target, propertyName: options.propertyName, validationOptions: options.options, constraintCls: constraintCls, - constraints: options.constraints + constraints: options.constraints, + context: (options.options || {}).context }; getFromContainer(MetadataStorage).addValidationMetadata(new ValidationMetadata(validationMetadataArgs)); } diff --git a/src/types.d.ts b/src/types.d.ts index 67607af4eb..879048a75a 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,3 +1,4 @@ declare var window: any; -declare module "ansicolor"; \ No newline at end of file +declare module "ansicolor"; +declare module "google-libphonenumber"; diff --git a/src/validation-schema/ValidationSchemaToMetadataTransformer.ts b/src/validation-schema/ValidationSchemaToMetadataTransformer.ts index fcc9eed5ef..ad4063efcc 100644 --- a/src/validation-schema/ValidationSchemaToMetadataTransformer.ts +++ b/src/validation-schema/ValidationSchemaToMetadataTransformer.ts @@ -2,7 +2,6 @@ import {ValidationSchema} from "./ValidationSchema"; import {ValidationMetadata} from "../metadata/ValidationMetadata"; import {ValidationMetadataArgs} from "../metadata/ValidationMetadataArgs"; import {ValidationOptions} from "../decorator/ValidationOptions"; -import {ValidationTypes} from "../validation/ValidationTypes"; /** * Used to transform validation schemas to validation metadatas. @@ -13,9 +12,6 @@ export class ValidationSchemaToMetadataTransformer { const metadatas: ValidationMetadata[] = []; Object.keys(schema.properties).forEach(property => { schema.properties[property].forEach(validation => { - if (!ValidationTypes.isValid(validation.type)) - throw new Error(`Validation schema ${schema.name}#${property} as incorrect type ${validation.type}`); - const validationOptions: ValidationOptions = { message: validation.message, groups: validation.groups, @@ -36,4 +32,4 @@ export class ValidationSchemaToMetadataTransformer { return metadatas; } -} \ No newline at end of file +} diff --git a/src/validation/ValidationExecutor.ts b/src/validation/ValidationExecutor.ts index 4c4248acfc..741fcc9676 100644 --- a/src/validation/ValidationExecutor.ts +++ b/src/validation/ValidationExecutor.ts @@ -43,7 +43,7 @@ export class ValidationExecutor { /** * If there is no metadata registered it means possibly the dependencies are not flatterned and * more than one instance is used. - * + * * TODO: This needs proper handling, forcing to use the same container or some other proper solution. */ if (!this.metadataStorage.hasValidationMetaData) { @@ -60,29 +60,39 @@ export class ValidationExecutor { if (!this.validatorOptions || !this.validatorOptions.validationError || this.validatorOptions.validationError.target === undefined || - this.validatorOptions.validationError.target === true) + this.validatorOptions.validationError.target === true) { validationError.target = object; + } validationError.value = undefined; validationError.property = undefined; validationError.children = []; - validationError.constraints = { unknownValue: "an unknown value was passed to the validate function" }; + validationError.constraints = {unknownValue: "an unknown value was passed to the validate function"}; validationErrors.push(validationError); return; } - if (this.validatorOptions && this.validatorOptions.whitelist) + if (this.validatorOptions && this.validatorOptions.whitelist) { this.whitelist(object, groupedMetadatas, validationErrors); + } + + const PREDEFINED_VALIDATION_TYPES = [ + ValidationTypes.NESTED_VALIDATION, + ValidationTypes.CONDITIONAL_VALIDATION, + ValidationTypes.WHITELIST, + ValidationTypes.IS_DEFINED + ]; // General validation Object.keys(groupedMetadatas).forEach(propertyName => { + const value = (object as any)[propertyName]; const definedMetadatas = groupedMetadatas[propertyName].filter(metadata => metadata.type === ValidationTypes.IS_DEFINED); const metadatas = groupedMetadatas[propertyName].filter( - metadata => metadata.type !== ValidationTypes.IS_DEFINED && metadata.type !== ValidationTypes.WHITELIST); - const customValidationMetadatas = metadatas.filter(metadata => metadata.type === ValidationTypes.CUSTOM_VALIDATION); + metadata => metadata.type !== ValidationTypes.IS_DEFINED && metadata.type !== ValidationTypes.WHITELIST); + const customValidationMetadatas = metadatas.filter(metadata => PREDEFINED_VALIDATION_TYPES.indexOf(metadata.type) === -1); const nestedValidationMetadatas = metadatas.filter(metadata => metadata.type === ValidationTypes.NESTED_VALIDATION); const conditionalValidationMetadatas = metadatas.filter(metadata => metadata.type === ValidationTypes.CONDITIONAL_VALIDATION); @@ -95,14 +105,13 @@ export class ValidationExecutor { } // handle IS_DEFINED validation type the special way - it should work no matter skipMissingProperties is set or not - this.defaultValidations(object, value, definedMetadatas, validationError.constraints); + this.runValidations(object, value, definedMetadatas, validationError.constraints); if ((value === null || value === undefined) && this.validatorOptions && this.validatorOptions.skipMissingProperties === true) { return; } - this.defaultValidations(object, value, metadatas, validationError.constraints); - this.customValidations(object, value, customValidationMetadatas, validationError.constraints); + this.runValidations(object, value, customValidationMetadatas, validationError.constraints); this.nestedValidations(value, nestedValidationMetadatas, validationError.children); this.mapContexts(object, value, metadatas, validationError); @@ -128,7 +137,7 @@ export class ValidationExecutor { notAllowedProperties.forEach(property => { validationErrors.push({ target: object, property, value: (object as any)[property], children: undefined, - constraints: { [ValidationTypes.WHITELIST]: `property ${property} should not exist` } + constraints: {[ValidationTypes.WHITELIST]: `property ${property} should not exist`} }); }); @@ -193,28 +202,7 @@ export class ValidationExecutor { .reduce((resultA, resultB) => resultA && resultB, true); } - private defaultValidations(object: Object, - value: any, - metadatas: ValidationMetadata[], - errorMap: { [key: string]: string }) { - return metadatas - .filter(metadata => { - if (metadata.each) { - if (value instanceof Array) { - return !value.every((subValue: any) => this.validator.validateValueByMetadata(subValue, metadata)); - } - - } else { - return !this.validator.validateValueByMetadata(value, metadata); - } - }) - .forEach(metadata => { - const [key, message] = this.createValidationError(object, value, metadata); - errorMap[key] = message; - }); - } - - private customValidations(object: Object, + private runValidations(object: Object, value: any, metadatas: ValidationMetadata[], errorMap: { [key: string]: string }) { @@ -223,8 +211,9 @@ export class ValidationExecutor { getFromContainer(MetadataStorage) .getTargetValidatorConstraints(metadata.constraintCls) .forEach(customConstraintMetadata => { - if (customConstraintMetadata.async && this.ignoreAsyncValidations) + if (customConstraintMetadata.async && this.ignoreAsyncValidations) { return; + } const validationArguments: ValidationArguments = { targetName: object.constructor ? (object.constructor as any).name : undefined, @@ -259,7 +248,9 @@ export class ValidationExecutor { } metadatas.forEach(metadata => { - if (metadata.type !== ValidationTypes.NESTED_VALIDATION) return; + if (metadata.type !== ValidationTypes.NESTED_VALIDATION) { + return; + } const targetSchema = typeof metadata.target === "string" ? metadata.target as string : undefined; if (value instanceof Array) { @@ -330,8 +321,7 @@ export class ValidationExecutor { message = customValidatorMetadata.instance.defaultMessage(validationArguments); } - if (!message) - message = ValidationTypes.getMessage(type, metadata.each); + message = message || ""; } const messageString = ValidationUtils.replaceMessageSpecialTokens(message, validationArguments); diff --git a/src/validation/ValidationTypeOptions.ts b/src/validation/ValidationTypeOptions.ts index fe91f7a91d..361dbc82a3 100644 --- a/src/validation/ValidationTypeOptions.ts +++ b/src/validation/ValidationTypeOptions.ts @@ -4,4 +4,4 @@ export interface IsNumberOptions { allowNaN?: boolean; allowInfinity?: boolean; -} \ No newline at end of file +} diff --git a/src/validation/ValidationTypes.ts b/src/validation/ValidationTypes.ts index eb128cdfe9..6f5f98b996 100644 --- a/src/validation/ValidationTypes.ts +++ b/src/validation/ValidationTypes.ts @@ -1,5 +1,3 @@ -import {ValidationArguments} from "./ValidationArguments"; - /** * Validation types. */ @@ -10,270 +8,6 @@ export class ValidationTypes { static NESTED_VALIDATION = "nestedValidation"; static CONDITIONAL_VALIDATION = "conditionalValidation"; static WHITELIST = "whitelistValidation"; - - /* common checkers */ static IS_DEFINED = "isDefined"; - static EQUALS = "equals"; - static NOT_EQUALS = "notEquals"; - static IS_EMPTY = "isEmpty"; - static IS_NOT_EMPTY = "isNotEmpty"; - static IS_IN = "isIn"; - static IS_NOT_IN = "isNotIn"; - - /* type checkers */ - static IS_BOOLEAN = "isBoolean"; - static IS_DATE = "isDate"; - static IS_NUMBER = "isNumber"; - static IS_STRING = "isString"; - static IS_DATE_STRING = "isDateString"; - static IS_ARRAY = "isArray"; - static IS_INT = "isInt"; - static IS_ENUM = "isEnum"; - - /* number checkers */ - static IS_DIVISIBLE_BY = "isDivisibleBy"; - static IS_POSITIVE = "isPositive"; - static IS_NEGATIVE = "isNegative"; - static MIN = "min"; - static MAX = "max"; - - /* date checkers */ - static MIN_DATE = "minDate"; - static MAX_DATE = "maxDate"; - - /* string-as-type checkers */ - static IS_BOOLEAN_STRING = "isBooleanString"; - static IS_NUMBER_STRING = "isNumberString"; - - /* string checkers */ - static CONTAINS = "contains"; - static NOT_CONTAINS = "notContains"; - static IS_ALPHA = "isAlpha"; - static IS_ALPHANUMERIC = "isAlphanumeric"; - static IS_ASCII = "isAscii"; - static IS_BASE64 = "isBase64"; - static IS_BYTE_LENGTH = "isByteLength"; - static IS_CREDIT_CARD = "isCreditCard"; - static IS_CURRENCY = "isCurrency"; - static IS_EMAIL = "isEmail"; - static IS_FQDN = "isFqdn"; - static IS_FULL_WIDTH = "isFullWidth"; - static IS_HALF_WIDTH = "isHalfWidth"; - static IS_VARIABLE_WIDTH = "isVariableWidth"; - static IS_HEX_COLOR = "isHexColor"; - static IS_HEXADECIMAL = "isHexadecimal"; - static IS_IP = "isIp"; - static IS_ISBN = "isIsbn"; - static IS_ISIN = "isIsin"; - static IS_ISO8601 = "isIso8601"; - static IS_JSON = "isJson"; - static IS_LOWERCASE = "isLowercase"; - static IS_MOBILE_PHONE = "isMobilePhone"; - static IS_PHONE_NUMBER = "isPhoneNumber"; - static IS_MONGO_ID = "isMongoId"; - static IS_MULTIBYTE = "isMultibyte"; - static IS_SURROGATE_PAIR = "isSurrogatePair"; - static IS_URL = "isUrl"; - static IS_UUID = "isUuid"; - static LENGTH = "length"; - static IS_UPPERCASE = "isUppercase"; - static MIN_LENGTH = "minLength"; - static MAX_LENGTH = "maxLength"; - static MATCHES = "matches"; - static IS_MILITARY_TIME = "isMilitaryTime"; - - /* array checkers */ - static ARRAY_CONTAINS = "arrayContains"; - static ARRAY_NOT_CONTAINS = "arrayNotContains"; - static ARRAY_NOT_EMPTY = "arrayNotEmpty"; - static ARRAY_MIN_SIZE = "arrayMinSize"; - static ARRAY_MAX_SIZE = "arrayMaxSize"; - static ARRAY_UNIQUE = "arrayUnique"; - - /* object chekers */ - static IS_INSTANCE = "isInstance"; - - /** - * Checks if validation type is valid. - */ - static isValid(type: string) { - return type !== "isValid" && - type !== "getMessage" && - Object.keys(this).map(key => (this as any)[key]).indexOf(type) !== -1; - } - - /** - * Gets default validation error message for the given validation type. - */ - static getMessage(type: string, isEach: boolean): string|((args: ValidationArguments) => string) { - const eachPrefix = isEach ? "each value in " : ""; - switch (type) { - - /* system chceck */ - case this.NESTED_VALIDATION: - return eachPrefix + "nested property $property must be either object or array"; - /* common checkers */ - case this.IS_DEFINED: - return eachPrefix + "$property should not be null or undefined"; - case this.EQUALS: - return eachPrefix + "$property must be equal to $constraint1"; - case this.NOT_EQUALS: - return eachPrefix + "$property should not be equal to $constraint1"; - case this.IS_EMPTY: - return eachPrefix + "$property must be empty"; - case this.IS_NOT_EMPTY: - return eachPrefix + "$property should not be empty"; - case this.IS_IN: - return eachPrefix + "$property must be one of the following values: $constraint1"; - case this.IS_NOT_IN: - return eachPrefix + "$property should not be one of the following values: $constraint1"; - - /* type checkers */ - case this.IS_BOOLEAN: - return eachPrefix + "$property must be a boolean value"; - case this.IS_DATE: - return eachPrefix + "$property must be a Date instance"; - case this.IS_NUMBER: - return eachPrefix + "$property must be a number"; - case this.IS_INT: - return eachPrefix + "$property must be an integer number"; - case this.IS_STRING: - return eachPrefix + "$property must be a string"; - case this.IS_DATE_STRING: - return eachPrefix + "$property must be a ISOString"; - case this.IS_ARRAY: - return eachPrefix + "$property must be an array"; - case this.IS_ENUM: - return eachPrefix + "$property must be a valid enum value"; - - /* number checkers */ - case this.IS_DIVISIBLE_BY: - return eachPrefix + "$property must be divisible by $constraint1"; - case this.IS_POSITIVE: - return eachPrefix + "$property must be a positive number"; - case this.IS_NEGATIVE: - return eachPrefix + "$property must be a negative number"; - case this.MIN: - return eachPrefix + "$property must not be less than $constraint1"; - case this.MAX: - return eachPrefix + "$property must not be greater than $constraint1"; - - /* date checkers */ - case this.MIN_DATE: - return "minimal allowed date for " + eachPrefix + "$property is $constraint1"; - case this.MAX_DATE: - return "maximal allowed date for " + eachPrefix + "$property is $constraint1"; - - /* string-as-type checkers */ - case this.IS_BOOLEAN_STRING: - return eachPrefix + "$property must be a boolean string"; - case this.IS_NUMBER_STRING: - return eachPrefix + "$property must be a number string"; - - /* string checkers */ - case this.CONTAINS: - return eachPrefix + "$property must contain a $constraint1 string"; - case this.NOT_CONTAINS: - return eachPrefix + "$property should not contain a $constraint1 string"; - case this.IS_ALPHA: - return eachPrefix + "$property must contain only letters (a-zA-Z)"; - case this.IS_ALPHANUMERIC: - return eachPrefix + "$property must contain only letters and numbers"; - case this.IS_ASCII: - return eachPrefix + "$property must contain only ASCII characters"; - case this.IS_BASE64: - return eachPrefix + "$property must be base64 encoded"; - case this.IS_BYTE_LENGTH: - return eachPrefix + "$property's byte length must fall into ($constraint1, $constraint2) range"; - case this.IS_CREDIT_CARD: - return eachPrefix + "$property must be a credit card"; - case this.IS_CURRENCY: - return eachPrefix + "$property must be a currency"; - case this.IS_EMAIL: - return eachPrefix + "$property must be an email"; - case this.IS_FQDN: - return eachPrefix + "$property must be a valid domain name"; - case this.IS_FULL_WIDTH: - return eachPrefix + "$property must contain a full-width characters"; - case this.IS_HALF_WIDTH: - return eachPrefix + "$property must contain a half-width characters"; - case this.IS_VARIABLE_WIDTH: - return eachPrefix + "$property must contain a full-width and half-width characters"; - case this.IS_HEX_COLOR: - return eachPrefix + "$property must be a hexadecimal color"; - case this.IS_HEXADECIMAL: - return eachPrefix + "$property must be a hexadecimal number"; - case this.IS_IP: - return eachPrefix + "$property must be an ip address"; - case this.IS_ISBN: - return eachPrefix + "$property must be an ISBN"; - case this.IS_ISIN: - return eachPrefix + "$property must be an ISIN (stock/security identifier)"; - case this.IS_ISO8601: - return eachPrefix + "$property must be a valid ISO 8601 date string"; - case this.IS_JSON: - return eachPrefix + "$property must be a json string"; - case this.IS_LOWERCASE: - return eachPrefix + "$property must be a lowercase string"; - case this.IS_MOBILE_PHONE: - return eachPrefix + "$property must be a phone number"; - case this.IS_PHONE_NUMBER: - return eachPrefix + "$property must be a valid phone number"; - case this.IS_MONGO_ID: - return eachPrefix + "$property must be a mongodb id"; - case this.IS_MULTIBYTE: - return eachPrefix + "$property must contain one or more multibyte chars"; - case this.IS_SURROGATE_PAIR: - return eachPrefix + "$property must contain any surrogate pairs chars"; - case this.IS_URL: - return eachPrefix + "$property must be an URL address"; - case this.IS_UUID: - return eachPrefix + "$property must be an UUID"; - case this.IS_UPPERCASE: - return eachPrefix + "$property must be uppercase"; - case this.LENGTH: - return (args: ValidationArguments) => { - const isMinLength = args.constraints[0] !== null && args.constraints[0] !== undefined; - const isMaxLength = args.constraints[1] !== null && args.constraints[1] !== undefined; - if (isMinLength && (!args.value || args.value.length < args.constraints[0])) { - return eachPrefix + "$property must be longer than or equal to $constraint1 characters"; - } else if (isMaxLength && (args.value.length > args.constraints[1])) { - return eachPrefix + "$property must be shorter than or equal to $constraint2 characters"; - } - return eachPrefix + "$property must be longer than or equal to $constraint1 and shorter than or equal to $constraint2 characters"; - }; - case this.MIN_LENGTH: - return eachPrefix + "$property must be longer than or equal to $constraint1 characters"; - case this.MAX_LENGTH: - return eachPrefix + "$property must be shorter than or equal to $constraint1 characters"; - case this.MATCHES: - return eachPrefix + "$property must match $constraint1 regular expression"; - - /* array checkers */ - case this.ARRAY_CONTAINS: - return eachPrefix + "$property must contain $constraint1 values"; - case this.ARRAY_NOT_CONTAINS: - return eachPrefix + "$property should not contain $constraint1 values"; - case this.ARRAY_NOT_EMPTY: - return eachPrefix + "$property should not be empty"; - case this.ARRAY_MIN_SIZE: - return eachPrefix + "$property must contain at least $constraint1 elements"; - case this.ARRAY_MAX_SIZE: - return eachPrefix + "$property must contain not more than $constraint1 elements"; - case this.ARRAY_UNIQUE: - return eachPrefix + "All $property's elements must be unique"; - - case this.IS_INSTANCE: - return (args: ValidationArguments) => { - if (args.constraints[0]) { - return eachPrefix + `$property must be an instance of ${args.constraints[0].name}`; - } else { - return eachPrefix + `${this.IS_INSTANCE} decorator expects and object as value, but got falsy value.`; - } - }; - } - - return ""; - } } diff --git a/src/validation/Validator.ts b/src/validation/Validator.ts index ff8ca18482..0322db1346 100644 --- a/src/validation/Validator.ts +++ b/src/validation/Validator.ts @@ -1,7 +1,4 @@ -import {ValidationMetadata} from "../metadata/ValidationMetadata"; -import {ValidationTypes} from "./ValidationTypes"; import {ValidationError} from "./ValidationError"; -import {IsNumberOptions} from "./ValidationTypeOptions"; import {ValidatorOptions} from "./ValidatorOptions"; import {ValidationExecutor} from "./ValidationExecutor"; import {ValidationOptions} from "../decorator/ValidationOptions"; @@ -11,20 +8,11 @@ import {ValidationOptions} from "../decorator/ValidationOptions"; */ export class Validator { - // ------------------------------------------------------------------------- - // Private Properties - // ------------------------------------------------------------------------- - - private validatorJs = require("validator"); - private libPhoneNumber = { - phoneUtil: require("google-libphonenumber").PhoneNumberUtil.getInstance(), - }; - /** * Performs validation of the given object based on decorators or validation schema. * Common method for `validateOrReject` and `validate` methods. */ - private coreValidate(objectOrSchemaName: Object|string, objectOrValidationOptions: Object|ValidationOptions, maybeValidatorOptions?: ValidatorOptions): Promise { + private coreValidate(objectOrSchemaName: Object | string, objectOrValidationOptions: Object | ValidationOptions, maybeValidatorOptions?: ValidatorOptions): Promise { const object = typeof objectOrSchemaName === "string" ? objectOrValidationOptions as Object : objectOrSchemaName as Object; const options = typeof objectOrSchemaName === "string" ? maybeValidatorOptions : objectOrValidationOptions as ValidationOptions; const schema = typeof objectOrSchemaName === "string" ? objectOrSchemaName as string : undefined; @@ -55,7 +43,7 @@ export class Validator { /** * Performs validation of the given object based on decorators or validation schema. */ - validate(objectOrSchemaName: Object|string, objectOrValidationOptions: Object|ValidationOptions, maybeValidatorOptions?: ValidatorOptions): Promise { + validate(objectOrSchemaName: Object | string, objectOrValidationOptions: Object | ValidationOptions, maybeValidatorOptions?: ValidatorOptions): Promise { return this.coreValidate(objectOrSchemaName, objectOrValidationOptions, maybeValidatorOptions); } @@ -72,10 +60,12 @@ export class Validator { /** * Performs validation of the given object based on decorators or validation schema and reject on error. */ - async validateOrReject(objectOrSchemaName: Object|string, objectOrValidationOptions: Object|ValidationOptions, maybeValidatorOptions?: ValidatorOptions): Promise { + async validateOrReject(objectOrSchemaName: Object | string, objectOrValidationOptions: Object | ValidationOptions, maybeValidatorOptions?: ValidatorOptions): Promise { const errors = await this.coreValidate(objectOrSchemaName, objectOrValidationOptions, maybeValidatorOptions); - if (errors.length) + if (errors.length) { return Promise.reject(errors); + } + // FIXME: else??? } /** @@ -92,7 +82,7 @@ export class Validator { /** * Performs validation of the given object based on decorators or validation schema. */ - validateSync(objectOrSchemaName: Object|string, objectOrValidationOptions: Object|ValidationOptions, maybeValidatorOptions?: ValidatorOptions): ValidationError[] { + validateSync(objectOrSchemaName: Object | string, objectOrValidationOptions: Object | ValidationOptions, maybeValidatorOptions?: ValidatorOptions): ValidationError[] { const object = typeof objectOrSchemaName === "string" ? objectOrValidationOptions as Object : objectOrSchemaName as Object; const options = typeof objectOrSchemaName === "string" ? maybeValidatorOptions : objectOrValidationOptions as ValidationOptions; const schema = typeof objectOrSchemaName === "string" ? objectOrSchemaName as string : undefined; @@ -104,736 +94,5 @@ export class Validator { return executor.stripEmptyErrors(validationErrors); } - /** - * Performs validation of the given object based on the given ValidationMetadata object. - */ - validateValueByMetadata(value: any, metadata: ValidationMetadata): boolean { - switch (metadata.type) { - /* common checkers */ - case ValidationTypes.IS_DEFINED: - return this.isDefined(value); - case ValidationTypes.EQUALS: - return this.equals(value, metadata.constraints[0]); - case ValidationTypes.NOT_EQUALS: - return this.notEquals(value, metadata.constraints[0]); - case ValidationTypes.IS_EMPTY: - return this.isEmpty(value); - case ValidationTypes.IS_NOT_EMPTY: - return this.isNotEmpty(value); - case ValidationTypes.IS_IN: - return this.isIn(value, metadata.constraints[0]); - case ValidationTypes.IS_NOT_IN: - return this.isNotIn(value, metadata.constraints[0]); - - /* type checkers */ - case ValidationTypes.IS_BOOLEAN: - return this.isBoolean(value); - case ValidationTypes.IS_DATE: - return this.isDate(value); - case ValidationTypes.IS_STRING: - return this.isString(value); - case ValidationTypes.IS_DATE_STRING: - return this.isDateString(value); - case ValidationTypes.IS_ARRAY: - return this.isArray(value); - case ValidationTypes.IS_NUMBER: - return this.isNumber(value, metadata.constraints[0]); - case ValidationTypes.IS_INT: - return this.isInt(value); - case ValidationTypes.IS_ENUM: - return this.isEnum(value, metadata.constraints[0]); - - /* number checkers */ - case ValidationTypes.IS_DIVISIBLE_BY: - return this.isDivisibleBy(value, metadata.constraints[0]); - case ValidationTypes.IS_POSITIVE: - return this.isPositive(value); - case ValidationTypes.IS_NEGATIVE: - return this.isNegative(value); - case ValidationTypes.MIN: - return this.min(value, metadata.constraints[0]); - case ValidationTypes.MAX: - return this.max(value, metadata.constraints[0]); - - /* date checkers */ - case ValidationTypes.MIN_DATE: - return this.minDate(value, metadata.constraints[0]); - case ValidationTypes.MAX_DATE: - return this.maxDate(value, metadata.constraints[0]); - - /* string-as-type checkers */ - case ValidationTypes.IS_BOOLEAN_STRING: - return this.isBooleanString(value); - case ValidationTypes.IS_NUMBER_STRING: - return this.isNumberString(value); - - /* string checkers */ - case ValidationTypes.CONTAINS: - return this.contains(value, metadata.constraints[0]); - case ValidationTypes.NOT_CONTAINS: - return this.notContains(value, metadata.constraints[0]); - case ValidationTypes.IS_ALPHA: - return this.isAlpha(value); - case ValidationTypes.IS_ALPHANUMERIC: - return this.isAlphanumeric(value); - case ValidationTypes.IS_ASCII: - return this.isAscii(value); - case ValidationTypes.IS_BASE64: - return this.isBase64(value); - case ValidationTypes.IS_BYTE_LENGTH: - return this.isByteLength(value, metadata.constraints[0], metadata.constraints[1]); - case ValidationTypes.IS_CREDIT_CARD: - return this.isCreditCard(value); - case ValidationTypes.IS_CURRENCY: - return this.isCurrency(value, metadata.constraints[0]); - case ValidationTypes.IS_EMAIL: - return this.isEmail(value, metadata.constraints[0]); - case ValidationTypes.IS_FQDN: - return this.isFQDN(value, metadata.constraints[0]); - case ValidationTypes.IS_FULL_WIDTH: - return this.isFullWidth(value); - case ValidationTypes.IS_HALF_WIDTH: - return this.isHalfWidth(value); - case ValidationTypes.IS_VARIABLE_WIDTH: - return this.isVariableWidth(value); - case ValidationTypes.IS_HEX_COLOR: - return this.isHexColor(value); - case ValidationTypes.IS_HEXADECIMAL: - return this.isHexadecimal(value); - case ValidationTypes.IS_IP: - return this.isIP(value, metadata.constraints[0]); - case ValidationTypes.IS_ISBN: - return this.isISBN(value, metadata.constraints[0]); - case ValidationTypes.IS_ISIN: - return this.isISIN(value); - case ValidationTypes.IS_ISO8601: - return this.isISO8601(value); - case ValidationTypes.IS_JSON: - return this.isJSON(value); - case ValidationTypes.IS_LOWERCASE: - return this.isLowercase(value); - case ValidationTypes.IS_MOBILE_PHONE: - return this.isMobilePhone(value, metadata.constraints[0]); - case ValidationTypes.IS_PHONE_NUMBER: - return this.isPhoneNumber(value, metadata.constraints[0]); - case ValidationTypes.IS_MONGO_ID: - return this.isMongoId(value); - case ValidationTypes.IS_MULTIBYTE: - return this.isMultibyte(value); - case ValidationTypes.IS_SURROGATE_PAIR: - return this.isSurrogatePair(value); - case ValidationTypes.IS_URL: - return this.isURL(value, metadata.constraints[0]); - case ValidationTypes.IS_UUID: - return this.isUUID(value, metadata.constraints[0]); - case ValidationTypes.IS_UPPERCASE: - return this.isUppercase(value); - case ValidationTypes.LENGTH: - return this.length(value, metadata.constraints[0], metadata.constraints[1]); - case ValidationTypes.MIN_LENGTH: - return this.minLength(value, metadata.constraints[0]); - case ValidationTypes.MAX_LENGTH: - return this.maxLength(value, metadata.constraints[0]); - case ValidationTypes.MATCHES: - return this.matches(value, metadata.constraints[0], metadata.constraints[1]); - case ValidationTypes.IS_MILITARY_TIME: - return this.isMilitaryTime(value); - - /* array checkers */ - case ValidationTypes.ARRAY_CONTAINS: - return this.arrayContains(value, metadata.constraints[0]); - case ValidationTypes.ARRAY_NOT_CONTAINS: - return this.arrayNotContains(value, metadata.constraints[0]); - case ValidationTypes.ARRAY_NOT_EMPTY: - return this.arrayNotEmpty(value); - case ValidationTypes.ARRAY_MIN_SIZE: - return this.arrayMinSize(value, metadata.constraints[0]); - case ValidationTypes.ARRAY_MAX_SIZE: - return this.arrayMaxSize(value, metadata.constraints[0]); - case ValidationTypes.ARRAY_UNIQUE: - return this.arrayUnique(value); - - case ValidationTypes.IS_INSTANCE: - return this.isInstance(value, metadata.constraints[0]); - } - return true; - } - - // ------------------------------------------------------------------------- - // Validation Methods: common checkers - // ------------------------------------------------------------------------- - - /** - * Checks if value is defined (!== undefined, !== null). - */ - isDefined(value: any): boolean { - return value !== undefined && value !== null; - } - - /** - * Checks if value matches ("===") the comparison. - */ - equals(value: any, comparison: any): boolean { - return value === comparison; - } - - /** - * Checks if value does not match ("!==") the comparison. - */ - notEquals(value: any, comparison: any): boolean { - return value !== comparison; - } - - /** - * Checks if given value is empty (=== '', === null, === undefined). - */ - isEmpty(value: any): boolean { - return value === "" || value === null || value === undefined; - } - - /** - * Checks if given value is not empty (!== '', !== null, !== undefined). - */ - isNotEmpty(value: any): boolean { - return value !== "" && value !== null && value !== undefined; - } - - /** - * Checks if given value is in a array of allowed values. - */ - isIn(value: any, possibleValues: any[]): boolean { - return !(possibleValues instanceof Array) || possibleValues.some(possibleValue => possibleValue === value); - } - - /** - * Checks if given value not in a array of allowed values. - */ - isNotIn(value: any, possibleValues: any[]): boolean { - return !(possibleValues instanceof Array) || !possibleValues.some(possibleValue => possibleValue === value); - } - - // ------------------------------------------------------------------------- - // Validation Methods: type checkers - // ------------------------------------------------------------------------- - - /** - * Checks if a given value is a real boolean. - */ - isBoolean(value: any): boolean { - return value instanceof Boolean || typeof value === "boolean"; - } - - /** - * Checks if a given value is a real date. - */ - isDate(value: any): boolean { - return value instanceof Date && !isNaN(value.getTime()); - } - - /** - * Checks if a given value is a real string. - */ - isString(value: any): boolean { - return value instanceof String || typeof value === "string"; - } - - /** - * Checks if a given value is a ISOString date. - */ - isDateString(value: any): boolean { - const regex = /^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|\+[0-2]\d(?:\:[0-5]\d)?)?$/g; - return this.isString(value) && regex.test(value); - } - - /** - * Checks if a given value is an array - */ - isArray(value: any): boolean { - return value instanceof Array; - } - - /** - * Checks if a given value is an enum - */ - isEnum(value: any, entity: any): boolean { - const enumValues = Object.keys(entity) - .map(k => entity[k]); - return enumValues.indexOf(value) >= 0; - } - - /** - * Checks if a given value is a number. - */ - isNumber(value: any, options: IsNumberOptions = {}): boolean { - if (value === Infinity || value === -Infinity) { - return options.allowInfinity; - } - - if (Number.isNaN(value)) { - return options.allowNaN; - } - - return Number.isFinite(value); - } - - /** - * Checks if value is an integer. - */ - isInt(val: number): boolean { - return Number.isInteger(val); - } - - // ------------------------------------------------------------------------- - // Validation Methods: number checkers - // ------------------------------------------------------------------------- - - /** - * Checks if value is a number that's divisible by another. - */ - isDivisibleBy(value: number, num: number): boolean { - return typeof value === "number" && - typeof num === "number" && - this.validatorJs.isDivisibleBy(String(value), num); - } - - /** - * Checks if the value is a positive number. - */ - isPositive(value: number): boolean { - return typeof value === "number" && value > 0; - } - - /** - * Checks if the value is a negative number. - */ - isNegative(value: number): boolean { - return typeof value === "number" && value < 0; - } - - /** - * Checks if the first number is greater than or equal to the second. - */ - min(num: number, min: number): boolean { - return typeof num === "number" && typeof min === "number" && num >= min; - } - - /** - * Checks if the first number is less than or equal to the second. - */ - max(num: number, max: number): boolean { - return typeof num === "number" && typeof max === "number" && num <= max; - } - - // ------------------------------------------------------------------------- - // Validation Methods: date checkers - // ------------------------------------------------------------------------- - - /** - * Checks if the value is a date that's after the specified date. - */ - minDate(date: Date, minDate: Date): boolean { - return date && date.getTime() >= minDate.getTime(); - } - - /** - * Checks if the value is a date that's before the specified date. - */ - maxDate(date: Date, maxDate: Date): boolean { - return date && date.getTime() <= maxDate.getTime(); - } - - // ------------------------------------------------------------------------- - // Validation Methods: string-as-type checkers - // ------------------------------------------------------------------------- - - /** - * Checks if a string is a boolean. - * If given value is not a string, then it returns false. - */ - isBooleanString(value: string): boolean { - return typeof value === "string" && this.validatorJs.isBoolean(value); - } - - /** - * Checks if the string is numeric. - * If given value is not a string, then it returns false. - */ - isNumberString(value: string, options?: ValidatorJS.IsNumericOptions): boolean { - return typeof value === "string" && this.validatorJs.isNumeric(value, options); - } - - // ------------------------------------------------------------------------- - // Validation Methods: string checkers - // ------------------------------------------------------------------------- - - /** - * Checks if the string contains the seed. - * If given value is not a string, then it returns false. - */ - contains(value: string, seed: string): boolean { - return typeof value === "string" && this.validatorJs.contains(value, seed); - } - - /** - * Checks if the string does not contain the seed. - * If given value is not a string, then it returns false. - */ - notContains(value: string, seed: string): boolean { - return typeof value === "string" && !this.validatorJs.contains(value, seed); - } - - /** - * Checks if the string contains only letters (a-zA-Z). - * If given value is not a string, then it returns false. - */ - isAlpha(value: string): boolean { - return typeof value === "string" && this.validatorJs.isAlpha(value); - } - - /** - * Checks if the string contains only letters and numbers. - * If given value is not a string, then it returns false. - */ - isAlphanumeric(value: string): boolean { - return typeof value === "string" && this.validatorJs.isAlphanumeric(value); - } - - /** - * Checks if the string contains ASCII chars only. - * If given value is not a string, then it returns false. - */ - isAscii(value: string): boolean { - return typeof value === "string" && this.validatorJs.isAscii(value); - } - - /** - * Checks if a string is base64 encoded. - * If given value is not a string, then it returns false. - */ - isBase64(value: string): boolean { - return typeof value === "string" && this.validatorJs.isBase64(value); - } - - /** - * Checks if the string's length (in bytes) falls in a range. - * If given value is not a string, then it returns false. - */ - isByteLength(value: string, min: number, max?: number): boolean { - return typeof value === "string" && this.validatorJs.isByteLength(value, min, max); - } - - /** - * Checks if the string is a credit card. - * If given value is not a string, then it returns false. - */ - isCreditCard(value: string): boolean { - return typeof value === "string" && this.validatorJs.isCreditCard(value); - } - - /** - * Checks if the string is a valid currency amount. - * If given value is not a string, then it returns false. - */ - isCurrency(value: string, options?: ValidatorJS.IsCurrencyOptions): boolean { - return typeof value === "string" && this.validatorJs.isCurrency(value, options); - } - - /** - * Checks if the string is an email. - * If given value is not a string, then it returns false. - */ - isEmail(value: string, options?: ValidatorJS.IsEmailOptions): boolean { - return typeof value === "string" && this.validatorJs.isEmail(value, options); - } - - /** - * Checks if the string is a fully qualified domain name (e.g. domain.com). - * If given value is not a string, then it returns false. - */ - isFQDN(value: string, options?: ValidatorJS.IsFQDNOptions): boolean { - return typeof value === "string" && this.validatorJs.isFQDN(value, options); - } - - /** - * Checks if the string contains any full-width chars. - * If given value is not a string, then it returns false. - */ - isFullWidth(value: string): boolean { - return typeof value === "string" && this.validatorJs.isFullWidth(value); - } - - /** - * Checks if the string contains any half-width chars. - * If given value is not a string, then it returns false. - */ - isHalfWidth(value: string): boolean { - return typeof value === "string" && this.validatorJs.isHalfWidth(value); - } - - /** - * Checks if the string contains variable-width chars. - * If given value is not a string, then it returns false. - */ - isVariableWidth(value: string): boolean { - return typeof value === "string" && this.validatorJs.isVariableWidth(value); - } - - /** - * Checks if the string is a hexadecimal color. - * If given value is not a string, then it returns false. - */ - isHexColor(value: string): boolean { - return typeof value === "string" && this.validatorJs.isHexColor(value); - } - - /** - * Checks if the string is a hexadecimal number. - * If given value is not a string, then it returns false. - */ - isHexadecimal(value: string): boolean { - return typeof value === "string" && this.validatorJs.isHexadecimal(value); - } - - /** - * Checks if the string is an IP (version 4 or 6). - * If given value is not a string, then it returns false. - */ - isIP(value: string, version?: "4"|"6"): boolean { - return typeof value === "string" && this.validatorJs.isIP(value, version); - } - - /** - * Checks if the string is an ISBN (version 10 or 13). - * If given value is not a string, then it returns false. - */ - isISBN(value: string, version?: "10"|"13"): boolean { - return typeof value === "string" && this.validatorJs.isISBN(value, version); - } - - /** - * Checks if the string is an ISIN (stock/security identifier). - * If given value is not a string, then it returns false. - */ - isISIN(value: string): boolean { - return typeof value === "string" && this.validatorJs.isISIN(value); - } - - /** - * Checks if the string is a valid ISO 8601 date. - * If given value is not a string, then it returns false. - */ - isISO8601(value: string): boolean { - return typeof value === "string" && this.validatorJs.isISO8601(value); - } - - /** - * Checks if the string is valid JSON (note: uses JSON.parse). - * If given value is not a string, then it returns false. - */ - isJSON(value: string): boolean { - return typeof value === "string" && this.validatorJs.isJSON(value); - } - - /** - * Checks if the string is lowercase. - * If given value is not a string, then it returns false. - */ - isLowercase(value: string): boolean { - return typeof value === "string" && this.validatorJs.isLowercase(value); - } - - /** - * Checks if the string is a mobile phone number (locale is one of ['zh-CN', 'zh-TW', 'en-ZA', 'en-AU', 'en-HK', - * 'pt-PT', 'fr-FR', 'el-GR', 'en-GB', 'en-US', 'en-ZM', 'ru-RU', 'nb-NO', 'nn-NO', 'vi-VN', 'en-NZ']). - * If given value is not a string, then it returns false. - */ - isMobilePhone(value: string, locale: ValidatorJS.MobilePhoneLocale): boolean { - return typeof value === "string" && this.validatorJs.isMobilePhone(value, locale); - } - - /** - * Checks if the string is a valid phone number. - * @param value the potential phone number string to test - * @param {string} region 2 characters uppercase country code (e.g. DE, US, CH). - * If users must enter the intl. prefix (e.g. +41), then you may pass "ZZ" or null as region. - * See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github]{@link https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33} - */ - isPhoneNumber(value: string, region: string): boolean { - try { - const phoneNum = this.libPhoneNumber.phoneUtil.parseAndKeepRawInput(value, region); - return this.libPhoneNumber.phoneUtil.isValidNumber(phoneNum); - } catch (error) { - // logging? - return false; - } - } - - /** - * Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. - * If given value is not a string, then it returns false. - */ - isMongoId(value: string): boolean { - return typeof value === "string" && this.validatorJs.isMongoId(value); - } - - /** - * Checks if the string contains one or more multibyte chars. - * If given value is not a string, then it returns false. - */ - isMultibyte(value: string): boolean { - return typeof value === "string" && this.validatorJs.isMultibyte(value); - } - - /** - * Checks if the string contains any surrogate pairs chars. - * If given value is not a string, then it returns false. - */ - isSurrogatePair(value: string): boolean { - return typeof value === "string" && this.validatorJs.isSurrogatePair(value); - } - - /** - * Checks if the string is an url. - * If given value is not a string, then it returns false. - */ - isURL(value: string, options?: ValidatorJS.IsURLOptions): boolean { - return typeof value === "string" && this.validatorJs.isURL(value, options); - } - - /** - * Checks if the string is a UUID (version 3, 4 or 5). - * If given value is not a string, then it returns false. - */ - isUUID(value: string, version?: "3"|"4"|"5"): boolean { - return typeof value === "string" && this.validatorJs.isUUID(value, version); - } - - /** - * Checks if the string is uppercase. - * If given value is not a string, then it returns false. - */ - isUppercase(value: string): boolean { - return typeof value === "string" && this.validatorJs.isUppercase(value); - } - - /** - * Checks if the string's length falls in a range. Note: this function takes into account surrogate pairs. - * If given value is not a string, then it returns false. - */ - length(value: string, min: number, max?: number): boolean { - return typeof value === "string" && this.validatorJs.isLength(value, min, max); - } - - /** - * Checks if the string's length is not less than given number. Note: this function takes into account surrogate pairs. - * If given value is not a string, then it returns false. - */ - minLength(value: string, min: number) { - return typeof value === "string" && this.length(value, min); - } - - /** - * Checks if the string's length is not more than given number. Note: this function takes into account surrogate pairs. - * If given value is not a string, then it returns false. - */ - maxLength(value: string, max: number) { - return typeof value === "string" && this.length(value, 0, max); - } - - /** - * Checks if string matches the pattern. Either matches('foo', /foo/i) or matches('foo', 'foo', 'i'). - * If given value is not a string, then it returns false. - */ - matches(value: string, pattern: RegExp, modifiers?: string): boolean { - return typeof value === "string" && this.validatorJs.matches(value, pattern, modifiers); - } - - /** - * Checks if the string represents a time without a given timezone in the format HH:MM (military) - * If the given value does not match the pattern HH:MM, then it returns false. - */ - isMilitaryTime(value: string): boolean { - return this.matches(value, /^([01]\d|2[0-3]):?([0-5]\d)$/); - } - - // ------------------------------------------------------------------------- - // Validation Methods: array checkers - // ------------------------------------------------------------------------- - - /** - * Checks if array contains all values from the given array of values. - * If null or undefined is given then this function returns false. - */ - arrayContains(array: any[], values: any[]) { - if (!(array instanceof Array)) - return false; - - return !array || values.every(value => array.indexOf(value) !== -1); - } - - /** - * Checks if array does not contain any of the given values. - * If null or undefined is given then this function returns false. - */ - arrayNotContains(array: any[], values: any[]) { - if (!(array instanceof Array)) - return false; - - return !array || values.every(value => array.indexOf(value) === -1); - } - - /** - * Checks if given array is not empty. - * If null or undefined is given then this function returns false. - */ - arrayNotEmpty(array: any[]) { - if (!(array instanceof Array)) - return false; - - return array instanceof Array && array.length > 0; - } - - /** - * Checks if array's length is as minimal this number. - * If null or undefined is given then this function returns false. - */ - arrayMinSize(array: any[], min: number) { - if (!(array instanceof Array)) - return false; - - return array instanceof Array && array.length >= min; - } - - /** - * Checks if array's length is as maximal this number. - * If null or undefined is given then this function returns false. - */ - arrayMaxSize(array: any[], max: number) { - if (!(array instanceof Array)) - return false; - - return array instanceof Array && array.length <= max; - } - - /** - * Checks if all array's values are unique. Comparison for objects is reference-based. - * If null or undefined is given then this function returns false. - */ - arrayUnique(array: any[]) { - if (!(array instanceof Array)) - return false; - - const uniqueItems = array.filter((a, b, c) => c.indexOf(a) === b); - return array.length === uniqueItems.length; - } - - /** - * Checks if the value is an instance of the specified object. - */ - isInstance(object: any, targetTypeConstructor: new (...args: any[]) => any) { - return targetTypeConstructor - && typeof targetTypeConstructor === "function" - && object instanceof targetTypeConstructor; - } } diff --git a/src/validation/ValidatorConstraintInterface.ts b/src/validation/ValidatorConstraintInterface.ts index 5d485bd0df..b526a9608c 100644 --- a/src/validation/ValidatorConstraintInterface.ts +++ b/src/validation/ValidatorConstraintInterface.ts @@ -14,4 +14,4 @@ export interface ValidatorConstraintInterface { */ defaultMessage?(validationArguments?: ValidationArguments): string; -} \ No newline at end of file +} diff --git a/src/validation/ValidatorOptions.ts b/src/validation/ValidatorOptions.ts index 67cc143038..ed2b8ccd3e 100644 --- a/src/validation/ValidatorOptions.ts +++ b/src/validation/ValidatorOptions.ts @@ -53,4 +53,9 @@ export interface ValidatorOptions { */ forbidUnknownValues?: boolean; -} \ No newline at end of file + /** + * A transient set of data passed through to the validation result for response mapping + */ + context?: any; + +} diff --git a/test/functional/conditional-validation.spec.ts b/test/functional/conditional-validation.spec.ts index 92e8a32af2..492650c6d6 100644 --- a/test/functional/conditional-validation.spec.ts +++ b/test/functional/conditional-validation.spec.ts @@ -1,10 +1,12 @@ import "es6-shim"; -import {IsNotEmpty, ValidateIf, IsOptional, Equals} from "../../src/decorator/decorators"; import {Validator} from "../../src/validation/Validator"; -import {ValidatorOptions} from "../../src/validation/ValidatorOptions"; -import {expect, should, use } from "chai"; +import {expect, should, use} from "chai"; import * as chaiAsPromised from "chai-as-promised"; +import {Equals} from "../../src/decorator/common/Equals"; +import {IsNotEmpty} from "../../src/decorator/common/IsNotEmpty"; +import {ValidateIf} from "../../src/decorator/system/ValidateIf"; +import {IsOptional} from "../../src/decorator/system/IsOptional"; should(); use(chaiAsPromised); @@ -19,8 +21,8 @@ const validator = new Validator(); // Specifications: common decorators // ------------------------------------------------------------------------- -describe("conditional validation", function() { - it("shouldn't validate a property when the condition is false", function() { +describe("conditional validation", function () { + it("shouldn't validate a property when the condition is false", function () { class MyClass { @ValidateIf(o => false) @IsNotEmpty() @@ -33,7 +35,7 @@ describe("conditional validation", function() { }); }); - it("should validate a property when the condition is true", function() { + it("should validate a property when the condition is true", function () { class MyClass { @ValidateIf(o => true) @IsNotEmpty() @@ -45,12 +47,12 @@ describe("conditional validation", function() { errors.length.should.be.equal(1); errors[0].target.should.be.equal(model); errors[0].property.should.be.equal("title"); - errors[0].constraints.should.be.eql({ isNotEmpty: "title should not be empty" }); + errors[0].constraints.should.be.eql({isNotEmpty: "title should not be empty"}); errors[0].value.should.be.equal(""); }); }); - it("should pass the object being validated to the condition function", function() { + it("should pass the object being validated to the condition function", function () { class MyClass { @ValidateIf(o => { expect(o).to.be.instanceOf(MyClass); @@ -79,7 +81,7 @@ describe("conditional validation", function() { errors.length.should.be.equal(1); errors[0].target.should.be.equal(model); errors[0].property.should.be.equal("title"); - errors[0].constraints.should.be.eql({ equals: "title must be equal to test" }); + errors[0].constraints.should.be.eql({equals: "title must be equal to test"}); errors[0].value.should.be.equal(""); }); }); @@ -96,7 +98,7 @@ describe("conditional validation", function() { errors.length.should.be.equal(1); errors[0].target.should.be.equal(model); errors[0].property.should.be.equal("title"); - errors[0].constraints.should.be.eql({ equals: "title must be equal to test" }); + errors[0].constraints.should.be.eql({equals: "title must be equal to test"}); errors[0].value.should.be.equal("bad_value"); }); }); diff --git a/test/functional/custom-decorators.spec.ts b/test/functional/custom-decorators.spec.ts index 6bcdc77d98..55a166d14f 100644 --- a/test/functional/custom-decorators.spec.ts +++ b/test/functional/custom-decorators.spec.ts @@ -3,14 +3,14 @@ import {Validator} from "../../src/validation/Validator"; import {ValidationArguments} from "../../src/validation/ValidationArguments"; import {registerDecorator} from "../../src/register-decorator"; import {ValidationOptions} from "../../src/decorator/ValidationOptions"; -import {ValidatorConstraint} from "../../src/decorator/decorators"; +import {ValidatorConstraint} from "../../src/decorator/ValidatorConstraint"; import {ValidatorConstraintInterface} from "../../src/validation/ValidatorConstraintInterface"; -import {should, use } from "chai"; +import {should as chaiShould, use} from "chai"; import * as chaiAsPromised from "chai-as-promised"; -should(); +const should = chaiShould(); use(chaiAsPromised); // ------------------------------------------------------------------------- @@ -23,9 +23,9 @@ const validator = new Validator(); // Specifications: common decorators // ------------------------------------------------------------------------- -describe("custom decorators", function() { +describe("custom decorators", function () { - describe("decorator with inline validation", function() { + describe("decorator with inline validation", function () { function IsLongerThan(property: string, validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { @@ -41,7 +41,7 @@ describe("custom decorators", function() { const relatedValue = (args.object as any)[relatedPropertyName]; if (relatedValue === undefined || relatedValue === null) return true; - + return typeof value === "string" && typeof relatedValue === "string" && value.length > relatedValue.length; @@ -50,17 +50,17 @@ describe("custom decorators", function() { }); }; } - + class MyClass { @IsLongerThan("lastName", { message: "$property must be longer then $constraint1. Given value: $value" }) firstName: string; - + lastName: string; } - it("if firstName is not empty and lastLame is empty then it should succeed", function() { + it("if firstName is not empty and lastLame is empty then it should succeed", function () { const model = new MyClass(); model.firstName = "hell no world"; return validator.validate(model).then(errors => { @@ -68,29 +68,29 @@ describe("custom decorators", function() { }); }); - it("if firstName is empty and lastLame is not empty then it should fail", function() { + it("if firstName is empty and lastLame is not empty then it should fail", function () { const model = new MyClass(); model.firstName = ""; model.lastName = "Kim"; return validator.validate(model).then(errors => { errors.length.should.be.equal(1); - errors[0].constraints.should.be.eql({ isLongerThan: "firstName must be longer then lastName. Given value: " }); + errors[0].constraints.should.be.eql({isLongerThan: "firstName must be longer then lastName. Given value: "}); }); }); - it("if firstName is shorter then lastLame then it should fail", function() { + it("if firstName is shorter then lastLame then it should fail", function () { const model = new MyClass(); model.firstName = "Li"; model.lastName = "Kim"; return validator.validate(model).then(errors => { errors.length.should.be.equal(1); - errors[0].constraints.should.be.eql({ isLongerThan: "firstName must be longer then lastName. Given value: Li" }); + errors[0].constraints.should.be.eql({isLongerThan: "firstName must be longer then lastName. Given value: Li"}); }); }); - + }); - - describe("decorator with default message", function() { + + describe("decorator with default message", function () { function IsLonger(property: string, validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { @@ -106,7 +106,7 @@ describe("custom decorators", function() { const relatedValue = (args.object as any)[relatedPropertyName]; if (relatedValue === undefined || relatedValue === null) return true; - + return typeof value === "string" && typeof relatedValue === "string" && value.length > relatedValue.length; @@ -118,15 +118,15 @@ describe("custom decorators", function() { }); }; } - + class SecondClass { @IsLonger("lastName") firstName: string; - + lastName: string; } - it("if firstName is not empty and lastLame is empty then it should succeed", function() { + it("if firstName is not empty and lastLame is empty then it should succeed", function () { const model = new SecondClass(); model.firstName = "hell no world"; return validator.validate(model).then(errors => { @@ -134,31 +134,31 @@ describe("custom decorators", function() { }); }); - it("if firstName is empty and lastLame is not empty then it should fail", function() { + it("if firstName is empty and lastLame is not empty then it should fail", function () { const model = new SecondClass(); model.firstName = ""; model.lastName = "Kim"; return validator.validate(model).then(errors => { errors.length.should.be.equal(1); - errors[0].constraints.should.be.eql({ isLonger: "firstName must be longer then lastName" }); + errors[0].constraints.should.be.eql({isLonger: "firstName must be longer then lastName"}); }); }); - it("if firstName is shorter then lastLame then it should fail", function() { + it("if firstName is shorter then lastLame then it should fail", function () { const model = new SecondClass(); model.firstName = "Li"; model.lastName = "Kim"; return validator.validate(model).then(errors => { errors.length.should.be.equal(1); - errors[0].constraints.should.be.eql({ isLonger: "firstName must be longer then lastName" }); + errors[0].constraints.should.be.eql({isLonger: "firstName must be longer then lastName"}); }); }); - + }); - describe("decorator with separate validation constraint class", function() { + describe("decorator with separate validation constraint class", function () { - @ValidatorConstraint({ name: "isShortenThan" }) + @ValidatorConstraint({name: "isShortenThan"}) class IsShortenThanConstraint implements ValidatorConstraintInterface { validate(value: any, args: ValidationArguments) { @@ -166,10 +166,10 @@ describe("custom decorators", function() { const relatedValue = (args.object as any)[relatedPropertyName]; if (value === null || value === undefined) return true; - - return typeof value === "string" && - typeof relatedValue === "string" && - value.length < relatedValue.length; + + return typeof value === "string" && + typeof relatedValue === "string" && + value.length < relatedValue.length; } } @@ -195,7 +195,7 @@ describe("custom decorators", function() { lastName: string; } - it("if firstName is not empty and lastLame is empty then it should succeed", function() { + it("if firstName is not empty and lastLame is empty then it should succeed", function () { const model = new MyClass(); model.firstName = "hell no world"; return validator.validate(model).then(errors => { @@ -203,26 +203,52 @@ describe("custom decorators", function() { }); }); - it("if firstName is empty and lastLame is not empty then it should fail", function() { + it("if firstName is empty and lastLame is not empty then it should fail", function () { const model = new MyClass(); model.firstName = ""; model.lastName = "Kim"; return validator.validate(model).then(errors => { errors.length.should.be.equal(1); - errors[0].constraints.should.be.eql({ isShortenThan: "lastName must be shorter then firstName. Given value: Kim" }); + errors[0].constraints.should.be.eql({isShortenThan: "lastName must be shorter then firstName. Given value: Kim"}); + should.not.exist(errors[0].contexts); }); }); - it("if firstName is shorter then lastLame then it should fail", function() { + it("if firstName is shorter then lastLame then it should fail", function () { const model = new MyClass(); model.firstName = "Li"; model.lastName = "Kim"; return validator.validate(model).then(errors => { errors.length.should.be.equal(1); - errors[0].constraints.should.be.eql({ isShortenThan: "lastName must be shorter then firstName. Given value: Kim" }); + errors[0].constraints.should.be.eql({isShortenThan: "lastName must be shorter then firstName. Given value: Kim"}); + should.not.exist(errors[0].contexts); }); }); + describe("with context", () => { + class MyClass { + firstName: string; + + @IsShortenThan("firstName", { + message: "$property must be shorter then $constraint1. Given value: $value", + context: {foo: "bar"} + }) + lastName: string; + } + + it("if firstName is shorter then lastLame then error should contain context", function () { + const model = new MyClass(); + model.firstName = "Li"; + model.lastName = "Kim"; + return validator.validate(model).then(errors => { + errors.length.should.be.equal(1); + errors[0].constraints.should.be.eql({isShortenThan: "lastName must be shorter then firstName. Given value: Kim"}); + errors[0].contexts["isShortenThan"].should.be.eql({foo: "bar"}); + }); + }); + + }); + }); }); diff --git a/test/functional/inherited-validation.spec.ts b/test/functional/inherited-validation.spec.ts index 23494b5c71..94dc75fe91 100644 --- a/test/functional/inherited-validation.spec.ts +++ b/test/functional/inherited-validation.spec.ts @@ -1,10 +1,11 @@ import "es6-shim"; -import {Contains, MinLength} from "../../src/decorator/decorators"; import {Validator} from "../../src/validation/Validator"; import {should, use } from "chai"; import * as chaiAsPromised from "chai-as-promised"; +import {Contains} from "../../src/decorator/string/Contains"; +import {MinLength} from "../../src/decorator/string/MinLength"; should(); use(chaiAsPromised); @@ -53,4 +54,4 @@ describe("inherited validation", function() { }); }); -}); \ No newline at end of file +}); diff --git a/test/functional/nested-validation.spec.ts b/test/functional/nested-validation.spec.ts index 40b551e6e4..fe6c5d23af 100644 --- a/test/functional/nested-validation.spec.ts +++ b/test/functional/nested-validation.spec.ts @@ -1,13 +1,15 @@ 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"; import * as chaiAsPromised from "chai-as-promised"; +import {Contains} from "../../src/decorator/string/Contains"; +import {MinLength} from "../../src/decorator/string/MinLength"; +import {IsDefined} from "../../src/decorator/system/IsDefined"; +import {ValidateNested} from "../../src/decorator/system/ValidateNested"; should(); use(chaiAsPromised); diff --git a/test/functional/reject-validation.spec.ts b/test/functional/reject-validation.spec.ts index b3d35a5a5d..3210b36106 100644 --- a/test/functional/reject-validation.spec.ts +++ b/test/functional/reject-validation.spec.ts @@ -1,13 +1,13 @@ import "es6-shim"; import { ValidationError } from "./../../src/validation/ValidationError"; -import { Contains, MinLength } from "../../src/decorator/decorators"; import { Validator } from "../../src/validation/Validator"; import { expect } from "chai"; import {should, use } from "chai"; import * as chaiAsPromised from "chai-as-promised"; +import {Contains} from "../../src/decorator/string/Contains"; should(); use(chaiAsPromised); diff --git a/test/functional/sync-validation.ts b/test/functional/sync-validation.ts index 925d53d01e..f57dd91bd2 100644 --- a/test/functional/sync-validation.ts +++ b/test/functional/sync-validation.ts @@ -3,7 +3,9 @@ import {Validator} from "../../src/validation/Validator"; import {ValidationArguments} from "../../src/validation/ValidationArguments"; import {registerDecorator} from "../../src/register-decorator"; import {ValidationOptions} from "../../src/decorator/ValidationOptions"; -import {ValidatorConstraint, Validate, IsNotEmpty} from "../../src/decorator/decorators"; +import {IsNotEmpty} from "../../src/decorator/common/IsNotEmpty"; +import {Validate} from "../../src/decorator/Validate"; +import {ValidatorConstraint} from "../../src/decorator/ValidatorConstraint"; import {ValidatorConstraintInterface} from "../../src/validation/ValidatorConstraintInterface"; import {should, use } from "chai"; diff --git a/test/functional/validation-error.spec.ts b/test/functional/validation-error.spec.ts index ab4520cb42..cf74c44529 100644 --- a/test/functional/validation-error.spec.ts +++ b/test/functional/validation-error.spec.ts @@ -1,11 +1,13 @@ import "es6-shim"; -import { IsNotEmpty, IsString, IsUrl, IsOptional, ValidateNested, MinLength } from "../../src/decorator/decorators"; -import { Validator } from "../../src/validation/Validator"; -import { expect } from "chai"; - -import {should, use } from "chai"; +import {Validator} from "../../src/validation/Validator"; +import {should, use} from "chai"; import * as chaiAsPromised from "chai-as-promised"; +import {IsString} from "../../src/decorator/typechecker/IsString"; +import {IsUrl} from "../../src/decorator/string/IsUrl"; +import {MinLength} from "../../src/decorator/string/MinLength"; +import {IsOptional} from "../../src/decorator/system/IsOptional"; +import {ValidateNested} from "../../src/decorator/system/ValidateNested"; should(); use(chaiAsPromised); @@ -23,61 +25,62 @@ const validator = new Validator(); * - testing arrays * - testing color codes? */ -describe("ValidationError", function () { - it("should correctly log error message without ANSI escape codes", async function () { - class NestedClass { - - @IsString() - public name: string; - - @IsUrl() - public url: string; - - @IsOptional() - @ValidateNested() - public insideNested: NestedClass; - - constructor(url: string, name: any, insideNested?: NestedClass) { - this.url = url; - this.name = name; - this.insideNested = insideNested; - } - - } - class RootClass { - @IsString() - @MinLength(15) - public title: string; - - @ValidateNested() - public nestedObj: NestedClass; - - @ValidateNested({ each: true }) - public nestedArr: NestedClass[]; - - constructor() { - this.title = (5 as any); - this.nestedObj = new NestedClass("invalid-url", 5, new NestedClass("invalid-url", 5)); - this.nestedArr = [new NestedClass("invalid-url", 5), new NestedClass("invalid-url", 5)]; - } - } - - const validationErrors = await validator.validate(new RootClass()); - - validationErrors[0].toString().should.be.equal("An instance of RootClass has failed the validation:\n" + - " - property title has failed the following constraints: minLength, isString \n"); - - validationErrors[1].toString().should.be.equal("An instance of RootClass has failed the validation:\n" + - " - property nestedObj.name has failed the following constraints: isString \n" + - " - property nestedObj.url has failed the following constraints: isUrl \n" + - " - property nestedObj.insideNested.name has failed the following constraints: isString \n" + - " - property nestedObj.insideNested.url has failed the following constraints: isUrl \n"); - - validationErrors[2].toString().should.be.equal("An instance of RootClass has failed the validation:\n" + - " - property nestedArr[0].name has failed the following constraints: isString \n" + - " - property nestedArr[0].url has failed the following constraints: isUrl \n" + - " - property nestedArr[1].name has failed the following constraints: isString \n" + - " - property nestedArr[1].url has failed the following constraints: isUrl \n"); - }); +describe("ValidationError", function () { + it("should correctly log error message without ANSI escape codes", async function () { + class NestedClass { + + @IsString() + public name: string; + + @IsUrl() + public url: string; + + @IsOptional() + @ValidateNested() + public insideNested: NestedClass; + + constructor(url: string, name: any, insideNested?: NestedClass) { + this.url = url; + this.name = name; + this.insideNested = insideNested; + } + + } + + class RootClass { + @IsString() + @MinLength(15) + public title: string; + + @ValidateNested() + public nestedObj: NestedClass; + + @ValidateNested({each: true}) + public nestedArr: NestedClass[]; + + constructor() { + this.title = (5 as any); + this.nestedObj = new NestedClass("invalid-url", 5, new NestedClass("invalid-url", 5)); + this.nestedArr = [new NestedClass("invalid-url", 5), new NestedClass("invalid-url", 5)]; + } + } + + const validationErrors = await validator.validate(new RootClass()); + + validationErrors[0].toString().should.be.equal("An instance of RootClass has failed the validation:\n" + + " - property title has failed the following constraints: minLength, isString \n"); + + validationErrors[1].toString().should.be.equal("An instance of RootClass has failed the validation:\n" + + " - property nestedObj.name has failed the following constraints: isString \n" + + " - property nestedObj.url has failed the following constraints: isUrl \n" + + " - property nestedObj.insideNested.name has failed the following constraints: isString \n" + + " - property nestedObj.insideNested.url has failed the following constraints: isUrl \n"); + + validationErrors[2].toString().should.be.equal("An instance of RootClass has failed the validation:\n" + + " - property nestedArr[0].name has failed the following constraints: isString \n" + + " - property nestedArr[0].url has failed the following constraints: isUrl \n" + + " - property nestedArr[1].name has failed the following constraints: isString \n" + + " - property nestedArr[1].url has failed the following constraints: isUrl \n"); + }); }); diff --git a/test/functional/validation-functions-and-decorators.spec.ts b/test/functional/validation-functions-and-decorators.spec.ts index 1d06a71d81..f92b214252 100644 --- a/test/functional/validation-functions-and-decorators.spec.ts +++ b/test/functional/validation-functions-and-decorators.spec.ts @@ -1,77 +1,77 @@ import "es6-shim"; import {expect} from "chai"; -import { - IsBooleanString, - IsPositive, - IsNegative, - Contains, - Equals, - MinDate, - MaxDate, - IsAlpha, - IsAlphanumeric, - IsAscii, - IsBase64, - IsBoolean, - IsByteLength, - IsCreditCard, - IsCurrency, - IsDate, - IsDivisibleBy, - IsEmail, - IsEnum, - IsFQDN, - IsFullWidth, - IsHalfWidth, - IsVariableWidth, - IsHexColor, - IsHexadecimal, - IsIP, - IsISBN, - IsISO8601, - IsIn, - IsInt, - IsJSON, - Length, - IsLowercase, - IsMongoId, - IsMultibyte, - IsNumberString, - IsSurrogatePair, - IsUrl, - IsUUID, - IsUppercase, - Matches, - MinLength, - MaxLength, - Min, - Max, - IsNotEmpty, - IsMilitaryTime, - ArrayNotEmpty, - ArrayMinSize, - ArrayMaxSize, - NotEquals, - IsEmpty, - IsDefined, - IsNotIn, - IsNumber, - IsString, - NotContains, - ArrayContains, - ArrayNotContains, - ArrayUnique, - IsArray, - IsDateString, - IsInstance, - IsPhoneNumber -} from "../../src/decorator/decorators"; import {Validator} from "../../src/validation/Validator"; import {ValidatorOptions} from "../../src/validation/ValidatorOptions"; import {should, use } from "chai"; import * as chaiAsPromised from "chai-as-promised"; +import {equals, Equals} from "../../src/decorator/common/Equals"; +import {notEquals, NotEquals} from "../../src/decorator/common/NotEquals"; +import {IsEmpty, isEmpty} from "../../src/decorator/common/IsEmpty"; +import {isNotEmpty, IsNotEmpty} from "../../src/decorator/common/IsNotEmpty"; +import {IsIn, isIn} from "../../src/decorator/common/IsIn"; +import {IsNotIn, isNotIn} from "../../src/decorator/common/IsNotIn"; +import {IsPhoneNumber} from "../../src/decorator/string/IsPhoneNumber"; +import {IsBoolean, isBoolean} from "../../src/decorator/typechecker/IsBoolean"; +import {IsDate, isDate} from "../../src/decorator/typechecker/IsDate"; +import {IsNumber, isNumber} from "../../src/decorator/typechecker/IsNumber"; +import {IsInt, isInt} from "../../src/decorator/typechecker/IsInt"; +import {IsString, isString} from "../../src/decorator/typechecker/IsString"; +import {IsDateString, isDateString} from "../../src/decorator/string-as-type/IsDateString"; +import {IsArray, isArray} from "../../src/decorator/typechecker/IsArray"; +import {isEnum, IsEnum} from "../../src/decorator/typechecker/IsEnum"; +import {IsDivisibleBy, isDivisibleBy} from "../../src/decorator/number/IsDivisibleBy"; +import {IsPositive, isPositive} from "../../src/decorator/number/IsPositive"; +import {isNegative, IsNegative} from "../../src/decorator/number/IsNegative"; +import {Min, min} from "../../src/decorator/number/Min"; +import {Max, max} from "../../src/decorator/number/Max"; +import {minDate, MinDate} from "../../src/decorator/date/MinDate"; +import {maxDate, MaxDate} from "../../src/decorator/date/MaxDate"; +import {isBooleanString, IsBooleanString} from "../../src/decorator/string-as-type/IsBooleanString"; +import {IsNumberString, isNumberString} from "../../src/decorator/string-as-type/IsNumberString"; +import {Contains, contains} from "../../src/decorator/string/Contains"; +import {NotContains, notContains} from "../../src/decorator/string/NotContains"; +import {IsAlpha, isAlpha} from "../../src/decorator/string/IsAlpha"; +import {isAlphanumeric, IsAlphanumeric} from "../../src/decorator/string/IsAlphanumeric"; +import {IsAscii, isAscii} from "../../src/decorator/string/IsAscii"; +import {IsBase64, isBase64} from "../../src/decorator/string/IsBase64"; +import {IsByteLength, isByteLength} from "../../src/decorator/string/IsByteLength"; +import {IsCreditCard, isCreditCard} from "../../src/decorator/string/IsCreditCard"; +import {IsCurrency, isCurrency} from "../../src/decorator/string/IsCurrency"; +import {IsEmail, isEmail} from "../../src/decorator/string/IsEmail"; +import {isFQDN, IsFQDN} from "../../src/decorator/string/IsFQDN"; +import {isFullWidth, IsFullWidth} from "../../src/decorator/string/IsFullWidth"; +import {isHalfWidth, IsHalfWidth} from "../../src/decorator/string/IsHalfWidth"; +import {IsVariableWidth, isVariableWidth} from "../../src/decorator/string/IsVariableWidth"; +import {isHexColor, IsHexColor} from "../../src/decorator/string/IsHexColor"; +import {isHexadecimal, IsHexadecimal} from "../../src/decorator/string/IsHexadecimal"; +import {isIP, IsIP} from "../../src/decorator/string/IsIP"; +import {isISBN, IsISBN} from "../../src/decorator/string/IsISBN"; +import {isISIN, IsISIN} from "../../src/decorator/string/IsISIN"; +import {IsISO8601, isISO8601} from "../../src/decorator/string/IsISO8601"; +import {IsJSON, isJSON} from "../../src/decorator/string/IsJSON"; +import {isLowercase, IsLowercase} from "../../src/decorator/string/IsLowercase"; +import {IsMobilePhone} from "../../src/decorator/string/IsMobilePhone"; +import {isMongoId, IsMongoId} from "../../src/decorator/string/IsMongoId"; +import {IsMultibyte, isMultibyte} from "../../src/decorator/string/IsMultibyte"; +import {isSurrogatePair, IsSurrogatePair} from "../../src/decorator/string/IsSurrogatePair"; +import {IsUrl, isURL} from "../../src/decorator/string/IsUrl"; +import {IsUUID, isUUID} from "../../src/decorator/string/IsUUID"; +import {IsUppercase, isUppercase} from "../../src/decorator/string/IsUppercase"; +import {length, Length} from "../../src/decorator/string/Length"; +import {MinLength, minLength} from "../../src/decorator/string/MinLength"; +import {maxLength, MaxLength} from "../../src/decorator/string/MaxLength"; +import {Matches, matches} from "../../src/decorator/string/Matches"; +import {IsMilitaryTime} from "../../src/decorator/string/IsMilitaryTime"; +import {ArrayContains, arrayContains} from "../../src/decorator/array/ArrayContains"; +import {ArrayNotContains, arrayNotContains} from "../../src/decorator/array/ArrayNotContains"; +import {arrayNotEmpty, ArrayNotEmpty} from "../../src/decorator/array/ArrayNotEmpty"; +import {arrayMinSize, ArrayMinSize} from "../../src/decorator/array/ArrayMinSize"; +import {ArrayMaxSize, arrayMaxSize} from "../../src/decorator/array/ArrayMaxSize"; +import {ArrayUnique, arrayUnique} from "../../src/decorator/array/ArrayUnique"; +import {IsInstance, isInstance} from "../../src/decorator/typechecker/IsInstance"; +import {isDefined, IsDefined} from "../../src/decorator/system/IsDefined"; should(); use(chaiAsPromised); @@ -97,7 +97,7 @@ export function checkInvalidValues(object: { someProperty: any }, values: any[], object.someProperty = value; return validator .validate(object, validatorOptions) - .then(errors => errors.length.should.be.equal(1)); + .then(errors => errors.length.should.be.equal(1, `no errors (unexpectedly) on value: ${value}`)); }); Promise.all(promises).then(() => done(), err => done(err)); } @@ -162,11 +162,11 @@ describe("IsDefined", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isDefined(value).should.be.true); + validValues.forEach(value => isDefined(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isDefined(value).should.be.false); + invalidValues.forEach(value => isDefined(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -197,11 +197,11 @@ describe("Equals", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.equals(value, constraint).should.be.true); + validValues.forEach(value => equals(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.equals(value, constraint).should.be.false); + invalidValues.forEach(value => equals(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -232,11 +232,11 @@ describe("NotEquals", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.notEquals(value, constraint).should.be.true); + validValues.forEach(value => notEquals(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.notEquals(value, constraint).should.be.false); + invalidValues.forEach(value => notEquals(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -266,11 +266,11 @@ describe("IsEmpty", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isEmpty(value).should.be.true); + validValues.forEach(value => isEmpty(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isEmpty(value).should.be.false); + invalidValues.forEach(value => isEmpty(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -300,11 +300,11 @@ describe("IsNotEmpty", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isNotEmpty(value).should.be.true); + validValues.forEach(value => isNotEmpty(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isNotEmpty(value).should.be.false); + invalidValues.forEach(value => isNotEmpty(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -335,11 +335,11 @@ describe("IsIn", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isIn(value, constraint).should.be.true); + validValues.forEach(value => isIn(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isIn(value, constraint).should.be.false); + invalidValues.forEach(value => isIn(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -370,11 +370,11 @@ describe("IsNotIn", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isNotIn(value, constraint).should.be.true); + validValues.forEach(value => isNotIn(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isNotIn(value, constraint).should.be.false); + invalidValues.forEach(value => isNotIn(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -408,11 +408,11 @@ describe("IsBoolean", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isBoolean(value).should.be.true); + validValues.forEach(value => isBoolean(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isBoolean(value).should.be.false); + invalidValues.forEach(value => isBoolean(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -442,11 +442,11 @@ describe("IsDate", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isDate(value).should.be.true); + validValues.forEach(value => isDate(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isDate(value).should.be.false); + invalidValues.forEach(value => isDate(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -502,11 +502,11 @@ describe("IsNumber", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isNumber(value).should.be.true); + validValues.forEach(value => isNumber(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isNumber(value).should.be.false); + invalidValues.forEach(value => isNumber(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -546,11 +546,11 @@ describe("IsInt", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isInt(value).should.be.true); + validValues.forEach(value => isInt(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isInt(value as any).should.be.false); + invalidValues.forEach(value => isInt(value as any).should.be.false); }); it("should return error object with proper data", function(done) { @@ -587,11 +587,11 @@ describe("IsString", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isString(value).should.be.true); + validValues.forEach(value => isString(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isString(value as any).should.be.false); + invalidValues.forEach(value => isString(value as any).should.be.false); }); it("should return error object with proper data", function(done) { @@ -638,11 +638,11 @@ describe("IsDateString", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => expect(validator.isDateString(value)).be.true); + validValues.forEach(value => expect(isDateString(value)).be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => expect(validator.isDateString(value as any)).be.false); + invalidValues.forEach(value => expect(isDateString(value as any)).be.false); }); it("should return error object with proper data", function(done) { @@ -680,11 +680,11 @@ describe("IsArray", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isArray(value).should.be.true); + validValues.forEach(value => isArray(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isArray(value as any).should.be.false); + invalidValues.forEach(value => isArray(value as any).should.be.false); }); it("should return error object with proper data", function(done) { @@ -747,19 +747,19 @@ describe("IsEnum", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isEnum(value, MyEnum).should.be.true); + validValues.forEach(value => isEnum(value, MyEnum).should.be.true); }); it("should not fail if method in validator said that its valid (string enum)", function() { - validStringValues.forEach(value => validator.isEnum(value, MyStringEnum).should.be.true); + validStringValues.forEach(value => isEnum(value, MyStringEnum).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isEnum(value, MyEnum).should.be.false); + invalidValues.forEach(value => isEnum(value, MyEnum).should.be.false); }); it("should fail if method in validator said that its invalid (string enum)", function() { - invalidValues.forEach(value => validator.isEnum(value, MyStringEnum).should.be.false); + invalidValues.forEach(value => isEnum(value, MyStringEnum).should.be.false); }); it("should return error object with proper data", function(done) { @@ -801,11 +801,11 @@ describe("IsDivisibleBy", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isDivisibleBy(value, constraint).should.be.true); + validValues.forEach(value => isDivisibleBy(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isDivisibleBy(value as any, constraint).should.be.false); + invalidValues.forEach(value => isDivisibleBy(value as any, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -857,11 +857,11 @@ describe("IsPositive", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isPositive(value).should.be.true); + validValues.forEach(value => isPositive(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isPositive(value as any).should.be.false); + invalidValues.forEach(value => isPositive(value as any).should.be.false); }); it("should return error object with proper data", function(done) { @@ -914,11 +914,11 @@ describe("IsNegative", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isNegative(value).should.be.true); + validValues.forEach(value => isNegative(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isNegative(value as any).should.be.false); + invalidValues.forEach(value => isNegative(value as any).should.be.false); }); it("should return error object with proper data", function(done) { @@ -949,11 +949,11 @@ describe("Min", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.min(value, constraint).should.be.true); + validValues.forEach(value => min(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.min(value, constraint).should.be.false); + invalidValues.forEach(value => min(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -984,11 +984,11 @@ describe("Max", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.max(value, constraint).should.be.true); + validValues.forEach(value => max(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.max(value, constraint).should.be.false); + invalidValues.forEach(value => max(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1023,11 +1023,11 @@ describe("MinDate", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.minDate(value, constraint).should.be.true); + validValues.forEach(value => minDate(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.minDate(value, constraint).should.be.false); + invalidValues.forEach(value => minDate(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1058,11 +1058,11 @@ describe("MaxDate", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.maxDate(value, constraint).should.be.true); + validValues.forEach(value => maxDate(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.maxDate(value, constraint).should.be.false); + invalidValues.forEach(value => maxDate(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1105,11 +1105,11 @@ describe("IsBooleanString", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isBooleanString(value).should.be.true); + validValues.forEach(value => isBooleanString(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isBooleanString(value).should.be.false); + invalidValues.forEach(value => isBooleanString(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1150,11 +1150,11 @@ describe("IsNumberString", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isNumberString(value).should.be.true); + validValues.forEach(value => isNumberString(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isNumberString(value).should.be.false); + invalidValues.forEach(value => isNumberString(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1189,11 +1189,11 @@ describe("Contains", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.contains(value, constraint).should.be.true); + validValues.forEach(value => contains(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.contains(value, constraint).should.be.false); + invalidValues.forEach(value => contains(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1224,11 +1224,11 @@ describe("NotContains", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.notContains(value, constraint).should.be.true); + validValues.forEach(value => notContains(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.notContains(value, constraint).should.be.false); + invalidValues.forEach(value => notContains(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1259,11 +1259,11 @@ describe("IsAlpha", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isAlpha(value).should.be.true); + validValues.forEach(value => isAlpha(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isAlpha(value).should.be.false); + invalidValues.forEach(value => isAlpha(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1294,11 +1294,11 @@ describe("IsAlphanumeric", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isAlphanumeric(value).should.be.true); + validValues.forEach(value => isAlphanumeric(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isAlphanumeric(value).should.be.false); + invalidValues.forEach(value => isAlphanumeric(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1329,11 +1329,11 @@ describe("IsAscii", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isAscii(value).should.be.true); + validValues.forEach(value => isAscii(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isAscii(value).should.be.false); + invalidValues.forEach(value => isAscii(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1364,11 +1364,11 @@ describe("IsBase64", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isBase64(value).should.be.true); + validValues.forEach(value => isBase64(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isBase64(value).should.be.false); + invalidValues.forEach(value => isBase64(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1400,11 +1400,11 @@ describe("IsByteLength", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isByteLength(value, constraint1, constraint2).should.be.true); + validValues.forEach(value => isByteLength(value, constraint1, constraint2).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isByteLength(value, constraint1, constraint2).should.be.false); + invalidValues.forEach(value => isByteLength(value, constraint1, constraint2).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1441,11 +1441,11 @@ describe("IsCreditCard", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isCreditCard(value).should.be.true); + validValues.forEach(value => isCreditCard(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isCreditCard(value).should.be.false); + invalidValues.forEach(value => isCreditCard(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1519,11 +1519,11 @@ describe("IsCurrency", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isCurrency(value).should.be.true); + validValues.forEach(value => isCurrency(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isCurrency(value).should.be.false); + invalidValues.forEach(value => isCurrency(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1579,13 +1579,13 @@ describe("IsEmail", function() { it("should not fail if method in validator said that its valid", function() { validValues.forEach(value => { - console.log(value, validator.isEmail(value)); - return validator.isEmail(value).should.be.true; + console.log(value, isEmail(value)); + return isEmail(value).should.be.true; }); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isEmail(value).should.be.false); + invalidValues.forEach(value => isEmail(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1632,11 +1632,11 @@ describe("IsFQDN", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isFQDN(value).should.be.true); + validValues.forEach(value => isFQDN(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isFQDN(value).should.be.false); + invalidValues.forEach(value => isFQDN(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1676,11 +1676,11 @@ describe("IsFullWidth", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isFullWidth(value).should.be.true); + validValues.forEach(value => isFullWidth(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isFullWidth(value).should.be.false); + invalidValues.forEach(value => isFullWidth(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1719,11 +1719,11 @@ describe("IsHalfWidth", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isHalfWidth(value).should.be.true); + validValues.forEach(value => isHalfWidth(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isHalfWidth(value).should.be.false); + invalidValues.forEach(value => isHalfWidth(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1767,11 +1767,11 @@ describe("IsVariableWidth", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isVariableWidth(value).should.be.true); + validValues.forEach(value => isVariableWidth(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isVariableWidth(value).should.be.false); + invalidValues.forEach(value => isVariableWidth(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1812,11 +1812,11 @@ describe("IsHexColor", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isHexColor(value).should.be.true); + validValues.forEach(value => isHexColor(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isHexColor(value).should.be.false); + invalidValues.forEach(value => isHexColor(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1855,11 +1855,11 @@ describe("IsHexadecimal", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isHexadecimal(value).should.be.true); + validValues.forEach(value => isHexadecimal(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isHexadecimal(value).should.be.false); + invalidValues.forEach(value => isHexadecimal(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1926,11 +1926,11 @@ describe("IsIP", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isIP(value).should.be.true); + validValues.forEach(value => isIP(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isIP(value).should.be.false); + invalidValues.forEach(value => isIP(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -1939,6 +1939,12 @@ describe("IsIP", function() { checkReturnedError(new MyClass(), invalidValues, validationType, message, done); }); + it("should heed the version param", function() { + isIP("123.123.123.123", "4").should.be.true; + isIP("123.123.123.123", "6").should.be.false; + isIP("::1", "4").should.be.false; + isIP("::1", "6").should.be.true; + }); }); describe("IsISBN version 10", function() { @@ -1970,11 +1976,11 @@ describe("IsISBN version 10", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isISBN(value, "10").should.be.true); + validValues.forEach(value => isISBN(value, "10").should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isISBN(value, "10").should.be.false); + invalidValues.forEach(value => isISBN(value, "10").should.be.false); }); it("should return error object with proper data", function(done) { @@ -1983,6 +1989,11 @@ describe("IsISBN version 10", function() { checkReturnedError(new MyClass(), invalidValues, validationType, message, done); }); + it("should heed the version param", function() { + isISBN("99921-58-10-7", "10").should.be.true; + isISBN("978-3-16-148410-0", "10").should.be.false; + }); + }); describe("IsISBN version 13", function() { @@ -2012,11 +2023,11 @@ describe("IsISBN version 13", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isISBN(value, "13").should.be.true); + validValues.forEach(value => isISBN(value, "13").should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isISBN(value, "13").should.be.false); + invalidValues.forEach(value => isISBN(value, "13").should.be.false); }); it("should return error object with proper data", function(done) { @@ -2025,8 +2036,56 @@ describe("IsISBN version 13", function() { checkReturnedError(new MyClass(), invalidValues, validationType, message, done); }); + it("should heed the version param", function() { + isISBN("978-3-16-148410-0", "13").should.be.true; + isISBN("99921-58-10-7", "13").should.be.false; + }); + +}); + +describe("IsISIN", function() { + + const validValues = [ + "US5949181045", "US38259P5089", "US0378331005" + , "BMG491BT1088", "IE00B4BNMY34", "US0231351067" + , "US64110L1061", "US30303M1027", "CH0031240127" + ]; + const invalidValues = [ + null, undefined, "9783836221190", "978-3-8362-2119-0", "978 3 8362 2119 0" + , "3836221195", "3-8362-2119-5", "3 8362 2119 5" + , "01234567890ab", "foo", "" + ]; + + class MyClass { + @IsISIN() + someProperty: string; + } + + it("should not fail if validator.validate said that its valid", function(done) { + checkValidValues(new MyClass(), validValues, done); + }); + + it("should fail if validator.validate said that its invalid", function(done) { + checkInvalidValues(new MyClass(), invalidValues, done); + }); + + it("should not fail if method in validator said that its valid", function() { + validValues.forEach(value => isISIN(value).should.be.true); + }); + + it("should fail if method in validator said that its invalid", function() { + invalidValues.forEach(value => isISIN(value).should.be.false); + }); + + it("should return error object with proper data", function(done) { + const validationType = "isIsin"; + const message = "someProperty must be an ISIN (stock/security identifier)"; + checkReturnedError(new MyClass(), invalidValues, validationType, message, done); + }); + }); + describe("IsISO8601", function() { const validValues = [ @@ -2113,11 +2172,11 @@ describe("IsISO8601", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isISO8601(value).should.be.true); + validValues.forEach(value => isISO8601(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isISO8601(value).should.be.false); + invalidValues.forEach(value => isISO8601(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2147,11 +2206,11 @@ describe("IsJSON", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isJSON(value).should.be.true); + validValues.forEach(value => isJSON(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isJSON(value).should.be.false); + invalidValues.forEach(value => isJSON(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2191,11 +2250,11 @@ describe("IsLowercase", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isLowercase(value).should.be.true); + validValues.forEach(value => isLowercase(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isLowercase(value).should.be.false); + invalidValues.forEach(value => isLowercase(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2234,11 +2293,11 @@ describe("IsMongoId", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isMongoId(value).should.be.true); + validValues.forEach(value => isMongoId(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isMongoId(value).should.be.false); + invalidValues.forEach(value => isMongoId(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2281,11 +2340,11 @@ describe("IsMultibyte", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isMultibyte(value).should.be.true); + validValues.forEach(value => isMultibyte(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isMultibyte(value).should.be.false); + invalidValues.forEach(value => isMultibyte(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2325,11 +2384,11 @@ describe("IsSurrogatePair", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isSurrogatePair(value).should.be.true); + validValues.forEach(value => isSurrogatePair(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isSurrogatePair(value).should.be.false); + invalidValues.forEach(value => isSurrogatePair(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2424,19 +2483,19 @@ describe("IsUrl", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isURL(value).should.be.true); + validValues.forEach(value => isURL(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isURL(value).should.be.false); + invalidValues.forEach(value => isURL(value).should.be.false); }); it("should fail on localhost without require_tld option", function () { - validator.isURL("http://localhost:3000/").should.be.false; + isURL("http://localhost:3000/").should.be.false; }); it("should pass on localhost with require_tld option", function () { - validator.isURL("http://localhost:3000/", { require_tld: false }).should.be.true; + isURL("http://localhost:3000/", { require_tld: false }).should.be.true; }); it("should return error object with proper data", function(done) { @@ -2480,11 +2539,11 @@ describe("IsUUID", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isUUID(value).should.be.true); + validValues.forEach(value => isUUID(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isUUID(value).should.be.false); + invalidValues.forEach(value => isUUID(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2525,11 +2584,11 @@ describe("IsUUID v3", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isUUID(value, "3").should.be.true); + validValues.forEach(value => isUUID(value, "3").should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isUUID(value, "3").should.be.false); + invalidValues.forEach(value => isUUID(value, "3").should.be.false); }); it("should return error object with proper data", function(done) { @@ -2573,11 +2632,11 @@ describe("IsUUID v4", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isUUID(value, "4").should.be.true); + validValues.forEach(value => isUUID(value, "4").should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isUUID(value, "4").should.be.false); + invalidValues.forEach(value => isUUID(value, "4").should.be.false); }); it("should return error object with proper data", function(done) { @@ -2621,11 +2680,11 @@ describe("IsUUID v5", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isUUID(value, "5").should.be.true); + validValues.forEach(value => isUUID(value, "5").should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isUUID(value, "5").should.be.false); + invalidValues.forEach(value => isUUID(value, "5").should.be.false); }); it("should return error object with proper data", function(done) { @@ -2665,11 +2724,11 @@ describe("IsUppercase", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isUppercase(value).should.be.true); + validValues.forEach(value => isUppercase(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isUppercase(value).should.be.false); + invalidValues.forEach(value => isUppercase(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2701,11 +2760,11 @@ describe("Length", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.length(value, constraint1, constraint2).should.be.true); + validValues.forEach(value => length(value, constraint1, constraint2).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.length(value, constraint1, constraint2).should.be.false); + invalidValues.forEach(value => length(value, constraint1, constraint2).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2742,11 +2801,11 @@ describe("MinLength", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.minLength(value, constraint1).should.be.true); + validValues.forEach(value => minLength(value, constraint1).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.minLength(value, constraint1).should.be.false); + invalidValues.forEach(value => minLength(value, constraint1).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2777,11 +2836,11 @@ describe("MaxLength", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.maxLength(value, constraint1).should.be.true); + validValues.forEach(value => maxLength(value, constraint1).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.maxLength(value, constraint1).should.be.false); + invalidValues.forEach(value => maxLength(value, constraint1).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2812,11 +2871,11 @@ describe("Matches", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.matches(value, constraint).should.be.true); + validValues.forEach(value => matches(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.matches(value, constraint).should.be.false); + invalidValues.forEach(value => matches(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2851,6 +2910,57 @@ describe("IsMilitaryTime", function() { }); +describe("isMobilePhone", function() { + describe("with region", function() { + const validValues = [ + "01517551023", "0151 755 10 23", + "+491517551023", "+49 151 755 10 23", + "+49 (0) 1517551023", "+49 151 755 10 23", + ]; + const invalidValues = [undefined, null, "asdf", "1"]; + + class MyClass { + @IsMobilePhone("de-DE") + someProperty: string; + } + + it("should not fail if validator.validate said that its valid", function(done) { + checkValidValues(new MyClass(), validValues, done); + }); + + it("should fail if validator.validate said that its invalid", function(done) { + checkInvalidValues(new MyClass(), invalidValues, done); + }); + }); + + describe("no region", function() { + const validValues = [ + "+491517551023", "+49 151 755 10 23", + "+49 (0) 1517551023", "+49 151 755 10 23" + ]; + const invalidValues = [ + // most numbers are valid in any of the supported locales.. + // so choosing failing fixtures is hard. + "12345678901234567894654321", + undefined, null, "asdf", "1" + ]; + + class MyClass { + @IsMobilePhone("any") + someProperty: string; + } + + it("should not fail if validator.validate said that its valid", function(done) { + checkValidValues(new MyClass(), validValues, done); + }); + + it("should fail if validator.validate said that its invalid", function(done) { + checkInvalidValues(new MyClass(), invalidValues, done); + }); + }); +}); + + describe("isPhoneNumber", function() { describe("with region", function() { const validValues = [ @@ -2926,11 +3036,11 @@ describe("ArrayContains", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.arrayContains(value, constraint).should.be.true); + validValues.forEach(value => arrayContains(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.arrayContains(value, constraint).should.be.false); + invalidValues.forEach(value => arrayContains(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2961,11 +3071,11 @@ describe("ArrayNotContains", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.arrayNotContains(value, constraint).should.be.true); + validValues.forEach(value => arrayNotContains(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.arrayNotContains(value, constraint).should.be.false); + invalidValues.forEach(value => arrayNotContains(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -2995,11 +3105,11 @@ describe("ArrayNotEmpty", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.arrayNotEmpty(value).should.be.true); + validValues.forEach(value => arrayNotEmpty(value).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.arrayNotEmpty(value).should.be.false); + invalidValues.forEach(value => arrayNotEmpty(value).should.be.false); }); it("should return error object with proper data", function(done) { @@ -3030,11 +3140,11 @@ describe("ArrayMinSize", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.arrayMinSize(value, constraint).should.be.true); + validValues.forEach(value => arrayMinSize(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.arrayMinSize(value, constraint).should.be.false); + invalidValues.forEach(value => arrayMinSize(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -3065,11 +3175,11 @@ describe("ArrayMaxSize", function() { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.arrayMaxSize(value, constraint).should.be.true); + validValues.forEach(value => arrayMaxSize(value, constraint).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.arrayMaxSize(value, constraint).should.be.false); + invalidValues.forEach(value => arrayMaxSize(value, constraint).should.be.false); }); it("should return error object with proper data", function(done) { @@ -3099,11 +3209,11 @@ describe("ArrayUnique", function () { }); it("should not fail if method in validator said that its valid", function () { - validValues.forEach(value => validator.arrayUnique(value).should.be.true); + validValues.forEach(value => arrayUnique(value).should.be.true); }); it("should fail if method in validator said that its invalid", function () { - invalidValues.forEach(value => validator.arrayUnique(value).should.be.false); + invalidValues.forEach(value => arrayUnique(value).should.be.false); }); it("should return error object with proper data", function (done) { @@ -3136,11 +3246,11 @@ describe("isInstance", function () { }); it("should not fail if method in validator said that its valid", function() { - validValues.forEach(value => validator.isInstance(value, MySubClass).should.be.true); + validValues.forEach(value => isInstance(value, MySubClass).should.be.true); }); it("should fail if method in validator said that its invalid", function() { - invalidValues.forEach(value => validator.isInstance(value, MySubClass).should.be.false); + invalidValues.forEach(value => isInstance(value, MySubClass).should.be.false); }); it("should return error object with proper data", function(done) { diff --git a/test/functional/validation-options.spec.ts b/test/functional/validation-options.spec.ts index 77eb07faee..b1dbb15452 100644 --- a/test/functional/validation-options.spec.ts +++ b/test/functional/validation-options.spec.ts @@ -1,11 +1,12 @@ import "es6-shim"; -import {Contains, Matches, MinLength, ValidateNested} from "../../src/decorator/decorators"; import {Validator} from "../../src/validation/Validator"; -import {ValidationError} from "../../src"; - -import {should, use } from "chai"; - +import {ValidationError} from "../../src/validation/ValidationError"; +import {should, use} from "chai"; import * as chaiAsPromised from "chai-as-promised"; +import {Contains} from "../../src/decorator/string/Contains"; +import {MinLength} from "../../src/decorator/string/MinLength"; +import {Matches} from "../../src/decorator/string/Matches"; +import {ValidateNested} from "../../src/decorator/system/ValidateNested"; should(); use(chaiAsPromised); diff --git a/test/functional/validator-options.spec.ts b/test/functional/validator-options.spec.ts index 6cd6303609..174c7eb7e6 100644 --- a/test/functional/validator-options.spec.ts +++ b/test/functional/validator-options.spec.ts @@ -1,5 +1,5 @@ import "es6-shim"; -import {IsNotEmpty} from "../../src/decorator/decorators"; +import {IsNotEmpty} from "../../src/decorator/common/IsNotEmpty"; import {Validator} from "../../src/validation/Validator"; import {expect} from "chai"; diff --git a/test/functional/whitelist-validation.spec.ts b/test/functional/whitelist-validation.spec.ts index 3750ee3d55..56ee9fddf4 100644 --- a/test/functional/whitelist-validation.spec.ts +++ b/test/functional/whitelist-validation.spec.ts @@ -1,5 +1,4 @@ import "es6-shim"; -import {Allow, IsDefined, Min, ValidateNested} from "../../src/decorator/decorators"; import {Validator} from "../../src/validation/Validator"; import {expect} from "chai"; import {ValidationTypes} from "../../src/validation/ValidationTypes"; @@ -7,6 +6,9 @@ import {ValidationTypes} from "../../src/validation/ValidationTypes"; import {should, use } from "chai"; import * as chaiAsPromised from "chai-as-promised"; +import {Min} from "../../src/decorator/number/Min"; +import {IsDefined} from "../../src/decorator/system/IsDefined"; +import {Allow} from "../../src/decorator/system/Allow"; should(); use(chaiAsPromised);