diff --git a/README.md b/README.md index 886712dd76..42b52518fc 100644 --- a/README.md +++ b/README.md @@ -795,10 +795,11 @@ isBoolean(value); ## 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. | +| `@IsNullable()` | Checks if given value is null 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). | diff --git a/src/decorator/common/IsNullable.ts b/src/decorator/common/IsNullable.ts new file mode 100644 index 0000000000..397ad8ce8f --- /dev/null +++ b/src/decorator/common/IsNullable.ts @@ -0,0 +1,28 @@ +import { ValidationOptions } from '../ValidationOptions'; +import { ValidationMetadataArgs } from '../../metadata/ValidationMetadataArgs'; +import { ValidationTypes } from '../../validation/ValidationTypes'; +import { ValidationMetadata } from '../../metadata/ValidationMetadata'; +import { getMetadataStorage } from '../../metadata/MetadataStorage'; + +export const IS_NULLABLE = 'isNullable'; + +/** + * Checks if value is null and if so, ignores all validators. + */ +export function IsNullable(validationOptions?: ValidationOptions): PropertyDecorator { + return function (object: object, propertyName: string): void { + const args: ValidationMetadataArgs = { + type: ValidationTypes.CONDITIONAL_VALIDATION, + name: IS_NULLABLE, + target: object.constructor, + propertyName: propertyName, + constraints: [ + (object: any, value: any): boolean => { + return object[propertyName] !== null; + }, + ], + validationOptions: validationOptions, + }; + getMetadataStorage().addValidationMetadata(new ValidationMetadata(args)); + }; +} diff --git a/test/functional/conditional-validation.spec.ts b/test/functional/conditional-validation.spec.ts index e633763799..08be9e5d06 100644 --- a/test/functional/conditional-validation.spec.ts +++ b/test/functional/conditional-validation.spec.ts @@ -1,5 +1,6 @@ import { IsNotEmpty, ValidateIf, IsOptional, Equals } from '../../src/decorator/decorators'; import { Validator } from '../../src/validation/Validator'; +import {IsNullable} from "../../src/decorator/common/IsNullable"; const validator = new Validator(); @@ -92,4 +93,51 @@ describe('conditional validation', () => { expect(errors[0].value).toEqual('bad_value'); }); }); + + it('should validate a property when value is supplied', () => { + class MyClass { + @IsNullable() + @Equals('test') + title: string = 'bad_value'; + } + + const model = new MyClass(); + return validator.validate(model).then(errors => { + expect(errors.length).toEqual(1); + expect(errors[0].target).toEqual(model); + expect(errors[0].property).toEqual('title'); + expect(errors[0].constraints).toEqual({ equals: 'title must be equal to test' }); + expect(errors[0].value).toEqual('bad_value'); + }); + }); + + it('should validate a property when value is undefined', () => { + class MyClass { + @IsNullable() + @Equals('test') + title: string = undefined; + } + + const model = new MyClass(); + return validator.validate(model).then(errors => { + expect(errors.length).toEqual(1); + expect(errors[0].target).toEqual(model); + expect(errors[0].property).toEqual('title'); + expect(errors[0].constraints).toEqual({ equals: 'title must be equal to test' }); + expect(errors[0].value).toEqual(undefined); + }); + }); + + it("shouldn't validate a property when value is null", () => { + class MyClass { + @IsNullable() + @Equals('test') + title: string = null; + } + + const model = new MyClass(); + return validator.validate(model).then(errors => { + expect(errors.length).toEqual(0); + }); + }); });