From dac6000dc1234ca16b15546ff89c97c73420cddd Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 10:48:40 +0100 Subject: [PATCH 01/34] fix(exceptions): corrected NotFoundException name prop --- lib/exceptions/NotFoundException.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/exceptions/NotFoundException.ts b/lib/exceptions/NotFoundException.ts index 489d5bc4..da41d63b 100644 --- a/lib/exceptions/NotFoundException.ts +++ b/lib/exceptions/NotFoundException.ts @@ -1,7 +1,7 @@ import { HttpException } from './HttpException'; export class NotFoundException extends HttpException { - public name = 'BadRequestException'; + public name = 'NotFoundException'; public constructor(message?: string) { super(404, message); From 0bd73b20bccc38eb3a022661e128ee2ec49e4ffb Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 11:02:49 +0100 Subject: [PATCH 02/34] refactor(exceptions): added default error messages --- lib/exceptions/BadRequestException.ts | 2 +- lib/exceptions/NotFoundException.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/exceptions/BadRequestException.ts b/lib/exceptions/BadRequestException.ts index fc9bd976..7f14ed08 100644 --- a/lib/exceptions/BadRequestException.ts +++ b/lib/exceptions/BadRequestException.ts @@ -3,7 +3,7 @@ import { HttpException } from './HttpException'; export class BadRequestException extends HttpException { public name = 'BadRequestException'; - public constructor(message?: string, errors?: string[]) { + public constructor(message: string = 'Bad request', errors?: string[]) { super(400, message, errors); } } diff --git a/lib/exceptions/NotFoundException.ts b/lib/exceptions/NotFoundException.ts index da41d63b..b13c137c 100644 --- a/lib/exceptions/NotFoundException.ts +++ b/lib/exceptions/NotFoundException.ts @@ -3,7 +3,7 @@ import { HttpException } from './HttpException'; export class NotFoundException extends HttpException { public name = 'NotFoundException'; - public constructor(message?: string) { + public constructor(message: string = 'Not found') { super(404, message); } } From 4d081cdf23bb9c981a1c81407be9c8255487a726 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 11:08:27 +0100 Subject: [PATCH 03/34] feat(exceptions): added more common http exceptions --- lib/exceptions/InternalServerErrorException.ts | 9 +++++++++ lib/exceptions/UnauthorizedException.ts | 9 +++++++++ lib/exceptions/index.ts | 4 +++- 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 lib/exceptions/InternalServerErrorException.ts create mode 100644 lib/exceptions/UnauthorizedException.ts diff --git a/lib/exceptions/InternalServerErrorException.ts b/lib/exceptions/InternalServerErrorException.ts new file mode 100644 index 00000000..9f710b26 --- /dev/null +++ b/lib/exceptions/InternalServerErrorException.ts @@ -0,0 +1,9 @@ +import { HttpException } from './HttpException'; + +export class InternalServerErrorException extends HttpException { + public name = 'InternalServerErrorException'; + + public constructor(message: string = 'Internal server error') { + super(500, message); + } +} diff --git a/lib/exceptions/UnauthorizedException.ts b/lib/exceptions/UnauthorizedException.ts new file mode 100644 index 00000000..125b379f --- /dev/null +++ b/lib/exceptions/UnauthorizedException.ts @@ -0,0 +1,9 @@ +import { HttpException } from './HttpException'; + +export class UnauthorizedException extends HttpException { + public name = 'UnauthorizedException'; + + public constructor(message: string = 'Unauthorized') { + super(401, message); + } +} diff --git a/lib/exceptions/index.ts b/lib/exceptions/index.ts index 593d67f3..2c1a9159 100644 --- a/lib/exceptions/index.ts +++ b/lib/exceptions/index.ts @@ -1,3 +1,5 @@ export * from './HttpException'; -export * from './NotFoundException'; export * from './BadRequestException'; +export * from './InternalServerErrorException'; +export * from './NotFoundException'; +export * from './UnauthorizedException'; From 377d55b84204e4a2d1009a24c0f9a2b5dd654e7e Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 11:34:42 +0100 Subject: [PATCH 04/34] test(exceptions): added HttpException spec --- lib/exceptions/HttpException.spec.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 lib/exceptions/HttpException.spec.ts diff --git a/lib/exceptions/HttpException.spec.ts b/lib/exceptions/HttpException.spec.ts new file mode 100644 index 00000000..1ea266b6 --- /dev/null +++ b/lib/exceptions/HttpException.spec.ts @@ -0,0 +1,25 @@ +import { HttpException } from './HttpException'; + +describe('HttpException', () => { + it(`Should use 'HttpException' as name`, () => + expect(new HttpException(500)).toHaveProperty('name', 'HttpException')); + + it('Should set the given number as statusCode', () => + expect(new HttpException(500)).toHaveProperty('statusCode', 500)); + + it('Should set the given string as message', () => + expect(new HttpException(403, 'Forbidden')).toHaveProperty('message', 'Forbidden')); + + it('Should set the given array of strings as errors', () => + expect( + new HttpException(400, 'Bad request', ['First name is required', 'Last name is required']) + ).toHaveProperty('errors', ['First name is required', 'Last name is required'])); + + it('Should set the name, statusCode, message and errors', () => + expect(new HttpException(400, 'Bad request', ['Invalid email address'])).toMatchObject({ + name: 'HttpException', + statusCode: 400, + message: 'Bad request', + errors: ['Invalid email address'] + })); +}); From 676796dac6e2385a39b4f972aa1bfe01d8b74181 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 11:52:41 +0100 Subject: [PATCH 05/34] test(exceptions): added tests for common errors + updated test structure --- lib/pipes/index.ts | 1 + .../decorators/httpCode.decorator.spec.ts | 2 +- .../decorators/httpMethod.decorator.spec.ts | 2 +- .../decorators/parameter.decorator.spec.ts | 2 +- .../decorators/setHeader.decorator.spec.ts | 2 +- lib/{ => test}/e2e.test.ts | 12 +++++----- .../exceptions/HttpException.spec.ts | 24 ++++++++++++++++++- .../pipes/parseBoolean.pipe.spec.ts | 2 +- lib/{ => test}/pipes/parseDate.pipe.spec.ts | 2 +- lib/{ => test}/pipes/parseNumber.pipe.spec.ts | 2 +- .../pipes/validateEnum.pipe.spec.ts | 2 +- 11 files changed, 38 insertions(+), 15 deletions(-) rename lib/{ => test}/decorators/httpCode.decorator.spec.ts (81%) rename lib/{ => test}/decorators/httpMethod.decorator.spec.ts (96%) rename lib/{ => test}/decorators/parameter.decorator.spec.ts (98%) rename lib/{ => test}/decorators/setHeader.decorator.spec.ts (89%) rename lib/{ => test}/e2e.test.ts (95%) rename lib/{ => test}/exceptions/HttpException.spec.ts (50%) rename lib/{ => test}/pipes/parseBoolean.pipe.spec.ts (90%) rename lib/{ => test}/pipes/parseDate.pipe.spec.ts (96%) rename lib/{ => test}/pipes/parseNumber.pipe.spec.ts (91%) rename lib/{ => test}/pipes/validateEnum.pipe.spec.ts (95%) diff --git a/lib/pipes/index.ts b/lib/pipes/index.ts index 7e6d43d3..6597e0dd 100644 --- a/lib/pipes/index.ts +++ b/lib/pipes/index.ts @@ -2,3 +2,4 @@ export * from './parseBoolean.pipe'; export * from './parseNumber.pipe'; export * from './validation.pipe'; export * from './validateEnum.pipe'; +export * from './parseDate.pipe'; diff --git a/lib/decorators/httpCode.decorator.spec.ts b/lib/test/decorators/httpCode.decorator.spec.ts similarity index 81% rename from lib/decorators/httpCode.decorator.spec.ts rename to lib/test/decorators/httpCode.decorator.spec.ts index 8a13358c..e401274e 100644 --- a/lib/decorators/httpCode.decorator.spec.ts +++ b/lib/test/decorators/httpCode.decorator.spec.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import { HttpCode, HTTP_CODE_TOKEN } from './httpCode.decorator'; +import { HttpCode, HTTP_CODE_TOKEN } from '../../decorators'; class Test { @HttpCode(201) diff --git a/lib/decorators/httpMethod.decorator.spec.ts b/lib/test/decorators/httpMethod.decorator.spec.ts similarity index 96% rename from lib/decorators/httpMethod.decorator.spec.ts rename to lib/test/decorators/httpMethod.decorator.spec.ts index 04f2f8cc..5adb1bee 100644 --- a/lib/decorators/httpMethod.decorator.spec.ts +++ b/lib/test/decorators/httpMethod.decorator.spec.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import { Delete, Get, HTTP_METHOD_TOKEN, HttpVerb, Post, Put } from './httpMethod.decorators'; +import { Delete, Get, HTTP_METHOD_TOKEN, HttpVerb, Post, Put } from '../../decorators'; class Test { @Get() diff --git a/lib/decorators/parameter.decorator.spec.ts b/lib/test/decorators/parameter.decorator.spec.ts similarity index 98% rename from lib/decorators/parameter.decorator.spec.ts rename to lib/test/decorators/parameter.decorator.spec.ts index e816cc24..51455342 100644 --- a/lib/decorators/parameter.decorator.spec.ts +++ b/lib/test/decorators/parameter.decorator.spec.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import 'reflect-metadata'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { Body, PARAMETER_TOKEN, Req, Request, Res, Response, Header, Query } from './parameter.decorators'; +import { Body, PARAMETER_TOKEN, Req, Request, Res, Response, Header, Query } from '../../decorators'; describe('Parameter decorators', () => { it('Body should be set.', () => { diff --git a/lib/decorators/setHeader.decorator.spec.ts b/lib/test/decorators/setHeader.decorator.spec.ts similarity index 89% rename from lib/decorators/setHeader.decorator.spec.ts rename to lib/test/decorators/setHeader.decorator.spec.ts index 393f855f..a906f3e9 100644 --- a/lib/decorators/setHeader.decorator.spec.ts +++ b/lib/test/decorators/setHeader.decorator.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import 'reflect-metadata'; -import { HEADER_TOKEN, SetHeader } from './setHeader.decorator'; +import { HEADER_TOKEN, SetHeader } from '../../decorators'; @SetHeader('X-Api', 'true') class Test { diff --git a/lib/e2e.test.ts b/lib/test/e2e.test.ts similarity index 95% rename from lib/e2e.test.ts rename to lib/test/e2e.test.ts index 332f20ec..34973970 100644 --- a/lib/e2e.test.ts +++ b/lib/test/e2e.test.ts @@ -3,12 +3,12 @@ import { IsBoolean, IsDate, IsEnum, IsInt, IsNotEmpty, IsOptional } from 'class- import express from 'express'; import type { NextApiRequest, NextApiResponse } from 'next'; import request from 'supertest'; -import { createHandler } from './createHandler'; -import { Body, Delete, Get, Header, HttpCode, Post, Put, Query, Req, Res, Response, SetHeader } from './decorators'; -import { ValidationPipe } from './pipes'; -import { ParseBooleanPipe } from './pipes/parseBoolean.pipe'; -import { ParseDatePipe } from './pipes/parseDate.pipe'; -import { ParseNumberPipe } from './pipes/parseNumber.pipe'; +import { createHandler } from '../createHandler'; +import { Body, Delete, Get, Header, HttpCode, Post, Put, Query, Req, Res, Response, SetHeader } from '../decorators'; +import { ValidationPipe } from '../pipes'; +import { ParseBooleanPipe } from '../pipes/parseBoolean.pipe'; +import { ParseDatePipe } from '../pipes/parseDate.pipe'; +import { ParseNumberPipe } from '../pipes/parseNumber.pipe'; enum CreateSource { ONLINE = 'online', diff --git a/lib/exceptions/HttpException.spec.ts b/lib/test/exceptions/HttpException.spec.ts similarity index 50% rename from lib/exceptions/HttpException.spec.ts rename to lib/test/exceptions/HttpException.spec.ts index 1ea266b6..59ec6dc1 100644 --- a/lib/exceptions/HttpException.spec.ts +++ b/lib/test/exceptions/HttpException.spec.ts @@ -1,4 +1,10 @@ -import { HttpException } from './HttpException'; +import { + HttpException, + BadRequestException, + InternalServerErrorException, + NotFoundException, + UnauthorizedException +} from '../../exceptions'; describe('HttpException', () => { it(`Should use 'HttpException' as name`, () => @@ -22,4 +28,20 @@ describe('HttpException', () => { message: 'Bad request', errors: ['Invalid email address'] })); + + describe('Common errors', () => { + it('Should set the default status codes', () => { + expect(new BadRequestException()).toHaveProperty('statusCode', 400); + expect(new InternalServerErrorException()).toHaveProperty('statusCode', 500); + expect(new NotFoundException()).toHaveProperty('statusCode', 404); + expect(new UnauthorizedException()).toHaveProperty('statusCode', 401); + }); + + it('Should set the default error messages', () => { + expect(new BadRequestException()).toHaveProperty('message', 'Bad request'); + expect(new InternalServerErrorException()).toHaveProperty('message', 'Internal server error'); + expect(new NotFoundException()).toHaveProperty('message', 'Not found'); + expect(new UnauthorizedException()).toHaveProperty('message', 'Unauthorized'); + }); + }); }); diff --git a/lib/pipes/parseBoolean.pipe.spec.ts b/lib/test/pipes/parseBoolean.pipe.spec.ts similarity index 90% rename from lib/pipes/parseBoolean.pipe.spec.ts rename to lib/test/pipes/parseBoolean.pipe.spec.ts index 98789f71..6b4d9ed1 100644 --- a/lib/pipes/parseBoolean.pipe.spec.ts +++ b/lib/test/pipes/parseBoolean.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseBooleanPipe } from './parseBoolean.pipe'; +import { ParseBooleanPipe } from '../../pipes'; describe('ParseBooleanPipe', () => { it('Should parse the given string as boolean (true)', () => expect(ParseBooleanPipe()('true')).toStrictEqual(true)); diff --git a/lib/pipes/parseDate.pipe.spec.ts b/lib/test/pipes/parseDate.pipe.spec.ts similarity index 96% rename from lib/pipes/parseDate.pipe.spec.ts rename to lib/test/pipes/parseDate.pipe.spec.ts index 35f0b389..46b5838d 100644 --- a/lib/pipes/parseDate.pipe.spec.ts +++ b/lib/test/pipes/parseDate.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseDatePipe } from './parseDate.pipe'; +import { ParseDatePipe } from '../../pipes'; describe('ParseDatePipe', () => { it('Should parse the given date', () => { diff --git a/lib/pipes/parseNumber.pipe.spec.ts b/lib/test/pipes/parseNumber.pipe.spec.ts similarity index 91% rename from lib/pipes/parseNumber.pipe.spec.ts rename to lib/test/pipes/parseNumber.pipe.spec.ts index 1e97ada0..1def2eec 100644 --- a/lib/pipes/parseNumber.pipe.spec.ts +++ b/lib/test/pipes/parseNumber.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseNumberPipe } from './parseNumber.pipe'; +import { ParseNumberPipe } from '../../pipes'; describe('ParseNumberPipe', () => { it('Should parse the given string as number', () => expect(ParseNumberPipe()('10')).toStrictEqual(10)); diff --git a/lib/pipes/validateEnum.pipe.spec.ts b/lib/test/pipes/validateEnum.pipe.spec.ts similarity index 95% rename from lib/pipes/validateEnum.pipe.spec.ts rename to lib/test/pipes/validateEnum.pipe.spec.ts index 1856ed46..373d692c 100644 --- a/lib/pipes/validateEnum.pipe.spec.ts +++ b/lib/test/pipes/validateEnum.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ValidateEnumPipe } from './validateEnum.pipe'; +import { ValidateEnumPipe } from '../../pipes'; enum UserStatus { ACTIVE = 'active', From 4665c52fb738bc871ab0dbbe0c342fb5ca04bd0a Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 12:21:51 +0100 Subject: [PATCH 06/34] docs(expections): added new build-in exceptions to the readme --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d4c3ad0d..f0d6d666 100644 --- a/README.md +++ b/README.md @@ -161,10 +161,18 @@ Pipes are being used to validate and transform incoming values. The pipes can be ## Exceptions -The following built-in exceptions are provided by this package: +### Built-in exceptions + +The following common HTTP exceptions are provided by this package. + +| | Status code | Default message | +| ------------------------------ | ----------- | ------------------------- | +| `BadRequestException` | `400` | `'Bad request'` | +| `UnauthorizedException` | `401` | `'Unauthorized'` | +| `NotFoundException` | `404` | `'Not found'` | +| `InternalServerErrorException` | `500` | `'Internal server error'` | + -* `NotFoundException` -* `BadRequestException` ### Custom exceptions @@ -175,7 +183,7 @@ Any exception class that extends the base `HttpException` will be handled by the import { HttpException } from '@storyofams/next-api-decorators'; export class ForbiddenException extends HttpException { - public constructor(message?: string) { + public constructor(message?: string = 'Forbidden') { super(403, message); } } From 6a7ff64d20879c7719204f06eb49609569fbe606 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 12:29:29 +0100 Subject: [PATCH 07/34] docs(pipes): added enum and date pipes in the readme --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f0d6d666..63bd998c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Collection of decorators to create typed Next.js API routes, with easy request validation and transformation. + ## Installation Add the package to your project: @@ -50,6 +51,7 @@ Your `tsconfig.json` needs the following flags: "experimentalDecorators": true ``` + ## Usage ### Basic example @@ -113,6 +115,7 @@ class User { export default createHandler(User); ``` + ## Available decorators ### Class decorators @@ -141,8 +144,6 @@ export default createHandler(User); | `@Header(name: string)` | Gets a header value by name. | - - ## Built-in pipes Pipes are being used to validate and transform incoming values. The pipes can be added to the `@Query` decorator like: @@ -153,17 +154,17 @@ Pipes are being used to validate and transform incoming values. The pipes can be ⚠️ Beware that they throw when the value is invalid. -| | Description | Remarks | -| ------------------ | ------------------------------------------- | --------------------------------------------- | -| `ParseNumberPipe` | Validates and transforms `Number` strings. | Uses `parseFloat` under the hood | -| `ParseBooleanPipe` | Validates and transforms `Boolean` strings. | Allows `'true'` and `'false'` as valid values | +| | Description | Remarks | +| ------------------ | ------------------------------------------- | ------------------------------------------------- | +| `ParseBooleanPipe` | Validates and transforms `Boolean` strings. | Allows `'true'` and `'false'` as valid values | +| `ParseDatePipe` | Validates and transforms `Date` strings. | Allows valid `ISO 8601` formatted date strings | +| `ParseEnumPipe` | Validates and transforms `Enum` strings. | Allows strings that are present in the given enum | +| `ParseNumberPipe` | Validates and transforms `Number` strings. | Uses `parseFloat` under the hood | ## Exceptions -### Built-in exceptions - -The following common HTTP exceptions are provided by this package. +The following common exceptions are provided by this package. | | Status code | Default message | | ------------------------------ | ----------- | ------------------------- | From 6978dcdb8fdcd0d6207c94d7ed452ecdb9ad6253 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 12:43:11 +0100 Subject: [PATCH 08/34] feat(exceptions): added UnprocessableEntityException --- README.md | 4 +--- lib/exceptions/UnprocessableEntityException.ts | 9 +++++++++ lib/exceptions/index.ts | 1 + lib/test/exceptions/HttpException.spec.ts | 5 ++++- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 lib/exceptions/UnprocessableEntityException.ts diff --git a/README.md b/README.md index 63bd998c..ad585dbd 100644 --- a/README.md +++ b/README.md @@ -171,11 +171,9 @@ The following common exceptions are provided by this package. | `BadRequestException` | `400` | `'Bad request'` | | `UnauthorizedException` | `401` | `'Unauthorized'` | | `NotFoundException` | `404` | `'Not found'` | +| `UnprocessableEntityException` | `422` | `'Unprocessable Entity'` | | `InternalServerErrorException` | `500` | `'Internal server error'` | - - - ### Custom exceptions Any exception class that extends the base `HttpException` will be handled by the built-in error handler. diff --git a/lib/exceptions/UnprocessableEntityException.ts b/lib/exceptions/UnprocessableEntityException.ts new file mode 100644 index 00000000..b5a9de84 --- /dev/null +++ b/lib/exceptions/UnprocessableEntityException.ts @@ -0,0 +1,9 @@ +import { HttpException } from './HttpException'; + +export class UnprocessableEntityException extends HttpException { + public name = 'UnprocessableEntityException'; + + public constructor(message: string = 'Unprocessable Entity', errors?: string[]) { + super(422, message, errors); + } +} diff --git a/lib/exceptions/index.ts b/lib/exceptions/index.ts index 2c1a9159..1e907d67 100644 --- a/lib/exceptions/index.ts +++ b/lib/exceptions/index.ts @@ -3,3 +3,4 @@ export * from './BadRequestException'; export * from './InternalServerErrorException'; export * from './NotFoundException'; export * from './UnauthorizedException'; +export * from './UnprocessableEntityException'; diff --git a/lib/test/exceptions/HttpException.spec.ts b/lib/test/exceptions/HttpException.spec.ts index 59ec6dc1..4df93314 100644 --- a/lib/test/exceptions/HttpException.spec.ts +++ b/lib/test/exceptions/HttpException.spec.ts @@ -3,7 +3,8 @@ import { BadRequestException, InternalServerErrorException, NotFoundException, - UnauthorizedException + UnauthorizedException, + UnprocessableEntityException } from '../../exceptions'; describe('HttpException', () => { @@ -35,6 +36,7 @@ describe('HttpException', () => { expect(new InternalServerErrorException()).toHaveProperty('statusCode', 500); expect(new NotFoundException()).toHaveProperty('statusCode', 404); expect(new UnauthorizedException()).toHaveProperty('statusCode', 401); + expect(new UnprocessableEntityException()).toHaveProperty('statusCode', 422); }); it('Should set the default error messages', () => { @@ -42,6 +44,7 @@ describe('HttpException', () => { expect(new InternalServerErrorException()).toHaveProperty('message', 'Internal server error'); expect(new NotFoundException()).toHaveProperty('message', 'Not found'); expect(new UnauthorizedException()).toHaveProperty('message', 'Unauthorized'); + expect(new UnprocessableEntityException()).toHaveProperty('message', 'Unprocessable Entity'); }); }); }); From 491fc290b3f8eb0902d7700966b2d617f7fbcfdb Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 12:55:06 +0100 Subject: [PATCH 09/34] refactor(test): moved test folder outside lib --- jest.config.json | 2 +- .../decorators/httpCode.decorator.spec.ts | 2 +- .../decorators/httpMethod.decorator.spec.ts | 2 +- .../decorators/parameter.decorator.spec.ts | 2 +- .../decorators/setHeader.decorator.spec.ts | 2 +- {lib/test => test}/e2e.test.ts | 25 ++++++++++++++----- .../exceptions/HttpException.spec.ts | 2 +- .../pipes/parseBoolean.pipe.spec.ts | 2 +- .../pipes/parseDate.pipe.spec.ts | 2 +- .../pipes/parseNumber.pipe.spec.ts | 2 +- .../pipes/validateEnum.pipe.spec.ts | 2 +- tsconfig.json | 4 +-- 12 files changed, 31 insertions(+), 18 deletions(-) rename {lib/test => test}/decorators/httpCode.decorator.spec.ts (81%) rename {lib/test => test}/decorators/httpMethod.decorator.spec.ts (96%) rename {lib/test => test}/decorators/parameter.decorator.spec.ts (98%) rename {lib/test => test}/decorators/setHeader.decorator.spec.ts (89%) rename {lib/test => test}/e2e.test.ts (94%) rename {lib/test => test}/exceptions/HttpException.spec.ts (98%) rename {lib/test => test}/pipes/parseBoolean.pipe.spec.ts (91%) rename {lib/test => test}/pipes/parseDate.pipe.spec.ts (96%) rename {lib/test => test}/pipes/parseNumber.pipe.spec.ts (91%) rename {lib/test => test}/pipes/validateEnum.pipe.spec.ts (95%) diff --git a/jest.config.json b/jest.config.json index 76e66a05..f2e771d2 100644 --- a/jest.config.json +++ b/jest.config.json @@ -1,6 +1,6 @@ { "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": "lib", + "rootDir": ".", "testRegex": ".(spec|test).ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" diff --git a/lib/test/decorators/httpCode.decorator.spec.ts b/test/decorators/httpCode.decorator.spec.ts similarity index 81% rename from lib/test/decorators/httpCode.decorator.spec.ts rename to test/decorators/httpCode.decorator.spec.ts index e401274e..5b4a2827 100644 --- a/lib/test/decorators/httpCode.decorator.spec.ts +++ b/test/decorators/httpCode.decorator.spec.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import { HttpCode, HTTP_CODE_TOKEN } from '../../decorators'; +import { HttpCode, HTTP_CODE_TOKEN } from '../../lib/decorators'; class Test { @HttpCode(201) diff --git a/lib/test/decorators/httpMethod.decorator.spec.ts b/test/decorators/httpMethod.decorator.spec.ts similarity index 96% rename from lib/test/decorators/httpMethod.decorator.spec.ts rename to test/decorators/httpMethod.decorator.spec.ts index 5adb1bee..c54a1043 100644 --- a/lib/test/decorators/httpMethod.decorator.spec.ts +++ b/test/decorators/httpMethod.decorator.spec.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import { Delete, Get, HTTP_METHOD_TOKEN, HttpVerb, Post, Put } from '../../decorators'; +import { Delete, Get, HTTP_METHOD_TOKEN, HttpVerb, Post, Put } from '../../lib/decorators'; class Test { @Get() diff --git a/lib/test/decorators/parameter.decorator.spec.ts b/test/decorators/parameter.decorator.spec.ts similarity index 98% rename from lib/test/decorators/parameter.decorator.spec.ts rename to test/decorators/parameter.decorator.spec.ts index 51455342..428f3721 100644 --- a/lib/test/decorators/parameter.decorator.spec.ts +++ b/test/decorators/parameter.decorator.spec.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import 'reflect-metadata'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { Body, PARAMETER_TOKEN, Req, Request, Res, Response, Header, Query } from '../../decorators'; +import { Body, PARAMETER_TOKEN, Req, Request, Res, Response, Header, Query } from '../../lib/decorators'; describe('Parameter decorators', () => { it('Body should be set.', () => { diff --git a/lib/test/decorators/setHeader.decorator.spec.ts b/test/decorators/setHeader.decorator.spec.ts similarity index 89% rename from lib/test/decorators/setHeader.decorator.spec.ts rename to test/decorators/setHeader.decorator.spec.ts index a906f3e9..2f94e584 100644 --- a/lib/test/decorators/setHeader.decorator.spec.ts +++ b/test/decorators/setHeader.decorator.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import 'reflect-metadata'; -import { HEADER_TOKEN, SetHeader } from '../../decorators'; +import { HEADER_TOKEN, SetHeader } from '../../lib/decorators'; @SetHeader('X-Api', 'true') class Test { diff --git a/lib/test/e2e.test.ts b/test/e2e.test.ts similarity index 94% rename from lib/test/e2e.test.ts rename to test/e2e.test.ts index 34973970..9df5d939 100644 --- a/lib/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -3,12 +3,25 @@ import { IsBoolean, IsDate, IsEnum, IsInt, IsNotEmpty, IsOptional } from 'class- import express from 'express'; import type { NextApiRequest, NextApiResponse } from 'next'; import request from 'supertest'; -import { createHandler } from '../createHandler'; -import { Body, Delete, Get, Header, HttpCode, Post, Put, Query, Req, Res, Response, SetHeader } from '../decorators'; -import { ValidationPipe } from '../pipes'; -import { ParseBooleanPipe } from '../pipes/parseBoolean.pipe'; -import { ParseDatePipe } from '../pipes/parseDate.pipe'; -import { ParseNumberPipe } from '../pipes/parseNumber.pipe'; +import { + createHandler, + Body, + Delete, + Get, + Header, + HttpCode, + Post, + Put, + Query, + Req, + Res, + Response, + SetHeader, + ValidationPipe, + ParseBooleanPipe, + ParseDatePipe, + ParseNumberPipe +} from '../lib'; enum CreateSource { ONLINE = 'online', diff --git a/lib/test/exceptions/HttpException.spec.ts b/test/exceptions/HttpException.spec.ts similarity index 98% rename from lib/test/exceptions/HttpException.spec.ts rename to test/exceptions/HttpException.spec.ts index 4df93314..b89aef84 100644 --- a/lib/test/exceptions/HttpException.spec.ts +++ b/test/exceptions/HttpException.spec.ts @@ -5,7 +5,7 @@ import { NotFoundException, UnauthorizedException, UnprocessableEntityException -} from '../../exceptions'; +} from '../../lib/exceptions'; describe('HttpException', () => { it(`Should use 'HttpException' as name`, () => diff --git a/lib/test/pipes/parseBoolean.pipe.spec.ts b/test/pipes/parseBoolean.pipe.spec.ts similarity index 91% rename from lib/test/pipes/parseBoolean.pipe.spec.ts rename to test/pipes/parseBoolean.pipe.spec.ts index 6b4d9ed1..76389327 100644 --- a/lib/test/pipes/parseBoolean.pipe.spec.ts +++ b/test/pipes/parseBoolean.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseBooleanPipe } from '../../pipes'; +import { ParseBooleanPipe } from '../../lib/pipes'; describe('ParseBooleanPipe', () => { it('Should parse the given string as boolean (true)', () => expect(ParseBooleanPipe()('true')).toStrictEqual(true)); diff --git a/lib/test/pipes/parseDate.pipe.spec.ts b/test/pipes/parseDate.pipe.spec.ts similarity index 96% rename from lib/test/pipes/parseDate.pipe.spec.ts rename to test/pipes/parseDate.pipe.spec.ts index 46b5838d..704fa4cc 100644 --- a/lib/test/pipes/parseDate.pipe.spec.ts +++ b/test/pipes/parseDate.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseDatePipe } from '../../pipes'; +import { ParseDatePipe } from '../../lib/pipes'; describe('ParseDatePipe', () => { it('Should parse the given date', () => { diff --git a/lib/test/pipes/parseNumber.pipe.spec.ts b/test/pipes/parseNumber.pipe.spec.ts similarity index 91% rename from lib/test/pipes/parseNumber.pipe.spec.ts rename to test/pipes/parseNumber.pipe.spec.ts index 1def2eec..689cf597 100644 --- a/lib/test/pipes/parseNumber.pipe.spec.ts +++ b/test/pipes/parseNumber.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseNumberPipe } from '../../pipes'; +import { ParseNumberPipe } from '../../lib/pipes'; describe('ParseNumberPipe', () => { it('Should parse the given string as number', () => expect(ParseNumberPipe()('10')).toStrictEqual(10)); diff --git a/lib/test/pipes/validateEnum.pipe.spec.ts b/test/pipes/validateEnum.pipe.spec.ts similarity index 95% rename from lib/test/pipes/validateEnum.pipe.spec.ts rename to test/pipes/validateEnum.pipe.spec.ts index 373d692c..5e0a4726 100644 --- a/lib/test/pipes/validateEnum.pipe.spec.ts +++ b/test/pipes/validateEnum.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ValidateEnumPipe } from '../../pipes'; +import { ValidateEnumPipe } from '../../lib/pipes'; enum UserStatus { ACTIVE = 'active', diff --git a/tsconfig.json b/tsconfig.json index 72ed4526..851fb5d4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,5 @@ "moduleResolution": "node" }, "exclude": ["node_modules"], - "include": ["lib/**/*.ts"] -} \ No newline at end of file + "include": ["lib/**/*.ts", "test/**/*.ts"] +} From 681cf2eb4c0103e424e9577a18ae3c64e7fc9dd1 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 12:58:47 +0100 Subject: [PATCH 10/34] docs: simplified basic example in readme --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ad585dbd..43c9e5bb 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Your `tsconfig.json` needs the following flags: ```ts // pages/api/user.ts -import { createHandler, Get, Post, Query, Body, NotFoundException } from '@storyofams/next-api-decorators'; +import { createHandler, Get, Query, NotFoundException } from '@storyofams/next-api-decorators'; class User { // GET /api/user @@ -72,12 +72,6 @@ class User { return user; } - - // POST /api/user - @Post() - public createUser(@Body() body: any) { - return DB.createUser(body); - } } export default createHandler(User); @@ -94,7 +88,8 @@ $ yarn add class-validator class-transformer Then you can define your DTOs like: ```ts -import { createHandler, Post, Body } from '@storyofams/next-api-decorators'; +// pages/api/user.ts +import { createHandler, Post, HttpCode, Body } from '@storyofams/next-api-decorators'; import { IsNotEmpty, IsEmail } from 'class-validator'; class CreateUserDto { @@ -106,7 +101,9 @@ class CreateUserDto { } class User { + // POST /api/user @Post() + @HttpCode(201) public createUser(@Body() body: CreateUserDto) { return User.create(body); } From f5a6ce6256e5a65539dd04cc6c955dde4d1eb378 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 13:06:49 +0100 Subject: [PATCH 11/34] test(e2e): added exception test in e2e tests --- test/e2e.test.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/e2e.test.ts b/test/e2e.test.ts index 9df5d939..7ccd10e8 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -20,7 +20,8 @@ import { ValidationPipe, ParseBooleanPipe, ParseDatePipe, - ParseNumberPipe + ParseNumberPipe, + NotFoundException } from '../lib'; enum CreateSource { @@ -66,6 +67,10 @@ class TestHandler { @Query('redirect', ParseBooleanPipe) redirect: boolean, @Query('startAt', ParseDatePipe) startAt: Date ) { + if (id !== 'my-id') { + throw new NotFoundException('Invalid ID'); + } + return { contentType, id, @@ -137,6 +142,19 @@ describe('E2E', () => { }) )); + it('read', () => + request(server) + .get('/?id=invalid-id&step=1&redirect=true&startAt=2021-01-01T22:00:00') + .set('Content-Type', 'application/json') + .expect(404) + .then(res => + expect(res).toMatchObject({ + body: { + message: 'Invalid ID' + } + }) + )); + it('read without "step"', () => request(server) .get('/?id=my-id&redirect=true') From 248e638758e77c63f41b5d43c1e6559c34d4664d Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 15:12:28 +0100 Subject: [PATCH 12/34] chore(exceptions): reflect web standards in default messages --- lib/exceptions/BadRequestException.ts | 2 +- lib/exceptions/InternalServerErrorException.ts | 2 +- lib/exceptions/NotFoundException.ts | 2 +- test/exceptions/HttpException.spec.ts | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/exceptions/BadRequestException.ts b/lib/exceptions/BadRequestException.ts index 7f14ed08..2c3a854b 100644 --- a/lib/exceptions/BadRequestException.ts +++ b/lib/exceptions/BadRequestException.ts @@ -3,7 +3,7 @@ import { HttpException } from './HttpException'; export class BadRequestException extends HttpException { public name = 'BadRequestException'; - public constructor(message: string = 'Bad request', errors?: string[]) { + public constructor(message: string = 'Bad Request', errors?: string[]) { super(400, message, errors); } } diff --git a/lib/exceptions/InternalServerErrorException.ts b/lib/exceptions/InternalServerErrorException.ts index 9f710b26..7a380fdb 100644 --- a/lib/exceptions/InternalServerErrorException.ts +++ b/lib/exceptions/InternalServerErrorException.ts @@ -3,7 +3,7 @@ import { HttpException } from './HttpException'; export class InternalServerErrorException extends HttpException { public name = 'InternalServerErrorException'; - public constructor(message: string = 'Internal server error') { + public constructor(message: string = 'Internal Server Error') { super(500, message); } } diff --git a/lib/exceptions/NotFoundException.ts b/lib/exceptions/NotFoundException.ts index b13c137c..de2a8561 100644 --- a/lib/exceptions/NotFoundException.ts +++ b/lib/exceptions/NotFoundException.ts @@ -3,7 +3,7 @@ import { HttpException } from './HttpException'; export class NotFoundException extends HttpException { public name = 'NotFoundException'; - public constructor(message: string = 'Not found') { + public constructor(message: string = 'Not Found') { super(404, message); } } diff --git a/test/exceptions/HttpException.spec.ts b/test/exceptions/HttpException.spec.ts index b89aef84..3326b158 100644 --- a/test/exceptions/HttpException.spec.ts +++ b/test/exceptions/HttpException.spec.ts @@ -40,9 +40,9 @@ describe('HttpException', () => { }); it('Should set the default error messages', () => { - expect(new BadRequestException()).toHaveProperty('message', 'Bad request'); - expect(new InternalServerErrorException()).toHaveProperty('message', 'Internal server error'); - expect(new NotFoundException()).toHaveProperty('message', 'Not found'); + expect(new BadRequestException()).toHaveProperty('message', 'Bad Request'); + expect(new InternalServerErrorException()).toHaveProperty('message', 'Internal Server Error'); + expect(new NotFoundException()).toHaveProperty('message', 'Not Found'); expect(new UnauthorizedException()).toHaveProperty('message', 'Unauthorized'); expect(new UnprocessableEntityException()).toHaveProperty('message', 'Unprocessable Entity'); }); From 97f3ed08e8af0361b62785f9e89879269767be1b Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 15:17:25 +0100 Subject: [PATCH 13/34] test(e2e): updated test description --- test/e2e.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e.test.ts b/test/e2e.test.ts index 7ccd10e8..e9bccd34 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -142,7 +142,7 @@ describe('E2E', () => { }) )); - it('read', () => + it('read with invalid "id"', () => request(server) .get('/?id=invalid-id&step=1&redirect=true&startAt=2021-01-01T22:00:00') .set('Content-Type', 'application/json') From 5f9dcb7cad374fb035c975356e6757a434128e35 Mon Sep 17 00:00:00 2001 From: ggurkal Date: Fri, 5 Mar 2021 15:29:56 +0100 Subject: [PATCH 14/34] test: coverage report path --- jest.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.json b/jest.config.json index f2e771d2..d3c88ff8 100644 --- a/jest.config.json +++ b/jest.config.json @@ -5,6 +5,6 @@ "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "coverageDirectory": "../coverage", + "coverageDirectory": "coverage", "testEnvironment": "node" } From c0f399b61896cd8bd124a2d86faf14d39e023fe1 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 15:48:10 +0100 Subject: [PATCH 15/34] docs(readme): updated default error messages --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 43c9e5bb..653b25df 100644 --- a/README.md +++ b/README.md @@ -165,11 +165,11 @@ The following common exceptions are provided by this package. | | Status code | Default message | | ------------------------------ | ----------- | ------------------------- | -| `BadRequestException` | `400` | `'Bad request'` | +| `BadRequestException` | `400` | `'Bad Request'` | | `UnauthorizedException` | `401` | `'Unauthorized'` | -| `NotFoundException` | `404` | `'Not found'` | +| `NotFoundException` | `404` | `'Not Found'` | | `UnprocessableEntityException` | `422` | `'Unprocessable Entity'` | -| `InternalServerErrorException` | `500` | `'Internal server error'` | +| `InternalServerErrorException` | `500` | `'Internal Server Error'` | ### Custom exceptions From a7b4f3b102c00d34958f8836cf83b6eca103f460 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Fri, 5 Mar 2021 18:09:02 +0100 Subject: [PATCH 16/34] refactor: bundled unit tests with related files --- .../decorators/httpCode}/httpCode.decorator.spec.ts | 2 +- lib/decorators/{ => httpCode}/httpCode.decorator.ts | 0 lib/decorators/httpCode/index.ts | 1 + .../decorators/httpMethod/httpMethod.decorators.spec.ts | 2 +- lib/decorators/{ => httpMethod}/httpMethod.decorators.ts | 2 +- lib/decorators/httpMethod/index.ts | 1 + lib/decorators/index.ts | 8 ++++---- lib/decorators/parameter/index.ts | 1 + .../decorators/parameter/parameter.decorators.spec.ts | 2 +- lib/decorators/{ => parameter}/parameter.decorators.ts | 2 +- lib/decorators/setHeader/index.ts | 1 + .../decorators/setHeader}/setHeader.decorator.spec.ts | 2 +- lib/decorators/{ => setHeader}/setHeader.decorator.ts | 0 {test => lib}/exceptions/HttpException.spec.ts | 2 +- lib/exceptions/{ => common}/BadRequestException.ts | 2 +- .../{ => common}/InternalServerErrorException.ts | 2 +- lib/exceptions/{ => common}/NotFoundException.ts | 2 +- lib/exceptions/{ => common}/UnauthorizedException.ts | 2 +- .../{ => common}/UnprocessableEntityException.ts | 2 +- lib/exceptions/common/index.ts | 5 +++++ lib/exceptions/index.ts | 6 +----- lib/pipes/index.ts | 8 ++++---- lib/pipes/parseBoolean/index.ts | 1 + .../pipes/parseBoolean}/parseBoolean.pipe.spec.ts | 2 +- lib/pipes/{ => parseBoolean}/parseBoolean.pipe.ts | 6 +++--- lib/pipes/parseDate/index.ts | 1 + .../pipes => lib/pipes/parseDate}/parseDate.pipe.spec.ts | 2 +- lib/pipes/{ => parseDate}/parseDate.pipe.ts | 6 +++--- lib/pipes/parseNumber/index.ts | 1 + .../pipes/parseNumber}/parseNumber.pipe.spec.ts | 2 +- lib/pipes/{ => parseNumber}/parseNumber.pipe.ts | 6 +++--- lib/pipes/validateEnum/index.ts | 1 + .../pipes/validateEnum}/validateEnum.pipe.spec.ts | 2 +- lib/pipes/{ => validateEnum}/validateEnum.pipe.ts | 6 +++--- 34 files changed, 50 insertions(+), 41 deletions(-) rename {test/decorators => lib/decorators/httpCode}/httpCode.decorator.spec.ts (81%) rename lib/decorators/{ => httpCode}/httpCode.decorator.ts (100%) create mode 100644 lib/decorators/httpCode/index.ts rename test/decorators/httpMethod.decorator.spec.ts => lib/decorators/httpMethod/httpMethod.decorators.spec.ts (96%) rename lib/decorators/{ => httpMethod}/httpMethod.decorators.ts (95%) create mode 100644 lib/decorators/httpMethod/index.ts create mode 100644 lib/decorators/parameter/index.ts rename test/decorators/parameter.decorator.spec.ts => lib/decorators/parameter/parameter.decorators.spec.ts (98%) rename lib/decorators/{ => parameter}/parameter.decorators.ts (97%) create mode 100644 lib/decorators/setHeader/index.ts rename {test/decorators => lib/decorators/setHeader}/setHeader.decorator.spec.ts (89%) rename lib/decorators/{ => setHeader}/setHeader.decorator.ts (100%) rename {test => lib}/exceptions/HttpException.spec.ts (98%) rename lib/exceptions/{ => common}/BadRequestException.ts (80%) rename lib/exceptions/{ => common}/InternalServerErrorException.ts (80%) rename lib/exceptions/{ => common}/NotFoundException.ts (78%) rename lib/exceptions/{ => common}/UnauthorizedException.ts (79%) rename lib/exceptions/{ => common}/UnprocessableEntityException.ts (82%) create mode 100644 lib/exceptions/common/index.ts create mode 100644 lib/pipes/parseBoolean/index.ts rename {test/pipes => lib/pipes/parseBoolean}/parseBoolean.pipe.spec.ts (90%) rename lib/pipes/{ => parseBoolean}/parseBoolean.pipe.ts (80%) create mode 100644 lib/pipes/parseDate/index.ts rename {test/pipes => lib/pipes/parseDate}/parseDate.pipe.spec.ts (96%) rename lib/pipes/{ => parseDate}/parseDate.pipe.ts (92%) create mode 100644 lib/pipes/parseNumber/index.ts rename {test/pipes => lib/pipes/parseNumber}/parseNumber.pipe.spec.ts (91%) rename lib/pipes/{ => parseNumber}/parseNumber.pipe.ts (81%) create mode 100644 lib/pipes/validateEnum/index.ts rename {test/pipes => lib/pipes/validateEnum}/validateEnum.pipe.spec.ts (95%) rename lib/pipes/{ => validateEnum}/validateEnum.pipe.ts (85%) diff --git a/test/decorators/httpCode.decorator.spec.ts b/lib/decorators/httpCode/httpCode.decorator.spec.ts similarity index 81% rename from test/decorators/httpCode.decorator.spec.ts rename to lib/decorators/httpCode/httpCode.decorator.spec.ts index 5b4a2827..8a13358c 100644 --- a/test/decorators/httpCode.decorator.spec.ts +++ b/lib/decorators/httpCode/httpCode.decorator.spec.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import { HttpCode, HTTP_CODE_TOKEN } from '../../lib/decorators'; +import { HttpCode, HTTP_CODE_TOKEN } from './httpCode.decorator'; class Test { @HttpCode(201) diff --git a/lib/decorators/httpCode.decorator.ts b/lib/decorators/httpCode/httpCode.decorator.ts similarity index 100% rename from lib/decorators/httpCode.decorator.ts rename to lib/decorators/httpCode/httpCode.decorator.ts diff --git a/lib/decorators/httpCode/index.ts b/lib/decorators/httpCode/index.ts new file mode 100644 index 00000000..8fb4f9f9 --- /dev/null +++ b/lib/decorators/httpCode/index.ts @@ -0,0 +1 @@ +export * from './httpCode.decorator'; diff --git a/test/decorators/httpMethod.decorator.spec.ts b/lib/decorators/httpMethod/httpMethod.decorators.spec.ts similarity index 96% rename from test/decorators/httpMethod.decorator.spec.ts rename to lib/decorators/httpMethod/httpMethod.decorators.spec.ts index c54a1043..04f2f8cc 100644 --- a/test/decorators/httpMethod.decorator.spec.ts +++ b/lib/decorators/httpMethod/httpMethod.decorators.spec.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import { Delete, Get, HTTP_METHOD_TOKEN, HttpVerb, Post, Put } from '../../lib/decorators'; +import { Delete, Get, HTTP_METHOD_TOKEN, HttpVerb, Post, Put } from './httpMethod.decorators'; class Test { @Get() diff --git a/lib/decorators/httpMethod.decorators.ts b/lib/decorators/httpMethod/httpMethod.decorators.ts similarity index 95% rename from lib/decorators/httpMethod.decorators.ts rename to lib/decorators/httpMethod/httpMethod.decorators.ts index 872f32da..2e11ff7f 100644 --- a/lib/decorators/httpMethod.decorators.ts +++ b/lib/decorators/httpMethod/httpMethod.decorators.ts @@ -1,4 +1,4 @@ -import { Handler } from '../internals/handler'; +import { Handler } from '../../internals/handler'; export enum HttpVerb { GET = 'GET', diff --git a/lib/decorators/httpMethod/index.ts b/lib/decorators/httpMethod/index.ts new file mode 100644 index 00000000..c6574e67 --- /dev/null +++ b/lib/decorators/httpMethod/index.ts @@ -0,0 +1 @@ +export * from './httpMethod.decorators'; diff --git a/lib/decorators/index.ts b/lib/decorators/index.ts index 363131ea..f96747c0 100644 --- a/lib/decorators/index.ts +++ b/lib/decorators/index.ts @@ -1,4 +1,4 @@ -export * from './httpCode.decorator'; -export * from './httpMethod.decorators'; -export * from './parameter.decorators'; -export * from './setHeader.decorator'; +export * from './httpCode'; +export * from './httpMethod'; +export * from './parameter'; +export * from './setHeader'; diff --git a/lib/decorators/parameter/index.ts b/lib/decorators/parameter/index.ts new file mode 100644 index 00000000..e117bb43 --- /dev/null +++ b/lib/decorators/parameter/index.ts @@ -0,0 +1 @@ +export * from './parameter.decorators'; diff --git a/test/decorators/parameter.decorator.spec.ts b/lib/decorators/parameter/parameter.decorators.spec.ts similarity index 98% rename from test/decorators/parameter.decorator.spec.ts rename to lib/decorators/parameter/parameter.decorators.spec.ts index 428f3721..e816cc24 100644 --- a/test/decorators/parameter.decorator.spec.ts +++ b/lib/decorators/parameter/parameter.decorators.spec.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import 'reflect-metadata'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { Body, PARAMETER_TOKEN, Req, Request, Res, Response, Header, Query } from '../../lib/decorators'; +import { Body, PARAMETER_TOKEN, Req, Request, Res, Response, Header, Query } from './parameter.decorators'; describe('Parameter decorators', () => { it('Body should be set.', () => { diff --git a/lib/decorators/parameter.decorators.ts b/lib/decorators/parameter/parameter.decorators.ts similarity index 97% rename from lib/decorators/parameter.decorators.ts rename to lib/decorators/parameter/parameter.decorators.ts index 4036d2be..c5c38fbe 100644 --- a/lib/decorators/parameter.decorators.ts +++ b/lib/decorators/parameter/parameter.decorators.ts @@ -1,4 +1,4 @@ -import type { ParameterPipe } from '../pipes/ParameterPipe'; +import type { ParameterPipe } from '../../pipes/ParameterPipe'; export interface MetaParameter { index: number; diff --git a/lib/decorators/setHeader/index.ts b/lib/decorators/setHeader/index.ts new file mode 100644 index 00000000..62ab36dd --- /dev/null +++ b/lib/decorators/setHeader/index.ts @@ -0,0 +1 @@ +export * from './setHeader.decorator'; diff --git a/test/decorators/setHeader.decorator.spec.ts b/lib/decorators/setHeader/setHeader.decorator.spec.ts similarity index 89% rename from test/decorators/setHeader.decorator.spec.ts rename to lib/decorators/setHeader/setHeader.decorator.spec.ts index 2f94e584..393f855f 100644 --- a/test/decorators/setHeader.decorator.spec.ts +++ b/lib/decorators/setHeader/setHeader.decorator.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import 'reflect-metadata'; -import { HEADER_TOKEN, SetHeader } from '../../lib/decorators'; +import { HEADER_TOKEN, SetHeader } from './setHeader.decorator'; @SetHeader('X-Api', 'true') class Test { diff --git a/lib/decorators/setHeader.decorator.ts b/lib/decorators/setHeader/setHeader.decorator.ts similarity index 100% rename from lib/decorators/setHeader.decorator.ts rename to lib/decorators/setHeader/setHeader.decorator.ts diff --git a/test/exceptions/HttpException.spec.ts b/lib/exceptions/HttpException.spec.ts similarity index 98% rename from test/exceptions/HttpException.spec.ts rename to lib/exceptions/HttpException.spec.ts index 3326b158..91b293ce 100644 --- a/test/exceptions/HttpException.spec.ts +++ b/lib/exceptions/HttpException.spec.ts @@ -5,7 +5,7 @@ import { NotFoundException, UnauthorizedException, UnprocessableEntityException -} from '../../lib/exceptions'; +} from '.'; describe('HttpException', () => { it(`Should use 'HttpException' as name`, () => diff --git a/lib/exceptions/BadRequestException.ts b/lib/exceptions/common/BadRequestException.ts similarity index 80% rename from lib/exceptions/BadRequestException.ts rename to lib/exceptions/common/BadRequestException.ts index 2c3a854b..b43d50b0 100644 --- a/lib/exceptions/BadRequestException.ts +++ b/lib/exceptions/common/BadRequestException.ts @@ -1,4 +1,4 @@ -import { HttpException } from './HttpException'; +import { HttpException } from '../HttpException'; export class BadRequestException extends HttpException { public name = 'BadRequestException'; diff --git a/lib/exceptions/InternalServerErrorException.ts b/lib/exceptions/common/InternalServerErrorException.ts similarity index 80% rename from lib/exceptions/InternalServerErrorException.ts rename to lib/exceptions/common/InternalServerErrorException.ts index 7a380fdb..ab71ef86 100644 --- a/lib/exceptions/InternalServerErrorException.ts +++ b/lib/exceptions/common/InternalServerErrorException.ts @@ -1,4 +1,4 @@ -import { HttpException } from './HttpException'; +import { HttpException } from '../HttpException'; export class InternalServerErrorException extends HttpException { public name = 'InternalServerErrorException'; diff --git a/lib/exceptions/NotFoundException.ts b/lib/exceptions/common/NotFoundException.ts similarity index 78% rename from lib/exceptions/NotFoundException.ts rename to lib/exceptions/common/NotFoundException.ts index de2a8561..64dd9948 100644 --- a/lib/exceptions/NotFoundException.ts +++ b/lib/exceptions/common/NotFoundException.ts @@ -1,4 +1,4 @@ -import { HttpException } from './HttpException'; +import { HttpException } from '../HttpException'; export class NotFoundException extends HttpException { public name = 'NotFoundException'; diff --git a/lib/exceptions/UnauthorizedException.ts b/lib/exceptions/common/UnauthorizedException.ts similarity index 79% rename from lib/exceptions/UnauthorizedException.ts rename to lib/exceptions/common/UnauthorizedException.ts index 125b379f..87c44450 100644 --- a/lib/exceptions/UnauthorizedException.ts +++ b/lib/exceptions/common/UnauthorizedException.ts @@ -1,4 +1,4 @@ -import { HttpException } from './HttpException'; +import { HttpException } from '../HttpException'; export class UnauthorizedException extends HttpException { public name = 'UnauthorizedException'; diff --git a/lib/exceptions/UnprocessableEntityException.ts b/lib/exceptions/common/UnprocessableEntityException.ts similarity index 82% rename from lib/exceptions/UnprocessableEntityException.ts rename to lib/exceptions/common/UnprocessableEntityException.ts index b5a9de84..8e3545fe 100644 --- a/lib/exceptions/UnprocessableEntityException.ts +++ b/lib/exceptions/common/UnprocessableEntityException.ts @@ -1,4 +1,4 @@ -import { HttpException } from './HttpException'; +import { HttpException } from '../HttpException'; export class UnprocessableEntityException extends HttpException { public name = 'UnprocessableEntityException'; diff --git a/lib/exceptions/common/index.ts b/lib/exceptions/common/index.ts new file mode 100644 index 00000000..2e17964b --- /dev/null +++ b/lib/exceptions/common/index.ts @@ -0,0 +1,5 @@ +export * from './BadRequestException'; +export * from './InternalServerErrorException'; +export * from './NotFoundException'; +export * from './UnauthorizedException'; +export * from './UnprocessableEntityException'; diff --git a/lib/exceptions/index.ts b/lib/exceptions/index.ts index 1e907d67..ea7ba516 100644 --- a/lib/exceptions/index.ts +++ b/lib/exceptions/index.ts @@ -1,6 +1,2 @@ export * from './HttpException'; -export * from './BadRequestException'; -export * from './InternalServerErrorException'; -export * from './NotFoundException'; -export * from './UnauthorizedException'; -export * from './UnprocessableEntityException'; +export * from './common'; diff --git a/lib/pipes/index.ts b/lib/pipes/index.ts index 6597e0dd..7f940110 100644 --- a/lib/pipes/index.ts +++ b/lib/pipes/index.ts @@ -1,5 +1,5 @@ -export * from './parseBoolean.pipe'; -export * from './parseNumber.pipe'; export * from './validation.pipe'; -export * from './validateEnum.pipe'; -export * from './parseDate.pipe'; +export * from './parseBoolean'; +export * from './parseNumber'; +export * from './validateEnum'; +export * from './parseDate'; diff --git a/lib/pipes/parseBoolean/index.ts b/lib/pipes/parseBoolean/index.ts new file mode 100644 index 00000000..b2a5b7f2 --- /dev/null +++ b/lib/pipes/parseBoolean/index.ts @@ -0,0 +1 @@ +export * from './parseBoolean.pipe'; diff --git a/test/pipes/parseBoolean.pipe.spec.ts b/lib/pipes/parseBoolean/parseBoolean.pipe.spec.ts similarity index 90% rename from test/pipes/parseBoolean.pipe.spec.ts rename to lib/pipes/parseBoolean/parseBoolean.pipe.spec.ts index 76389327..98789f71 100644 --- a/test/pipes/parseBoolean.pipe.spec.ts +++ b/lib/pipes/parseBoolean/parseBoolean.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseBooleanPipe } from '../../lib/pipes'; +import { ParseBooleanPipe } from './parseBoolean.pipe'; describe('ParseBooleanPipe', () => { it('Should parse the given string as boolean (true)', () => expect(ParseBooleanPipe()('true')).toStrictEqual(true)); diff --git a/lib/pipes/parseBoolean.pipe.ts b/lib/pipes/parseBoolean/parseBoolean.pipe.ts similarity index 80% rename from lib/pipes/parseBoolean.pipe.ts rename to lib/pipes/parseBoolean/parseBoolean.pipe.ts index 9c82fb80..a0e42661 100644 --- a/lib/pipes/parseBoolean.pipe.ts +++ b/lib/pipes/parseBoolean/parseBoolean.pipe.ts @@ -1,6 +1,6 @@ -import { BadRequestException } from '../exceptions'; -import type { ParameterPipe, PipeOptions, PipeMetadata } from './ParameterPipe'; -import { validatePipeOptions } from './validatePipeOptions'; +import { BadRequestException } from '../../exceptions'; +import type { ParameterPipe, PipeOptions, PipeMetadata } from '../ParameterPipe'; +import { validatePipeOptions } from '../validatePipeOptions'; export function ParseBooleanPipe(options?: PipeOptions): ParameterPipe { return (value: any, metadata?: PipeMetadata) => { diff --git a/lib/pipes/parseDate/index.ts b/lib/pipes/parseDate/index.ts new file mode 100644 index 00000000..9d968c1b --- /dev/null +++ b/lib/pipes/parseDate/index.ts @@ -0,0 +1 @@ +export * from './parseDate.pipe'; diff --git a/test/pipes/parseDate.pipe.spec.ts b/lib/pipes/parseDate/parseDate.pipe.spec.ts similarity index 96% rename from test/pipes/parseDate.pipe.spec.ts rename to lib/pipes/parseDate/parseDate.pipe.spec.ts index 704fa4cc..35f0b389 100644 --- a/test/pipes/parseDate.pipe.spec.ts +++ b/lib/pipes/parseDate/parseDate.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseDatePipe } from '../../lib/pipes'; +import { ParseDatePipe } from './parseDate.pipe'; describe('ParseDatePipe', () => { it('Should parse the given date', () => { diff --git a/lib/pipes/parseDate.pipe.ts b/lib/pipes/parseDate/parseDate.pipe.ts similarity index 92% rename from lib/pipes/parseDate.pipe.ts rename to lib/pipes/parseDate/parseDate.pipe.ts index 24954418..95b9a9e5 100644 --- a/lib/pipes/parseDate.pipe.ts +++ b/lib/pipes/parseDate/parseDate.pipe.ts @@ -1,6 +1,6 @@ -import { BadRequestException } from '../exceptions'; -import type { PipeMetadata, PipeOptions } from './ParameterPipe'; -import { validatePipeOptions } from './validatePipeOptions'; +import { BadRequestException } from '../../exceptions'; +import type { PipeMetadata, PipeOptions } from '../ParameterPipe'; +import { validatePipeOptions } from '../validatePipeOptions'; // The following variables and functions are taken from the validator.js (https://github.com/validatorjs/validator.js/blob/master/src/lib/isISO8601.js) diff --git a/lib/pipes/parseNumber/index.ts b/lib/pipes/parseNumber/index.ts new file mode 100644 index 00000000..111a9c97 --- /dev/null +++ b/lib/pipes/parseNumber/index.ts @@ -0,0 +1 @@ +export * from './parseNumber.pipe'; diff --git a/test/pipes/parseNumber.pipe.spec.ts b/lib/pipes/parseNumber/parseNumber.pipe.spec.ts similarity index 91% rename from test/pipes/parseNumber.pipe.spec.ts rename to lib/pipes/parseNumber/parseNumber.pipe.spec.ts index 689cf597..1e97ada0 100644 --- a/test/pipes/parseNumber.pipe.spec.ts +++ b/lib/pipes/parseNumber/parseNumber.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ParseNumberPipe } from '../../lib/pipes'; +import { ParseNumberPipe } from './parseNumber.pipe'; describe('ParseNumberPipe', () => { it('Should parse the given string as number', () => expect(ParseNumberPipe()('10')).toStrictEqual(10)); diff --git a/lib/pipes/parseNumber.pipe.ts b/lib/pipes/parseNumber/parseNumber.pipe.ts similarity index 81% rename from lib/pipes/parseNumber.pipe.ts rename to lib/pipes/parseNumber/parseNumber.pipe.ts index 9bcc0167..12178852 100644 --- a/lib/pipes/parseNumber.pipe.ts +++ b/lib/pipes/parseNumber/parseNumber.pipe.ts @@ -1,6 +1,6 @@ -import { BadRequestException } from '../exceptions'; -import type { ParameterPipe, PipeOptions, PipeMetadata } from './ParameterPipe'; -import { validatePipeOptions } from './validatePipeOptions'; +import { BadRequestException } from '../../exceptions'; +import type { ParameterPipe, PipeOptions, PipeMetadata } from '../ParameterPipe'; +import { validatePipeOptions } from '../validatePipeOptions'; export function ParseNumberPipe(options?: PipeOptions): ParameterPipe { return (value: any, metadata?: PipeMetadata) => { diff --git a/lib/pipes/validateEnum/index.ts b/lib/pipes/validateEnum/index.ts new file mode 100644 index 00000000..edb1ac03 --- /dev/null +++ b/lib/pipes/validateEnum/index.ts @@ -0,0 +1 @@ +export * from './validateEnum.pipe'; diff --git a/test/pipes/validateEnum.pipe.spec.ts b/lib/pipes/validateEnum/validateEnum.pipe.spec.ts similarity index 95% rename from test/pipes/validateEnum.pipe.spec.ts rename to lib/pipes/validateEnum/validateEnum.pipe.spec.ts index 5e0a4726..1856ed46 100644 --- a/test/pipes/validateEnum.pipe.spec.ts +++ b/lib/pipes/validateEnum/validateEnum.pipe.spec.ts @@ -1,4 +1,4 @@ -import { ValidateEnumPipe } from '../../lib/pipes'; +import { ValidateEnumPipe } from './validateEnum.pipe'; enum UserStatus { ACTIVE = 'active', diff --git a/lib/pipes/validateEnum.pipe.ts b/lib/pipes/validateEnum/validateEnum.pipe.ts similarity index 85% rename from lib/pipes/validateEnum.pipe.ts rename to lib/pipes/validateEnum/validateEnum.pipe.ts index aa4f6ed5..12b1b3ce 100644 --- a/lib/pipes/validateEnum.pipe.ts +++ b/lib/pipes/validateEnum/validateEnum.pipe.ts @@ -1,6 +1,6 @@ -import { BadRequestException } from '../exceptions'; -import type { ParameterPipe, PipeOptions, PipeMetadata } from './ParameterPipe'; -import { validatePipeOptions } from './validatePipeOptions'; +import { BadRequestException } from '../../exceptions'; +import type { ParameterPipe, PipeOptions, PipeMetadata } from '../ParameterPipe'; +import { validatePipeOptions } from '../validatePipeOptions'; interface ValidateEnumPipeOptions> extends PipeOptions { type: T; From 7284b8078e89d34199ecee868b3bcc1fa260fa89 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Mon, 8 Mar 2021 16:57:46 +0100 Subject: [PATCH 17/34] docs(readme): updated decorators and pipes in readme --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 653b25df..f46ca0d1 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ export default createHandler(User); | | Description | | ----------------------- | ------------------------------------------- | +| `@Req()` | Gets the request object. | +| `@Res()` | Gets the response object. | | `@Body()` | Gets the request body. | | `@Query(key: string)` | Gets a query string parameter value by key. | | `@Header(name: string)` | Gets a header value by name. | @@ -151,13 +153,12 @@ Pipes are being used to validate and transform incoming values. The pipes can be ⚠️ Beware that they throw when the value is invalid. -| | Description | Remarks | -| ------------------ | ------------------------------------------- | ------------------------------------------------- | -| `ParseBooleanPipe` | Validates and transforms `Boolean` strings. | Allows `'true'` and `'false'` as valid values | -| `ParseDatePipe` | Validates and transforms `Date` strings. | Allows valid `ISO 8601` formatted date strings | -| `ParseEnumPipe` | Validates and transforms `Enum` strings. | Allows strings that are present in the given enum | -| `ParseNumberPipe` | Validates and transforms `Number` strings. | Uses `parseFloat` under the hood | - +| | Description | Remarks | +| ------------------ | ------------------------------------------- | -------------------------------------------------- | +| `ParseBooleanPipe` | Validates and transforms `Boolean` strings. | Allows `'true'` and `'false'` as valid values. | +| `ParseDatePipe` | Validates and transforms `Date` strings. | Allows valid `ISO 8601` formatted date strings. | +| `ParseNumberPipe` | Validates and transforms `Number` strings. | Uses `parseFloat` under the hood. | +| `ValidateEnumPipe` | Validates string based on `Enum` values. | Allows strings that are present in the given enum. | ## Exceptions From e4bed0f43fb3bc2cdb61c599eb0ef06b830e19de Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Mon, 8 Mar 2021 18:20:02 +0100 Subject: [PATCH 18/34] chore(test): normalized test descriptions --- .../httpCode/httpCode.decorator.spec.ts | 2 +- .../httpMethod/httpMethod.decorators.spec.ts | 2 +- .../parameter/parameter.decorators.spec.ts | 14 +++++++------- .../setHeader/setHeader.decorator.spec.ts | 2 +- test/e2e.test.ts | 18 +++++++++--------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/decorators/httpCode/httpCode.decorator.spec.ts b/lib/decorators/httpCode/httpCode.decorator.spec.ts index 8a13358c..b45e1490 100644 --- a/lib/decorators/httpCode/httpCode.decorator.spec.ts +++ b/lib/decorators/httpCode/httpCode.decorator.spec.ts @@ -7,5 +7,5 @@ class Test { public create(): void {} } -it('HttpCode decorator should be set.', () => +it('Should set the HttpCode decorator.', () => expect(Reflect.getMetadata(HTTP_CODE_TOKEN, Test, 'create')).toStrictEqual(201)); diff --git a/lib/decorators/httpMethod/httpMethod.decorators.spec.ts b/lib/decorators/httpMethod/httpMethod.decorators.spec.ts index 04f2f8cc..b9eb3657 100644 --- a/lib/decorators/httpMethod/httpMethod.decorators.spec.ts +++ b/lib/decorators/httpMethod/httpMethod.decorators.spec.ts @@ -19,7 +19,7 @@ class Test { public delete(): void {} } -it('HttpMethod decorator should be set.', () => { +it('Should set the HttpMethod decorator.', () => { const meta = Reflect.getMetadata(HTTP_METHOD_TOKEN, Test); expect(meta).toBeInstanceOf(Map); expect(meta).toMatchObject( diff --git a/lib/decorators/parameter/parameter.decorators.spec.ts b/lib/decorators/parameter/parameter.decorators.spec.ts index e816cc24..172dc072 100644 --- a/lib/decorators/parameter/parameter.decorators.spec.ts +++ b/lib/decorators/parameter/parameter.decorators.spec.ts @@ -5,7 +5,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { Body, PARAMETER_TOKEN, Req, Request, Res, Response, Header, Query } from './parameter.decorators'; describe('Parameter decorators', () => { - it('Body should be set.', () => { + it('Should set the Body decorator.', () => { class Test { public index(@Body() body: any) {} } @@ -16,7 +16,7 @@ describe('Parameter decorators', () => { expect(meta).toMatchObject(expect.arrayContaining([expect.objectContaining({ index: 0, location: 'body' })])); }); - it('Header should be set.', () => { + it('Should set the Header decorator for the given names.', () => { class Test { public index(@Header('Content-Type') contentType: string, @Header('Referer') referer: string): void {} } @@ -32,7 +32,7 @@ describe('Parameter decorators', () => { ); }); - it('Query should be set for the whole query string.', () => { + it('Should set the Query decorator for the whole query string.', () => { class Test { public index(@Query() query: any) {} } @@ -45,7 +45,7 @@ describe('Parameter decorators', () => { ); }); - it('Query parameters should be set.', () => { + it('Should set the Query decorator for the given keys.', () => { class Test { public index( @Query('firstName') firstName: string, @@ -66,7 +66,7 @@ describe('Parameter decorators', () => { ); }); - it('Req should be set.', () => { + it('Should set the Req decorator.', () => { class Test { public index(@Req() req: NextApiRequest) {} } @@ -77,7 +77,7 @@ describe('Parameter decorators', () => { expect(meta).toMatchObject(expect.arrayContaining([expect.objectContaining({ index: 0, location: 'request' })])); }); - it('Res should be set.', () => { + it('Should set the Res decorator.', () => { class Test { public index(@Res() res: NextApiResponse) {} } @@ -88,7 +88,7 @@ describe('Parameter decorators', () => { expect(meta).toMatchObject(expect.arrayContaining([expect.objectContaining({ index: 0, location: 'response' })])); }); - it('Request and Response should be set.', () => { + it('Should set the Request and Response decoractors (aliases).', () => { class Test { public index(@Request() req: NextApiRequest, @Response() res: NextApiResponse) {} } diff --git a/lib/decorators/setHeader/setHeader.decorator.spec.ts b/lib/decorators/setHeader/setHeader.decorator.spec.ts index 393f855f..7a7689bf 100644 --- a/lib/decorators/setHeader/setHeader.decorator.spec.ts +++ b/lib/decorators/setHeader/setHeader.decorator.spec.ts @@ -8,7 +8,7 @@ class Test { public index(): void {} } -it('SetHeader should be set.', () => { +it('Should set the SetHeader decorator for the given name.', () => { const meta = Reflect.getMetadata(HEADER_TOKEN, Test); const methodMeta = Reflect.getMetadata(HEADER_TOKEN, Test, 'index'); diff --git a/test/e2e.test.ts b/test/e2e.test.ts index e9bccd34..0832406f 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -82,8 +82,8 @@ class TestHandler { }; } - @HttpCode(201) @Post() + @HttpCode(201) @SetHeader('X-Method', 'create') public create(@Header('Content-Type') contentType: string, @Body(ValidationPipe) body: CreateDto) { return { contentType, receivedBody: body, test: this.testField, instanceOf: body instanceof CreateDto }; @@ -120,7 +120,7 @@ describe('E2E', () => { server.all('/', createHandler(TestHandler)); }); - it('read', () => + it('Should successfully `GET` the request with a 200 status code.', () => request(server) .get('/?id=my-id&step=1&redirect=true&startAt=2021-01-01T22:00:00') .set('Content-Type', 'application/json') @@ -142,7 +142,7 @@ describe('E2E', () => { }) )); - it('read with invalid "id"', () => + it('Should throw a 404 error when an invalid ID is given.', () => request(server) .get('/?id=invalid-id&step=1&redirect=true&startAt=2021-01-01T22:00:00') .set('Content-Type', 'application/json') @@ -155,7 +155,7 @@ describe('E2E', () => { }) )); - it('read without "step"', () => + it('Should return a 400 error when a required parameter is missing.', () => request(server) .get('/?id=my-id&redirect=true') .set('Content-Type', 'application/json') @@ -168,7 +168,7 @@ describe('E2E', () => { }) )); - it('create', () => + it('Should successfully `POST` the request with a 201 status code.', () => request(server) .post('/') .send({ @@ -198,7 +198,7 @@ describe('E2E', () => { }) )); - it('Returns error for create', () => + it('Should return a 400 error when the an invalid enum is given.', () => request(server) .post('/') .send({ @@ -218,7 +218,7 @@ describe('E2E', () => { }) )); - it('update', () => + it('Should successfully `PUT` the request with a 200 status code.', () => request(server) .put('/?id=user-id') .send({ firstName: 'Ada', lastName: 'Lovelace', dateOfBirth: '1815-12-10' }) @@ -242,7 +242,7 @@ describe('E2E', () => { }) )); - it('delete', () => + it('Should successfully `DELETE` the request with a 200 status code.', () => request(server) .delete('/?id=user-id') .send({ firstName: 'Ada', lastName: 'Lovelace', dateOfBirth: '1815-12-10' }) @@ -266,7 +266,7 @@ describe('E2E', () => { }) )); - it('should throw express style 404 for an undefined http verb', () => + it('Should return a express style 404 for an undefined HTTP verb.', () => request(server) .patch('/') .set('Content-Type', 'application/json') From a0a7d524818dc3f42fdf19ea1026af772e246d58 Mon Sep 17 00:00:00 2001 From: ggurkal Date: Mon, 8 Mar 2021 23:33:58 +0100 Subject: [PATCH 19/34] chore: remove redundant parts --- lib/decorators/httpMethod/httpMethod.decorators.ts | 2 +- lib/internals/handler.ts | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/decorators/httpMethod/httpMethod.decorators.ts b/lib/decorators/httpMethod/httpMethod.decorators.ts index 2e11ff7f..7f725534 100644 --- a/lib/decorators/httpMethod/httpMethod.decorators.ts +++ b/lib/decorators/httpMethod/httpMethod.decorators.ts @@ -18,7 +18,7 @@ function applyHttpMethod(verb: HttpVerb) { Reflect.defineMetadata(HTTP_METHOD_TOKEN, methods, target.constructor); - Handler(verb)(target, propertyKey, descriptor); + Handler()(target, propertyKey, descriptor); }; } diff --git a/lib/internals/handler.ts b/lib/internals/handler.ts index 6695c205..10c4acc0 100644 --- a/lib/internals/handler.ts +++ b/lib/internals/handler.ts @@ -18,8 +18,6 @@ function getParameterValue( return req.body; case 'header': return name ? req.headers[name.toLowerCase()] : req.headers; - case 'method': - return req.method; case 'request': return req; case 'response': @@ -29,18 +27,10 @@ function getParameterValue( } } -export function Handler(method?: HttpVerb): MethodDecorator { - if (!method) { - method = HttpVerb.GET; - } - +export function Handler(): MethodDecorator { return function (target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor) { const originalHandler = descriptor.value; descriptor.value = async function (req: NextApiRequest, res: NextApiResponse) { - if (req.method !== method) { - return notFound(req, res); - } - const httpCode: number | undefined = Reflect.getMetadata(HTTP_CODE_TOKEN, target.constructor, propertyKey); const metaParameters: Array = ( Reflect.getMetadata(PARAMETER_TOKEN, target.constructor, propertyKey) ?? [] From e1af0d380e8e6e0e95697a67fe27974981d9af2c Mon Sep 17 00:00:00 2001 From: ggurkal Date: Mon, 8 Mar 2021 23:34:08 +0100 Subject: [PATCH 20/34] test: internals --- lib/internals/classValidator.spec.ts | 60 ++++++++++++++++++++++++++++ lib/internals/loadPackage.spec.ts | 8 ++++ 2 files changed, 68 insertions(+) create mode 100644 lib/internals/classValidator.spec.ts create mode 100644 lib/internals/loadPackage.spec.ts diff --git a/lib/internals/classValidator.spec.ts b/lib/internals/classValidator.spec.ts new file mode 100644 index 00000000..6befc251 --- /dev/null +++ b/lib/internals/classValidator.spec.ts @@ -0,0 +1,60 @@ +import 'reflect-metadata'; +import { Expose } from 'class-transformer'; +import { IsNotEmpty } from 'class-validator'; +import { validateObject } from './classValidator'; +import * as lp from './loadPackage'; + +describe('validateObject', () => { + it('Should return the value if "class-validator" is not being used.', async () => { + const spy = jest + .spyOn(lp, 'loadPackage') + .mockImplementation((name: string) => (name === 'class-validator' ? false : require(name))); + + class Dto { + @IsNotEmpty() + public email!: string; + } + + const result = await validateObject(Dto, { secondaryEmail: 'dev@storyofams.com' }); + + expect(result).toHaveProperty('secondaryEmail', 'dev@storyofams.com'); + + spy.mockRestore(); + }); + + it('Should return the value if "class-transformer" is not being used.', async () => { + const spy = jest + .spyOn(lp, 'loadPackage') + .mockImplementation((name: string) => (name === 'class-transformer' ? false : require(name))); + + class Dto { + @IsNotEmpty() + public email!: string; + } + + const result = await validateObject(Dto, { secondaryEmail: 'dev@storyofams.com' }); + + expect(result).toHaveProperty('secondaryEmail', 'dev@storyofams.com'); + + spy.mockRestore(); + }); + + it('Should return only exposed properties.', async () => { + class Dto { + @Expose() + @IsNotEmpty() + public email!: string; + } + + const result = await validateObject( + Dto, + { email: 'dev@storyofams.com', secondaryEmail: 'hello@storyofams.com' }, + { + transformOptions: { excludeExtraneousValues: true } + } + ); + + expect(result).toHaveProperty('email', 'dev@storyofams.com'); + expect(result).not.toHaveProperty('secondaryEmail', 'hello@storyofams.com'); + }); +}); diff --git a/lib/internals/loadPackage.spec.ts b/lib/internals/loadPackage.spec.ts new file mode 100644 index 00000000..9b2bb0cb --- /dev/null +++ b/lib/internals/loadPackage.spec.ts @@ -0,0 +1,8 @@ +import { loadPackage } from './loadPackage'; + +describe('loadPackage', () => { + it('Should load a package correctly.', () => expect(loadPackage('class-validator')).not.toEqual(false)); + + it('Should return false for a non existing package.', () => + expect(loadPackage('a-package-that-does-not-exist')).toEqual(false)); +}); From 6bf7a64e22d27a22354325db8eca055c54cb5cd4 Mon Sep 17 00:00:00 2001 From: ggurkal Date: Mon, 8 Mar 2021 23:34:18 +0100 Subject: [PATCH 21/34] test: validation pipe --- lib/pipes/validation.pipe.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 lib/pipes/validation.pipe.spec.ts diff --git a/lib/pipes/validation.pipe.spec.ts b/lib/pipes/validation.pipe.spec.ts new file mode 100644 index 00000000..ef80b556 --- /dev/null +++ b/lib/pipes/validation.pipe.spec.ts @@ -0,0 +1,9 @@ +import { ValidationPipe } from './validation.pipe'; + +describe('ValidationPipe', () => { + it('Should return the value as is when there is no meta type defined.', () => + expect(ValidationPipe()({ firstName: 'Uncle', lastName: 'Bob' })).toMatchObject({ + firstName: 'Uncle', + lastName: 'Bob' + })); +}); From e808be7d653ee6a96f43e5ae8fd182bcc529f4c1 Mon Sep 17 00:00:00 2001 From: ggurkal Date: Mon, 8 Mar 2021 23:34:46 +0100 Subject: [PATCH 22/34] test(e2e): nested validation & query with pipe --- test/e2e.test.ts | 53 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/test/e2e.test.ts b/test/e2e.test.ts index 0832406f..fd75e28d 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -1,5 +1,6 @@ import 'reflect-metadata'; -import { IsBoolean, IsDate, IsEnum, IsInt, IsNotEmpty, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsBoolean, IsDate, IsEnum, IsInt, IsNotEmpty, IsOptional, ValidateNested } from 'class-validator'; import express from 'express'; import type { NextApiRequest, NextApiResponse } from 'next'; import request from 'supertest'; @@ -29,6 +30,14 @@ enum CreateSource { OFFLINE = 'offline' } +class Address { + @IsNotEmpty() + public city!: string; + + @IsNotEmpty() + public country!: string; +} + class CreateDto { @IsNotEmpty() public firstName!: string; @@ -52,6 +61,17 @@ class CreateDto { @IsEnum(CreateSource) @IsOptional() public source?: CreateSource; + + @Type(() => Address) + @ValidateNested() + @IsOptional() + public addresses?: Address[]; +} + +class QueryDto { + @IsOptional() + @IsEnum(CreateSource) + public source?: CreateSource; } @SetHeader('X-Api', 'true') @@ -85,8 +105,12 @@ class TestHandler { @Post() @HttpCode(201) @SetHeader('X-Method', 'create') - public create(@Header('Content-Type') contentType: string, @Body(ValidationPipe) body: CreateDto) { - return { contentType, receivedBody: body, test: this.testField, instanceOf: body instanceof CreateDto }; + public create( + @Query(ValidationPipe) query: QueryDto, + @Header('Content-Type') contentType: string, + @Body(ValidationPipe) body: CreateDto + ) { + return { ...query, contentType, receivedBody: body, test: this.testField, instanceOf: body instanceof CreateDto }; } @Put() @@ -170,7 +194,7 @@ describe('E2E', () => { it('Should successfully `POST` the request with a 201 status code.', () => request(server) - .post('/') + .post('/?source=online') .send({ firstName: 'Ada', lastName: 'Lovelace', @@ -186,6 +210,7 @@ describe('E2E', () => { 'x-method': 'create' }, body: { + source: CreateSource.ONLINE, contentType: 'application/json', test: 'test', instanceOf: true, @@ -198,6 +223,26 @@ describe('E2E', () => { }) )); + it('Should return a 400 error when "addresses[0].country" is not set.', () => + request(server) + .post('/') + .send({ + firstName: 'Ada', + lastName: 'Lovelace', + dateOfBirth: new Date('1815-12-10'), + birthYear: 1815, + isActive: true, + addresses: [{ city: 'Amsterdam' }] + } as CreateDto) + .expect(400) + .then(res => + expect(res).toMatchObject({ + body: { + errors: expect.arrayContaining([expect.stringContaining('addresses.0.country should not be empty')]) + } + }) + )); + it('Should return a 400 error when the an invalid enum is given.', () => request(server) .post('/') From d1dfeec9f5b40c419bb5b3a46c112ede9b07893c Mon Sep 17 00:00:00 2001 From: ggurkal Date: Mon, 8 Mar 2021 23:35:03 +0100 Subject: [PATCH 23/34] test(e2e): return stream --- test/e2e-stream.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/e2e-stream.test.ts diff --git a/test/e2e-stream.test.ts b/test/e2e-stream.test.ts new file mode 100644 index 00000000..5a023de4 --- /dev/null +++ b/test/e2e-stream.test.ts @@ -0,0 +1,39 @@ +import 'reflect-metadata'; +import fs from 'fs'; +import express from 'express'; +import request from 'supertest'; +import { createHandler, Get } from '../lib'; + +class TestHandler { + @Get() + public getStream() { + fs.writeFileSync('./test-stream.txt', 'hello stream!'); + return fs.createReadStream('./test-stream.txt'); + } +} + +describe('E2E', () => { + let server: express.Express; + beforeAll(() => { + server = express(); + server.use(express.json()); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + server.all('/', createHandler(TestHandler)); + }); + + afterAll(() => fs.unlinkSync('./test-stream.txt')); + + it('Should return file contents from Stream object.', () => + request(server) + .get('/') + .expect(200) + .then(res => + expect(res).toMatchObject({ + header: { + 'transfer-encoding': 'chunked' + }, + text: 'hello stream!' + }) + )); +}); From ce5622c396d1176b8918306aa3480a1d39f729fd Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Tue, 9 Mar 2021 10:24:40 +0100 Subject: [PATCH 24/34] refactor: general folder structure --- .gitignore | 3 --- lib/decorators/.eslintrc | 2 +- lib/decorators/{httpCode => }/httpCode.decorator.spec.ts | 0 lib/decorators/{httpCode => }/httpCode.decorator.ts | 0 lib/decorators/httpCode/index.ts | 1 - .../{httpMethod => }/httpMethod.decorators.spec.ts | 0 lib/decorators/{httpMethod => }/httpMethod.decorators.ts | 2 +- lib/decorators/httpMethod/index.ts | 1 - lib/decorators/index.ts | 8 ++++---- .../{parameter => }/parameter.decorators.spec.ts | 0 lib/decorators/{parameter => }/parameter.decorators.ts | 2 +- lib/decorators/parameter/index.ts | 1 - .../{setHeader => }/setHeader.decorator.spec.ts | 0 lib/decorators/{setHeader => }/setHeader.decorator.ts | 0 lib/decorators/setHeader/index.ts | 1 - lib/exceptions/{common => }/BadRequestException.ts | 2 +- lib/exceptions/HttpException.spec.ts | 2 +- .../{common => }/InternalServerErrorException.ts | 2 +- lib/exceptions/{common => }/NotFoundException.ts | 2 +- lib/exceptions/{common => }/UnauthorizedException.ts | 2 +- .../{common => }/UnprocessableEntityException.ts | 2 +- lib/exceptions/common/index.ts | 5 ----- lib/exceptions/index.ts | 6 +++++- lib/pipes/index.ts | 7 ++----- lib/pipes/parseBoolean/index.ts | 1 - lib/pipes/parseDate/index.ts | 1 - lib/pipes/parseNumber/index.ts | 1 - lib/pipes/parsers/index.ts | 3 +++ .../{parseBoolean => parsers}/parseBoolean.pipe.spec.ts | 0 lib/pipes/{parseBoolean => parsers}/parseBoolean.pipe.ts | 0 lib/pipes/{parseDate => parsers}/parseDate.pipe.spec.ts | 0 lib/pipes/{parseDate => parsers}/parseDate.pipe.ts | 0 .../{parseNumber => parsers}/parseNumber.pipe.spec.ts | 0 lib/pipes/{parseNumber => parsers}/parseNumber.pipe.ts | 0 lib/pipes/{validateEnum => validators}/index.ts | 1 + .../validateEnum.pipe.spec.ts | 0 .../{validateEnum => validators}/validateEnum.pipe.ts | 0 lib/pipes/{ => validators}/validation.pipe.spec.ts | 0 lib/pipes/{ => validators}/validation.pipe.ts | 4 ++-- 39 files changed, 26 insertions(+), 36 deletions(-) rename lib/decorators/{httpCode => }/httpCode.decorator.spec.ts (100%) rename lib/decorators/{httpCode => }/httpCode.decorator.ts (100%) delete mode 100644 lib/decorators/httpCode/index.ts rename lib/decorators/{httpMethod => }/httpMethod.decorators.spec.ts (100%) rename lib/decorators/{httpMethod => }/httpMethod.decorators.ts (95%) delete mode 100644 lib/decorators/httpMethod/index.ts rename lib/decorators/{parameter => }/parameter.decorators.spec.ts (100%) rename lib/decorators/{parameter => }/parameter.decorators.ts (97%) delete mode 100644 lib/decorators/parameter/index.ts rename lib/decorators/{setHeader => }/setHeader.decorator.spec.ts (100%) rename lib/decorators/{setHeader => }/setHeader.decorator.ts (100%) delete mode 100644 lib/decorators/setHeader/index.ts rename lib/exceptions/{common => }/BadRequestException.ts (80%) rename lib/exceptions/{common => }/InternalServerErrorException.ts (80%) rename lib/exceptions/{common => }/NotFoundException.ts (78%) rename lib/exceptions/{common => }/UnauthorizedException.ts (79%) rename lib/exceptions/{common => }/UnprocessableEntityException.ts (82%) delete mode 100644 lib/exceptions/common/index.ts delete mode 100644 lib/pipes/parseBoolean/index.ts delete mode 100644 lib/pipes/parseDate/index.ts delete mode 100644 lib/pipes/parseNumber/index.ts create mode 100644 lib/pipes/parsers/index.ts rename lib/pipes/{parseBoolean => parsers}/parseBoolean.pipe.spec.ts (100%) rename lib/pipes/{parseBoolean => parsers}/parseBoolean.pipe.ts (100%) rename lib/pipes/{parseDate => parsers}/parseDate.pipe.spec.ts (100%) rename lib/pipes/{parseDate => parsers}/parseDate.pipe.ts (100%) rename lib/pipes/{parseNumber => parsers}/parseNumber.pipe.spec.ts (100%) rename lib/pipes/{parseNumber => parsers}/parseNumber.pipe.ts (100%) rename lib/pipes/{validateEnum => validators}/index.ts (51%) rename lib/pipes/{validateEnum => validators}/validateEnum.pipe.spec.ts (100%) rename lib/pipes/{validateEnum => validators}/validateEnum.pipe.ts (100%) rename lib/pipes/{ => validators}/validation.pipe.spec.ts (100%) rename lib/pipes/{ => validators}/validation.pipe.ts (78%) diff --git a/.gitignore b/.gitignore index bcd4c1b1..594fdb23 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,3 @@ npm-debug.log* yarn-debug.log* yarn-error.log* - -# storybook -storybook-static diff --git a/lib/decorators/.eslintrc b/lib/decorators/.eslintrc index 3935b0ac..b8a5800c 100644 --- a/lib/decorators/.eslintrc +++ b/lib/decorators/.eslintrc @@ -2,4 +2,4 @@ "rules": { "@typescript-eslint/ban-types": "off" } -} \ No newline at end of file +} diff --git a/lib/decorators/httpCode/httpCode.decorator.spec.ts b/lib/decorators/httpCode.decorator.spec.ts similarity index 100% rename from lib/decorators/httpCode/httpCode.decorator.spec.ts rename to lib/decorators/httpCode.decorator.spec.ts diff --git a/lib/decorators/httpCode/httpCode.decorator.ts b/lib/decorators/httpCode.decorator.ts similarity index 100% rename from lib/decorators/httpCode/httpCode.decorator.ts rename to lib/decorators/httpCode.decorator.ts diff --git a/lib/decorators/httpCode/index.ts b/lib/decorators/httpCode/index.ts deleted file mode 100644 index 8fb4f9f9..00000000 --- a/lib/decorators/httpCode/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './httpCode.decorator'; diff --git a/lib/decorators/httpMethod/httpMethod.decorators.spec.ts b/lib/decorators/httpMethod.decorators.spec.ts similarity index 100% rename from lib/decorators/httpMethod/httpMethod.decorators.spec.ts rename to lib/decorators/httpMethod.decorators.spec.ts diff --git a/lib/decorators/httpMethod/httpMethod.decorators.ts b/lib/decorators/httpMethod.decorators.ts similarity index 95% rename from lib/decorators/httpMethod/httpMethod.decorators.ts rename to lib/decorators/httpMethod.decorators.ts index 7f725534..3f3fc334 100644 --- a/lib/decorators/httpMethod/httpMethod.decorators.ts +++ b/lib/decorators/httpMethod.decorators.ts @@ -1,4 +1,4 @@ -import { Handler } from '../../internals/handler'; +import { Handler } from '../internals/handler'; export enum HttpVerb { GET = 'GET', diff --git a/lib/decorators/httpMethod/index.ts b/lib/decorators/httpMethod/index.ts deleted file mode 100644 index c6574e67..00000000 --- a/lib/decorators/httpMethod/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './httpMethod.decorators'; diff --git a/lib/decorators/index.ts b/lib/decorators/index.ts index f96747c0..363131ea 100644 --- a/lib/decorators/index.ts +++ b/lib/decorators/index.ts @@ -1,4 +1,4 @@ -export * from './httpCode'; -export * from './httpMethod'; -export * from './parameter'; -export * from './setHeader'; +export * from './httpCode.decorator'; +export * from './httpMethod.decorators'; +export * from './parameter.decorators'; +export * from './setHeader.decorator'; diff --git a/lib/decorators/parameter/parameter.decorators.spec.ts b/lib/decorators/parameter.decorators.spec.ts similarity index 100% rename from lib/decorators/parameter/parameter.decorators.spec.ts rename to lib/decorators/parameter.decorators.spec.ts diff --git a/lib/decorators/parameter/parameter.decorators.ts b/lib/decorators/parameter.decorators.ts similarity index 97% rename from lib/decorators/parameter/parameter.decorators.ts rename to lib/decorators/parameter.decorators.ts index c5c38fbe..4036d2be 100644 --- a/lib/decorators/parameter/parameter.decorators.ts +++ b/lib/decorators/parameter.decorators.ts @@ -1,4 +1,4 @@ -import type { ParameterPipe } from '../../pipes/ParameterPipe'; +import type { ParameterPipe } from '../pipes/ParameterPipe'; export interface MetaParameter { index: number; diff --git a/lib/decorators/parameter/index.ts b/lib/decorators/parameter/index.ts deleted file mode 100644 index e117bb43..00000000 --- a/lib/decorators/parameter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './parameter.decorators'; diff --git a/lib/decorators/setHeader/setHeader.decorator.spec.ts b/lib/decorators/setHeader.decorator.spec.ts similarity index 100% rename from lib/decorators/setHeader/setHeader.decorator.spec.ts rename to lib/decorators/setHeader.decorator.spec.ts diff --git a/lib/decorators/setHeader/setHeader.decorator.ts b/lib/decorators/setHeader.decorator.ts similarity index 100% rename from lib/decorators/setHeader/setHeader.decorator.ts rename to lib/decorators/setHeader.decorator.ts diff --git a/lib/decorators/setHeader/index.ts b/lib/decorators/setHeader/index.ts deleted file mode 100644 index 62ab36dd..00000000 --- a/lib/decorators/setHeader/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './setHeader.decorator'; diff --git a/lib/exceptions/common/BadRequestException.ts b/lib/exceptions/BadRequestException.ts similarity index 80% rename from lib/exceptions/common/BadRequestException.ts rename to lib/exceptions/BadRequestException.ts index b43d50b0..2c3a854b 100644 --- a/lib/exceptions/common/BadRequestException.ts +++ b/lib/exceptions/BadRequestException.ts @@ -1,4 +1,4 @@ -import { HttpException } from '../HttpException'; +import { HttpException } from './HttpException'; export class BadRequestException extends HttpException { public name = 'BadRequestException'; diff --git a/lib/exceptions/HttpException.spec.ts b/lib/exceptions/HttpException.spec.ts index 91b293ce..1b3666d4 100644 --- a/lib/exceptions/HttpException.spec.ts +++ b/lib/exceptions/HttpException.spec.ts @@ -30,7 +30,7 @@ describe('HttpException', () => { errors: ['Invalid email address'] })); - describe('Common errors', () => { + describe('Default errors', () => { it('Should set the default status codes', () => { expect(new BadRequestException()).toHaveProperty('statusCode', 400); expect(new InternalServerErrorException()).toHaveProperty('statusCode', 500); diff --git a/lib/exceptions/common/InternalServerErrorException.ts b/lib/exceptions/InternalServerErrorException.ts similarity index 80% rename from lib/exceptions/common/InternalServerErrorException.ts rename to lib/exceptions/InternalServerErrorException.ts index ab71ef86..7a380fdb 100644 --- a/lib/exceptions/common/InternalServerErrorException.ts +++ b/lib/exceptions/InternalServerErrorException.ts @@ -1,4 +1,4 @@ -import { HttpException } from '../HttpException'; +import { HttpException } from './HttpException'; export class InternalServerErrorException extends HttpException { public name = 'InternalServerErrorException'; diff --git a/lib/exceptions/common/NotFoundException.ts b/lib/exceptions/NotFoundException.ts similarity index 78% rename from lib/exceptions/common/NotFoundException.ts rename to lib/exceptions/NotFoundException.ts index 64dd9948..de2a8561 100644 --- a/lib/exceptions/common/NotFoundException.ts +++ b/lib/exceptions/NotFoundException.ts @@ -1,4 +1,4 @@ -import { HttpException } from '../HttpException'; +import { HttpException } from './HttpException'; export class NotFoundException extends HttpException { public name = 'NotFoundException'; diff --git a/lib/exceptions/common/UnauthorizedException.ts b/lib/exceptions/UnauthorizedException.ts similarity index 79% rename from lib/exceptions/common/UnauthorizedException.ts rename to lib/exceptions/UnauthorizedException.ts index 87c44450..125b379f 100644 --- a/lib/exceptions/common/UnauthorizedException.ts +++ b/lib/exceptions/UnauthorizedException.ts @@ -1,4 +1,4 @@ -import { HttpException } from '../HttpException'; +import { HttpException } from './HttpException'; export class UnauthorizedException extends HttpException { public name = 'UnauthorizedException'; diff --git a/lib/exceptions/common/UnprocessableEntityException.ts b/lib/exceptions/UnprocessableEntityException.ts similarity index 82% rename from lib/exceptions/common/UnprocessableEntityException.ts rename to lib/exceptions/UnprocessableEntityException.ts index 8e3545fe..b5a9de84 100644 --- a/lib/exceptions/common/UnprocessableEntityException.ts +++ b/lib/exceptions/UnprocessableEntityException.ts @@ -1,4 +1,4 @@ -import { HttpException } from '../HttpException'; +import { HttpException } from './HttpException'; export class UnprocessableEntityException extends HttpException { public name = 'UnprocessableEntityException'; diff --git a/lib/exceptions/common/index.ts b/lib/exceptions/common/index.ts deleted file mode 100644 index 2e17964b..00000000 --- a/lib/exceptions/common/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './BadRequestException'; -export * from './InternalServerErrorException'; -export * from './NotFoundException'; -export * from './UnauthorizedException'; -export * from './UnprocessableEntityException'; diff --git a/lib/exceptions/index.ts b/lib/exceptions/index.ts index ea7ba516..1e907d67 100644 --- a/lib/exceptions/index.ts +++ b/lib/exceptions/index.ts @@ -1,2 +1,6 @@ export * from './HttpException'; -export * from './common'; +export * from './BadRequestException'; +export * from './InternalServerErrorException'; +export * from './NotFoundException'; +export * from './UnauthorizedException'; +export * from './UnprocessableEntityException'; diff --git a/lib/pipes/index.ts b/lib/pipes/index.ts index 7f940110..a5ad51fe 100644 --- a/lib/pipes/index.ts +++ b/lib/pipes/index.ts @@ -1,5 +1,2 @@ -export * from './validation.pipe'; -export * from './parseBoolean'; -export * from './parseNumber'; -export * from './validateEnum'; -export * from './parseDate'; +export * from './validators'; +export * from './parsers'; diff --git a/lib/pipes/parseBoolean/index.ts b/lib/pipes/parseBoolean/index.ts deleted file mode 100644 index b2a5b7f2..00000000 --- a/lib/pipes/parseBoolean/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './parseBoolean.pipe'; diff --git a/lib/pipes/parseDate/index.ts b/lib/pipes/parseDate/index.ts deleted file mode 100644 index 9d968c1b..00000000 --- a/lib/pipes/parseDate/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './parseDate.pipe'; diff --git a/lib/pipes/parseNumber/index.ts b/lib/pipes/parseNumber/index.ts deleted file mode 100644 index 111a9c97..00000000 --- a/lib/pipes/parseNumber/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './parseNumber.pipe'; diff --git a/lib/pipes/parsers/index.ts b/lib/pipes/parsers/index.ts new file mode 100644 index 00000000..59d2dbf7 --- /dev/null +++ b/lib/pipes/parsers/index.ts @@ -0,0 +1,3 @@ +export * from './parseBoolean.pipe'; +export * from './parseNumber.pipe'; +export * from './parseDate.pipe'; diff --git a/lib/pipes/parseBoolean/parseBoolean.pipe.spec.ts b/lib/pipes/parsers/parseBoolean.pipe.spec.ts similarity index 100% rename from lib/pipes/parseBoolean/parseBoolean.pipe.spec.ts rename to lib/pipes/parsers/parseBoolean.pipe.spec.ts diff --git a/lib/pipes/parseBoolean/parseBoolean.pipe.ts b/lib/pipes/parsers/parseBoolean.pipe.ts similarity index 100% rename from lib/pipes/parseBoolean/parseBoolean.pipe.ts rename to lib/pipes/parsers/parseBoolean.pipe.ts diff --git a/lib/pipes/parseDate/parseDate.pipe.spec.ts b/lib/pipes/parsers/parseDate.pipe.spec.ts similarity index 100% rename from lib/pipes/parseDate/parseDate.pipe.spec.ts rename to lib/pipes/parsers/parseDate.pipe.spec.ts diff --git a/lib/pipes/parseDate/parseDate.pipe.ts b/lib/pipes/parsers/parseDate.pipe.ts similarity index 100% rename from lib/pipes/parseDate/parseDate.pipe.ts rename to lib/pipes/parsers/parseDate.pipe.ts diff --git a/lib/pipes/parseNumber/parseNumber.pipe.spec.ts b/lib/pipes/parsers/parseNumber.pipe.spec.ts similarity index 100% rename from lib/pipes/parseNumber/parseNumber.pipe.spec.ts rename to lib/pipes/parsers/parseNumber.pipe.spec.ts diff --git a/lib/pipes/parseNumber/parseNumber.pipe.ts b/lib/pipes/parsers/parseNumber.pipe.ts similarity index 100% rename from lib/pipes/parseNumber/parseNumber.pipe.ts rename to lib/pipes/parsers/parseNumber.pipe.ts diff --git a/lib/pipes/validateEnum/index.ts b/lib/pipes/validators/index.ts similarity index 51% rename from lib/pipes/validateEnum/index.ts rename to lib/pipes/validators/index.ts index edb1ac03..59e7f67f 100644 --- a/lib/pipes/validateEnum/index.ts +++ b/lib/pipes/validators/index.ts @@ -1 +1,2 @@ +export * from './validation.pipe'; export * from './validateEnum.pipe'; diff --git a/lib/pipes/validateEnum/validateEnum.pipe.spec.ts b/lib/pipes/validators/validateEnum.pipe.spec.ts similarity index 100% rename from lib/pipes/validateEnum/validateEnum.pipe.spec.ts rename to lib/pipes/validators/validateEnum.pipe.spec.ts diff --git a/lib/pipes/validateEnum/validateEnum.pipe.ts b/lib/pipes/validators/validateEnum.pipe.ts similarity index 100% rename from lib/pipes/validateEnum/validateEnum.pipe.ts rename to lib/pipes/validators/validateEnum.pipe.ts diff --git a/lib/pipes/validation.pipe.spec.ts b/lib/pipes/validators/validation.pipe.spec.ts similarity index 100% rename from lib/pipes/validation.pipe.spec.ts rename to lib/pipes/validators/validation.pipe.spec.ts diff --git a/lib/pipes/validation.pipe.ts b/lib/pipes/validators/validation.pipe.ts similarity index 78% rename from lib/pipes/validation.pipe.ts rename to lib/pipes/validators/validation.pipe.ts index e4b00a00..36b0cbf9 100644 --- a/lib/pipes/validation.pipe.ts +++ b/lib/pipes/validators/validation.pipe.ts @@ -1,7 +1,7 @@ import type { ClassTransformOptions } from 'class-transformer'; import type { ValidatorOptions } from 'class-validator'; -import { validateObject } from '../internals/classValidator'; -import type { ParameterPipe, PipeMetadata } from './ParameterPipe'; +import { validateObject } from '../../internals/classValidator'; +import type { ParameterPipe, PipeMetadata } from '../ParameterPipe'; export interface ValidationPipeOptions extends ValidatorOptions { transformOptions?: ClassTransformOptions; From bf27cac9ecace5257ad89788721ed33ed27f3ac1 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Tue, 9 Mar 2021 10:37:36 +0100 Subject: [PATCH 25/34] docs(readme): added footnote to Res decorator --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f46ca0d1..571f0c5f 100644 --- a/README.md +++ b/README.md @@ -137,11 +137,12 @@ export default createHandler(User); | | Description | | ----------------------- | ------------------------------------------- | | `@Req()` | Gets the request object. | -| `@Res()` | Gets the response object. | +| `@Res()`* | Gets the response object. | | `@Body()` | Gets the request body. | | `@Query(key: string)` | Gets a query string parameter value by key. | | `@Header(name: string)` | Gets a header value by name. | +\* When using `@Res()`, you are in charge of sending the response to the client. Therefore, the return statement won't be handled by this package and the response won't be served. ## Built-in pipes From 4487753a314edfaa73623a6bea1f9b9fccd7a091 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Tue, 9 Mar 2021 18:33:56 +0100 Subject: [PATCH 26/34] docs(readme): added motivation section --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 571f0c5f..488d259a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,13 @@ --- -Collection of decorators to create typed Next.js API routes, with easy request validation and transformation. +This package contains a collection of decorators to create typed Next.js API routes, with easy request validation and transformation. + +## Motivation + +Building serverless functions declaratively with classes and decorators makes dealing with Next.js API routes easier and brings order and sanity to your `/pages/api` codebase. + +The structure is heavily inspired by NestJS, which is an amazing framework for a lot of use cases. Having said that, a separate NestJS repo for your backend can also bring unneeded overhead and complexity to projects with a smaller set of backend requirements. Combining the structure of NestJS, with the ease of use of Next.js, brings the best of both worlds for the right use case. ## Installation From 4276a677daf0da40f9b694cf863ad4228d0d8f63 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Tue, 9 Mar 2021 18:34:48 +0100 Subject: [PATCH 27/34] docs(readme): updated babel configuration instructions --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 488d259a..8b647a53 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,16 @@ Since decorators are still in proposal state, you need to add the following plug $ yarn add -D babel-plugin-transform-typescript-metadata @babel/plugin-proposal-decorators babel-plugin-parameter-decorator ``` -Make sure to add the following lines to the `plugins` section in your babel configuration file: +Make sure to add the following lines to the start of the `plugins` section in your babel configuration file: ```json -"babel-plugin-transform-typescript-metadata", -["@babel/plugin-proposal-decorators", { "legacy": true }], -"babel-plugin-parameter-decorator", +{ + "plugins": [ + "babel-plugin-transform-typescript-metadata", + ["@babel/plugin-proposal-decorators", { "legacy": true }], + "babel-plugin-parameter-decorator", + // ... other plugins + ] +} ``` Your `tsconfig.json` needs the following flags: From d8096725d539a274e2dc5e74650dcf4417549a59 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Tue, 9 Mar 2021 18:35:49 +0100 Subject: [PATCH 28/34] docs(readme): clarified Res decorator behaviour --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b647a53..6f390fac 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ export default createHandler(User); | `@Query(key: string)` | Gets a query string parameter value by key. | | `@Header(name: string)` | Gets a header value by name. | -\* When using `@Res()`, you are in charge of sending the response to the client. Therefore, the return statement won't be handled by this package and the response won't be served. +\* Note that when you inject `@Res()` in a method handler you become responsible for managing the response. When doing so, you must issue some kind of response by making a call on the response object (e.g., `res.json(...)` or `res.send(...)`), or the HTTP server will hang. ## Built-in pipes From 9fa8824b1160331ad8b541f6b377cde15b88ef83 Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Tue, 9 Mar 2021 18:40:09 +0100 Subject: [PATCH 29/34] docs(readme): changed json code blocks to json5 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6f390fac..9c8157cb 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ $ yarn add -D babel-plugin-transform-typescript-metadata @babel/plugin-proposal- ``` Make sure to add the following lines to the start of the `plugins` section in your babel configuration file: -```json +```json5 { "plugins": [ "babel-plugin-transform-typescript-metadata", @@ -58,7 +58,7 @@ Make sure to add the following lines to the start of the `plugins` section in yo Your `tsconfig.json` needs the following flags: -```json +```json5 "experimentalDecorators": true ``` From 38331efb14cb6d07ceab61d89c87b712befa4ccb Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Wed, 10 Mar 2021 16:16:05 +0100 Subject: [PATCH 30/34] docs(readme): added @babel/core to instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c8157cb..78bb2b88 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This package contains a collection of decorators to create typed Next.js API rou Building serverless functions declaratively with classes and decorators makes dealing with Next.js API routes easier and brings order and sanity to your `/pages/api` codebase. -The structure is heavily inspired by NestJS, which is an amazing framework for a lot of use cases. Having said that, a separate NestJS repo for your backend can also bring unneeded overhead and complexity to projects with a smaller set of backend requirements. Combining the structure of NestJS, with the ease of use of Next.js, brings the best of both worlds for the right use case. +The structure is heavily inspired by NestJS, which is an amazing framework for a lot of use cases. On the other hand, a separate NestJS repo for your backend can also bring unneeded overhead and complexity to projects with a smaller set of backend requirements. Combining the structure of NestJS, with the ease of use of Next.js, brings the best of both worlds for the right use case. ## Installation @@ -41,7 +41,7 @@ $ yarn add @storyofams/next-api-decorators Since decorators are still in proposal state, you need to add the following plugins to your `devDependencies` in order to use them: ```bash -$ yarn add -D babel-plugin-transform-typescript-metadata @babel/plugin-proposal-decorators babel-plugin-parameter-decorator +$ yarn add -D @babel/core babel-plugin-transform-typescript-metadata @babel/plugin-proposal-decorators babel-plugin-parameter-decorator ``` Make sure to add the following lines to the start of the `plugins` section in your babel configuration file: From 5268ad1bb12d4a8d38b5e7749ef98c8fe9020ebd Mon Sep 17 00:00:00 2001 From: ggurkal Date: Wed, 10 Mar 2021 22:03:18 +0100 Subject: [PATCH 31/34] refactor(pipes): allow pipe fn to return undefined for nullable --- lib/pipes/ParameterPipe.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pipes/ParameterPipe.ts b/lib/pipes/ParameterPipe.ts index 452eb6ed..b3e84c3c 100644 --- a/lib/pipes/ParameterPipe.ts +++ b/lib/pipes/ParameterPipe.ts @@ -7,4 +7,7 @@ export interface PipeOptions { readonly nullable?: boolean; } -export type ParameterPipe = (value: any, metadata?: PipeMetadata) => TOutput; +export type ParameterPipe = ( + value: any, + metadata?: PipeMetadata +) => TOutput | undefined; From 4c5acd145e2e7d038c75c0fc032240b459e95ab7 Mon Sep 17 00:00:00 2001 From: ggurkal Date: Wed, 10 Mar 2021 22:03:37 +0100 Subject: [PATCH 32/34] chore(pipes): add nullable validation fn --- lib/pipes/validateNullable.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lib/pipes/validateNullable.ts diff --git a/lib/pipes/validateNullable.ts b/lib/pipes/validateNullable.ts new file mode 100644 index 00000000..3c82696b --- /dev/null +++ b/lib/pipes/validateNullable.ts @@ -0,0 +1,3 @@ +export function validateNullable(value: any, nullable?: boolean): boolean { + return !!nullable && value == null; +} From 1e6e0cca0f8c06d23576a611e33ec2c21a6d1be1 Mon Sep 17 00:00:00 2001 From: ggurkal Date: Wed, 10 Mar 2021 22:04:08 +0100 Subject: [PATCH 33/34] refactor(parsers): nullable check --- lib/pipes/parsers/parseBoolean.pipe.ts | 5 +++++ lib/pipes/parsers/parseDate.pipe.ts | 5 +++++ lib/pipes/parsers/parseNumber.pipe.ts | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/lib/pipes/parsers/parseBoolean.pipe.ts b/lib/pipes/parsers/parseBoolean.pipe.ts index a0e42661..630d97a1 100644 --- a/lib/pipes/parsers/parseBoolean.pipe.ts +++ b/lib/pipes/parsers/parseBoolean.pipe.ts @@ -1,11 +1,16 @@ import { BadRequestException } from '../../exceptions'; import type { ParameterPipe, PipeOptions, PipeMetadata } from '../ParameterPipe'; +import { validateNullable } from '../validateNullable'; import { validatePipeOptions } from '../validatePipeOptions'; export function ParseBooleanPipe(options?: PipeOptions): ParameterPipe { return (value: any, metadata?: PipeMetadata) => { validatePipeOptions(value, metadata?.name, options); + if (validateNullable(value, options?.nullable)) { + return undefined; + } + if (value === true || value === 'true') { return true; } diff --git a/lib/pipes/parsers/parseDate.pipe.ts b/lib/pipes/parsers/parseDate.pipe.ts index 95b9a9e5..043551cc 100644 --- a/lib/pipes/parsers/parseDate.pipe.ts +++ b/lib/pipes/parsers/parseDate.pipe.ts @@ -1,5 +1,6 @@ import { BadRequestException } from '../../exceptions'; import type { PipeMetadata, PipeOptions } from '../ParameterPipe'; +import { validateNullable } from '../validateNullable'; import { validatePipeOptions } from '../validatePipeOptions'; // The following variables and functions are taken from the validator.js (https://github.com/validatorjs/validator.js/blob/master/src/lib/isISO8601.js) @@ -43,6 +44,10 @@ export function ParseDatePipe(options?: PipeOptions) { return (value: any, metadata?: PipeMetadata) => { validatePipeOptions(value, metadata?.name, options); + if (validateNullable(value, options?.nullable)) { + return undefined; + } + if (value && !isISO8601(value, { strict: true })) { throw new BadRequestException( `Validation failed${metadata?.name ? ` for ${metadata.name}` : ''} (date string is expected)` diff --git a/lib/pipes/parsers/parseNumber.pipe.ts b/lib/pipes/parsers/parseNumber.pipe.ts index 12178852..dcfc713b 100644 --- a/lib/pipes/parsers/parseNumber.pipe.ts +++ b/lib/pipes/parsers/parseNumber.pipe.ts @@ -1,11 +1,16 @@ import { BadRequestException } from '../../exceptions'; import type { ParameterPipe, PipeOptions, PipeMetadata } from '../ParameterPipe'; +import { validateNullable } from '../validateNullable'; import { validatePipeOptions } from '../validatePipeOptions'; export function ParseNumberPipe(options?: PipeOptions): ParameterPipe { return (value: any, metadata?: PipeMetadata) => { validatePipeOptions(value, metadata?.name, options); + if (validateNullable(value, options?.nullable)) { + return undefined; + } + const isNumeric = ['string', 'number'].includes(typeof value) && !isNaN(parseFloat(value)) && isFinite(value); if (!isNumeric) { throw new BadRequestException( From 61270a525d203ba786a2e0f71b513cbe98908f67 Mon Sep 17 00:00:00 2001 From: ggurkal Date: Wed, 10 Mar 2021 22:04:29 +0100 Subject: [PATCH 34/34] test(parsers): add nullable tests --- lib/pipes/parsers/parseBoolean.pipe.spec.ts | 3 +++ lib/pipes/parsers/parseDate.pipe.spec.ts | 3 +++ lib/pipes/parsers/parseNumber.pipe.spec.ts | 3 +++ 3 files changed, 9 insertions(+) diff --git a/lib/pipes/parsers/parseBoolean.pipe.spec.ts b/lib/pipes/parsers/parseBoolean.pipe.spec.ts index 98789f71..af083091 100644 --- a/lib/pipes/parsers/parseBoolean.pipe.spec.ts +++ b/lib/pipes/parsers/parseBoolean.pipe.spec.ts @@ -9,6 +9,9 @@ describe('ParseBooleanPipe', () => { it('Should throw required error the given value is empty', () => expect(() => ParseBooleanPipe({ nullable: false })('')).toThrow()); + it('Should pass without a value when nullable', () => + expect(ParseBooleanPipe({ nullable: true })(undefined)).toStrictEqual(undefined)); + it('Should throw when the given string is not a boolean string', () => expect(() => ParseBooleanPipe()('test')).toThrow()); }); diff --git a/lib/pipes/parsers/parseDate.pipe.spec.ts b/lib/pipes/parsers/parseDate.pipe.spec.ts index 35f0b389..63db722d 100644 --- a/lib/pipes/parsers/parseDate.pipe.spec.ts +++ b/lib/pipes/parsers/parseDate.pipe.spec.ts @@ -33,4 +33,7 @@ describe('ParseDatePipe', () => { it('Should throw when the given value is `null`.', () => { expect(() => ParseDatePipe()(null)).toThrow(); }); + + it('Should pass without a value when nullable', () => + expect(ParseDatePipe({ nullable: true })(undefined)).toStrictEqual(undefined)); }); diff --git a/lib/pipes/parsers/parseNumber.pipe.spec.ts b/lib/pipes/parsers/parseNumber.pipe.spec.ts index 1e97ada0..076216d7 100644 --- a/lib/pipes/parsers/parseNumber.pipe.spec.ts +++ b/lib/pipes/parsers/parseNumber.pipe.spec.ts @@ -9,6 +9,9 @@ describe('ParseNumberPipe', () => { it('Should throw required error the given value is empty', () => expect(() => ParseNumberPipe({ nullable: false })('')).toThrow()); + it('Should pass without a value when nullable', () => + expect(ParseNumberPipe({ nullable: true })(undefined)).toStrictEqual(undefined)); + it('Should throw when the given string is not a numeric string', () => expect(() => ParseNumberPipe()('test')).toThrow()); });