diff --git a/README.md b/README.md index d8c61452a..a7fb70948 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ json separate from validating it, via the `cast` method. - [`.reach(Schema schema, String path, Object options)`](#reachschema-schema-string-path-object-options) - [`.addMethod(schemaType, name, method)`](#addmethodschematype-name-method) - [`ValidationError(String|Array errors, Any value, String path)`](#validationerrorstringarraystring-errors-any-value-string-path) - - [`mixed`](#mixed) + - [mixed](#mixed) - [`mixed.clone()`](#mixedclone) - [`mixed.concat(Schema schema)`](#mixedconcatschema-schema) - [`mixed.validate(Any value, [Object options, Function callback])`](#mixedvalidateany-value-object-options-function-callback) @@ -30,11 +30,12 @@ json separate from validating it, via the `cast` method. - [`mixed.cast(value) -> Any`](#mixedcastvalue---any) - [`mixed.isType(Any value) -> Boolean`](#mixedistypeany-value---boolean) - [`mixed.strict()` (default: `false`)](#mixedstrict-default-false) + - [`mixed.withMutation(Function fn)`](#mixedwithmutationfunction-fn) - [`mixed.default(Any value)`](#mixeddefaultany-value) - [`mixed.default() -> Any`](#mixeddefault---any) - - [`mixed.typeError(String message)` (default: '${path} (value: \`${value}\`) must be a \`${type}\` type')](#mixedtypeerrorstring-message-default-path-value-%5Cvalue%5C-must-be-a-%5Ctype%5C-type) - - [`mixed.nullable(Bool isNullable)` (default: `false`)](#mixednullablebool-isnullable-default-false) + - [`mixed.nullable(Bool isNullable = false)`](#mixednullablebool-isnullable--false) - [`mixed.required([String message])`](#mixedrequiredstring-message) + - [`mixed.typeError(String message)`](#mixedtypeerrorstring-message) - [`mixed.oneOf(Array arrayOfValues, [String message])` Alias: `equals`](#mixedoneofarrayany-arrayofvalues-string-message-alias-equals) - [`mixed.notOneOf(Array arrayOfValues, [String message])`](#mixednotoneofarrayany-arrayofvalues-string-message) - [`mixed.when(String key, Object options | Function func)`](#mixedwhenstring-key-object-options--function-func) diff --git a/lib/array.js b/lib/array.js index 33565e59f..29bfcdcfa 100644 --- a/lib/array.js +++ b/lib/array.js @@ -4,6 +4,7 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument var MixedSchema = require('./mixed'); var Promise = require('promise/lib/es6-extensions'); +var isAbsent = require('./util/isAbsent'); var _require = require('./locale.js'); @@ -22,23 +23,29 @@ var scopeError = function scopeError(value) { }; }; +var hasLength = function hasLength(value) { + return !isAbsent(value) && value.length > 0; +}; + module.exports = ArraySchema; function ArraySchema() { + var _this = this; + if (!(this instanceof ArraySchema)) return new ArraySchema(); MixedSchema.call(this, { type: 'array' }); - this.transforms.push(function (values) { - if (typeof values === 'string') try { - values = JSON.parse(values); - } catch (err) { - values = null; - } - - if (Array.isArray(values)) return this._subType ? values.map(this._subType.cast, this._subType) : values; + this.withMutation(function () { + _this.transform(function (values) { + if (typeof values === 'string') try { + values = JSON.parse(values); + } catch (err) { + values = null; + } - return this.isType(values) ? values : null; + return this.isType(values) ? values : null; + }); }); } @@ -48,6 +55,19 @@ inherits(ArraySchema, MixedSchema, { return Array.isArray(v); }, + _cast: function _cast(_value, _opts) { + var _this2 = this; + + var value = MixedSchema.prototype._cast.call(this, _value); + + //should ignore nulls here + if (!this._typeCheck(value) || !this._subType) return value; + + return value.map(function (v) { + return _this2._subType.cast(v, _opts); + }); + }, + _validate: function _validate(_value, _opts, _state) { var errors = [], context, @@ -96,7 +116,7 @@ inherits(ArraySchema, MixedSchema, { required: function required(msg) { var next = MixedSchema.prototype.required.call(this, msg || mixed.required); - return next.min(1, msg || mixed.required); + return next.test('required', msg || mixed.required, hasLength); }, min: function min(_min, message) { @@ -108,7 +128,7 @@ inherits(ArraySchema, MixedSchema, { exclusive: true, params: { min: _min }, test: function test(value) { - return value && value.length >= _min; + return isAbsent(value) || value.length >= _min; } }); }, @@ -121,7 +141,7 @@ inherits(ArraySchema, MixedSchema, { exclusive: true, params: { max: _max }, test: function test(value) { - return value && value.length <= _max; + return isAbsent(value) || value.length <= _max; } }); }, diff --git a/lib/boolean.js b/lib/boolean.js index af836dd8a..c152b1e12 100644 --- a/lib/boolean.js +++ b/lib/boolean.js @@ -5,14 +5,18 @@ var MixedSchema = require('./mixed'), module.exports = BooleanSchema; function BooleanSchema() { + var _this = this; + if (!(this instanceof BooleanSchema)) return new BooleanSchema(); MixedSchema.call(this, { type: 'boolean' }); - this.transforms.push(function (value) { - if (this.isType(value)) return value; - return (/true|1/i.test(value) - ); + this.withMutation(function () { + _this.transform(function (value) { + if (this.isType(value)) return value; + return (/true|1/i.test(value) + ); + }); }); } diff --git a/lib/date.js b/lib/date.js index e13a9a505..80c161101 100644 --- a/lib/date.js +++ b/lib/date.js @@ -2,6 +2,7 @@ var MixedSchema = require('./mixed'); var isoParse = require('./util/isodate'); var locale = require('./locale.js').date; +var isAbsent = require('./util/isAbsent'); var _require = require('./util/_'); @@ -13,15 +14,19 @@ var invalidDate = new Date(''); module.exports = DateSchema; function DateSchema() { + var _this = this; + if (!(this instanceof DateSchema)) return new DateSchema(); MixedSchema.call(this, { type: 'date' }); - this.transforms.push(function (value) { - if (this.isType(value)) return isDate(value) ? new Date(value) : value; + this.withMutation(function () { + _this.transform(function (value) { + if (this.isType(value)) return isDate(value) ? new Date(value) : value; - value = isoParse(value); - return value ? new Date(value) : invalidDate; + value = isoParse(value); + return value ? new Date(value) : invalidDate; + }); }); } @@ -42,7 +47,7 @@ inherits(DateSchema, MixedSchema, { message: msg || locale.min, params: { min: _min }, test: function test(value) { - return value && value >= limit; + return isAbsent(value) || value >= limit; } }); }, @@ -58,7 +63,7 @@ inherits(DateSchema, MixedSchema, { message: msg || locale.max, params: { max: _max }, test: function test(value) { - return !value || value <= limit; + return isAbsent(value) || value <= limit; } }); } diff --git a/lib/locale.js b/lib/locale.js index 4cbe963e7..16b048cdd 100644 --- a/lib/locale.js +++ b/lib/locale.js @@ -37,7 +37,7 @@ module.exports = { boolean: {}, object: { - noUnknown: '${path} field cannot have keys not specified in the objcet shape' + noUnknown: '${path} field cannot have keys not specified in the object shape' }, array: { diff --git a/lib/mixed.js b/lib/mixed.js index 0cbc4ab61..7a2279f5e 100644 --- a/lib/mixed.js +++ b/lib/mixed.js @@ -5,15 +5,24 @@ var Promise = require('promise/lib/es6-extensions'), ValidationError = require('./util/validation-error'), locale = require('./locale.js').mixed, _ = require('./util/_'), + isAbsent = require('./util/isAbsent'), cloneDeep = require('./util/clone'), createValidation = require('./util/createValidation'), BadSet = require('./util/set'); -var formatError = ValidationError.formatError; +var notEmpty = function notEmpty(value) { + return !isAbsent(value); +}; + +function runValidations(validations, endEarly, value, path) { + return endEarly ? Promise.all(validations) : _.collectErrors(validations, value, path); +} module.exports = SchemaType; function SchemaType() { + var _this = this; + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; if (!(this instanceof SchemaType)) return new SchemaType(); @@ -25,7 +34,10 @@ function SchemaType() { this._blacklist = new BadSet(); this.tests = []; this.transforms = []; - this._typeError = formatError(locale.notType); + + this.withMutation(function () { + _this.typeError(locale.notType); + }); if (_.has(options, 'default')) this._defaultDefault = options['default']; @@ -55,16 +67,20 @@ SchemaType.prototype = { if (!schema) return this; if (schema._type !== this._type && this._type !== 'mixed') throw new TypeError('You cannot `concat()` schema\'s of different types: ' + this._type + ' and ' + schema._type); - + var cloned = this.clone(); var next = _.merge(this.clone(), schema.clone()); // undefined isn't merged over, but is a valid value for default if (schema._default === undefined && _.has(this, '_default')) next._default = schema._default; - // trim exclusive tests, take the most recent ones - next.tests = _.uniq(next.tests.reverse(), function (fn, idx) { - return next[fn.VALIDATION_KEY] ? fn.VALIDATION_KEY : idx; - }).reverse(); + next.tests = cloned.tests; + next._exclusive = cloned._exclusive; + + // manually add the new tests to ensure + // the deduping logic is consistent + schema.tests.forEach(function (fn) { + next = next.test(fn.TEST); + }); next._type = schema._type; @@ -82,10 +98,10 @@ SchemaType.prototype = { }, _cast: function _cast(_value) { - var _this = this; + var _this2 = this; var value = _value === undefined ? _value : this.transforms.reduce(function (value, transform) { - return transform.call(_this, value, _value); + return transform.call(_this2, value, _value); }, _value); if (value === undefined && _.has(this, '_default')) value = this['default'](); @@ -108,9 +124,7 @@ SchemaType.prototype = { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var state = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - var valids = this._whitelist, - invalids = this._blacklist, - context = options.context, + var context = options.context, parent = state.parent, value = _value, schema = undefined, @@ -123,41 +137,21 @@ SchemaType.prototype = { var path = state.path; - var errors = []; - var reject = function reject() { - return Promise.reject(new ValidationError(errors, value)); - }; - if (!state.isCast && !isStrict) value = schema._cast(value, options); // value is cast, we can check if it meets type requirements - if (value !== undefined && !schema.isType(value)) { - errors.push(schema._typeError({ value: value, path: path, type: schema._type })); - if (endEarly) return reject(); - } - - // next check Whitelist for matching values - if (valids.length && !valids.has(value)) { - errors.push(schema._whitelistError(valids.values(), path)); - if (endEarly) return reject(); - } - - // next check Blacklist for matching values - if (invalids.has(value)) { - errors.push(schema._blacklistError(invalids.values(), path)); - if (endEarly) return reject(); - } - - // It makes no sense to validate further at this point if their are errors - if (errors.length) return reject(); - - var result = schema.tests.map(function (fn) { - return fn({ value: value, path: path, state: state, schema: schema, options: options }); - }); - - result = endEarly ? Promise.all(result) : _.collectErrors(result, value, path); - - return result.then(function () { + var validationParams = { value: value, path: path, state: state, schema: schema, options: options }; + var initialTests = []; + + if (schema._typeError) initialTests.push(schema._typeError(validationParams)); + if (schema._whitelistError) initialTests.push(schema._whitelistError(validationParams)); + if (schema._blacklistError) initialTests.push(schema._blacklistError(validationParams)); + + return runValidations(initialTests, endEarly, value, path).then(function () { + return runValidations(schema.tests.map(function (fn) { + return fn(validationParams); + }), endEarly, value, path); + }).then(function () { return value; }); }, @@ -198,20 +192,7 @@ SchemaType.prototype = { }, required: function required(msg) { - return this.test({ - name: 'required', - exclusive: true, - message: msg || locale.required, - test: function test(value) { - return value != null; - } - }); - }, - - typeError: function typeError(msg) { - var next = this.clone(); - next._typeError = formatError(msg); - return next; + return this.test('required', msg || locale.required, notEmpty); }, nullable: function nullable(value) { @@ -226,10 +207,22 @@ SchemaType.prototype = { return next; }, + /** + * Adds a test function to the schema's queue of tests. + * tests can be exclusive or non-exclusive. + * + * - exclusive tests, will replace any existing tests of the same name. + * - non-exclusive: can be stacked + * + * If a non-exclusive test is added to a schema with an exclusive test of the same name + * the exclusive test is removed and further tests of the same name will be stacked. + * + * If an exclusive test is added to a schema with non-exclusive tests of the same name + * the previous tests are removed and further tests of the same name will replace each other. + */ test: function test(name, message, _test, useCallback) { var opts = name, - next = this.clone(), - isExclusive; + next = this.clone(); if (typeof name === 'string') { if (typeof message === 'function') _test = message, message = name, name = null; @@ -243,17 +236,20 @@ SchemaType.prototype = { var validate = createValidation(opts); - isExclusive = opts.name && next._exclusive[opts.name] === true; - - if (opts.exclusive || isExclusive) { - if (!opts.name) throw new TypeError('You cannot have an exclusive validation without a `name`'); + var isExclusive = opts.exclusive || opts.name && next._exclusive[opts.name] === true; - next._exclusive[opts.name] = true; - validate.VALIDATION_KEY = opts.name; + if (opts.exclusive && !opts.name) { + throw new TypeError('You cannot have an exclusive validation without a `name`'); } - if (isExclusive) next.tests = next.tests.filter(function (fn) { - return fn.VALIDATION_KEY !== opts.name; + next._exclusive[opts.name] = !!opts.exclusive; + + next.tests = next.tests.filter(function (fn) { + if (fn.TEST_NAME === opts.name) { + if (isExclusive) return false; + if (fn.TEST.test === validate.TEST.test) return false; + } + return true; }); next.tests.push(validate); @@ -270,35 +266,67 @@ SchemaType.prototype = { return next; }, - oneOf: function oneOf(enums, msg) { + typeError: function typeError(message) { var next = this.clone(); - if (next.tests.length) throw new TypeError('Cannot specify values when there are validation rules specified'); + next._typeError = createValidation({ + message: message, + name: 'typeError', + test: function test(value) { + if (value !== undefined && !this.schema.isType(value)) return this.createError({ + params: { type: this.schema._type } + }); + return true; + } + }); + return next; + }, + + oneOf: function oneOf(enums) { + var message = arguments.length <= 1 || arguments[1] === undefined ? locale.oneOf : arguments[1]; - next._whitelistError = function (valids, path) { - return formatError(msg || locale.oneOf, { values: valids.join(', '), path: path }); - }; + var next = this.clone(); + + if (next.tests.length) throw new TypeError('Cannot specify values when there are validation rules specified'); enums.forEach(function (val) { next._blacklist['delete'](val); next._whitelist.add(val); }); + next._whitelistError = createValidation({ + message: message, + name: 'oneOf', + test: function test(value) { + var valids = this.schema._whitelist; + if (valids.length && !(valids.has(value) || isAbsent(value))) return this.createError({ params: { values: valids.values().join(', ') } }); + return true; + } + }); + return next; }, - notOneOf: function notOneOf(enums, msg) { - var next = this.clone(); + notOneOf: function notOneOf(enums) { + var message = arguments.length <= 1 || arguments[1] === undefined ? locale.notOneOf : arguments[1]; - next._blacklistError = function (invalids, path) { - return formatError(msg || locale.notOneOf, { values: invalids.join(', '), path: path }); - }; + var next = this.clone(); enums.forEach(function (val) { next._whitelist['delete'](val); next._blacklist.add(val); }); + next._blacklistError = createValidation({ + message: message, + name: 'notOneOf', + test: function test(value) { + var invalids = this.schema._blacklist; + if (invalids.length && invalids.has(value)) return this.createError({ params: { values: invalids.values().join(', ') } }); + return true; + } + }); + return next; }, diff --git a/lib/number.js b/lib/number.js index 0817e3f09..73d80feb0 100644 --- a/lib/number.js +++ b/lib/number.js @@ -1,6 +1,7 @@ 'use strict'; var SchemaObject = require('./mixed'); var locale = require('./locale.js').number; +var isAbsent = require('./util/isAbsent'); var _require = require('./util/_'); @@ -9,16 +10,24 @@ var inherits = _require.inherits; module.exports = NumberSchema; +var isInteger = function isInteger(val) { + return isAbsent(val) || val === (val | 0); +}; + function NumberSchema() { + var _this = this; + if (!(this instanceof NumberSchema)) return new NumberSchema(); SchemaObject.call(this, { type: 'number' }); - this.transforms.push(function (value) { - if (this.isType(value)) return value; - if (typeof value === 'boolean') return value ? 1 : 0; + this.withMutation(function () { + _this.transform(function (value) { + if (this.isType(value)) return value; + if (typeof value === 'boolean') return value ? 1 : 0; - return isDate(value) ? +value : parseFloat(value); + return isDate(value) ? +value : parseFloat(value); + }); }); } @@ -38,7 +47,7 @@ inherits(NumberSchema, SchemaObject, { params: { min: _min }, message: msg || locale.min, test: function test(value) { - return value == null || value >= _min; + return isAbsent(value) || value >= _min; } }); }, @@ -50,7 +59,7 @@ inherits(NumberSchema, SchemaObject, { params: { max: _max }, message: msg || locale.max, test: function test(value) { - return value == null || value <= _max; + return isAbsent(value) || value <= _max; } }); }, @@ -66,11 +75,9 @@ inherits(NumberSchema, SchemaObject, { integer: function integer(msg) { msg = msg || locale.integer; - return this.transform(function (v) { - return v != null ? v | 0 : v; - }).test('integer', msg, function (val) { - return val == null || val === (val | 0); - }); + return this.transform(function (value) { + return !isAbsent(value) ? value | 0 : value; + }).test('integer', msg, isInteger); }, round: function round(method) { @@ -79,8 +86,8 @@ inherits(NumberSchema, SchemaObject, { if (avail.indexOf(method.toLowerCase()) === -1) throw new TypeError('Only valid options for round() are: ' + avail.join(', ')); - return this.transform(function (v) { - return v != null ? Math[method](v) : v; + return this.transform(function (value) { + return !isAbsent(value) ? Math[method](value) : value; }); } }); \ No newline at end of file diff --git a/lib/object.js b/lib/object.js index 5a4b156cf..57617f039 100644 --- a/lib/object.js +++ b/lib/object.js @@ -4,8 +4,6 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument var MixedSchema = require('./mixed'); var Promise = require('promise/lib/es6-extensions'); -//, Reference = require('./util/Reference') -var cloneDeep = require('./util/clone'); var toposort = require('toposort'); var locale = require('./locale.js').object; var split = require('property-expr').split; @@ -38,6 +36,8 @@ var scopeError = function scopeError(value) { module.exports = ObjectSchema; function ObjectSchema(spec) { + var _this2 = this; + if (!(this instanceof ObjectSchema)) return new ObjectSchema(spec); MixedSchema.call(this, { type: 'object', 'default': function _default() { @@ -51,18 +51,18 @@ function ObjectSchema(spec) { } }); - this.transforms.push(function coerce(value) { - if (typeof value === 'string') { - try { - value = JSON.parse(value); - } catch (err) { - value = null; + this.withMutation(function () { + _this2.transform(function coerce(value) { + if (typeof value === 'string') { + try { + value = JSON.parse(value); + } catch (err) { + value = null; + } } - } - - if (this.isType(value)) return value; - - return null; + if (this.isType(value)) return value; + return null; + }); }); this.fields = Object.create(null); diff --git a/lib/string.js b/lib/string.js index 794fb6d38..b12d71878 100644 --- a/lib/string.js +++ b/lib/string.js @@ -5,21 +5,33 @@ var _require = require('./locale.js'); var mixed = _require.mixed; var locale = _require.string; +var isAbsent = require('./util/isAbsent'); var inherits = require('./util/_').inherits; var rEmail = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; var rUrl = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i; +var hasLength = function hasLength(value) { + return !isAbsent(value) && value.length > 0; +}; +var isTrimmed = function isTrimmed(value) { + return isAbsent(value) || value === value.trim(); +}; + module.exports = StringSchema; function StringSchema() { + var _this = this; + if (!(this instanceof StringSchema)) return new StringSchema(); MixedSchema.call(this, { type: 'string' }); - this.transforms.push(function (value) { - if (this.isType(value)) return value; - return value == null ? '' : value.toString ? value.toString() : '' + value; + this.withMutation(function () { + _this.transform(function (value) { + if (this.isType(value)) return value; + return value == null ? '' : value.toString ? value.toString() : '' + value; + }); }); } @@ -32,7 +44,7 @@ inherits(StringSchema, MixedSchema, { required: function required(msg) { var next = MixedSchema.prototype.required.call(this, msg || mixed.required); - return next.min(1, msg || mixed.required); + return next.test('required', msg || mixed.required, hasLength); }, min: function min(_min, msg) { @@ -42,7 +54,7 @@ inherits(StringSchema, MixedSchema, { message: msg || locale.min, params: { min: _min }, test: function test(value) { - return value == null || value.length >= _min; + return isAbsent(value) || value.length >= _min; } }); }, @@ -54,7 +66,7 @@ inherits(StringSchema, MixedSchema, { message: msg || locale.max, params: { max: _max }, test: function test(value) { - return value == null || value.length <= _max; + return isAbsent(value) || value.length <= _max; } }); }, @@ -64,7 +76,7 @@ inherits(StringSchema, MixedSchema, { message: msg || locale.matches, params: { regex: regex }, test: function test(value) { - return value == null || regex.test(value); + return isAbsent(value) || regex.test(value); } }); }, @@ -83,33 +95,31 @@ inherits(StringSchema, MixedSchema, { return this.transform(function (val) { return val != null ? val.trim() : val; - }).test('trim', msg, function (val) { - return val == null || val === val.trim(); - }); + }).test('trim', msg, isTrimmed); }, lowercase: function lowercase(msg) { - return this.transform(function (val) { - return val != null ? val.toLowerCase() : val; + return this.transform(function (value) { + return !isAbsent(value) ? value.toLowerCase() : value; }).test({ name: 'string_case', exclusive: true, message: msg || locale.lowercase, - test: function test(val) { - return val == null || val === val.toLowerCase(); + test: function test(value) { + return isAbsent(value) || value === value.toLowerCase(); } }); }, uppercase: function uppercase(msg) { - return this.transform(function (val) { - return val != null ? val.toUpperCase() : val; + return this.transform(function (value) { + return !isAbsent(value) ? value.toUpperCase() : value; }).test({ name: 'string_case', exclusive: true, message: msg || locale.uppercase, - test: function test(val) { - return val == null || val === val.toUpperCase(); + test: function test(value) { + return isAbsent(value) || value === value.toUpperCase(); } }); } diff --git a/lib/util/createValidation.js b/lib/util/createValidation.js index 415d941ee..6777ff1d7 100644 --- a/lib/util/createValidation.js +++ b/lib/util/createValidation.js @@ -5,15 +5,11 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } var Promise = require('promise/lib/es6-extensions'), - Condition = require('./condition'), - ValidationError = require('./validation-error'), - getter = require('property-expr').getter, - locale = require('../locale.js').mixed, - _ = require('./_'); + ValidationError = require('./validation-error'); var formatError = ValidationError.formatError; -function createErrorFactory(orginalMessage, orginalPath, value, params, originalType) { +function createErrorFactory(orginalMessage, orginalPath, value, orginalParams, originalType) { return function createError() { var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; @@ -23,24 +19,25 @@ function createErrorFactory(orginalMessage, orginalPath, value, params, original var message = _ref$message === undefined ? orginalMessage : _ref$message; var _ref$type = _ref.type; var type = _ref$type === undefined ? originalType : _ref$type; + var params = _ref.params; - return new ValidationError(formatError(message, _extends({ path: path, value: value }, params)), value, path, type); + return new ValidationError(formatError(message, _extends({ path: path, value: value }, orginalParams, params)), value, path, type); }; } -module.exports = function createValidation(_ref2) { - var name = _ref2.name; - var message = _ref2.message; - var test = _ref2.test; - var params = _ref2.params; - var useCallback = _ref2.useCallback; +module.exports = function createValidation(options) { + var name = options.name; + var message = options.message; + var test = options.test; + var params = options.params; + var useCallback = options.useCallback; - function validate(_ref3) { - var value = _ref3.value; - var path = _ref3.path; - var parent = _ref3.state.parent; + function validate(_ref2) { + var value = _ref2.value; + var path = _ref2.path; + var parent = _ref2.state.parent; - var rest = _objectWithoutProperties(_ref3, ['value', 'path', 'state']); + var rest = _objectWithoutProperties(_ref2, ['value', 'path', 'state']); var createError = createErrorFactory(message, path, value, params, name); var ctx = _extends({ path: path, parent: parent, createError: createError, type: name }, rest); @@ -54,7 +51,11 @@ module.exports = function createValidation(_ref2) { }); } - validate.test_name = name; + validate.TEST_NAME = name; + validate.TEST_FN = test; + validate.TEST = options; return validate; -}; \ No newline at end of file +}; + +module.exports.createErrorFactory = createErrorFactory; \ No newline at end of file diff --git a/lib/util/isAbsent.js b/lib/util/isAbsent.js new file mode 100644 index 000000000..55a588e7e --- /dev/null +++ b/lib/util/isAbsent.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function (value) { + return value == null; +}; \ No newline at end of file diff --git a/lib/util/validation-error.js b/lib/util/validation-error.js index be986eb34..4c30a6dc3 100644 --- a/lib/util/validation-error.js +++ b/lib/util/validation-error.js @@ -43,6 +43,7 @@ ValidationError.isError = function (err) { }; ValidationError.formatError = function (message, params) { + if (typeof message === 'string') message = replace(message); var fn = function fn(_ref2) { diff --git a/package.json b/package.json index 0dd7870e0..2f7c37dd1 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "toposort": "^0.2.10" }, "release-script": { - "defaultDryRun": "false", - "skipBuildStep": "true" + "defaultDryRun": "false" } }