From 07e033a894d72a9f5a3e8519dde574acccc3df5e Mon Sep 17 00:00:00 2001 From: glebbash Date: Fri, 8 Jul 2022 12:32:12 +0000 Subject: [PATCH] fix: add missing IsDate serialization --- src/decorators/is-date.spec.ts | 70 +++++++++++++++++++++++++++++++++- src/decorators/is-date.ts | 68 ++++++++++++++++++++++----------- 2 files changed, 114 insertions(+), 24 deletions(-) diff --git a/src/decorators/is-date.spec.ts b/src/decorators/is-date.spec.ts index a37d8ec..f5c3a6b 100644 --- a/src/decorators/is-date.spec.ts +++ b/src/decorators/is-date.spec.ts @@ -1,6 +1,6 @@ import { Result } from 'true-myth'; -import { input, make } from '../../tests/helpers'; +import { generateSchemas, input, make, output } from '../../tests/helpers'; import { IsDate } from '../nestjs-swagger-dto'; describe('IsDate', () => { @@ -10,6 +10,26 @@ describe('IsDate', () => { date!: Date; } + it('generates correct schema', async () => { + expect(await generateSchemas([Test])).toStrictEqual({ + Test: { + type: 'object', + properties: { + date: { + type: 'string', + format: 'date', + }, + }, + required: ['date'], + }, + }); + }); + + it('transforms to plain', async () => { + const dto = make(Test, { date: new Date('2011-12-30') }); + expect(output(dto)).toStrictEqual({ date: '2011-12-30' }); + }); + it('accepts date', async () => { expect(await input(Test, { date: '2011-12-30' })).toStrictEqual( Result.ok(make(Test, { date: new Date('2011-12-30') })) @@ -28,6 +48,20 @@ describe('IsDate', () => { expect(await input(Test, { date: '2011/12/02' })).toStrictEqual( Result.err('date is not formatted as `yyyy-mm-dd`') ); + + expect(await input(Test, {})).toStrictEqual(Result.err('date does not exist')); + }); + + it('works with optional fields', async () => { + class TestOptional { + @IsDate({ format: 'date', optional: true }) + date?: Date; + } + + const dto = make(TestOptional, {}); + expect(output(dto)).toStrictEqual({}); + + expect(await input(Test, {})).toStrictEqual(Result.ok(make(TestOptional, {}))); }); }); @@ -37,6 +71,26 @@ describe('IsDate', () => { date!: Date; } + it('generates correct schema', async () => { + expect(await generateSchemas([Test])).toStrictEqual({ + Test: { + type: 'object', + properties: { + date: { + type: 'string', + format: 'date-time', + }, + }, + required: ['date'], + }, + }); + }); + + it('transforms to plain', async () => { + const dto = make(Test, { date: new Date('2017-06-01T18:43:26.000Z') }); + expect(output(dto)).toStrictEqual({ date: '2017-06-01T18:43:26.000Z' }); + }); + it('accepts date-time', async () => { expect(await input(Test, { date: '2017-06-01T18:43:26.000Z' })).toStrictEqual( Result.ok(make(Test, { date: new Date('2017-06-01T18:43:26.000Z') })) @@ -55,6 +109,20 @@ describe('IsDate', () => { expect(await input(Test, { date: '2011/12/02' })).toStrictEqual( Result.err('date is not ISO8601 format') ); + + expect(await input(Test, {})).toStrictEqual(Result.err('date does not exist')); + }); + + it('works with optional fields', async () => { + class TestOptional { + @IsDate({ format: 'date-time', optional: true }) + date?: Date; + } + + const dto = make(TestOptional, {}); + expect(output(dto)).toStrictEqual({}); + + expect(await input(TestOptional, {})).toStrictEqual(Result.ok(make(TestOptional, {}))); }); }); }); diff --git a/src/decorators/is-date.ts b/src/decorators/is-date.ts index 4cb13d6..8599559 100644 --- a/src/decorators/is-date.ts +++ b/src/decorators/is-date.ts @@ -1,9 +1,9 @@ -import { Transform } from 'class-transformer'; +import { Transform, TransformationType } from 'class-transformer'; import { IsDate as IsDateCV, isDateString } from 'class-validator'; import { compose, PropertyOptions } from '../core'; -const crudeDateRegex = /^\d{4}-\d{2}-\d{2}$/; +const dateRegex = /^\d{4}-\d{2}-\d{2}$/; // TODO: array support export const IsDate = ({ @@ -13,25 +13,47 @@ export const IsDate = ({ compose( { type: 'string', format }, base, - format === 'date' - ? Transform(({ key, value }) => { - if (!crudeDateRegex.test(value)) { - return new Error(`${key} is not formatted as \`yyyy-mm-dd\``); - } - - const date = new Date(value); - if (isNaN(date.getTime())) { - return new Error(`${key} is not a valid Date`); - } - - return date; - }) - : Transform(({ key, value }) => { - if (!isDateString(value, { strict: true })) { - return new Error(`${key} is not ISO8601 format`); - } - - return new Date(value); - }), - IsDateCV({ message: ({ value }) => value.message }) + 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`); + } + + if (type === TransformationType.CLASS_TO_PLAIN) { + return stripTime(value.toISOString()); + } + + if (!dateRegex.test(value)) { + return new Error(`${key} is not formatted as \`yyyy-mm-dd\``); + } + + const date = new Date(value); + if (isNaN(date.getTime())) { + return new Error(`${key} is not a valid Date`); + } + + return date; +}); + +const TransformDateTime = Transform(({ key, value, type }) => { + if (value === undefined) { + return new Error(`${key} does not exist`); + } + + if (type === TransformationType.CLASS_TO_PLAIN) { + return value.toISOString(); + } + + if (!isDateString(value, { strict: true })) { + return new Error(`${key} is not ISO8601 format`); + } + + return new Date(value); +}); + +function stripTime(isoDate: string) { + return isoDate.split('T')[0]; +}