diff --git a/lib/types/object/index.js b/lib/types/object/index.js index 9d82e8044..9c5e3950e 100755 --- a/lib/types/object/index.js +++ b/lib/types/object/index.js @@ -308,7 +308,7 @@ internals.Object = class extends Any { 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); + const err = internals[dep.type].call(this, dep.key, hasKey && Hoek.reach(target, dep.key, { functions: true }), dep.peers, target, localState, options); if (err instanceof Errors.Err) { errors.push(err); if (options.abortEarly) { @@ -793,7 +793,7 @@ internals.with = 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); + const keysExist = Hoek.reach(parent, peer, { functions: true }); if (keysExist === undefined) { return this.createError('object.with', { @@ -815,7 +815,7 @@ internals.without = 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); + const keysExist = Hoek.reach(parent, peer, { functions: true }); if (keysExist !== undefined) { return this.createError('object.without', { @@ -834,7 +834,7 @@ internals.xor = function (key, value, peers, parent, state, options) { const present = []; for (let i = 0; i < peers.length; ++i) { const peer = peers[i]; - const keysExist = Hoek.reach(parent, peer); + const keysExist = Hoek.reach(parent, peer, { functions: true }); if (keysExist !== undefined) { present.push(peer); } @@ -862,7 +862,7 @@ internals.oxor = function (key, value, peers, parent, state, options) { const present = []; for (let i = 0; i < peers.length; ++i) { const peer = peers[i]; - const keysExist = Hoek.reach(parent, peer); + const keysExist = Hoek.reach(parent, peer, { functions: true }); if (keysExist !== undefined) { present.push(peer); } @@ -886,7 +886,7 @@ 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); + const keysExist = Hoek.reach(parent, peer, { functions: true }); if (keysExist !== undefined) { return; } @@ -906,7 +906,7 @@ internals.and = function (key, value, peers, parent, state, options) { const count = peers.length; for (let i = 0; i < count; ++i) { const peer = peers[i]; - const keysExist = Hoek.reach(parent, peer); + const keysExist = Hoek.reach(parent, peer, { functions: true }); if (keysExist === undefined) { missing.push(peer); @@ -935,7 +935,7 @@ internals.nand = function (key, value, peers, parent, state, options) { const present = []; for (let i = 0; i < peers.length; ++i) { const peer = peers[i]; - const keysExist = Hoek.reach(parent, peer); + const keysExist = Hoek.reach(parent, peer, { functions: true }); if (keysExist !== undefined) { present.push(peer); diff --git a/test/types/object.js b/test/types/object.js index 5bb9161ba..9582a6c07 100755 --- a/test/types/object.js +++ b/test/types/object.js @@ -2090,49 +2090,78 @@ describe('object', () => { }] ]); }); - }); - it('should apply labels with nested objects', () => { + it('should support nested keys in functions', () => { - const schema = Joi.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; - expect(error).to.be.an.error('"first" missing required peer "second"'); - expect(error.details).to.equal([{ - message: '"first" missing required peer "second"', - path: ['a'], - type: 'object.with', - context: { - main: 'a', - mainWithLabel: 'first', - peer: 'b.c', - peerWithLabel: 'second', - label: 'a', - key: 'a' - } - }]); + const schema = Joi.object({ + a: Joi.string(), + b: Joi.func().keys({ c: Joi.string(), d: Joi.number() }), + d: Joi.number() + }).with('a', 'b.c'); - 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' - } - }]); + Helper.validate(schema, [ + [{ a: 'test', b: Object.assign(() => {}, { c: 'test2' }) }, true], + [{ a: 'test', b: Object.assign(() => {}, { 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' + } + }] + }] + ]); + }); + + it('should apply labels with nested objects', () => { + + const schema = Joi.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; + expect(error).to.be.an.error('"first" missing required peer "second"'); + expect(error.details).to.equal([{ + message: '"first" missing required peer "second"', + path: ['a'], + type: 'object.with', + context: { + main: 'a', + mainWithLabel: 'first', + peer: 'b.c', + peerWithLabel: 'second', + label: 'a', + 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()', () => { @@ -2247,6 +2276,37 @@ describe('object', () => { }]); }); + it('should support nested keys in functions', () => { + + const schema = Joi.object({ + a: Joi.string(), + b: Joi.func().keys({ c: Joi.string(), d: Joi.number() }), + d: Joi.number() + }).without('a', ['b.c', 'b.d']); + + const sampleObject = { a: 'test', d: 9000 }; + const sampleObject2 = { a: 'test', b: Object.assign(() => {}, { d: 80 }) }; + + const error = schema.validate(sampleObject).error; + expect(error).to.equal(null); + + const error2 = schema.validate(sampleObject2).error; + expect(error2).to.be.an.error('"a" conflict with forbidden peer "b.d"'); + expect(error2.details).to.equal([{ + message: '"a" conflict with forbidden peer "b.d"', + path: ['a'], + type: 'object.without', + context: { + main: 'a', + mainWithLabel: 'a', + peer: 'b.d', + peerWithLabel: 'b.d', + key: 'a', + label: 'a' + } + }]); + }); + it('should apply labels with nested objects', () => { const schema = Joi.object({ @@ -2397,6 +2457,37 @@ describe('object', () => { }]); }); + it('should support nested keys in functions', () => { + + const schema = Joi.object({ + a: Joi.string(), + b: Joi.func().keys({ c: Joi.string(), d: Joi.number() }), + d: Joi.number() + }).xor('a', 'b.c'); + + const sampleObject = { a: 'test', b: Object.assign(() => {}, { d: 80 }) }; + const sampleObject2 = { a: 'test', b: Object.assign(() => {}, { c: 'test2' }) }; + + const error = schema.validate(sampleObject).error; + expect(error).to.equal(null); + + const error2 = schema.validate(sampleObject2).error; + expect(error2).to.be.an.error('"value" contains a conflict between exclusive peers [a, b.c]'); + expect(error2.details).to.equal([{ + message: '"value" contains a conflict between exclusive peers [a, b.c]', + path: [], + type: 'object.xor', + context: { + peers: ['a', 'b.c'], + peersWithLabels: ['a', 'b.c'], + present: ['a', 'b.c'], + presentWithLabels: ['a', 'b.c'], + key: undefined, + label: 'value' + } + }]); + }); + it('should apply labels without any nested peers', () => { const schema = Joi.object({ @@ -2532,6 +2623,37 @@ describe('object', () => { } }]); }); + + it('should support nested keys in functions', () => { + + const schema = Joi.object({ + a: Joi.string(), + b: Joi.func().keys({ c: Joi.string(), d: Joi.number() }), + d: Joi.number() + }).oxor('a', 'b.c'); + + const sampleObject = { a: 'test', b: Object.assign(() => {}, { d: 80 }) }; + const sampleObject2 = { a: 'test', b: Object.assign(() => {}, { c: 'test2' }) }; + + const error = schema.validate(sampleObject).error; + expect(error).to.equal(null); + + const error2 = schema.validate(sampleObject2).error; + expect(error2).to.be.an.error('"value" contains a conflict between optional exclusive peers [a, b.c]'); + expect(error2.details).to.equal([{ + message: '"value" contains a conflict between optional exclusive peers [a, b.c]', + path: [], + type: 'object.oxor', + context: { + peers: ['a', 'b.c'], + peersWithLabels: ['a', 'b.c'], + present: ['a', 'b.c'], + presentWithLabels: ['a', 'b.c'], + key: undefined, + label: 'value' + } + }]); + }); }); describe('or()', () => { @@ -2632,6 +2754,35 @@ describe('object', () => { }]); }); + it('should support nested keys in functions', () => { + + const schema = Joi.object({ + a: Joi.string(), + b: Joi.func().keys({ c: Joi.string() }), + d: Joi.number() + }).or('a', 'b.c'); + + const sampleObject = { b: Object.assign(() => {}, { c: 'bc' }) }; + const sampleObject2 = { d: 90 }; + + const error = schema.validate(sampleObject).error; + expect(error).to.equal(null); + + const error2 = schema.validate(sampleObject2).error; + expect(error2).to.be.an.error('"value" must contain at least one of [a, b.c]'); + expect(error2.details).to.equal([{ + message: '"value" must contain at least one of [a, b.c]', + path: [], + type: 'object.missing', + context: { + peers: ['a', 'b.c'], + peersWithLabels: ['a', 'b.c'], + key: undefined, + label: 'value' + } + }]); + }); + it('should apply labels with nested objects', () => { const schema = Joi.object({ @@ -2710,6 +2861,37 @@ describe('object', () => { }]); }); + it('should support nested keys in functions', () => { + + const schema = Joi.object({ + a: Joi.string(), + b: Joi.func().keys({ c: Joi.string(), d: Joi.number() }), + d: Joi.number() + }).and('a', 'b.c'); + + const sampleObject = { a: 'test', b: Object.assign(() => {}, { c: 'test2' }) }; + const sampleObject2 = { a: 'test', b: Object.assign(() => {}, { d: 80 }) }; + + const error = schema.validate(sampleObject).error; + expect(error).to.equal(null); + + const error2 = schema.validate(sampleObject2).error; + expect(error2).to.be.an.error('"value" contains [a] without its required peers [b.c]'); + expect(error2.details).to.equal([{ + message: '"value" contains [a] without its required peers [b.c]', + path: [], + type: 'object.and', + context: { + present: ['a'], + presentWithLabels: ['a'], + missing: ['b.c'], + missingWithLabels: ['b.c'], + key: undefined, + label: 'value' + } + }]); + }); + it('should apply labels with nested objects', () => { const schema = Joi.object({ @@ -2813,6 +2995,37 @@ describe('object', () => { }]); }); + it('should support nested keys in functions', () => { + + const schema = Joi.object({ + a: Joi.string(), + b: Joi.func().keys({ c: Joi.string(), d: Joi.number() }), + d: Joi.number() + }).nand('a', 'b.c'); + + const sampleObject = { a: 'test', b: Object.assign(() => {}, { d: 80 }) }; + const sampleObject2 = { a: 'test', b: Object.assign(() => {}, { c: 'test2' }) }; + + const error = schema.validate(sampleObject).error; + expect(error).to.equal(null); + + const error2 = schema.validate(sampleObject2).error; + expect(error2).to.be.an.error('"a" must not exist simultaneously with [b.c]'); + expect(error2.details).to.equal([{ + message: '"a" must not exist simultaneously with [b.c]', + path: [], + type: 'object.nand', + context: { + main: 'a', + mainWithLabel: 'a', + peers: ['b.c'], + peersWithLabels: ['b.c'], + key: undefined, + label: 'value' + } + }]); + }); + it('should apply labels with nested objects', () => { const schema = Joi.object({