Skip to content

Commit

Permalink
Add optional "config" param to array.unique for ignoreUndefined. Closes
Browse files Browse the repository at this point in the history
  • Loading branch information
WesTyler committed May 24, 2018
1 parent e520c6d commit 8071b8a
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 5 deletions.
18 changes: 16 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
- [`array.min(limit)`](#arrayminlimit)
- [`array.max(limit)`](#arraymaxlimit)
- [`array.length(limit)`](#arraylengthlimit)
- [`array.unique([comparator])`](#arrayuniquecomparator)
- [`array.unique([comparator], [options])`](#arrayuniquecomparator-options)
- [`boolean` - inherits from `Any`](#boolean---inherits-from-any)
- [`boolean.truthy(value)`](#booleantruthyvalue)
- [`boolean.falsy(value)`](#booleanfalsyvalue)
Expand Down Expand Up @@ -1022,13 +1022,15 @@ const schema = Joi.object({
});
```

#### `array.unique([comparator])`
#### `array.unique([comparator], [options])`

Requires the array values to be unique.

You can provide a custom `comparator` that is either :
- a function that takes 2 parameters to compare. This function should return whether the 2 parameters are equal or not, you are also **responsible** for this function not to fail, any `Error` would bubble out of Joi.
- a string in dot notation representing the path of the element to do uniqueness check on. Any missing path will be considered undefined, and can as well only exist once.
You can also provide an `options` object containing:
- `ignoreUndefined`. When set to `true`, undefined values for the dot notation string comparator will not cause the array to fail on uniqueness.

Note: remember that if you provide a custom comparator function, different types can be passed as parameter depending on the rules you set on items.

Expand All @@ -1046,6 +1048,18 @@ const schema = Joi.array().unique((a, b) => a.property === b.property);
const schema = Joi.array().unique('customer.id');
```

```js
let schema = Joi.array().unique('identifier');

schema.validate([{}, {}]);
// ValidationError: "value" position 1 contains a duplicate value

schema = Joi.array().unique('identifier', { ignoreUndefined: true });

schema.validate([{}, {}]);
// error: null
```

### `boolean` - inherits from `Any`

Generates a schema object that matches a boolean data type. Can also be called via `bool()`. If the validation `convert`
Expand Down
13 changes: 10 additions & 3 deletions lib/types/array/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,13 +477,19 @@ internals.Array = class extends Any {
});
}

unique(comparator) {
unique(comparator, configs) {

Hoek.assert(comparator === undefined ||
typeof comparator === 'function' ||
typeof comparator === 'string', 'comparator must be a function or a string');

const settings = {};
Hoek.assert(configs === undefined ||
typeof configs === 'object', 'configs must be an object');

const settings = {
ignoreUndefined: (configs && configs.ignoreUndefined) || false
};


if (typeof comparator === 'string') {
settings.path = comparator;
Expand All @@ -505,6 +511,7 @@ internals.Array = class extends Any {
};

const compare = settings.comparator || Hoek.deepEqual;
const ignoreUndefined = settings.ignoreUndefined;

for (let i = 0; i < value.length; ++i) {
const item = settings.path ? Hoek.reach(value[i], settings.path) : value[i];
Expand Down Expand Up @@ -543,7 +550,7 @@ internals.Array = class extends Any {
records.set(item, i);
}
else {
if (records[item] !== undefined) {
if ((!ignoreUndefined || item !== undefined) && records[item] !== undefined) {
const localState = {
key: state.key,
path: state.path.concat(i),
Expand Down
56 changes: 56 additions & 0 deletions test/types/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -1479,6 +1479,62 @@ describe('array', () => {
]);
});

it('ignores undefined value when ignoreUndefined is true', () => {

const schema = Joi.array().unique('a', { ignoreUndefined: true });

Helper.validate(schema, [
[[{ a: 'b' }, { a: 'c' }], true],
[[{ c: 'd' }, { c: 'd' }], true],
[[{ a: 'b', c: 'd' }, { a: 'b', c: 'd' }], false, null, {
message: '"value" position 1 contains a duplicate value',
details: [{
message: '"value" position 1 contains a duplicate value',
path: [1],
type: 'array.unique',
context: {
pos: 1,
value: { a: 'b', c: 'd' },
dupePos: 0,
dupeValue: { a: 'b', c: 'd' },
label: 'value',
key: 1,
path: 'a'
}
}]
}],
[[{ a: 'b', c: 'c' }, { a: 'b', c: 'd' }], false, null, {
message: '"value" position 1 contains a duplicate value',
details: [{
message: '"value" position 1 contains a duplicate value',
path: [1],
type: 'array.unique',
context: {
pos: 1,
value: { a: 'b', c: 'd' },
dupePos: 0,
dupeValue: { a: 'b', c: 'c' },
label: 'value',
key: 1,
path: 'a'
}
}]
}]
]);
});

it('fails with invalid configs', () => {

expect(() => {

Joi.array().unique('id', 'invalid configs');
}).to.throw(Error, 'configs must be an object');
expect(() => {

Joi.array().unique('id', {});
}).to.not.throw();
});

it('fails with invalid comparator', () => {

expect(() => {
Expand Down

0 comments on commit 8071b8a

Please sign in to comment.