From d32e8dd582d7ab4dfce1f15fa619d171bc88a8d6 Mon Sep 17 00:00:00 2001 From: titivuk Date: Thu, 28 Oct 2021 01:18:40 +0200 Subject: [PATCH 1/4] fix(common): ParseUUIDPipe - throw exceptions with exceptionFactory only --- packages/common/pipes/parse-uuid.pipe.ts | 19 ++++- .../common/test/pipes/parse-int.pipe.spec.ts | 1 - .../common/test/pipes/parse-uuid.pipe.spec.ts | 72 ++++++++++++++----- packages/common/utils/is-uuid.ts | 17 ----- 4 files changed, 71 insertions(+), 38 deletions(-) delete mode 100644 packages/common/utils/is-uuid.ts diff --git a/packages/common/pipes/parse-uuid.pipe.ts b/packages/common/pipes/parse-uuid.pipe.ts index 2679e8f93ad..ae35eb70de0 100644 --- a/packages/common/pipes/parse-uuid.pipe.ts +++ b/packages/common/pipes/parse-uuid.pipe.ts @@ -9,7 +9,13 @@ import { ErrorHttpStatusCode, HttpErrorByCode, } from '../utils/http-error-by-code.util'; -import { isUUID } from '../utils/is-uuid'; + +const uuid = { + 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, + 4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + 5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i, +}; export interface ParseUUIDPipeOptions { version?: '3' | '4' | '5'; @@ -35,8 +41,9 @@ export class ParseUUIDPipe implements PipeTransform { exceptionFactory || (error => new HttpErrorByCode[errorHttpStatusCode](error)); } + async transform(value: string, metadata: ArgumentMetadata): Promise { - if (!isUUID(value, this.version)) { + if (!this.isUUID(value, this.version)) { throw this.exceptionFactory( `Validation failed (uuid ${ this.version ? 'v' + this.version : '' @@ -45,4 +52,12 @@ export class ParseUUIDPipe implements PipeTransform { } return value; } + + protected isUUID(str: unknown, version = 'all') { + if (typeof str !== 'string') { + throw this.exceptionFactory('The value passed as UUID is not a string'); + } + const pattern = uuid[version]; + return pattern && pattern.test(str); + } } diff --git a/packages/common/test/pipes/parse-int.pipe.spec.ts b/packages/common/test/pipes/parse-int.pipe.spec.ts index 3bb7ab1857d..d9d78de27eb 100644 --- a/packages/common/test/pipes/parse-int.pipe.spec.ts +++ b/packages/common/test/pipes/parse-int.pipe.spec.ts @@ -1,4 +1,3 @@ -import * as sinon from 'sinon'; import { expect } from 'chai'; import { ArgumentMetadata } from '../../interfaces'; import { ParseIntPipe } from '../../pipes/parse-int.pipe'; diff --git a/packages/common/test/pipes/parse-uuid.pipe.spec.ts b/packages/common/test/pipes/parse-uuid.pipe.spec.ts index 3f6cb79fb1b..bcb3a76b39a 100644 --- a/packages/common/test/pipes/parse-uuid.pipe.spec.ts +++ b/packages/common/test/pipes/parse-uuid.pipe.spec.ts @@ -1,9 +1,18 @@ import { expect } from 'chai'; +import { HttpStatus } from '../../enums'; +import { HttpException } from '../../exceptions'; import { ArgumentMetadata } from '../../interfaces'; import { ParseUUIDPipe } from '../../pipes/parse-uuid.pipe'; +class TestException extends HttpException { + constructor() { + super('This is a TestException', HttpStatus.I_AM_A_TEAPOT); + } +} + describe('ParseUUIDPipe', () => { let target: ParseUUIDPipe; + const exceptionFactory = (error: any) => new TestException(); describe('transform', () => { const v3 = 'e8b5a51d-11c8-3310-a6ab-367563f20686'; @@ -12,53 +21,80 @@ describe('ParseUUIDPipe', () => { describe('when validation passes', () => { it('should return string if value is uuid v3, v4 or v5', async () => { - target = new ParseUUIDPipe(); + target = new ParseUUIDPipe({ exceptionFactory }); expect(await target.transform(v3, {} as ArgumentMetadata)).to.equal(v3); expect(await target.transform(v4, {} as ArgumentMetadata)).to.equal(v4); expect(await target.transform(v5, {} as ArgumentMetadata)).to.equal(v5); }); it('should return string if value is uuid v3', async () => { - target = new ParseUUIDPipe({ version: '3' }); + target = new ParseUUIDPipe({ version: '3', exceptionFactory }); expect(await target.transform(v3, {} as ArgumentMetadata)).to.equal(v3); }); it('should return string if value is uuid v4', async () => { - target = new ParseUUIDPipe({ version: '4' }); + target = new ParseUUIDPipe({ version: '4', exceptionFactory }); expect(await target.transform(v4, {} as ArgumentMetadata)).to.equal(v4); }); it('should return string if value is uuid v5', async () => { - target = new ParseUUIDPipe({ version: '5' }); + target = new ParseUUIDPipe({ version: '5', exceptionFactory }); expect(await target.transform(v5, {} as ArgumentMetadata)).to.equal(v5); }); }); describe('when validation fails', () => { it('should throw an error', async () => { - target = new ParseUUIDPipe(); - expect(target.transform('123a', {} as ArgumentMetadata)).to.be.rejected; + target = new ParseUUIDPipe({ exceptionFactory }); + await expect( + target.transform('123a', {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); + }); + + it('should throw an error - not a string', async () => { + target = new ParseUUIDPipe({ exceptionFactory }); + await expect( + target.transform(undefined, {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); }); it('should throw an error - v3', async () => { - target = new ParseUUIDPipe({ version: '3' }); - expect(target.transform('123a', {} as ArgumentMetadata)).to.be.rejected; - expect(target.transform(v4, {} as ArgumentMetadata)).to.be.rejected; - expect(target.transform(v5, {} as ArgumentMetadata)).to.be.rejected; + target = new ParseUUIDPipe({ version: '3', exceptionFactory }); + await expect( + target.transform('123a', {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); + await expect( + target.transform(v4, {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); + await expect( + target.transform(v5, {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); }); it('should throw an error - v4', async () => { - target = new ParseUUIDPipe({ version: '4' }); - expect(target.transform('123a', {} as ArgumentMetadata)).to.be.rejected; - expect(target.transform(v3, {} as ArgumentMetadata)).to.be.rejected; - expect(target.transform(v5, {} as ArgumentMetadata)).to.be.rejected; + target = new ParseUUIDPipe({ version: '4', exceptionFactory }); + await expect( + target.transform('123a', {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); + await expect( + target.transform(v3, {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); + await expect( + target.transform(v5, {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); }); it('should throw an error - v5 ', async () => { - target = new ParseUUIDPipe({ version: '5' }); - expect(target.transform('123a', {} as ArgumentMetadata)).to.be.rejected; - expect(target.transform(v3, {} as ArgumentMetadata)).to.be.rejected; - expect(target.transform(v4, {} as ArgumentMetadata)).to.be.rejected; + target = new ParseUUIDPipe({ version: '5', exceptionFactory }); + await expect( + target.transform('123a', {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); + await expect( + target.transform(v3, {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); + await expect( + target.transform(v4, {} as ArgumentMetadata), + ).to.be.rejectedWith(TestException); }); }); }); diff --git a/packages/common/utils/is-uuid.ts b/packages/common/utils/is-uuid.ts deleted file mode 100644 index 0e9572f4d61..00000000000 --- a/packages/common/utils/is-uuid.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { BadRequestException } from '../exceptions'; -import { isString } from './shared.utils'; - -const uuid = { - 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, - 4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - 5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i, -}; - -export function isUUID(str: any, version = 'all') { - if (!isString(str)) { - throw new BadRequestException('The value passed as UUID is not a string'); - } - const pattern = uuid[version]; - return pattern && pattern.test(str); -} From 7cc81067feb6d58cb0d51ebce40e7bc61dc14280 Mon Sep 17 00:00:00 2001 From: titivuk Date: Mon, 1 Nov 2021 18:23:53 +0100 Subject: [PATCH 2/4] fix(common): ParseUUIDPipe - make uuid regexp dictionary protected static property --- packages/common/pipes/parse-uuid.pipe.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/common/pipes/parse-uuid.pipe.ts b/packages/common/pipes/parse-uuid.pipe.ts index ae35eb70de0..850d26f435b 100644 --- a/packages/common/pipes/parse-uuid.pipe.ts +++ b/packages/common/pipes/parse-uuid.pipe.ts @@ -10,13 +10,6 @@ import { HttpErrorByCode, } from '../utils/http-error-by-code.util'; -const uuid = { - 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, - 4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - 5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i, -}; - export interface ParseUUIDPipeOptions { version?: '3' | '4' | '5'; errorHttpStatusCode?: ErrorHttpStatusCode; @@ -25,6 +18,12 @@ export interface ParseUUIDPipeOptions { @Injectable() export class ParseUUIDPipe implements PipeTransform { + protected static uuidRegExps = { + 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, + 4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + 5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i, + }; private readonly version: '3' | '4' | '5'; protected exceptionFactory: (errors: string) => any; @@ -57,7 +56,7 @@ export class ParseUUIDPipe implements PipeTransform { if (typeof str !== 'string') { throw this.exceptionFactory('The value passed as UUID is not a string'); } - const pattern = uuid[version]; + const pattern = ParseUUIDPipe.uuidRegExps[version]; return pattern && pattern.test(str); } } From 9c0cdd295056af94830ebb59d090bb0900099f4b Mon Sep 17 00:00:00 2001 From: titivuk Date: Wed, 29 Dec 2021 17:35:27 +0100 Subject: [PATCH 3/4] fix(common): ParseUUIDPipe - use isString() util function --- packages/common/pipes/parse-uuid.pipe.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/common/pipes/parse-uuid.pipe.ts b/packages/common/pipes/parse-uuid.pipe.ts index 850d26f435b..53cae3e26c4 100644 --- a/packages/common/pipes/parse-uuid.pipe.ts +++ b/packages/common/pipes/parse-uuid.pipe.ts @@ -9,6 +9,7 @@ import { ErrorHttpStatusCode, HttpErrorByCode, } from '../utils/http-error-by-code.util'; +import { isString } from '../utils/shared.utils'; export interface ParseUUIDPipeOptions { version?: '3' | '4' | '5'; @@ -53,7 +54,7 @@ export class ParseUUIDPipe implements PipeTransform { } protected isUUID(str: unknown, version = 'all') { - if (typeof str !== 'string') { + if (!isString(str)) { throw this.exceptionFactory('The value passed as UUID is not a string'); } const pattern = ParseUUIDPipe.uuidRegExps[version]; From ef704186274d5b22abb26c8444ca164e59ca9b70 Mon Sep 17 00:00:00 2001 From: titivuk Date: Mon, 7 Mar 2022 18:01:42 +0100 Subject: [PATCH 4/4] fix(common): ParseUUIDPipe - use optional chaining --- packages/common/pipes/parse-uuid.pipe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/pipes/parse-uuid.pipe.ts b/packages/common/pipes/parse-uuid.pipe.ts index 53cae3e26c4..35f4914de96 100644 --- a/packages/common/pipes/parse-uuid.pipe.ts +++ b/packages/common/pipes/parse-uuid.pipe.ts @@ -58,6 +58,6 @@ export class ParseUUIDPipe implements PipeTransform { throw this.exceptionFactory('The value passed as UUID is not a string'); } const pattern = ParseUUIDPipe.uuidRegExps[version]; - return pattern && pattern.test(str); + return pattern?.test(str); } }