diff --git a/README.md b/README.md index 886712dd76..5b8b1f8edc 100644 --- a/README.md +++ b/README.md @@ -805,6 +805,7 @@ isBoolean(value); | `@IsNotEmpty()` | Checks if given value is not empty (!== '', !== null, !== undefined). | | `@IsIn(values: any[])` | Checks if value is in an array of allowed values. | | `@IsNotIn(values: any[])` | Checks if value is not in an array of disallowed values. | +| `@DoesMatch(condition: (obj, value) => boolean)` | Checks if a given value follows a custom condition. | | **Type validation decorators** | | | `@IsBoolean()` | Checks if a value is a boolean. | | `@IsDate()` | Checks if the value is a date. | diff --git a/src/decorator/common/DoesMatch.ts b/src/decorator/common/DoesMatch.ts new file mode 100644 index 0000000000..40fb07c8b1 --- /dev/null +++ b/src/decorator/common/DoesMatch.ts @@ -0,0 +1,20 @@ +import { ValidationOptions } from '../ValidationOptions'; +import { ValidateBy, buildMessage } from './ValidateBy'; + +export const DOES_MATCH = 'doesMatch'; + +/** + * Checks if a given value follows a custom condition. + */ +export function DoesMatch( + condition: (object: any, value: any) => boolean, + validationOptions?: ValidationOptions +): PropertyDecorator { + return ValidateBy({ + name: DOES_MATCH, + validator: { + validate: (value, args): boolean => condition(args?.object, value), + defaultMessage: buildMessage(eachPrefix => eachPrefix + '$property does not match', validationOptions), + }, + }); +} diff --git a/src/decorator/decorators.ts b/src/decorator/decorators.ts index d449e9301a..b5e3b71ba7 100644 --- a/src/decorator/decorators.ts +++ b/src/decorator/decorators.ts @@ -23,6 +23,7 @@ export * from './common/IsEmpty'; export * from './common/IsNotEmpty'; export * from './common/IsIn'; export * from './common/IsNotIn'; +export * from './common/DoesMatch'; // ------------------------------------------------------------------------- // Number checkers diff --git a/test/functional/validation-functions-and-decorators.spec.ts b/test/functional/validation-functions-and-decorators.spec.ts index 78ceddcd6d..caf60f812d 100644 --- a/test/functional/validation-functions-and-decorators.spec.ts +++ b/test/functional/validation-functions-and-decorators.spec.ts @@ -193,6 +193,7 @@ import { isTaxId, IsTaxId, IsISO4217CurrencyCode, + DoesMatch, } from '../../src/decorator/decorators'; import { Validator } from '../../src/validation/Validator'; import { ValidatorOptions } from '../../src/validation/ValidatorOptions'; @@ -519,6 +520,38 @@ describe('IsNotIn', () => { }); }); +describe('DoesMatch', () => { + const validValues = ['foobar']; + const invalidValues = ['bar']; + class MyClass { + @DoesMatch((obj, value) => obj.propertyToMatch === value) + someProperty: string; + + @IsString() + propertyToMatch: string; + } + + it('should not fail if validator.validate said that its valid', () => { + const obj = new MyClass(); + obj.propertyToMatch = 'foobar'; + return checkValidValues(obj, validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + const obj = new MyClass(); + obj.propertyToMatch = 'foobar'; + return checkInvalidValues(obj, invalidValues); + }); + + it('should return error object with proper data', () => { + const obj = new MyClass(); + obj.propertyToMatch = 'foobar'; + const validationType = 'doesMatch'; + const message = 'someProperty does not match'; + return checkReturnedError(obj, invalidValues, validationType, message); + }); +}); + // ------------------------------------------------------------------------- // Specifications: type check // -------------------------------------------------------------------------