Skip to content

Commit

Permalink
Ref adjust. Closes #1807
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Jun 2, 2019
1 parent 295b97b commit 56f79b9
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 2 deletions.
4 changes: 4 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,10 @@ References support the following arguments:
- `contextPrefix` - overrides the default `$` context prefix signifier.
- `ancestor` - if set to a number, sets the reference [relative starting point](#Relative-references). Cannot be combined
with separator prefix characters. Defaults to the reference key prefix (or `1` if none present).
- `adjust` - a function with the signature `function(value)` where `value` is the resolved reference value and the return
value is the adjusted value to use. For example `(value) => value + 5` will add 5 to the resolved value. Note that the
`adjust` feature will not perform any type validation on the adjusted value and it must match the value expected by the
rule it is used in.
- Other options can also be passed based on what [`Hoek.reach`](https://github.com/hapijs/hoek/blob/master/API.md#reachobj-chain-options) supports.

Note that references can only be used where explicitly supported such as in `valid()` or `invalid()` rules. If upwards
Expand Down
16 changes: 14 additions & 2 deletions lib/ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = exports = internals.Ref = class {
constructor(key, options = {}) {

Hoek.assert(typeof key === 'string', 'Invalid reference key:', key);
Hoek.assert(!options.adjust || typeof options.adjust === 'function', 'options.adjust must be a function');

this.settings = Hoek.clone(options);
this[internals.symbol] = true;
Expand Down Expand Up @@ -57,7 +58,12 @@ module.exports = exports = internals.Ref = class {
Hoek.assert(!ancestor || ancestor <= state.ancestors.length, 'Invalid reference exceeds the schema root:', this.display);

const target = this.isContext ? options.context : (ancestor ? state.ancestors[ancestor - 1] : value);
return Hoek.reach(target, this.key, this.settings);
let resolved = Hoek.reach(target, this.key, this.settings);
if (this.settings.adjust) {
resolved = this.settings.adjust(resolved);
}

return resolved;
}

toString() {
Expand All @@ -67,11 +73,17 @@ module.exports = exports = internals.Ref = class {

describe() {

return {
const about = {
type: this.isContext ? 'context' : 'ref',
key: this.key,
path: this.path
};

if (this.settings.adjust) {
about.adjust = this.settings.adjust;
}

return about;
}

static isRef(ref) {
Expand Down
57 changes: 57 additions & 0 deletions test/ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,63 @@ describe('ref', () => {
]);
});

it('sets adjust function', () => {

const adjust = (v) => 2 * v;
const ref = Joi.ref('b', { adjust });
const schema = Joi.object({
a: ref,
b: Joi.number()
});

Helper.validate(schema, [
[{ b: 5 }, true],
[{ a: 10, b: 5 }, true],
[{ a: 10, b: '5' }, true],
[{ a: 5 }, false, null, {
message: 'child "a" fails because ["a" must be one of [ref:b]]',
details: [{
message: '"a" must be one of [ref:b]',
path: ['a'],
type: 'any.allowOnly',
context: { value: 5, valids: [ref], label: 'a', key: 'a' }
}]
}],
[{ a: 5, b: 5 }, false, null, {
message: 'child "a" fails because ["a" must be one of [ref:b]]',
details: [{
message: '"a" must be one of [ref:b]',
path: ['a'],
type: 'any.allowOnly',
context: { value: 5, valids: [ref], label: 'a', key: 'a' }
}]
}]
]);

expect(schema.describe()).to.equal({
type: 'object',
children: {
b: {
type: 'number',
flags: { unsafe: false },
invalids: [Infinity, -Infinity]
},
a: {
type: 'any',
flags: { allowOnly: true },
valids: [
{
type: 'ref',
key: 'b',
path: ['b'],
adjust
}
]
}
}
});
});

it('uses ref as a valid value (empty key)', async () => {

const ref = Joi.ref('');
Expand Down

0 comments on commit 56f79b9

Please sign in to comment.