diff --git a/src/decorators/is-date.spec.ts b/src/decorators/is-date.spec.ts index f5c3a6b..373a9e9 100644 --- a/src/decorators/is-date.spec.ts +++ b/src/decorators/is-date.spec.ts @@ -61,7 +61,21 @@ describe('IsDate', () => { const dto = make(TestOptional, {}); expect(output(dto)).toStrictEqual({}); - expect(await input(Test, {})).toStrictEqual(Result.ok(make(TestOptional, {}))); + expect(await input(TestOptional, {})).toStrictEqual(Result.ok(make(TestOptional, {}))); + }); + + it('works with nullable fields', async () => { + class TestNullable { + @IsDate({ format: 'date', nullable: true }) + date!: Date | null; + } + + const dto = make(TestNullable, { date: null }); + expect(output(dto)).toStrictEqual({ date: null }); + + expect(await input(TestNullable, { date: null })).toStrictEqual( + Result.ok(make(TestNullable, { date: null })) + ); }); }); @@ -124,5 +138,19 @@ describe('IsDate', () => { expect(await input(TestOptional, {})).toStrictEqual(Result.ok(make(TestOptional, {}))); }); + + it('works with nullable fields', async () => { + class TestNullable { + @IsDate({ format: 'date-time', nullable: true }) + date!: Date | null; + } + + const dto = make(TestNullable, { date: null }); + expect(output(dto)).toStrictEqual({ date: null }); + + expect(await input(TestNullable, { date: null })).toStrictEqual( + Result.ok(make(TestNullable, { date: null })) + ); + }); }); }); diff --git a/src/decorators/is-date.ts b/src/decorators/is-date.ts index 8599559..4603961 100644 --- a/src/decorators/is-date.ts +++ b/src/decorators/is-date.ts @@ -1,7 +1,8 @@ -import { Transform, TransformationType } from 'class-transformer'; +import { TransformationType, TransformFnParams } from 'class-transformer'; import { IsDate as IsDateCV, isDateString } from 'class-validator'; import { compose, PropertyOptions } from '../core'; +import { TransformHandlingOptional } from './utils/transform-handling-optional'; const dateRegex = /^\d{4}-\d{2}-\d{2}$/; @@ -13,17 +14,13 @@ export const IsDate = ({ compose( { type: 'string', format }, base, - format === 'date' ? TransformDate : TransformDateTime, + TransformHandlingOptional(base, format === 'date' ? transformDate : transformDateTime), IsDateCV({ message: ({ value }) => value?.message }) ); -const TransformDate = Transform(({ key, value, type }) => { - if (value === undefined) { - return new Error(`${key} does not exist`); - } - +function transformDate({ key, value, type }: TransformFnParams) { if (type === TransformationType.CLASS_TO_PLAIN) { - return stripTime(value.toISOString()); + return value.toISOString().split('T')[0]; } if (!dateRegex.test(value)) { @@ -36,13 +33,9 @@ const TransformDate = Transform(({ key, value, type }) => { } return date; -}); - -const TransformDateTime = Transform(({ key, value, type }) => { - if (value === undefined) { - return new Error(`${key} does not exist`); - } +} +function transformDateTime({ key, value, type }: TransformFnParams) { if (type === TransformationType.CLASS_TO_PLAIN) { return value.toISOString(); } @@ -52,8 +45,4 @@ const TransformDateTime = Transform(({ key, value, type }) => { } return new Date(value); -}); - -function stripTime(isoDate: string) { - return isoDate.split('T')[0]; } diff --git a/src/decorators/utils/transform-handling-optional.ts b/src/decorators/utils/transform-handling-optional.ts new file mode 100644 index 0000000..b898772 --- /dev/null +++ b/src/decorators/utils/transform-handling-optional.ts @@ -0,0 +1,22 @@ +import { Transform, TransformFnParams } from 'class-transformer'; + +export function TransformHandlingOptional( + config: { optional?: true; nullable?: true }, + transform: (params: TransformFnParams) => unknown +) { + return Transform((params: TransformFnParams) => { + if (config.optional && params.value === undefined) { + return params.value; + } + + if (config.nullable && params.value === null) { + return params.value; + } + + if (params.value === null || params.value === undefined) { + return new Error(`${params.key} does not exist`); + } + + return transform(params); + }); +}