From ab98d65c59f52109c5ea06a5104e4d8bc2bf9110 Mon Sep 17 00:00:00 2001 From: PleBea Date: Mon, 1 Dec 2025 02:48:54 +0900 Subject: [PATCH 1/2] fix: update UUID validation to support versions 1-8, nil, max, and all --- src/decorator/string/IsUUID.ts | 4 +- ...alidation-functions-and-decorators.spec.ts | 350 +++++++++++++++++- 2 files changed, 349 insertions(+), 5 deletions(-) diff --git a/src/decorator/string/IsUUID.ts b/src/decorator/string/IsUUID.ts index a1ac241cf6..dd29323754 100644 --- a/src/decorator/string/IsUUID.ts +++ b/src/decorator/string/IsUUID.ts @@ -6,7 +6,7 @@ import * as ValidatorJS from 'validator'; export const IS_UUID = 'isUuid'; /** - * Checks if the string is a UUID (version 3, 4 or 5). + * Checks if the string is a UUID (version 1-8, nil, max, all). * If given value is not a string, then it returns false. */ export function isUUID(value: unknown, version?: ValidatorJS.UUIDVersion): boolean { @@ -14,7 +14,7 @@ export function isUUID(value: unknown, version?: ValidatorJS.UUIDVersion): boole } /** - * Checks if the string is a UUID (version 3, 4 or 5). + * Checks if the string is a UUID (version 1-8, nil, max, all). * If given value is not a string, then it returns false. */ export function IsUUID(version?: ValidatorJS.UUIDVersion, validationOptions?: ValidationOptions): PropertyDecorator { diff --git a/test/functional/validation-functions-and-decorators.spec.ts b/test/functional/validation-functions-and-decorators.spec.ts index 6e7491d20e..79b1c8c8d2 100644 --- a/test/functional/validation-functions-and-decorators.spec.ts +++ b/test/functional/validation-functions-and-decorators.spec.ts @@ -3535,9 +3535,16 @@ describe('IsUrl', () => { describe('IsUUID', () => { const validValues = [ - 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', - 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', - 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', // v4 + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', // v5 + 'e32c8312-5012-11e8-a3b3-069326100140', // v1 + '00000000-0000-2000-8000-000000000000', // v2 + '1ec9414c-232a-6b00-b3c8-9e6bdec7bc00', // v6 + '017f22e2-79b0-7cc3-98c4-dc0c0c07398f', // v7 + 'a0eebc99-9c0b-8ef8-bb6d-6bb9bd380a11', // v8 + '00000000-0000-0000-0000-000000000000', // nil + 'ffffffff-ffff-ffff-ffff-ffffffffffff', // max ]; const invalidValues = [ null, @@ -3719,6 +3726,343 @@ describe('IsUUID v5', () => { }); }); +describe('IsUUID v1', () => { + const validValues = ['e32c8312-5012-11e8-a3b3-069326100140', 'd9428888-122b-11e1-b85c-61cd3cbb3210']; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', // v4 + ]; + + class MyClass { + @IsUUID('1') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, '1')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, '1')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + +describe('IsUUID v2', () => { + const validValues = ['00000000-0000-2000-8000-000000000000']; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', // v4 + ]; + + class MyClass { + @IsUUID('2') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, '2')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, '2')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + +describe('IsUUID v6', () => { + const validValues = ['1ec9414c-232a-6b00-b3c8-9e6bdec7bc00']; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', // v4 + ]; + + class MyClass { + @IsUUID('6') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, '6')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, '6')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + +describe('IsUUID v7', () => { + const validValues = ['017f22e2-79b0-7cc3-98c4-dc0c0c07398f']; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', // v4 + ]; + + class MyClass { + @IsUUID('7') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, '7')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, '7')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + +describe('IsUUID v8', () => { + const validValues = ['a0eebc99-9c0b-8ef8-bb6d-6bb9bd380a11']; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', // v4 + ]; + + class MyClass { + @IsUUID('8') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, '8')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, '8')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + +describe('IsUUID nil', () => { + const validValues = ['00000000-0000-0000-0000-000000000000']; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', // v4 + ]; + + class MyClass { + @IsUUID('nil') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, 'nil')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, 'nil')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + +describe('IsUUID max', () => { + const validValues = ['ffffffff-ffff-ffff-ffff-ffffffffffff']; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', // v4 + ]; + + class MyClass { + @IsUUID('max') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, 'max')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, 'max')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + +describe('IsUUID all', () => { + const validValues = [ + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', // v3 + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', // v4 + '987FBC97-4BED-5078-AF07-9141BA07C9F3', // v5 + 'e32c8312-5012-11e8-a3b3-069326100140', // v1 + '00000000-0000-2000-8000-000000000000', // v2 + '1ec9414c-232a-6b00-b3c8-9e6bdec7bc00', // v6 + '017f22e2-79b0-7cc3-98c4-dc0c0c07398f', // v7 + 'a0eebc99-9c0b-8ef8-bb6d-6bb9bd380a11', // v8 + '00000000-0000-0000-0000-000000000000', // nil + 'ffffffff-ffff-ffff-ffff-ffffffffffff', // max + ]; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + ]; + + class MyClass { + @IsUUID('all') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, 'all')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, 'all')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + describe('IsFirebasePushId', () => { const validValues = [ '-M-Jh_1KAH5rYJF_7-kY', From e176387f335de3b40c6f778d6ca54f24280eb6fd Mon Sep 17 00:00:00 2001 From: PleBea Date: Fri, 5 Dec 2025 18:57:09 +0900 Subject: [PATCH 2/2] feat: add loose UUID validation and update documentation --- README.md | 2 +- src/decorator/string/IsUUID.ts | 4 +- ...alidation-functions-and-decorators.spec.ts | 44 +++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 886712dd76..f21ee75f03 100644 --- a/README.md +++ b/README.md @@ -885,7 +885,7 @@ isBoolean(value); | `@IsTaxId()` | Checks if the string is a valid tax ID. Default locale is `en-US`. | | `@IsUrl(options?: IsURLOptions)` | Checks if the string is a URL. | | `@IsMagnetURI()` | Checks if the string is a [magnet uri format](https://en.wikipedia.org/wiki/Magnet_URI_scheme). | -| `@IsUUID(version?: UUIDVersion)` | Checks if the string is a UUID (version 3, 4, 5 or all ). | +| `@IsUUID(version?: UUIDVersion)` | Checks if the string is a UUID (version 1-8, nil, max, loose, all). | | `@IsFirebasePushId()` | Checks if the string is a [Firebase Push ID](https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html) | | `@IsUppercase()` | Checks if the string is uppercase. | | `@Length(min: number, max?: number)` | Checks if the string's length falls in a range. | diff --git a/src/decorator/string/IsUUID.ts b/src/decorator/string/IsUUID.ts index dd29323754..c21388fcd6 100644 --- a/src/decorator/string/IsUUID.ts +++ b/src/decorator/string/IsUUID.ts @@ -6,7 +6,7 @@ import * as ValidatorJS from 'validator'; export const IS_UUID = 'isUuid'; /** - * Checks if the string is a UUID (version 1-8, nil, max, all). + * Checks if the string is a UUID (version 1-8, nil, max, loose, all). * If given value is not a string, then it returns false. */ export function isUUID(value: unknown, version?: ValidatorJS.UUIDVersion): boolean { @@ -14,7 +14,7 @@ export function isUUID(value: unknown, version?: ValidatorJS.UUIDVersion): boole } /** - * Checks if the string is a UUID (version 1-8, nil, max, all). + * Checks if the string is a UUID (version 1-8, nil, max, loose, all). * If given value is not a string, then it returns false. */ export function IsUUID(version?: ValidatorJS.UUIDVersion, validationOptions?: ValidationOptions): PropertyDecorator { diff --git a/test/functional/validation-functions-and-decorators.spec.ts b/test/functional/validation-functions-and-decorators.spec.ts index 79b1c8c8d2..0388dc126e 100644 --- a/test/functional/validation-functions-and-decorators.spec.ts +++ b/test/functional/validation-functions-and-decorators.spec.ts @@ -4063,6 +4063,50 @@ describe('IsUUID all', () => { }); }); +describe('IsUUID loose', () => { + const validValues = [ + 'A987FBC9-4BED-3078-8F07-9141BA07C9F3', + '{A987FBC9-4BED-3078-8F07-9141BA07C9F3}', + '{a987fbc9-4bed-3078-8f07-9141ba07c9f3}', + 'A987FBC94BED30788F079141BA07C9F3', + ]; + const invalidValues = [ + null, + undefined, + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + ]; + + class MyClass { + @IsUUID('loose') + someProperty: string; + } + + it('should not fail if validator.validate said that its valid', () => { + return checkValidValues(new MyClass(), validValues); + }); + + it('should fail if validator.validate said that its invalid', () => { + return checkInvalidValues(new MyClass(), invalidValues); + }); + + it('should not fail if method in validator said that its valid', () => { + validValues.forEach(value => expect(isUUID(value, 'loose')).toBeTruthy()); + }); + + it('should fail if method in validator said that its invalid', () => { + invalidValues.forEach(value => expect(isUUID(value, 'loose')).toBeFalsy()); + }); + + it('should return error object with proper data', () => { + const validationType = 'isUuid'; + const message = 'someProperty must be a UUID'; + return checkReturnedError(new MyClass(), invalidValues, validationType, message); + }); +}); + describe('IsFirebasePushId', () => { const validValues = [ '-M-Jh_1KAH5rYJF_7-kY',