diff --git a/README.md b/README.md index da2fc46d6..c9255abaf 100644 --- a/README.md +++ b/README.md @@ -139,10 +139,13 @@ yup.addMethod yup.ValidationError ``` -#### `.reach(Schema schema, String path, Object options)` +#### `.reach(Schema schema, String path, [Object value, Object context])` For nested schema's `yup.reach` will retrieve a nested schema based on the provided path. +For nested schema that need to resolve dynamically, you can provide a `value` and optionally +a `context` object. + ```js var schema = object().shape({ nested: object() diff --git a/src/array.js b/src/array.js index 32acccabc..0749b2bc0 100644 --- a/src/array.js +++ b/src/array.js @@ -20,6 +20,8 @@ function ArraySchema(){ MixedSchema.call(this, { type: 'array'}) + this._subType = null; + this.withMutation(() => { this.transform(function(values) { if (typeof values === 'string') diff --git a/src/util/reach.js b/src/util/reach.js index 33f1f4ceb..bcdfc3394 100644 --- a/src/util/reach.js +++ b/src/util/reach.js @@ -1,19 +1,38 @@ 'use strict'; -let { forEach } = require('property-expr'); +let { forEach } = require('property-expr') + , { has } = require('./_'); let trim = part => part.substr(0, part.length - 1).substr(1) -module.exports = function (obj, path) { - forEach(path, (part, isBracket, isArray) => { - if( isArray) - obj = obj._subType - else { - if (obj._subType) // we skipped an array - obj = obj._subType - - obj = obj.fields[isBracket ? trim(part) : part] - } +module.exports = function (obj, path, value, context) { + // if only one "value" arg then use it for both + context = context || value; + + let parent, lastPart; + + forEach(path, (_part, isBracket, isArray) => { + let part = isBracket ? trim(_part) : _part; + + if (isArray || has(obj, '_subType')) { // we skipped an array + obj = obj._resolve(context, parent)._subType; + value = value && value[0] + } + + if (!isArray) { + obj = obj._resolve(context, parent); + + if (!has(obj, 'fields')) + throw new Error( + `The schema does not contain the path: ${path}. ` + + `(failed at: ${lastPart} which is a type: "${obj._type}") ` + ) + + obj = obj.fields[part] + parent = value; + value = value && value[part] + lastPart = isBracket ? '[' + _part + ']' : '.' + _part + } }) - return obj + return obj._resolve(parent) } diff --git a/src/util/reference.js b/src/util/reference.js index 2af39d3f7..47ebd7f5f 100644 --- a/src/util/reference.js +++ b/src/util/reference.js @@ -18,6 +18,7 @@ export default class Ref { this.prefix = prefix; this.isContext = key.indexOf(prefix) === 0 this.path = this.isContext ? this.key.slice(this.prefix.length) : this.key + this._get = getter(this.path) this.map = mapFn || (value => value); } @@ -27,7 +28,7 @@ export default class Ref { if ((isContext && !context) || (!isContext && !context && !parent)) throw new Error('missing the context necessary to cast this value') - let value = getter(this.path)(isContext ? context : (parent || context)) + let value = this._get(isContext ? context : (parent || context)) return this.map(value) } diff --git a/test/yup.js b/test/yup.js index c198c3766..f881e39a9 100644 --- a/test/yup.js +++ b/test/yup.js @@ -85,6 +85,50 @@ describe('Yup', function(){ }) }) + it('should REACH conditionally correctly', function(){ + var num = number() + , inst = object().shape({ + num: number().max(4), + nested: object() + .shape({ + arr: array().when('$bar', function(bar) { + return bar !== 3 + ? array().of(number()) + : array().of( + object().shape({ + foo: number(), + num: number().when('foo', (foo) => { + if (foo === 5) + return num + }) + }) + ) + }) + }) + }) + + let context = { bar: 3 } + let value = { + bar: 3, + nested: { + arr: [{ foo: 5 }] + } + } + + reach(inst, 'nested.arr.num', value).should.equal(num) + reach(inst, 'nested.arr[].num', value).should.equal(num) + + reach(inst, 'nested.arr.num', value, context).should.equal(num) + reach(inst, 'nested.arr[].num', value, context).should.equal(num) + reach(inst, 'nested.arr[1].num', value, context).should.equal(num) + reach(inst, 'nested["arr"][1].num', value, context).should.not.equal(number()) + + return reach(inst, 'nested.arr[].num', value, context).isValid(5) + .then((valid) => { + valid.should.equal(true) + }) + }) + describe('BadSet', function(){ it('should preserve primitive types', function(){ var set = new BadSet()