Skip to content

Commit

Permalink
[changed] clean up interface, added lazy(), and fixed object strict s…
Browse files Browse the repository at this point in the history
…emantics
  • Loading branch information
jquense committed Apr 14, 2016
1 parent 475ef69 commit 6e9046b
Show file tree
Hide file tree
Showing 17 changed files with 656 additions and 460 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
@@ -1,6 +1,10 @@
{
"parser": "babel-eslint",
"extends": "eslint:recommended",
"globals": {
"sinon": true,
"expect": true
},
"env": {
"browser": true,
"node": true,
Expand Down
33 changes: 32 additions & 1 deletion README.md
Expand Up @@ -187,7 +187,7 @@ Adds a new method to the core schema types. A friendlier convenience method for
#### `yup.ref(path: string, options: { contextPrefix: string }): Ref`

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

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

#### `yup.lazy((value: any) => Schema): Lazy`

creates a schema that is evaluated at validation/cast time. Useful for creating
recursive schema like Trees, for polymophic fields and arrays.

__CAUTION!__ When defining parent-child recursive object schema, you want to reset the `default()`
to `undefined` on the child otherwise the object will infinitely nest itself when you cast it!.

```js
var node = object({
id: number(),
child: yup.lazy(() =>
node.default(undefined)
)
})

let renderable = yup.lazy(value => {
switch (typeof value) {
case 'number':
return number()
case 'string':
return string()
default:
return mixed()
}
})

let renderables = array().of(renderable)
```

#### `ValidationError(errors: string | Array<string>, value: any, path: string)`

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

Collects schema details (like meta, labels, and active tests) into a serializable
description object.

```
SchemaDescription {
type: string,
Expand Down
1 change: 1 addition & 0 deletions karma.conf.js
Expand Up @@ -11,6 +11,7 @@ module.exports = function (config) {
reporters: ['mocha'],

files: [
require.resolve('sinon/pkg/sinon-1.17.3.js'),
'tests-webpack.js'
],

Expand Down
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -28,6 +28,7 @@
"babel-loader": "^6.2.4",
"babel-plugin-add-module-exports": "^0.1.2",
"babel-plugin-transform-object-assign": "^6.5.0",
"babel-polyfill": "^6.7.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-es2015-loose": "^7.0.0",
"babel-preset-react": "^6.5.0",
Expand All @@ -49,8 +50,8 @@
"node-libs-browser": "^0.5.2",
"phantomjs": "^1.9.17",
"release-script": "^0.5.2",
"sinon": "^1.10.3",
"sinon-chai": "^2.5.0",
"sinon": "^1.17.3",
"sinon-chai": "^2.8.0",
"webpack": "^1.12.2"
},
"dependencies": {
Expand Down
34 changes: 18 additions & 16 deletions src/array.js
Expand Up @@ -44,7 +44,7 @@ inherits(ArraySchema, MixedSchema, {
},

_cast(_value, _opts) {
var value = MixedSchema.prototype._cast.call(this, _value)
var value = MixedSchema.prototype._cast.call(this, _value, _opts)

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

_validate(_value, _opts, _state){
_validate(_value, options = {}) {
var errors = []
, context, subType, schema, endEarly, recursive;
, subType, endEarly, recursive;

_state = _state || {}
context = _state.parent || (_opts || {}).context
schema = this._resolve(context)
subType = schema._subType
endEarly = schema._option('abortEarly', _opts)
recursive = schema._option('recursive', _opts)
subType = this._subType
endEarly = this._option('abortEarly', options)
recursive = this._option('recursive', options)

return MixedSchema.prototype._validate.call(this, _value, _opts, _state)
return MixedSchema.prototype._validate.call(this, _value, options)
.catch(endEarly ? null : err => {
errors = err
return err.value
})
.then(function(value){
if (!recursive || !subType || !schema._typeCheck(value) ) {
.then((value) => {
if (!recursive || !subType || !this._typeCheck(value) ) {
if (errors.length) throw errors[0]
return value
}

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

return subType._validate(item, _opts, state)
// object._validate note for isStrict explanation
var innerOptions = { ...options, path, key, strict: true, parent: value };

if (subType.validate)
return subType.validate(item, innerOptions)

return true
})

result = endEarly
? Promise.all(result).catch(scopeError(value))
: collectErrors(result, value, _state.path, errors)
: collectErrors(result, value, options.path, errors)

return result.then(() => value)
})
Expand Down
4 changes: 3 additions & 1 deletion src/index.js
@@ -1,7 +1,8 @@
'use strict';
var mixed = require('./mixed')
, bool = require('./boolean')
, Ref = require('./util/reference');
, Ref = require('./util/reference')
, Lazy = require('./util/lazy');

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

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

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

isSchema,

Expand Down
67 changes: 35 additions & 32 deletions src/mixed.js
Expand Up @@ -111,8 +111,18 @@ SchemaType.prototype = {
return !this._typeCheck || this._typeCheck(v)
},

resolve(context, parent) {
if (this._conditions.length) {
return this._conditions.reduce((schema, match) =>
match.resolve(schema, match.getValue(parent, context)), this)
}

return this
},

cast(value, opts = {}) {
var schema = this._resolve(opts.context, opts.parent)
let schema = this.resolve(opts.context, opts.parent)

return schema._cast(value, opts)
},

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

if (value === undefined && _.has(this, '_default'))
if (value === undefined && (_.has(this, '_default'))) {
value = this.default()
}

return value
},

_resolve(context, parent) {
if (this._conditions.length) {
return this._conditions.reduce((schema, match) =>
match.resolve(schema, match.getValue(parent, context)), this)
}
validate(value, options = {}, cb) {
if (typeof options === 'function')
cb = options, options = {}

return this
let schema = this.resolve(options.context, options.parent)

return nodeify(schema._validate(value, options), cb)
},

//-- tests
_validate(_value, options = {}, state = {}) {
let context = options.context
, parent = state.parent
, value = _value
_validate(_value, options = {}) {
let value = _value
, schema, endEarly, isStrict;

schema = this._resolve(context, parent)
isStrict = schema._option('strict', options)
endEarly = schema._option('abortEarly', options)
schema = this
isStrict = this._option('strict', options)
endEarly = this._option('abortEarly', options)

let path = state.path
let path = options.path
let label = this._label

if (!state.isCast && !isStrict)
value = schema._cast(value, options)

if (!isStrict) {
value = this._cast(value, options, options)
}
// value is cast, we can check if it meets type requirements
let validationParams = { value, path, state, schema, options, label }
let validationParams = { value, path, schema: this, options, label }
let initialTests = []

if (schema._typeError)
initialTests.push(schema._typeError(validationParams));
initialTests.push(this._typeError(validationParams));

if (schema._whitelistError)
initialTests.push(schema._whitelistError(validationParams));
if (this._whitelistError)
initialTests.push(this._whitelistError(validationParams));

if (schema._blacklistError)
initialTests.push(schema._blacklistError(validationParams));
if (this._blacklistError)
initialTests.push(this._blacklistError(validationParams));

return runValidations(initialTests, endEarly, value, path)
.then(() => runValidations(
schema.tests.map(fn => fn(validationParams))
this.tests.map(fn => fn(validationParams))
, endEarly
, value
, path
))
.then(() => value)
},

validate(value, options, cb) {
if (typeof options === 'function')
cb = options, options = {}

return nodeify(this._validate(value, options, {}), cb)
},

isValid(value, options, cb) {
if (typeof options === 'function')
Expand Down

0 comments on commit 6e9046b

Please sign in to comment.