Skip to content

Commit 6e9046b

Browse files
committed
[changed] clean up interface, added lazy(), and fixed object strict semantics
1 parent 475ef69 commit 6e9046b

17 files changed

+656
-460
lines changed

.eslintrc

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
{
22
"parser": "babel-eslint",
33
"extends": "eslint:recommended",
4+
"globals": {
5+
"sinon": true,
6+
"expect": true
7+
},
48
"env": {
59
"browser": true,
610
"node": true,

README.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Adds a new method to the core schema types. A friendlier convenience method for
187187
#### `yup.ref(path: string, options: { contextPrefix: string }): Ref`
188188

189189
Creates a reference to another sibling or sibling descendant field. Ref's are resolved
190-
at _run time_ and supported where specified. Ref's are evaluated in in the proper order so that
190+
at _validation/cast time_ and supported where specified. Ref's are evaluated in in the proper order so that
191191
the ref value is resolved before the field using the ref (be careful of circular dependencies!).
192192

193193
```js
@@ -203,6 +203,36 @@ inst.cast({ foo: { bar: 'boom' } }, { context: { x: 5 } })
203203
// { baz: 'boom', x: 5, { foo: { bar: 'boom' } }, }
204204
```
205205

206+
#### `yup.lazy((value: any) => Schema): Lazy`
207+
208+
creates a schema that is evaluated at validation/cast time. Useful for creating
209+
recursive schema like Trees, for polymophic fields and arrays.
210+
211+
__CAUTION!__ When defining parent-child recursive object schema, you want to reset the `default()`
212+
to `undefined` on the child otherwise the object will infinitely nest itself when you cast it!.
213+
214+
```js
215+
var node = object({
216+
id: number(),
217+
child: yup.lazy(() =>
218+
node.default(undefined)
219+
)
220+
})
221+
222+
let renderable = yup.lazy(value => {
223+
switch (typeof value) {
224+
case 'number':
225+
return number()
226+
case 'string':
227+
return string()
228+
default:
229+
return mixed()
230+
}
231+
})
232+
233+
let renderables = array().of(renderable)
234+
```
235+
206236
#### `ValidationError(errors: string | Array<string>, value: any, path: string)`
207237

208238
Thrown on failed validations, with the following properties
@@ -241,6 +271,7 @@ the cast object itself.
241271

242272
Collects schema details (like meta, labels, and active tests) into a serializable
243273
description object.
274+
244275
```
245276
SchemaDescription {
246277
type: string,

karma.conf.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = function (config) {
1111
reporters: ['mocha'],
1212

1313
files: [
14+
require.resolve('sinon/pkg/sinon-1.17.3.js'),
1415
'tests-webpack.js'
1516
],
1617

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"babel-loader": "^6.2.4",
2929
"babel-plugin-add-module-exports": "^0.1.2",
3030
"babel-plugin-transform-object-assign": "^6.5.0",
31+
"babel-polyfill": "^6.7.4",
3132
"babel-preset-es2015": "^6.6.0",
3233
"babel-preset-es2015-loose": "^7.0.0",
3334
"babel-preset-react": "^6.5.0",
@@ -49,8 +50,8 @@
4950
"node-libs-browser": "^0.5.2",
5051
"phantomjs": "^1.9.17",
5152
"release-script": "^0.5.2",
52-
"sinon": "^1.10.3",
53-
"sinon-chai": "^2.5.0",
53+
"sinon": "^1.17.3",
54+
"sinon-chai": "^2.8.0",
5455
"webpack": "^1.12.2"
5556
},
5657
"dependencies": {

src/array.js

+18-16
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ inherits(ArraySchema, MixedSchema, {
4444
},
4545

4646
_cast(_value, _opts) {
47-
var value = MixedSchema.prototype._cast.call(this, _value)
47+
var value = MixedSchema.prototype._cast.call(this, _value, _opts)
4848

4949
//should ignore nulls here
5050
if (!this._typeCheck(value) || !this._subType)
@@ -53,38 +53,40 @@ inherits(ArraySchema, MixedSchema, {
5353
return value.map(v => this._subType.cast(v, _opts))
5454
},
5555

56-
_validate(_value, _opts, _state){
56+
_validate(_value, options = {}) {
5757
var errors = []
58-
, context, subType, schema, endEarly, recursive;
58+
, subType, endEarly, recursive;
5959

60-
_state = _state || {}
61-
context = _state.parent || (_opts || {}).context
62-
schema = this._resolve(context)
63-
subType = schema._subType
64-
endEarly = schema._option('abortEarly', _opts)
65-
recursive = schema._option('recursive', _opts)
60+
subType = this._subType
61+
endEarly = this._option('abortEarly', options)
62+
recursive = this._option('recursive', options)
6663

67-
return MixedSchema.prototype._validate.call(this, _value, _opts, _state)
64+
return MixedSchema.prototype._validate.call(this, _value, options)
6865
.catch(endEarly ? null : err => {
6966
errors = err
7067
return err.value
7168
})
72-
.then(function(value){
73-
if (!recursive || !subType || !schema._typeCheck(value) ) {
69+
.then((value) => {
70+
if (!recursive || !subType || !this._typeCheck(value) ) {
7471
if (errors.length) throw errors[0]
7572
return value
7673
}
7774

7875
let result = value.map((item, key) => {
79-
var path = (_state.path || '') + '[' + key + ']'
80-
, state = { ..._state, path, key, parent: value};
76+
var path = (options.path || '') + '[' + key + ']'
8177

82-
return subType._validate(item, _opts, state)
78+
// object._validate note for isStrict explanation
79+
var innerOptions = { ...options, path, key, strict: true, parent: value };
80+
81+
if (subType.validate)
82+
return subType.validate(item, innerOptions)
83+
84+
return true
8385
})
8486

8587
result = endEarly
8688
? Promise.all(result).catch(scopeError(value))
87-
: collectErrors(result, value, _state.path, errors)
89+
: collectErrors(result, value, options.path, errors)
8890

8991
return result.then(() => value)
9092
})

src/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use strict';
22
var mixed = require('./mixed')
33
, bool = require('./boolean')
4-
, Ref = require('./util/reference');
4+
, Ref = require('./util/reference')
5+
, Lazy = require('./util/lazy');
56

67
var isSchema = schema => schema && !!schema.__isYupSchema__;
78

@@ -19,6 +20,7 @@ module.exports = {
1920

2021
ValidationError: require('./util/validation-error'),
2122
ref: (key, options) => new Ref(key, options),
23+
lazy: (fn) => new Lazy(fn),
2224

2325
isSchema,
2426

src/mixed.js

+35-32
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,18 @@ SchemaType.prototype = {
111111
return !this._typeCheck || this._typeCheck(v)
112112
},
113113

114+
resolve(context, parent) {
115+
if (this._conditions.length) {
116+
return this._conditions.reduce((schema, match) =>
117+
match.resolve(schema, match.getValue(parent, context)), this)
118+
}
119+
120+
return this
121+
},
122+
114123
cast(value, opts = {}) {
115-
var schema = this._resolve(opts.context, opts.parent)
124+
let schema = this.resolve(opts.context, opts.parent)
125+
116126
return schema._cast(value, opts)
117127
},
118128

@@ -121,67 +131,60 @@ SchemaType.prototype = {
121131
: this.transforms.reduce(
122132
(value, transform) => transform.call(this, value, _value), _value)
123133

124-
if (value === undefined && _.has(this, '_default'))
134+
if (value === undefined && (_.has(this, '_default'))) {
125135
value = this.default()
136+
}
126137

127138
return value
128139
},
129140

130-
_resolve(context, parent) {
131-
if (this._conditions.length) {
132-
return this._conditions.reduce((schema, match) =>
133-
match.resolve(schema, match.getValue(parent, context)), this)
134-
}
141+
validate(value, options = {}, cb) {
142+
if (typeof options === 'function')
143+
cb = options, options = {}
135144

136-
return this
145+
let schema = this.resolve(options.context, options.parent)
146+
147+
return nodeify(schema._validate(value, options), cb)
137148
},
138149

139150
//-- tests
140-
_validate(_value, options = {}, state = {}) {
141-
let context = options.context
142-
, parent = state.parent
143-
, value = _value
151+
_validate(_value, options = {}) {
152+
let value = _value
144153
, schema, endEarly, isStrict;
145154

146-
schema = this._resolve(context, parent)
147-
isStrict = schema._option('strict', options)
148-
endEarly = schema._option('abortEarly', options)
155+
schema = this
156+
isStrict = this._option('strict', options)
157+
endEarly = this._option('abortEarly', options)
149158

150-
let path = state.path
159+
let path = options.path
151160
let label = this._label
152161

153-
if (!state.isCast && !isStrict)
154-
value = schema._cast(value, options)
155-
162+
if (!isStrict) {
163+
value = this._cast(value, options, options)
164+
}
156165
// value is cast, we can check if it meets type requirements
157-
let validationParams = { value, path, state, schema, options, label }
166+
let validationParams = { value, path, schema: this, options, label }
158167
let initialTests = []
159168

160169
if (schema._typeError)
161-
initialTests.push(schema._typeError(validationParams));
170+
initialTests.push(this._typeError(validationParams));
162171

163-
if (schema._whitelistError)
164-
initialTests.push(schema._whitelistError(validationParams));
172+
if (this._whitelistError)
173+
initialTests.push(this._whitelistError(validationParams));
165174

166-
if (schema._blacklistError)
167-
initialTests.push(schema._blacklistError(validationParams));
175+
if (this._blacklistError)
176+
initialTests.push(this._blacklistError(validationParams));
168177

169178
return runValidations(initialTests, endEarly, value, path)
170179
.then(() => runValidations(
171-
schema.tests.map(fn => fn(validationParams))
180+
this.tests.map(fn => fn(validationParams))
172181
, endEarly
173182
, value
174183
, path
175184
))
176185
.then(() => value)
177186
},
178187

179-
validate(value, options, cb) {
180-
if (typeof options === 'function')
181-
cb = options, options = {}
182-
183-
return nodeify(this._validate(value, options, {}), cb)
184-
},
185188

186189
isValid(value, options, cb) {
187190
if (typeof options === 'function')

0 commit comments

Comments
 (0)