Skip to content

Commit

Permalink
[changed] Less aggressive type coercions
Browse files Browse the repository at this point in the history
Type casts no longer "succeed without fail".
For instance `boolean` will throw if a cast produces an
invalid type, instead of quietly coercing to `false`. By default
`cast` will now throw in these situations, passing `assert: false`
to cast options will disable this behavior and the value returned
will be the invalid value (NaN, InvalidDate, null) or the original value
if no good invalid value exists in the language

```
number().cast('foo', { assert: false }) // -> NaN
bool().cast('foo', { assert: false })   // -> 'foo'
```
  • Loading branch information
jquense committed Jun 24, 2016
1 parent ab94510 commit f2b0078
Show file tree
Hide file tree
Showing 39 changed files with 819 additions and 663 deletions.
9 changes: 6 additions & 3 deletions .eslintrc
Expand Up @@ -3,21 +3,24 @@
"extends": "eslint:recommended",
"globals": {
"sinon": true,
"expect": true
"expect": true,
"TestHelpers": true,
},
"env": {
"browser": true,
"node": true,
"mocha": true
"mocha": true,
},
"rules": {
"eqeqeq": 0,
"no-loop-func": 0,
"comma-dangle": 0,
"no-eval": 2,
"strict": 0,
"eol-last": 0,
"dot-notation": [2, { "allowKeywords": true }],
"semi": [0, "never"],
"curly": 0,
"eqeqeq": [2, "allow-null"],
"no-undef": 2,
"quotes": [2, "single", "avoid-escape"],
"no-trailing-spaces": 0,
Expand Down
48 changes: 41 additions & 7 deletions README.md
Expand Up @@ -633,7 +633,7 @@ transforms are run before validations and only applied when `strict` is `true`.
Transformations are useful for arbitrarily altering how the object is cast, __however, you should take care
not to mutate the passed in value.__ Transforms are run sequentially so each `value` represents the
current state of the cast, you can use the `orignalValue` param if you need to work on the raw initial value.
current state of the cast, you can use the `originalValue` param if you need to work on the raw initial value.
```javascript
var schema = yup.string().transform(function(currentValue, originalvalue){
Expand Down Expand Up @@ -671,6 +671,11 @@ var schema = yup.string();
schema.isValid('hello') //=> true
```
By default, the `cast` logic of `string` is to call `toString` on the value if it exists.
empty values are not coerced (use `ensure()` to coerce empty values to empty strings).
Failed casts return the input value.
#### `string.required(message: ?string): Schema`
The same as the `mixed()` schema required, except that empty strings are also considered 'missing' values.
Expand Down Expand Up @@ -702,6 +707,10 @@ Validates the value as an email address via a regex.
Validates the value as a valid URL via a regex.
#### `string.ensure(): Schema`
Transforms `undefined` and `null` values to an empty string along with
setting the `default` to an empty string.
#### `string.trim(message: ?string): Schema`
Expand All @@ -710,11 +719,13 @@ Transforms string values by removing leading and trailing whitespace. If
#### `string.lowercase(message: ?string): Schema`
Transforms the string value to lowercase. If `strict()` is set it will only validate that the value is lowercase.
Transforms the string value to lowercase. If `strict()` is set it
will only validate that the value is lowercase.
#### `string.uppercase(message: ?string): Schema`
Transforms the string value to uppercase. If `strict()` is set it will only validate that the value is uppercase.
Transforms the string value to uppercase. If `strict()` is set it
will only validate that the value is uppercase.
### number
Expand All @@ -725,6 +736,10 @@ var schema = yup.number();
schema.isValid(10) //=> true
```
The default `cast` logic of `number` is: [`parseFloat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat).
Failed casts return `NaN`.
#### `number.min(limit: number | Ref, message: ?string): Schema`
Set the minimum value allowed. The `${min}` interpolation can be used in the
Expand All @@ -745,13 +760,16 @@ Value must be a negative number.
#### `number.integer(message: ?string): Schema`
Transformation that coerces the value into an integer via truncation
` value | 0`. If `strict()` is set it will only validate that the value is an integer.
Validates that a number is an integer.
#### `number.truncate(): Schema`
#### `number.round(type: 'floor' | 'ceil' | 'round' = 'round'): Schema`
Transformation that coerces the value to an integer by stripping off the digits
to the right of the decimal point.
Rounds the value by the specified method (defaults to 'round').
#### `number.round(type: 'floor' | 'ceil' | 'trunc' | 'round' = 'round'): Schema`
Adjusts the value via the specified method of `Math` (defaults to 'round').
### boolean
Expand All @@ -773,6 +791,12 @@ var schema = yup.date();
schema.isValid(new Date) //=> true
```
The default `cast` logic of `date` is pass the value to the `Date` constructor, failing that, it will attempt
to parse the date as an ISO date string.
Failed casts return an invalid Date.
#### `date.min(limit: Date | string | Ref, message: ?string): Schema`
Set the minimum date allowed. When a string is provided it will attempt to cast to a date first
Expand Down Expand Up @@ -805,6 +829,10 @@ array().of(number())
array(number())
```
The default `cast` behavior for `array` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
Failed casts return: `null`;
#### `array.of(type: Schema): Schema`
Specify the schema of array elements. `of()` is optional and when omitted the array schema will
Expand Down Expand Up @@ -876,6 +904,10 @@ object({
})
```
The default `cast` behavior for `object` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
Failed casts return: `null`;
#### `object.shape(fields: object, noSortEdges: ?Array<[string, string]>): Schema`
Expand Down Expand Up @@ -909,6 +941,8 @@ Transforms all object keys to camelCase
Transforms all object keys to CONSTANT_CASE.
## Extending Schema Types
The simplest way to extend an existing type is just to cache a configured schema and use that through your application.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -59,8 +59,10 @@
"dependencies": {
"case": "^1.2.1",
"fn-name": "~1.0.1",
"lodash": "^4.13.1",
"property-expr": "^1.2.0",
"toposort": "^0.2.10",
"type-name": "^2.0.1",
"universal-promise": "^1.0.1"
},
"release-script": {
Expand Down
13 changes: 6 additions & 7 deletions src/util/condition.js → src/Condition.js
@@ -1,7 +1,5 @@
'use strict';
var { transform, has, isSchema } = require('./_')

module.exports = Conditional
import has from 'lodash/has';
import isSchema from './util/isSchema';

class Conditional {

Expand All @@ -18,8 +16,9 @@ class Conditional {
throw new TypeError('`is:` is required for `when()` conditions')

if (!options.then && !options.otherwise)
throw new TypeError('either `then:` or `otherwise:` is required for `when()` conditions')

throw new TypeError(
'either `then:` or `otherwise:` is required for `when()` conditions'
)

let isFn = typeof is === 'function'
? is : ((...values) => values.every(value => value === is))
Expand Down Expand Up @@ -47,4 +46,4 @@ class Conditional {
}
}

module.exports = Conditional;
export default Conditional;
2 changes: 1 addition & 1 deletion src/util/lazy.js → src/Lazy.js
@@ -1,4 +1,4 @@
var { isSchema } = require('./_')
import isSchema from './util/isSchema';

class Lazy {
constructor(mapFn) {
Expand Down
8 changes: 4 additions & 4 deletions src/util/reference.js → src/Reference.js
@@ -1,13 +1,13 @@
var getter = require('property-expr').getter
import { getter } from 'property-expr';

let validateName = d => {
if (typeof d !== 'string')
throw new TypeError('ref\'s must be strings, got: ' + d)
}

export default class Ref {
export default class Reference {
static isRef(value) {
return !!(value && (value.__isYupRef || value instanceof Ref))
return !!(value && (value.__isYupRef || value instanceof Reference))
}

constructor(key, mapFn, options = {}) {
Expand Down Expand Up @@ -40,4 +40,4 @@ export default class Ref {
}
}

Ref.prototype.__isYupRef = true
Reference.prototype.__isYupRef = true
4 changes: 2 additions & 2 deletions src/util/validation-error.js → src/ValidationError.js
@@ -1,5 +1,5 @@
'use strict';
var strReg = /\$\{\s*(\w+)\s*\}/g;

let strReg = /\$\{\s*(\w+)\s*\}/g;

let replace = str =>
params => str.replace(strReg, (_, key) => params[key] || '')
Expand Down
67 changes: 33 additions & 34 deletions src/array.js
@@ -1,18 +1,13 @@
'use strict';
var MixedSchema = require('./mixed')
, Promise = require('universal-promise')
, isAbsent = require('./util/isAbsent')
, { mixed, array: locale } = require('./locale.js')
, { inherits, collectErrors } = require('./util/_');

let scopeError = value => err => {
err.value = value
throw err
}
import inherits from './util/inherits';
import isAbsent from './util/isAbsent';
import MixedSchema from './mixed';
import { mixed, array as locale } from './locale.js';
import runValidations, { propagateErrors } from './util/runValidations';


let hasLength = value => !isAbsent(value) && value.length > 0;

module.exports = ArraySchema
export default ArraySchema

function ArraySchema(type) {
if (!(this instanceof ArraySchema))
Expand Down Expand Up @@ -54,41 +49,45 @@ inherits(ArraySchema, MixedSchema, {
},

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

subType = this._subType
endEarly = this._option('abortEarly', options)
recursive = this._option('recursive', options)

return MixedSchema.prototype._validate.call(this, _value, options)
.catch(endEarly ? null : err => {
errors = err
return err.value
})
let errors = []
let path = options.path
let subType = this._subType
let endEarly = this._option('abortEarly', options)
let recursive = this._option('recursive', options)

return MixedSchema.prototype._validate
.call(this, _value, options)
.catch(propagateErrors(endEarly, errors))
.then((value) => {
if (!recursive || !subType || !this._typeCheck(value) ) {
if (errors.length) throw errors[0]
return value
}

let validations = value.map((item, key) => {
var path = (options.path || '') + '[' + key + ']'
let validations = value.map((item, idx) => {
var path = (options.path || '') + '[' + idx + ']'

// object._validate note for isStrict explanation
var innerOptions = { ...options, path, key, strict: true, parent: value };
var innerOptions = {
...options,
path,
strict: true,
parent: value
};

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

return true
})

validations = endEarly
? Promise.all(validations).catch(scopeError(value))
: collectErrors({ validations, value, errors, path: options.path })

return validations.then(() => value)
return runValidations({
path,
value,
errors,
endEarly,
validations
})
})
},

Expand Down Expand Up @@ -137,8 +136,8 @@ inherits(ArraySchema, MixedSchema, {

ensure() {
return this
.default([])
.transform(val => val == null ? [] : [].concat(val))
.default(() => [])
.transform(val => val === null ? [] : [].concat(val))
},

compact(rejector){
Expand Down
20 changes: 12 additions & 8 deletions src/boolean.js
@@ -1,8 +1,7 @@
'use strict';
var MixedSchema = require('./mixed')
, inherits = require('./util/_').inherits;
import inherits from './util/inherits';
import MixedSchema from './mixed';

module.exports = BooleanSchema
export default BooleanSchema

function BooleanSchema(){
if (!(this instanceof BooleanSchema))
Expand All @@ -12,15 +11,20 @@ function BooleanSchema(){

this.withMutation(() => {
this.transform(function(value) {
if ( this.isType(value) ) return value
return (/true|1/i).test(value)
if (!this.isType(value)) {
if (/^(true|1)$/i.test(value)) return true
if (/^(false|0)$/i.test(value)) return false
}
return value
})
})
}

inherits(BooleanSchema, MixedSchema, {

_typeCheck(v){
return (typeof v === 'boolean') || (typeof v === 'object' && v instanceof Boolean)
_typeCheck(v) {
if (v instanceof Boolean) v = v.valueOf();

return typeof v === 'boolean'
}
})
17 changes: 9 additions & 8 deletions src/date.js
@@ -1,14 +1,15 @@
'use strict';
var MixedSchema = require('./mixed')
, isoParse = require('./util/isodate')
, locale = require('./locale.js').date
, isAbsent = require('./util/isAbsent')
, Ref = require('./util/reference')
, { isDate, inherits } = require('./util/_');
import MixedSchema from './mixed';
import inherits from './util/inherits';
import isoParse from './util/isodate';
import { date as locale } from './locale.js';
import isAbsent from './util/isAbsent';
import Ref from './Reference';

let invalidDate = new Date('')

module.exports = DateSchema
let isDate = obj => Object.prototype.toString.call(obj) === '[object Date]'

export default DateSchema

function DateSchema(){
if ( !(this instanceof DateSchema)) return new DateSchema()
Expand Down

0 comments on commit f2b0078

Please sign in to comment.