diff --git a/src/decorators/is-string.spec.ts b/src/decorators/is-string.spec.ts index f5add47..cfac473 100644 --- a/src/decorators/is-string.spec.ts +++ b/src/decorators/is-string.spec.ts @@ -169,7 +169,7 @@ describe('IsString', () => { }); }); - describe('date (date-time)', () => { + describe('date', () => { describe('date', () => { class Test { @IsString({ isDate: { format: 'date' } }) @@ -190,7 +190,7 @@ describe('IsString', () => { ['not_a_date'], ])('rejects %s', async (value) => { expect(await input(Test, { date: value })).toStrictEqual( - Result.err("date must be in a format 'YYYY-MM-DD'") + Result.err('date is not formatted as `yyyy-mm-dd` or not a valid Date') ); }); }); @@ -204,7 +204,6 @@ describe('IsString', () => { test.each([ ['2020'], ['2020-01-01'], - ['2020-01-01 00'], ['2020-01-01 00:00'], ['2020-01-01 00:00:00'], ['2020-01-01T00:00:00.000Z'], @@ -217,6 +216,7 @@ describe('IsString', () => { test.each([ ['2020-01-01 100'], + ['2020-01-01 00'], ['-1'], ['1633099642455'], ['Fri Oct 01 2021 18:14:15 GMT+0300 (Eastern European Summer Time)'], @@ -230,4 +230,71 @@ describe('IsString', () => { }); }); }); + + describe('custom validator', () => { + it('does not fail, if validator returns true', async () => { + class Test { + @IsString({ + customValidate: { + validator: () => true, + message: 'test error message', + }, + }) + stringField!: string; + } + + expect(await input(Test, { stringField: 'any' })).toStrictEqual( + Result.ok(make(Test, { stringField: 'any' })) + ); + }); + + it('returns message, if validator returns false', async () => { + class Test { + @IsString({ + customValidate: { + validator: () => false, + message: 'test error message', + }, + }) + stringField!: string; + } + + expect(await input(Test, { stringField: 'any' })).toStrictEqual( + Result.err('test error message') + ); + }); + + it('returns custom message, if validator returns false', async () => { + class Test { + @IsString({ + customValidate: { + validator: () => false, + message: ({ property }) => `${property} is invalid`, + }, + }) + stringField!: string; + } + + expect(await input(Test, { stringField: 'any' })).toStrictEqual( + Result.err('stringField is invalid') + ); + }); + + it('works with arrays', async () => { + class Test { + @IsString({ + isArray: true, + customValidate: { + validator: () => false, + message: ({ property }) => `each value in ${property} is invalid`, + }, + }) + stringField!: string[]; + } + + expect(await input(Test, { stringField: 'any' })).toStrictEqual( + Result.err('each value in stringField is invalid') + ); + }); + }); }); diff --git a/src/decorators/is-string.ts b/src/decorators/is-string.ts index 7a59c07..e58d7e8 100644 --- a/src/decorators/is-string.ts +++ b/src/decorators/is-string.ts @@ -15,7 +15,7 @@ export type DateFormat = 'date' | 'date-time'; const dateValidators: { [key in DateFormat]: CustomValidateOptions } = { date: { validator: (value) => /^\d{4}-\d{2}-\d{2}$/.test(value) && !isNaN(new Date(value).getDate()), - message: ({ property }) => `${property} is not formatted as \`yyyy-mm-dd\` and be a valid Date`, + message: ({ property }) => `${property} is not formatted as \`yyyy-mm-dd\` or not a valid Date`, }, 'date-time': { validator: (value) => isISO8601(value, { strict: true }) && !isNaN(new Date(value).getDate()), diff --git a/src/decorators/utils/custom-validator.ts b/src/decorators/utils/custom-validator.ts index 4d5dc01..a5bd42f 100644 --- a/src/decorators/utils/custom-validator.ts +++ b/src/decorators/utils/custom-validator.ts @@ -1,7 +1,7 @@ import { ValidateBy, ValidationArguments, ValidationOptions } from 'class-validator'; export type CustomValidateOptions = { - validator: (value: unknown, args: ValidationArguments) => boolean; + validator: (value: any, args: ValidationArguments) => boolean; message: string | ((validationArguments: ValidationArguments) => string); }; @@ -15,11 +15,8 @@ export function CustomValidate( constraints: [options], validator: { validate: options.validator, - defaultMessage: (args: ValidationArguments) => { - const eachPrefix = validationOptions?.each ? 'each value in ' : ''; - const msg = typeof options.message === 'string' ? options.message : options.message(args); - return eachPrefix + msg; - }, + defaultMessage: (args: ValidationArguments) => + typeof options.message === 'string' ? options.message : options.message(args), }, }, validationOptions