Skip to content

Commit

Permalink
feat: add optional matcher function for ArrayUnique decorator (#830)
Browse files Browse the repository at this point in the history
  • Loading branch information
Diluka authored Jan 11, 2021
1 parent 6d42226 commit 90b3ae9
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ isBoolean(value);
| `@ArrayNotEmpty()` | Checks if given array is not empty. |
| `@ArrayMinSize(min: number)` | Checks if the array's length is greater than or equal to the specified number. |
| `@ArrayMaxSize(max: number)` | Checks if the array's length is less or equal to the specified number. |
| `@ArrayUnique()` | Checks if all array's values are unique. Comparison for objects is reference-based. |
| `@ArrayUnique(identifier?: (o) => any)` | Checks if all array's values are unique. Comparison for objects is reference-based. Optional function can be speciefied which return value will be used for the comparsion. |
| **Object validation decorators** |
| `@IsInstance(value: any)` | Checks if the property is an instance of the passed value. |
| **Other decorators** | |
Expand Down
24 changes: 16 additions & 8 deletions src/decorator/array/ArrayUnique.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import { ValidationOptions } from '../ValidationOptions';
import { buildMessage, ValidateBy } from '../common/ValidateBy';

export const ARRAY_UNIQUE = 'arrayUnique';
export type ArrayUniqueIdentifier<T = any> = (o: T) => any;

/**
* Checks if all array's values are unique. Comparison for objects is reference-based.
* If null or undefined is given then this function returns false.
*/
export function arrayUnique(array: unknown): boolean {
export function arrayUnique(array: unknown[], identifier?: ArrayUniqueIdentifier): boolean {
if (!(array instanceof Array)) return false;

if (identifier) {
array = array.map(o => (o != null ? identifier(o) : o));
}

const uniqueItems = array.filter((a, b, c) => c.indexOf(a) === b);
return array.length === uniqueItems.length;
}
Expand All @@ -18,18 +23,21 @@ export function arrayUnique(array: unknown): boolean {
* Checks if all array's values are unique. Comparison for objects is reference-based.
* If null or undefined is given then this function returns false.
*/
export function ArrayUnique(validationOptions?: ValidationOptions): PropertyDecorator {
export function ArrayUnique<T = any>(
identifierOrOptions?: ArrayUniqueIdentifier<T> | ValidationOptions,
validationOptions?: ValidationOptions
): PropertyDecorator {
const identifier = typeof identifierOrOptions === 'function' ? identifierOrOptions : undefined;
const options = typeof identifierOrOptions !== 'function' ? identifierOrOptions : validationOptions;

return ValidateBy(
{
name: ARRAY_UNIQUE,
validator: {
validate: (value, args): boolean => arrayUnique(value),
defaultMessage: buildMessage(
eachPrefix => eachPrefix + "All $property's elements must be unique",
validationOptions
),
validate: (value, args): boolean => arrayUnique(value, identifier),
defaultMessage: buildMessage(eachPrefix => eachPrefix + "All $property's elements must be unique", options),
},
},
validationOptions
options
);
}
45 changes: 45 additions & 0 deletions test/functional/validation-functions-and-decorators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4365,6 +4365,7 @@ describe('ArrayUnique', () => {
['world', 'hello', 'superman'],
['world', 'superman', 'hello'],
['superman', 'world', 'hello'],
['1', '2', null, undefined],
];
const invalidValues: any[] = [
null,
Expand Down Expand Up @@ -4402,6 +4403,50 @@ describe('ArrayUnique', () => {
});
});

describe('ArrayUnique with identifier', () => {
const identifier = o => o.name;
const validValues = [
['world', 'hello', 'superman'],
['world', 'superman', 'hello'],
['superman', 'world', 'hello'],
['1', '2', null, undefined],
].map(list => list.map(name => ({ name })));
const invalidValues: any[] = [
null,
undefined,
['world', 'hello', 'hello'],
['world', 'hello', 'world'],
['1', '1', '1'],
].map(list => list?.map(name => (name != null ? { name } : name)));

class MyClass {
@ArrayUnique(identifier)
someProperty: { name: 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(arrayUnique(value, identifier)).toBeTruthy());
});

it('should fail if method in validator said that its invalid', () => {
invalidValues.forEach(value => expect(arrayUnique(value, identifier)).toBeFalsy());
});

it('should return error object with proper data', () => {
const validationType = 'arrayUnique';
const message = "All someProperty's elements must be unique";
return checkReturnedError(new MyClass(), invalidValues, validationType, message);
});
});

describe('isInstance', () => {
class MySubClass {
// Empty
Expand Down

0 comments on commit 90b3ae9

Please sign in to comment.