Skip to content

Commit f2b0078

Browse files
committed
[changed] Less aggressive type coercions
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' ```
1 parent ab94510 commit f2b0078

39 files changed

+819
-663
lines changed

.eslintrc

+6-3
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@
33
"extends": "eslint:recommended",
44
"globals": {
55
"sinon": true,
6-
"expect": true
6+
"expect": true,
7+
"TestHelpers": true,
78
},
89
"env": {
910
"browser": true,
1011
"node": true,
11-
"mocha": true
12+
"mocha": true,
1213
},
1314
"rules": {
15+
"eqeqeq": 0,
16+
"no-loop-func": 0,
17+
"comma-dangle": 0,
1418
"no-eval": 2,
1519
"strict": 0,
1620
"eol-last": 0,
1721
"dot-notation": [2, { "allowKeywords": true }],
1822
"semi": [0, "never"],
1923
"curly": 0,
20-
"eqeqeq": [2, "allow-null"],
2124
"no-undef": 2,
2225
"quotes": [2, "single", "avoid-escape"],
2326
"no-trailing-spaces": 0,

README.md

+41-7
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ transforms are run before validations and only applied when `strict` is `true`.
633633
634634
Transformations are useful for arbitrarily altering how the object is cast, __however, you should take care
635635
not to mutate the passed in value.__ Transforms are run sequentially so each `value` represents the
636-
current state of the cast, you can use the `orignalValue` param if you need to work on the raw initial value.
636+
current state of the cast, you can use the `originalValue` param if you need to work on the raw initial value.
637637
638638
```javascript
639639
var schema = yup.string().transform(function(currentValue, originalvalue){
@@ -671,6 +671,11 @@ var schema = yup.string();
671671
schema.isValid('hello') //=> true
672672
```
673673
674+
By default, the `cast` logic of `string` is to call `toString` on the value if it exists.
675+
empty values are not coerced (use `ensure()` to coerce empty values to empty strings).
676+
677+
Failed casts return the input value.
678+
674679
#### `string.required(message: ?string): Schema`
675680
676681
The same as the `mixed()` schema required, except that empty strings are also considered 'missing' values.
@@ -702,6 +707,10 @@ Validates the value as an email address via a regex.
702707
703708
Validates the value as a valid URL via a regex.
704709
710+
#### `string.ensure(): Schema`
711+
712+
Transforms `undefined` and `null` values to an empty string along with
713+
setting the `default` to an empty string.
705714
706715
#### `string.trim(message: ?string): Schema`
707716
@@ -710,11 +719,13 @@ Transforms string values by removing leading and trailing whitespace. If
710719
711720
#### `string.lowercase(message: ?string): Schema`
712721
713-
Transforms the string value to lowercase. If `strict()` is set it will only validate that the value is lowercase.
722+
Transforms the string value to lowercase. If `strict()` is set it
723+
will only validate that the value is lowercase.
714724
715725
#### `string.uppercase(message: ?string): Schema`
716726
717-
Transforms the string value to uppercase. If `strict()` is set it will only validate that the value is uppercase.
727+
Transforms the string value to uppercase. If `strict()` is set it
728+
will only validate that the value is uppercase.
718729
719730
### number
720731
@@ -725,6 +736,10 @@ var schema = yup.number();
725736
schema.isValid(10) //=> true
726737
```
727738
739+
The default `cast` logic of `number` is: [`parseFloat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat).
740+
741+
Failed casts return `NaN`.
742+
728743
#### `number.min(limit: number | Ref, message: ?string): Schema`
729744
730745
Set the minimum value allowed. The `${min}` interpolation can be used in the
@@ -745,13 +760,16 @@ Value must be a negative number.
745760
746761
#### `number.integer(message: ?string): Schema`
747762
748-
Transformation that coerces the value into an integer via truncation
749-
` value | 0`. If `strict()` is set it will only validate that the value is an integer.
763+
Validates that a number is an integer.
764+
765+
#### `number.truncate(): Schema`
750766
751-
#### `number.round(type: 'floor' | 'ceil' | 'round' = 'round'): Schema`
767+
Transformation that coerces the value to an integer by stripping off the digits
768+
to the right of the decimal point.
752769
753-
Rounds the value by the specified method (defaults to 'round').
770+
#### `number.round(type: 'floor' | 'ceil' | 'trunc' | 'round' = 'round'): Schema`
754771
772+
Adjusts the value via the specified method of `Math` (defaults to 'round').
755773
756774
### boolean
757775
@@ -773,6 +791,12 @@ var schema = yup.date();
773791
schema.isValid(new Date) //=> true
774792
```
775793
794+
The default `cast` logic of `date` is pass the value to the `Date` constructor, failing that, it will attempt
795+
to parse the date as an ISO date string.
796+
797+
Failed casts return an invalid Date.
798+
799+
776800
#### `date.min(limit: Date | string | Ref, message: ?string): Schema`
777801
778802
Set the minimum date allowed. When a string is provided it will attempt to cast to a date first
@@ -805,6 +829,10 @@ array().of(number())
805829
array(number())
806830
```
807831
832+
The default `cast` behavior for `array` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
833+
834+
Failed casts return: `null`;
835+
808836
#### `array.of(type: Schema): Schema`
809837
810838
Specify the schema of array elements. `of()` is optional and when omitted the array schema will
@@ -876,6 +904,10 @@ object({
876904
})
877905
```
878906
907+
The default `cast` behavior for `object` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
908+
909+
Failed casts return: `null`;
910+
879911
880912
#### `object.shape(fields: object, noSortEdges: ?Array<[string, string]>): Schema`
881913
@@ -909,6 +941,8 @@ Transforms all object keys to camelCase
909941
910942
Transforms all object keys to CONSTANT_CASE.
911943
944+
945+
912946
## Extending Schema Types
913947
914948
The simplest way to extend an existing type is just to cache a configured schema and use that through your application.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@
5959
"dependencies": {
6060
"case": "^1.2.1",
6161
"fn-name": "~1.0.1",
62+
"lodash": "^4.13.1",
6263
"property-expr": "^1.2.0",
6364
"toposort": "^0.2.10",
65+
"type-name": "^2.0.1",
6466
"universal-promise": "^1.0.1"
6567
},
6668
"release-script": {

src/util/condition.js src/Condition.js

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
'use strict';
2-
var { transform, has, isSchema } = require('./_')
3-
4-
module.exports = Conditional
1+
import has from 'lodash/has';
2+
import isSchema from './util/isSchema';
53

64
class Conditional {
75

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

2018
if (!options.then && !options.otherwise)
21-
throw new TypeError('either `then:` or `otherwise:` is required for `when()` conditions')
22-
19+
throw new TypeError(
20+
'either `then:` or `otherwise:` is required for `when()` conditions'
21+
)
2322

2423
let isFn = typeof is === 'function'
2524
? is : ((...values) => values.every(value => value === is))
@@ -47,4 +46,4 @@ class Conditional {
4746
}
4847
}
4948

50-
module.exports = Conditional;
49+
export default Conditional;

src/util/lazy.js src/Lazy.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
var { isSchema } = require('./_')
1+
import isSchema from './util/isSchema';
22

33
class Lazy {
44
constructor(mapFn) {

src/util/reference.js src/Reference.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
var getter = require('property-expr').getter
1+
import { getter } from 'property-expr';
22

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

8-
export default class Ref {
8+
export default class Reference {
99
static isRef(value) {
10-
return !!(value && (value.__isYupRef || value instanceof Ref))
10+
return !!(value && (value.__isYupRef || value instanceof Reference))
1111
}
1212

1313
constructor(key, mapFn, options = {}) {
@@ -40,4 +40,4 @@ export default class Ref {
4040
}
4141
}
4242

43-
Ref.prototype.__isYupRef = true
43+
Reference.prototype.__isYupRef = true

src/util/validation-error.js src/ValidationError.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
'use strict';
2-
var strReg = /\$\{\s*(\w+)\s*\}/g;
1+
2+
let strReg = /\$\{\s*(\w+)\s*\}/g;
33

44
let replace = str =>
55
params => str.replace(strReg, (_, key) => params[key] || '')

src/array.js

+33-34
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
'use strict';
2-
var MixedSchema = require('./mixed')
3-
, Promise = require('universal-promise')
4-
, isAbsent = require('./util/isAbsent')
5-
, { mixed, array: locale } = require('./locale.js')
6-
, { inherits, collectErrors } = require('./util/_');
7-
8-
let scopeError = value => err => {
9-
err.value = value
10-
throw err
11-
}
1+
import inherits from './util/inherits';
2+
import isAbsent from './util/isAbsent';
3+
import MixedSchema from './mixed';
4+
import { mixed, array as locale } from './locale.js';
5+
import runValidations, { propagateErrors } from './util/runValidations';
6+
127

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

15-
module.exports = ArraySchema
10+
export default ArraySchema
1611

1712
function ArraySchema(type) {
1813
if (!(this instanceof ArraySchema))
@@ -54,41 +49,45 @@ inherits(ArraySchema, MixedSchema, {
5449
},
5550

5651
_validate(_value, options = {}) {
57-
var errors = []
58-
, subType, endEarly, recursive;
59-
60-
subType = this._subType
61-
endEarly = this._option('abortEarly', options)
62-
recursive = this._option('recursive', options)
63-
64-
return MixedSchema.prototype._validate.call(this, _value, options)
65-
.catch(endEarly ? null : err => {
66-
errors = err
67-
return err.value
68-
})
52+
let errors = []
53+
let path = options.path
54+
let subType = this._subType
55+
let endEarly = this._option('abortEarly', options)
56+
let recursive = this._option('recursive', options)
57+
58+
return MixedSchema.prototype._validate
59+
.call(this, _value, options)
60+
.catch(propagateErrors(endEarly, errors))
6961
.then((value) => {
7062
if (!recursive || !subType || !this._typeCheck(value) ) {
7163
if (errors.length) throw errors[0]
7264
return value
7365
}
7466

75-
let validations = value.map((item, key) => {
76-
var path = (options.path || '') + '[' + key + ']'
67+
let validations = value.map((item, idx) => {
68+
var path = (options.path || '') + '[' + idx + ']'
7769

7870
// object._validate note for isStrict explanation
79-
var innerOptions = { ...options, path, key, strict: true, parent: value };
71+
var innerOptions = {
72+
...options,
73+
path,
74+
strict: true,
75+
parent: value
76+
};
8077

8178
if (subType.validate)
8279
return subType.validate(item, innerOptions)
8380

8481
return true
8582
})
8683

87-
validations = endEarly
88-
? Promise.all(validations).catch(scopeError(value))
89-
: collectErrors({ validations, value, errors, path: options.path })
90-
91-
return validations.then(() => value)
84+
return runValidations({
85+
path,
86+
value,
87+
errors,
88+
endEarly,
89+
validations
90+
})
9291
})
9392
},
9493

@@ -137,8 +136,8 @@ inherits(ArraySchema, MixedSchema, {
137136

138137
ensure() {
139138
return this
140-
.default([])
141-
.transform(val => val == null ? [] : [].concat(val))
139+
.default(() => [])
140+
.transform(val => val === null ? [] : [].concat(val))
142141
},
143142

144143
compact(rejector){

src/boolean.js

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
'use strict';
2-
var MixedSchema = require('./mixed')
3-
, inherits = require('./util/_').inherits;
1+
import inherits from './util/inherits';
2+
import MixedSchema from './mixed';
43

5-
module.exports = BooleanSchema
4+
export default BooleanSchema
65

76
function BooleanSchema(){
87
if (!(this instanceof BooleanSchema))
@@ -12,15 +11,20 @@ function BooleanSchema(){
1211

1312
this.withMutation(() => {
1413
this.transform(function(value) {
15-
if ( this.isType(value) ) return value
16-
return (/true|1/i).test(value)
14+
if (!this.isType(value)) {
15+
if (/^(true|1)$/i.test(value)) return true
16+
if (/^(false|0)$/i.test(value)) return false
17+
}
18+
return value
1719
})
1820
})
1921
}
2022

2123
inherits(BooleanSchema, MixedSchema, {
2224

23-
_typeCheck(v){
24-
return (typeof v === 'boolean') || (typeof v === 'object' && v instanceof Boolean)
25+
_typeCheck(v) {
26+
if (v instanceof Boolean) v = v.valueOf();
27+
28+
return typeof v === 'boolean'
2529
}
2630
})

src/date.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
'use strict';
2-
var MixedSchema = require('./mixed')
3-
, isoParse = require('./util/isodate')
4-
, locale = require('./locale.js').date
5-
, isAbsent = require('./util/isAbsent')
6-
, Ref = require('./util/reference')
7-
, { isDate, inherits } = require('./util/_');
1+
import MixedSchema from './mixed';
2+
import inherits from './util/inherits';
3+
import isoParse from './util/isodate';
4+
import { date as locale } from './locale.js';
5+
import isAbsent from './util/isAbsent';
6+
import Ref from './Reference';
87

98
let invalidDate = new Date('')
109

11-
module.exports = DateSchema
10+
let isDate = obj => Object.prototype.toString.call(obj) === '[object Date]'
11+
12+
export default DateSchema
1213

1314
function DateSchema(){
1415
if ( !(this instanceof DateSchema)) return new DateSchema()

0 commit comments

Comments
 (0)