diff --git a/lib/types/object/index.js b/lib/types/object/index.js index a1484fead..3315a31db 100644 --- a/lib/types/object/index.js +++ b/lib/types/object/index.js @@ -305,8 +305,10 @@ internals.Object = class extends Any { for (let i = 0; i < this._inner.dependencies.length; ++i) { const dep = this._inner.dependencies[i]; - const localState = new State(dep.key, dep.key === null ? state.path : [...state.path, dep.key]); - const err = internals[dep.type].call(this, dep.key !== null && target[dep.key], dep.peers, target, localState, options); + const hasKey = dep.key !== null; + const splitKey = hasKey && dep.key.split('.'); + const localState = hasKey ? new State(splitKey[splitKey.length - 1], [...state.path, ...splitKey]) : new State(null, state.path); + const err = internals[dep.type].call(this, dep.key, hasKey && Hoek.reach(target, dep.key), dep.peers, target, localState, options); if (err instanceof Errors.Err) { errors.push(err); if (options.abortEarly) { @@ -777,10 +779,10 @@ internals.keysToLabels = function (schema, keys) { }; -internals.with = function (value, peers, parent, state, options) { +internals.with = function (key, value, peers, parent, state, options) { if (value === undefined) { - return value; + return; } for (let i = 0; i < peers.length; ++i) { @@ -790,22 +792,20 @@ internals.with = function (value, peers, parent, state, options) { if (keysExist === undefined) { return this.createError('object.with', { - main: state.key, - mainWithLabel: internals.keysToLabels(this, state.key), + main: key, + mainWithLabel: internals.keysToLabels(this, key), peer, peerWithLabel: internals.keysToLabels(this, peer) }, state, options); } } - - return value; }; -internals.without = function (value, peers, parent, state, options) { +internals.without = function (key, value, peers, parent, state, options) { if (value === undefined) { - return value; + return; } for (let i = 0; i < peers.length; ++i) { @@ -814,19 +814,17 @@ internals.without = function (value, peers, parent, state, options) { if (keysExist !== undefined) { return this.createError('object.without', { - main: state.key, - mainWithLabel: internals.keysToLabels(this, state.key), + main: key, + mainWithLabel: internals.keysToLabels(this, key), peer, peerWithLabel: internals.keysToLabels(this, peer) }, state, options); } } - - return value; }; -internals.xor = function (value, peers, parent, state, options) { +internals.xor = function (key, value, peers, parent, state, options) { const present = []; for (let i = 0; i < peers.length; ++i) { @@ -839,7 +837,7 @@ internals.xor = function (value, peers, parent, state, options) { } if (present.length === 1) { - return value; + return; } const context = { peers, peersWithLabels: internals.keysToLabels(this, peers) }; @@ -855,14 +853,13 @@ internals.xor = function (value, peers, parent, state, options) { }; -internals.or = function (value, peers, parent, state, options) { +internals.or = function (key, value, peers, parent, state, options) { for (let i = 0; i < peers.length; ++i) { const peer = peers[i]; const keysExist = Hoek.reach(parent, peer); if (keysExist !== undefined) { - - return value; + return; } } @@ -873,7 +870,7 @@ internals.or = function (value, peers, parent, state, options) { }; -internals.and = function (value, peers, parent, state, options) { +internals.and = function (key, value, peers, parent, state, options) { const missing = []; const present = []; @@ -904,7 +901,7 @@ internals.and = function (value, peers, parent, state, options) { }; -internals.nand = function (value, peers, parent, state, options) { +internals.nand = function (key, value, peers, parent, state, options) { const present = []; for (let i = 0; i < peers.length; ++i) { diff --git a/test/types/object.js b/test/types/object.js index 19dadf44b..30bf218c9 100644 --- a/test/types/object.js +++ b/test/types/object.js @@ -175,7 +175,12 @@ describe('object', () => { message: '"value" must have at least 3 children', path: [], type: 'object.min', - context: { limit: 3, label: 'value', key: undefined, value: { item: 'something', item2: 'something else' } } + context: { + limit: 3, + label: 'value', + key: undefined, + value: { item: 'something', item2: 'something else' } + } }] }], [{ item: 'something', item2: 'something else', item3: 'something something else' }, true], @@ -203,7 +208,12 @@ describe('object', () => { message: '"value" must have less than or equal to 2 children', path: [], type: 'object.max', - context: { limit: 2, label: 'value', key: undefined, value: { item: 'something', item2: 'something else', item3: 'something something else' } } + context: { + limit: 2, + label: 'value', + key: undefined, + value: { item: 'something', item2: 'something else', item3: 'something something else' } + } }] }], ['', false, null, { @@ -233,13 +243,28 @@ describe('object', () => { }], [{ item: 'something', item2: 'something else' }, true], [{ item: 'something', item2: 'something else', item3: 'something something else' }, true], - [{ item: 'something', item2: 'something else', item3: 'something something else', item4: 'item4' }, false, null, { + [{ + item: 'something', + item2: 'something else', + item3: 'something something else', + item4: 'item4' + }, false, null, { message: '"value" must have less than or equal to 3 children', details: [{ message: '"value" must have less than or equal to 3 children', path: [], type: 'object.max', - context: { limit: 3, label: 'value', key: undefined, value: { item: 'something', item2: 'something else', item3: 'something something else', item4: 'item4' } } + context: { + limit: 3, + label: 'value', + key: undefined, + value: { + item: 'something', + item2: 'something else', + item3: 'something something else', + item4: 'item4' + } + } }] }], ['', false, null, { @@ -274,7 +299,12 @@ describe('object', () => { message: '"value" must have 2 children', path: [], type: 'object.length', - context: { limit: 2, label: 'value', key: undefined, value: { item: 'something', item2: 'something else', item3: 'something something else' } } + context: { + limit: 2, + label: 'value', + key: undefined, + value: { item: 'something', item2: 'something else', item3: 'something something else' } + } }] }], ['', false, null, { @@ -687,7 +717,7 @@ describe('object', () => { a: Joi.number().label('first'), b: Joi.object({ c: Joi.string().label('second'), d: Joi.number() }) }).with('a', ['b.c']); - const error = schema.validate({ a: 1 , b: { d: 2 } }).error; + const error = schema.validate({ a: 1, b: { d: 2 } }).error; expect(error).to.be.an.error('"first" missing required peer "second"'); expect(error.details).to.equal([{ message: '"first" missing required peer "second"', @@ -1794,7 +1824,12 @@ describe('object', () => { }).pattern(/\d+/, Joi.boolean()).pattern(/\w\w+/, 'x') }; - const err = await expect(Joi.validate({ x: { bb: 'y', 5: 'x' } }, schema, { abortEarly: false })).to.reject(); + const err = await expect(Joi.validate({ + x: { + bb: 'y', + 5: 'x' + } + }, schema, { abortEarly: false })).to.reject(); expect(err).to.be.an.error('child "x" fails because [child "5" fails because ["5" must be a boolean], child "bb" fails because ["bb" must be one of [x]]]'); expect(err.details).to.equal([ { @@ -1820,7 +1855,12 @@ describe('object', () => { }).pattern(Joi.number().positive(), Joi.boolean()).pattern(Joi.string().length(2), 'x') }; - const err = await expect(Joi.validate({ x: { bb: 'y', 5: 'x' } }, schema, { abortEarly: false })).to.reject(); + const err = await expect(Joi.validate({ + x: { + bb: 'y', + 5: 'x' + } + }, schema, { abortEarly: false })).to.reject(); expect(err).to.be.an.error('child "x" fails because [child "5" fails because ["5" must be a boolean], child "bb" fails because ["bb" must be one of [x]]]'); expect(err.details).to.equal([ { @@ -1969,27 +2009,50 @@ describe('object', () => { d: Joi.number() }).with('a', 'b.c'); - const sampleObject = { a: 'test', b: { c: 'test2' } }; - const sampleObject2 = { a: 'test', b: { d: 80 } }; + Helper.validate(schema, [ + [{ a: 'test', b: { c: 'test2' } }, true], + [{ a: 'test', b: { d: 80 } }, false, null, { + message: '"a" missing required peer "b.c"', + details: [{ + message: '"a" missing required peer "b.c"', + path: ['a'], + type: 'object.with', + context: { + main: 'a', + mainWithLabel: 'a', + peer: 'b.c', + peerWithLabel: 'b.c', + key: 'a', + label: 'a' + } + }] + }] + ]); - const error = schema.validate(sampleObject).error; - expect(error).to.equal(null); + const schema2 = Joi.object({ + a: Joi.object({ b: Joi.string() }), + b: Joi.object({ c: Joi.string() }) + }).with('a.b', 'b.c'); - const error2 = schema.validate(sampleObject2).error; - expect(error2).to.be.an.error('"a" missing required peer "b.c"'); - expect(error2.details).to.equal([{ - message: '"a" missing required peer "b.c"', - path: ['a'], - type: 'object.with', - context: { - main: 'a', - mainWithLabel: 'a', - peer: 'b.c', - peerWithLabel: 'b.c', - key: 'a', - label: 'a' - } - }]); + Helper.validate(schema2, [ + [{ a: { b: 'test' }, b: { c: 'test2' } }, true], + [{ a: { b: 'test' }, b: {} }, false, null, { + message: '"a.b" missing required peer "b.c"', + details: [{ + message: '"a.b" missing required peer "b.c"', + path: ['a', 'b'], + type: 'object.with', + context: { + main: 'a.b', + mainWithLabel: 'a.b', + peer: 'b.c', + peerWithLabel: 'b.c', + key: 'b', + label: 'b' + } + }] + }] + ]); }); }); @@ -1999,7 +2062,7 @@ describe('object', () => { a: Joi.number().label('first'), b: Joi.object({ c: Joi.string().label('second'), d: Joi.number() }) }).with('a', ['b.c']); - const error = schema.validate({ a: 1 , b: { d: 2 } }).error; + const error = schema.validate({ a: 1, b: { d: 2 } }).error; expect(error).to.be.an.error('"first" missing required peer "second"'); expect(error.details).to.equal([{ message: '"first" missing required peer "second"', @@ -2014,6 +2077,26 @@ describe('object', () => { key: 'a' } }]); + + const schema2 = Joi.object({ + a: Joi.object({ b: Joi.string().label('first') }), + b: Joi.object({ c: Joi.string().label('second') }) + }).with('a.b', ['b.c']); + const error2 = schema2.validate({ a: { b: 'test' }, b: {} }).error; + expect(error2).to.be.an.error('"first" missing required peer "second"'); + expect(error2.details).to.equal([{ + message: '"first" missing required peer "second"', + path: ['a', 'b'], + type: 'object.with', + context: { + main: 'a.b', + mainWithLabel: 'first', + peer: 'b.c', + peerWithLabel: 'second', + label: 'b', + key: 'b' + } + }]); }); describe('without()', () => { @@ -2296,7 +2379,7 @@ describe('object', () => { label: 'value', key: undefined } - }] ); + }]); }); it('should apply labels with too many nested peers', () => { @@ -2767,7 +2850,8 @@ describe('object', () => { it('uses constructor name for default type name', async () => { - const Foo = function Foo() { }; + const Foo = function Foo() { + }; const schema = Joi.object().type(Foo); const err = await expect(schema.validate({})).to.reject('"value" must be an instance of "Foo"'); @@ -2781,7 +2865,8 @@ describe('object', () => { it('uses custom type name if supplied', async () => { - const Foo = function () { }; + const Foo = function () { + }; const schema = Joi.object().type(Foo, 'Bar'); const err = await expect(schema.validate({})).to.reject('"value" must be an instance of "Bar"'); @@ -2795,7 +2880,8 @@ describe('object', () => { it('overrides constructor name with custom name', async () => { - const Foo = function Foo() { }; + const Foo = function Foo() { + }; const schema = Joi.object().type(Foo, 'Bar'); const err = await expect(schema.validate({})).to.reject('"value" must be an instance of "Bar"'); @@ -2824,7 +2910,9 @@ describe('object', () => { it('uses the constructor reference in the schema description', () => { - const Foo = function Foo() { }; + const Foo = function Foo() { + }; + const description = Joi.object().type(Foo).describe(); expect(new Foo()).to.be.an.instanceof(description.rules[0].arg.ctor); @@ -2984,7 +3072,10 @@ describe('object', () => { it('should set keys as optional', () => { - const schema = Joi.object({ a: Joi.number().required(), b: Joi.number().required() }).optionalKeys('a', 'b'); + const schema = Joi.object({ + a: Joi.number().required(), + b: Joi.number().required() + }).optionalKeys('a', 'b'); Helper.validate(schema, [ [{}, true], [{ a: 0 }, true], @@ -2997,7 +3088,10 @@ describe('object', () => { it('should set keys as forbidden', () => { - const schema = Joi.object({ a: Joi.number().required(), b: Joi.number().required() }).forbiddenKeys('a', 'b'); + const schema = Joi.object({ + a: Joi.number().required(), + b: Joi.number().required() + }).forbiddenKeys('a', 'b'); Helper.validate(schema, [ [{}, true], [{ a: undefined }, true],