Skip to content

Commit

Permalink
Merge pull request #3 from mdovhopo/master
Browse files Browse the repository at this point in the history
feat: date validation for IsString
  • Loading branch information
glebbash authored Oct 1, 2021
2 parents 49232f4 + f372132 commit afb4827
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 3 deletions.
129 changes: 129 additions & 0 deletions src/decorators/is-string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,133 @@ describe('IsString', () => {
);
});
});

describe('date', () => {
describe('date', () => {
class Test {
@IsString({ isDate: { format: 'date' } })
date!: string;
}

it('accepts date as YYYY-MM-DD', async () => {
expect(await input(Test, { date: '2020-01-01' })).toStrictEqual(
Result.ok(make(Test, { date: '2020-01-01' }))
);
});

test.each([
['2020-01-01_not_a_date'],
['2020-01-01 01:01:01'],
['2020-01-01T01:01:01.000Z'],
[''],
['not_a_date'],
])('rejects %s', async (value) => {
expect(await input(Test, { date: value })).toStrictEqual(
Result.err('date is not formatted as `yyyy-mm-dd` or not a valid Date')
);
});
});

describe('date-time', () => {
class Test {
@IsString({ isDate: { format: 'date-time' } })
date!: string;
}

test.each([
['2020'],
['2020-01-01'],
['2020-01-01 00:00'],
['2020-01-01 00:00:00'],
['2020-01-01T00:00:00.000Z'],
['2020-01-01T00:00:00+00:00'],
])('accepts %s', async (value) => {
expect(await input(Test, { date: value })).toStrictEqual(
Result.ok(make(Test, { date: value }))
);
});

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)'],
['2020333'],
[''],
['not_a_date'],
])('rejects %s', async (value) => {
expect(await input(Test, { date: value })).toStrictEqual(
Result.err('date is not in a ISO8601 format.')
);
});
});
});

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')
);
});
});
});
34 changes: 31 additions & 3 deletions src/decorators/is-string.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
import { IsEmail, IsString as IsStringCV, Length, Matches } from 'class-validator';
import {
IsEmail,
isISO8601,
IsString as IsStringCV,
Length,
Matches,
ValidationOptions,
} from 'class-validator';

import { Base, compose, noop } from '../core';
import { CustomValidate, CustomValidateOptions } from './utils/custom-validator';

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\` or not a valid Date`,
},
'date-time': {
validator: (value) => isISO8601(value, { strict: true }) && !isNaN(new Date(value).getDate()),
message: ({ property }) => `${property} is not in a ISO8601 format.`,
},
};

export const IsString = ({
maxLength,
minLength,
pattern,
canBeEmpty,
isEmail,
isDate,
customValidate,
...base
}: Base<string> & {
canBeEmpty?: true;
maxLength?: number;
minLength?: number;
pattern?: { regex: RegExp; message?: string };
pattern?: { regex: RegExp; message?: ValidationOptions['message'] };
isEmail?: true;
isDate?: { format: DateFormat };
customValidate?: CustomValidateOptions;
} = {}): PropertyDecorator =>
compose(
{
type: 'string',
minLength,
maxLength,
...(isEmail && { format: 'email' }),
...(isDate && { format: isDate.format }),
pattern: pattern?.regex.toString().slice(1, 1), // removes trailing slashes
},
base,
Expand All @@ -30,5 +56,7 @@ export const IsString = ({
? Length(minLength ?? 0, maxLength, { each: !!base.isArray })
: noop,
isEmail ? IsEmail(undefined, { each: !!base.isArray }) : noop,
pattern ? Matches(pattern.regex, { message: pattern.message, each: !!base.isArray }) : noop
pattern ? Matches(pattern.regex, { message: pattern.message, each: !!base.isArray }) : noop,
isDate ? CustomValidate(dateValidators[isDate.format], { each: !!base.isArray }) : noop,
customValidate ? CustomValidate(customValidate, { each: !!base.isArray }) : noop
);
24 changes: 24 additions & 0 deletions src/decorators/utils/custom-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ValidateBy, ValidationArguments, ValidationOptions } from 'class-validator';

export type CustomValidateOptions = {
validator: (value: any, args: ValidationArguments) => boolean;
message: string | ((validationArguments: ValidationArguments) => string);
};

export function CustomValidate(
options: CustomValidateOptions,
validationOptions?: ValidationOptions
): PropertyDecorator {
return ValidateBy(
{
name: 'CustomValidate',
constraints: [options],
validator: {
validate: options.validator,
defaultMessage: (args: ValidationArguments) =>
typeof options.message === 'string' ? options.message : options.message(args),
},
},
validationOptions
);
}

0 comments on commit afb4827

Please sign in to comment.