Skip to content

Commit 0a7b2d4

Browse files
committed
[fixed] type and whitelist/blacklist checks threw inconsistent errors
closes #19
1 parent 1274a45 commit 0a7b2d4

File tree

5 files changed

+113
-68
lines changed

5 files changed

+113
-68
lines changed

.eslintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
2424
"no-use-before-define": 0,
2525
"key-spacing": 0,
26-
"consitent-return": 0,
26+
"consistent-return": 0,
2727
"no-shadow": 0,
2828
"no-sequences": 0
2929
}

src/mixed.js

+73-54
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ var Promise = require('promise/lib/es6-extensions')
1010
, createValidation = require('./util/createValidation')
1111
, BadSet = require('./util/set');
1212

13-
let formatError = ValidationError.formatError
14-
1513
let notEmpty = value => !isAbsent(value);
1614

15+
function runValidations(validations, endEarly, value, path) {
16+
return endEarly
17+
? Promise.all(validations)
18+
: _.collectErrors(validations, value, path)
19+
}
20+
1721
module.exports = SchemaType
1822

1923
function SchemaType(options = {}){
@@ -27,7 +31,10 @@ function SchemaType(options = {}){
2731
this._blacklist = new BadSet()
2832
this.tests = []
2933
this.transforms = []
30-
this._typeError = formatError(locale.notType)
34+
35+
this.withMutation(() => {
36+
this.typeError(locale.notType)
37+
})
3138

3239
if (_.has(options, 'default'))
3340
this._defaultDefault = options.default
@@ -114,9 +121,7 @@ SchemaType.prototype = {
114121

115122
//-- tests
116123
_validate(_value, options = {}, state = {}) {
117-
let valids = this._whitelist
118-
, invalids = this._blacklist
119-
, context = options.context
124+
let context = options.context
120125
, parent = state.parent
121126
, value = _value
122127
, schema, endEarly, isStrict;
@@ -127,41 +132,28 @@ SchemaType.prototype = {
127132

128133
let path = state.path
129134

130-
let errors = [];
131-
let reject = () => Promise.reject(new ValidationError(errors, value));
132-
133135
if (!state.isCast && !isStrict)
134136
value = schema._cast(value, options)
135137

136138
// value is cast, we can check if it meets type requirements
137-
if (value !== undefined && !schema.isType(value)) {
138-
errors.push(schema._typeError({ value, path, type: schema._type }))
139-
if ( endEarly ) return reject()
140-
}
141-
142-
// next check Whitelist for matching values
143-
if (valids.length && !valids.has(value)) {
144-
errors.push(schema._whitelistError(valids.values(), path))
145-
if (endEarly) return reject()
146-
}
147-
148-
// next check Blacklist for matching values
149-
if (invalids.has(value)) {
150-
errors.push(schema._blacklistError(invalids.values(), path))
151-
if (endEarly) return reject()
152-
}
153-
154-
// It makes no sense to validate further at this point if their are errors
155-
if (errors.length)
156-
return reject()
157-
158-
let result = schema.tests.map(fn => fn({ value, path, state, schema, options }))
159-
160-
result = endEarly
161-
? Promise.all(result)
162-
: _.collectErrors(result, value, path)
163-
164-
return result.then(() => value);
139+
let validationParams = { value, path, state, schema, options }
140+
let initialTests = []
141+
142+
if (schema._typeError)
143+
initialTests.push(schema._typeError(validationParams));
144+
if (schema._whitelistError)
145+
initialTests.push(schema._whitelistError(validationParams));
146+
if (schema._blacklistError)
147+
initialTests.push(schema._blacklistError(validationParams));
148+
149+
return runValidations(initialTests, endEarly, value, path)
150+
.then(() => runValidations(
151+
schema.tests.map(fn => fn(validationParams))
152+
, endEarly
153+
, value
154+
, path
155+
))
156+
.then(() => value)
165157
},
166158

167159
validate(value, options, cb) {
@@ -212,12 +204,6 @@ SchemaType.prototype = {
212204
)
213205
},
214206

215-
typeError(msg){
216-
var next = this.clone()
217-
next._typeError = formatError(msg)
218-
return next
219-
},
220-
221207
nullable(value) {
222208
var next = this.clone()
223209
next._nullable = value === false ? false : true
@@ -296,34 +282,67 @@ SchemaType.prototype = {
296282
return next
297283
},
298284

299-
oneOf(enums, msg) {
285+
typeError(message) {
300286
var next = this.clone()
301287

302-
if( next.tests.length )
303-
throw new TypeError('Cannot specify values when there are validation rules specified')
288+
next._typeError = createValidation({
289+
message,
290+
name: 'typeError',
291+
test(value) {
292+
if (value !== undefined && !this.schema.isType(value))
293+
return this.createError({
294+
params: { type: this.schema._type }
295+
})
296+
return true
297+
}
298+
})
299+
return next
300+
},
304301

305-
next._whitelistError = (valids, path) =>
306-
formatError(msg || locale.oneOf, { values: valids.join(', '), path })
302+
oneOf(enums, message = locale.oneOf) {
303+
var next = this.clone();
307304

308-
enums.forEach( val => {
305+
if (next.tests.length)
306+
throw new TypeError('Cannot specify values when there are validation rules specified')
307+
308+
enums.forEach(val => {
309309
next._blacklist.delete(val)
310310
next._whitelist.add(val)
311311
})
312312

313+
next._whitelistError = createValidation({
314+
message,
315+
name: 'oneOf',
316+
test(value) {
317+
let valids = this.schema._whitelist
318+
if (valids.length && !valids.has(value))
319+
return this.createError({ params: { values: valids.values().join(', ') }})
320+
return true
321+
}
322+
})
323+
313324
return next
314325
},
315326

316-
notOneOf(enums, msg) {
317-
var next = this.clone()
318-
319-
next._blacklistError = (invalids, path) =>
320-
formatError(msg || locale.notOneOf, { values: invalids.join(', '), path })
327+
notOneOf(enums, message = locale.notOneOf) {
328+
var next = this.clone();
321329

322330
enums.forEach( val => {
323331
next._whitelist.delete(val)
324332
next._blacklist.add(val)
325333
})
326334

335+
next._blacklistError = createValidation({
336+
message,
337+
name: 'notOneOf',
338+
test(value) {
339+
let invalids = this.schema._blacklist
340+
if (invalids.length && invalids.has(value))
341+
return this.createError({ params: { values: invalids.values().join(', ') }})
342+
return true
343+
}
344+
})
345+
327346
return next
328347
},
329348

src/util/createValidation.js

+7-10
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
11
'use strict';
22
var Promise = require('promise/lib/es6-extensions')
3-
, Condition = require('./condition')
4-
, ValidationError = require('./validation-error')
5-
, getter = require('property-expr').getter
6-
, locale = require('../locale.js').mixed
7-
, _ = require('./_');
3+
, ValidationError = require('./validation-error');
84

95
let formatError = ValidationError.formatError
106

11-
12-
function createErrorFactory(orginalMessage, orginalPath, value, params, originalType) {
13-
return function createError({ path = orginalPath, message = orginalMessage, type = originalType } = {}) {
7+
function createErrorFactory(orginalMessage, orginalPath, value, orginalParams, originalType) {
8+
return function createError({ path = orginalPath, message = orginalMessage, type = originalType, params } = {}) {
149
return new ValidationError(
15-
formatError(message, { path, value, ...params}), value, path, type)
10+
formatError(message, { path, value, ...orginalParams, ...params }), value, path, type)
1611
}
1712
}
1813

1914
module.exports = function createValidation(options) {
2015
let { name, message, test, params, useCallback } = options
21-
16+
2217
function validate({ value, path, state: { parent }, ...rest }) {
2318
var createError = createErrorFactory(message, path, value, params, name)
2419
var ctx = { path, parent, createError, type: name, ...rest }
@@ -43,3 +38,5 @@ module.exports = function createValidation(options) {
4338

4439
return validate
4540
}
41+
42+
module.exports.createErrorFactory = createErrorFactory

src/util/validation-error.js

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ValidationError.isError = function(err){
3838
}
3939

4040
ValidationError.formatError = function(message, params) {
41+
4142
if ( typeof message === 'string')
4243
message = replace(message)
4344

test/mixed.js

+31-3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ describe( 'Mixed Types ', function(){
4040
return inst.cast().should.equal('hello')
4141
})
4242

43+
it('should check types', function(){
44+
var inst = string().strict().typeError('must be a ${type}!')
45+
46+
return Promise.all([
47+
inst.validate(5).should.be.rejected.then(function(err) {
48+
err.type.should.equal('typeError')
49+
err.message.should.equal('must be a string!')
50+
err.inner.length.should.equal(0)
51+
}),
52+
inst.validate(5, { abortEarly: false }).should.be.rejected.then(function(err) {
53+
chai.expect(err.type).to.not.exist
54+
err.message.should.equal('must be a string!')
55+
err.inner.length.should.equal(1)
56+
})
57+
])
58+
})
59+
4360
it('should limit values', function(){
4461
var inst = mixed().oneOf(['hello', 5])
4562

@@ -65,11 +82,22 @@ describe( 'Mixed Types ', function(){
6582
inst.validate(5).should.be.rejected.then(function(err){
6683
err.errors[0].should.equal('this must not be one the following values: hello, 5')
6784
}),
68-
6985
inst.oneOf([5]).isValid(5).should.eventually.equal(true)
7086
])
7187
})
7288

89+
it('should run subset of validations first', function(){
90+
var called = false;
91+
var inst = string()
92+
.strict()
93+
.test('test', 'boom', () => called = true)
94+
95+
return inst.validate(25).should.be.rejected
96+
.then(() => {
97+
called.should.equal(false)
98+
})
99+
})
100+
73101
it('should respect strict', function(){
74102
var inst = string().equals(['hello', '5'])
75103

@@ -181,7 +209,7 @@ describe( 'Mixed Types ', function(){
181209
message: 'invalid',
182210
exclusive: true,
183211
name: 'max',
184-
test: function(){
212+
test() {
185213
this.path.should.equal('test')
186214
this.parent.should.eql({ other: 5, test : 'hi' })
187215
this.options.context.should.eql({ user: 'jason' })
@@ -197,7 +225,7 @@ describe( 'Mixed Types ', function(){
197225
var inst = mixed().test({
198226
message: 'invalid ${path}',
199227
name: 'max',
200-
test: function(){
228+
test() {
201229
return this.createError({ path: 'my.path' })
202230
}
203231
})

0 commit comments

Comments
 (0)