diff --git a/packages/common/pipes/parse-uuid.pipe.ts b/packages/common/pipes/parse-uuid.pipe.ts index 2679e8f93ad..35f4914de96 100644 --- a/packages/common/pipes/parse-uuid.pipe.ts +++ b/packages/common/pipes/parse-uuid.pipe.ts @@ -9,7 +9,7 @@ import { ErrorHttpStatusCode, HttpErrorByCode, } from '../utils/http-error-by-code.util'; -import { isUUID } from '../utils/is-uuid'; +import { isString } from '../utils/shared.utils'; export interface ParseUUIDPipeOptions { version?: '3' | '4' | '5'; @@ -19,6 +19,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; @@ -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 (!isString(str)) { + throw this.exceptionFactory('The value passed as UUID is not a string'); + } + const pattern = ParseUUIDPipe.uuidRegExps[version]; + return 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); -}