From 5d65038ce92a15c0af909304824e79eebfbf8315 Mon Sep 17 00:00:00 2001 From: jquense Date: Sun, 18 Oct 2015 08:20:25 -0400 Subject: [PATCH] add type to error --- lib/array.js | 140 +++++++++++++++ lib/boolean.js | 23 +++ lib/date.js | 66 +++++++ lib/index.js | 33 ++++ lib/locale.js | 48 ++++++ lib/mixed.js | 321 +++++++++++++++++++++++++++++++++++ lib/number.js | 84 +++++++++ lib/object.js | 255 ++++++++++++++++++++++++++++ lib/string.js | 116 +++++++++++++ lib/util/_.js | 121 +++++++++++++ lib/util/clone.js | 61 +++++++ lib/util/condition.js | 62 +++++++ lib/util/createValidation.js | 60 +++++++ lib/util/isodate.js | 42 +++++ lib/util/reach.js | 22 +++ lib/util/reference.js | 19 +++ lib/util/set.js | 48 ++++++ lib/util/validation-error.js | 72 ++++++++ src/util/validation-error.js | 4 +- 19 files changed, 1595 insertions(+), 2 deletions(-) create mode 100644 lib/array.js create mode 100644 lib/boolean.js create mode 100644 lib/date.js create mode 100644 lib/index.js create mode 100644 lib/locale.js create mode 100644 lib/mixed.js create mode 100644 lib/number.js create mode 100644 lib/object.js create mode 100644 lib/string.js create mode 100644 lib/util/_.js create mode 100644 lib/util/clone.js create mode 100644 lib/util/condition.js create mode 100644 lib/util/createValidation.js create mode 100644 lib/util/isodate.js create mode 100644 lib/util/reach.js create mode 100644 lib/util/reference.js create mode 100644 lib/util/set.js create mode 100644 lib/util/validation-error.js diff --git a/lib/array.js b/lib/array.js new file mode 100644 index 0000000..33565e5 --- /dev/null +++ b/lib/array.js @@ -0,0 +1,140 @@ +'use strict'; + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var MixedSchema = require('./mixed'); +var Promise = require('promise/lib/es6-extensions'); + +var _require = require('./locale.js'); + +var mixed = _require.mixed; +var locale = _require.array; + +var _require2 = require('./util/_'); + +var inherits = _require2.inherits; +var collectErrors = _require2.collectErrors; + +var scopeError = function scopeError(value) { + return function (err) { + err.value = value; + throw err; + }; +}; + +module.exports = ArraySchema; + +function ArraySchema() { + 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; + + return this.isType(values) ? values : null; + }); +} + +inherits(ArraySchema, MixedSchema, { + + _typeCheck: function _typeCheck(v) { + return Array.isArray(v); + }, + + _validate: function _validate(_value, _opts, _state) { + var errors = [], + context, + subType, + schema, + 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); + + return MixedSchema.prototype._validate.call(this, _value, _opts, _state)['catch'](endEarly ? null : function (err) { + errors = err; + return err.value; + }).then(function (value) { + if (!recursive || !subType || !schema._typeCheck(value)) { + if (errors.length) throw errors[0]; + return value; + } + + var result = value.map(function (item, key) { + var path = (_state.path || '') + '[' + key + ']', + state = _extends({}, _state, { path: path, key: key, parent: value }); + + return subType._validate(item, _opts, state); + }); + + result = endEarly ? Promise.all(result)['catch'](scopeError(value)) : collectErrors(result, value, _state.path, errors); + + return result.then(function () { + return value; + }); + }); + }, + + of: function of(schema) { + var next = this.clone(); + next._subType = schema; + return next; + }, + + required: function required(msg) { + var next = MixedSchema.prototype.required.call(this, msg || mixed.required); + + return next.min(1, msg || mixed.required); + }, + + min: function min(_min, message) { + message = message || locale.min; + + return this.test({ + message: message, + name: 'min', + exclusive: true, + params: { min: _min }, + test: function test(value) { + return value && value.length >= _min; + } + }); + }, + + max: function max(_max, message) { + message = message || locale.max; + return this.test({ + message: message, + name: 'max', + exclusive: true, + params: { max: _max }, + test: function test(value) { + return value && value.length <= _max; + } + }); + }, + + compact: function compact(rejector) { + var reject = !rejector ? function (v) { + return !!v; + } : function (v, i, a) { + return !rejector(v, i, a); + }; + + return this.transform(function (values) { + return values != null ? values.filter(reject) : values; + }); + } +}); \ No newline at end of file diff --git a/lib/boolean.js b/lib/boolean.js new file mode 100644 index 0000000..4141587 --- /dev/null +++ b/lib/boolean.js @@ -0,0 +1,23 @@ +'use strict'; +var MixedSchema = require('./mixed'), + inherits = require('./util/_').inherits; + +module.exports = BooleanSchema; + +function BooleanSchema() { + 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); + }); +} + +inherits(BooleanSchema, MixedSchema, { + + _typeCheck: function _typeCheck(v) { + return typeof v === 'boolean'; + } +}); \ No newline at end of file diff --git a/lib/date.js b/lib/date.js new file mode 100644 index 0000000..e13a9a5 --- /dev/null +++ b/lib/date.js @@ -0,0 +1,66 @@ +'use strict'; +var MixedSchema = require('./mixed'); +var isoParse = require('./util/isodate'); +var locale = require('./locale.js').date; + +var _require = require('./util/_'); + +var isDate = _require.isDate; +var inherits = _require.inherits; + +var invalidDate = new Date(''); + +module.exports = DateSchema; + +function DateSchema() { + 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; + + value = isoParse(value); + return value ? new Date(value) : invalidDate; + }); +} + +inherits(DateSchema, MixedSchema, { + + _typeCheck: function _typeCheck(v) { + return isDate(v) && !isNaN(v.getTime()); + }, + + min: function min(_min, msg) { + var limit = this.cast(_min); + + if (!this._typeCheck(limit)) throw new TypeError('`min` must be a Date or a value that can be `cast()` to a Date'); + + return this.test({ + name: 'min', + exclusive: true, + message: msg || locale.min, + params: { min: _min }, + test: function test(value) { + return value && value >= limit; + } + }); + }, + + max: function max(_max, msg) { + var limit = this.cast(_max); + + if (!this._typeCheck(limit)) throw new TypeError('`max` must be a Date or a value that can be `cast()` to a Date'); + + return this.test({ + name: 'max', + exclusive: true, + message: msg || locale.max, + params: { max: _max }, + test: function test(value) { + return !value || value <= limit; + } + }); + } + +}); \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..9f76973 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,33 @@ +'use strict'; +var mixed = require('./mixed'), + bool = require('./boolean'); + +var isSchema = function isSchema(schema) { + return schema && !!schema.__isYupSchema__; +}; + +module.exports = { + mixed: mixed, + string: require('./string'), + number: require('./number'), + boolean: bool, + bool: bool, + date: require('./date'), + object: require('./object'), + array: require('./array'), + + reach: require('./util/reach'), + + ValidationError: require('./util/validation-error'), + + isSchema: isSchema, + + addMethod: function addMethod(schemaType, name, fn) { + if (!schemaType || !isSchema(schemaType.prototype)) throw new TypeError('You must provide a yup schema constructor function'); + + if (typeof name !== 'string') throw new TypeError('A Method name must be provided'); + if (typeof fn !== 'function') throw new TypeError('Method function must be provided'); + + schemaType.prototype[name] = fn; + } +}; \ No newline at end of file diff --git a/lib/locale.js b/lib/locale.js new file mode 100644 index 0000000..4cbe963 --- /dev/null +++ b/lib/locale.js @@ -0,0 +1,48 @@ +'use strict'; + +module.exports = { + mixed: { + 'default': '${path} is invalid', + notType: '${path} (value: `${value}`) must be a `${type}` type', + required: '${path} is a required field', + oneOf: '${path} must be one the following values: ${values}', + notOneOf: '${path} must not be one the following values: ${values}' + }, + + string: { + required: '${path} is a required field', + min: '${path} must be at least ${min} characters', + max: '${path} must be less than ${max} characters', + matches: '${path} must match the following: "${regex}"', + email: '${path} must be a valid email', + url: '${path} must be a valid URL', + trim: '${path} must be a trimmed string', + lowercase: '${path} must be a lowercase string', + uppercase: '${path} must be a uppercase string' + }, + + number: { + min: '${path} must be at least ${min}', + max: '${path} must be less than or equal to ${max}', + positive: '${path} must be a positive number', + negative: '${path} must be a negative number', + integer: '${path} must be an integer' + }, + + date: { + min: '${path} field must be later than ${min}', + max: '${path} field must be at earlier than ${max}' + }, + + boolean: {}, + + object: { + noUnknown: '${path} field cannot have keys not specified in the objcet shape' + }, + + array: { + required: '${path} is a required field', + min: '${path} field must have at least ${min} items', + max: '${path} field must have less than ${max} items' + } +}; \ No newline at end of file diff --git a/lib/mixed.js b/lib/mixed.js new file mode 100644 index 0000000..c0e2d3d --- /dev/null +++ b/lib/mixed.js @@ -0,0 +1,321 @@ +'use strict'; + +var Promise = require('promise/lib/es6-extensions'), + Condition = require('./util/condition'), + ValidationError = require('./util/validation-error'), + locale = require('./locale.js').mixed, + _ = require('./util/_'), + cloneDeep = require('./util/clone'), + createValidation = require('./util/createValidation'), + BadSet = require('./util/set'); + +var formatError = ValidationError.formatError; + +module.exports = SchemaType; + +function SchemaType() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + if (!(this instanceof SchemaType)) return new SchemaType(); + + this._deps = []; + this._options = { abortEarly: true, recursive: true }; + this._exclusive = Object.create(null); + this._whitelist = new BadSet(); + this._blacklist = new BadSet(); + this.tests = []; + this.transforms = []; + this._typeError = formatError(locale.notType); + + if (_.has(options, 'default')) this._defaultDefault = options['default']; + + this._type = options.type || 'mixed'; +} + +SchemaType.prototype = { + + __isYupSchema__: true, + + constructor: SchemaType, + + clone: function clone() { + return cloneDeep(this); + }, + + concat: function concat(schema) { + if (!schema) return this; + + if (schema._type !== this._type) throw new TypeError('You cannot `concat()` schema\'s of different types: ' + this._type + ' and ' + schema._type); + + 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(); + + return next; + }, + + isType: function isType(v) { + if (this._nullable && v === null) return true; + return !this._typeCheck || this._typeCheck(v); + }, + + cast: function cast(_value, _opts) { + var schema = this._resolve((_opts || {}).context); + return schema._cast(_value, _opts); + }, + + _cast: function _cast(_value) { + var _this = this; + + var value = _value === undefined ? _value : this.transforms.reduce(function (value, transform) { + return transform.call(_this, value, _value); + }, _value); + + if (value === undefined && _.has(this, '_default')) value = this['default'](); + + return value; + }, + + _resolve: function _resolve(context, parent) { + var schema = this; + + return this._deps.reduce(function (schema, match) { + return match.resolve(schema, match.getValue(parent, context)); + }, schema); + }, + + //-- tests + _validate: function _validate(value) { + 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, + parent = state.parent, + schema = undefined, + endEarly = undefined, + isStrict = undefined; + + schema = this._resolve(context, parent); + isStrict = schema._option('strict', options); + endEarly = schema._option('abortEarly', options); + + 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 () { + return value; + }); + }, + + validate: function validate(value, options, cb) { + if (typeof options === 'function') cb = options, options = {}; + + return nodeify(this._validate(value, options, {}), cb); + }, + + isValid: function isValid(value, options, cb) { + if (typeof options === 'function') cb = options, options = {}; + + return nodeify(this.validate(value, options).then(function () { + return true; + })['catch'](function (err) { + if (err.name === 'ValidationError') return false; + + throw err; + }), cb); + }, + + 'default': function _default(def) { + if (arguments.length === 0) { + var dflt = _.has(this, '_default') ? this._default : this._defaultDefault; + return typeof dflt === 'function' ? dflt.call(this) : cloneDeep(dflt); + } + + var next = this.clone(); + next._default = def; + return next; + }, + + strict: function strict() { + var next = this.clone(); + next._options.strict = true; + return next; + }, + + 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; + }, + + nullable: function nullable(value) { + var next = this.clone(); + next._nullable = value === false ? false : true; + return next; + }, + + transform: function transform(fn) { + var next = this.clone(); + next.transforms.push(fn); + return next; + }, + + test: function test(name, message, _test, useCallback) { + var opts = name, + next = this.clone(), + errorMsg, + isExclusive; + + if (typeof name === 'string') { + if (typeof message === 'function') _test = message, message = name, name = null; + + opts = { name: name, test: _test, message: message, useCallback: useCallback, exclusive: false }; + } + + if (typeof opts.message !== 'string' || typeof opts.test !== 'function') throw new TypeError('`message` and `test` are required parameters'); + + if (next._whitelist.length) throw new Error('Cannot add tests when specific valid values are specified'); + + 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`'); + + next._exclusive[opts.name] = true; + validate.VALIDATION_KEY = opts.name; + } + + if (isExclusive) next.tests = next.tests.filter(function (fn) { + return fn.VALIDATION_KEY !== opts.name; + }); + + next.tests.push(validate); + + return next; + }, + + when: function when(key, options) { + var next = this.clone(), + dep = new Condition(key, next._type, options); + + next._deps.push(dep); + + return next; + }, + + oneOf: function oneOf(enums, msg) { + var next = this.clone(); + + if (next.tests.length) throw new TypeError('Cannot specify values when there are validation rules specified'); + + next._whitelistError = function (valids, path) { + return formatError(msg || locale.oneOf, { values: valids.join(', '), path: path }); + }; + + enums.forEach(function (val) { + next._blacklist['delete'](val); + next._whitelist.add(val); + }); + + return next; + }, + + notOneOf: function notOneOf(enums, msg) { + var next = this.clone(); + + next._blacklistError = function (invalids, path) { + return formatError(msg || locale.notOneOf, { values: invalids.join(', '), path: path }); + }; + + enums.forEach(function (val) { + next._whitelist['delete'](val); + next._blacklist.add(val); + }); + + return next; + }, + + _option: function _option(key, overrides) { + return _.has(overrides, key) ? overrides[key] : this._options[key]; + } +}; + +var aliases = { + oneOf: ['equals', 'is'], + notOneOf: ['not', 'nope'] +}; + +for (var method in aliases) if (_.has(aliases, method)) aliases[method].forEach(function (alias) { + return SchemaType.prototype[alias] = SchemaType.prototype[method]; +}); //eslint-disable-line no-loop-func + +function nodeify(promise, cb) { + if (typeof cb !== 'function') return promise; + + promise.then(function (val) { + return cb(null, val); + }, function (err) { + return cb(err); + }); +} + +// [{ value, exclude }] + +// values.every(({ value, exclude }) => { +// var isEql = eql(value, otherval) +// return (exclude && !isEql) || isEql +// }) \ No newline at end of file diff --git a/lib/number.js b/lib/number.js new file mode 100644 index 0000000..c71d5cd --- /dev/null +++ b/lib/number.js @@ -0,0 +1,84 @@ +'use strict'; +var SchemaObject = require('./mixed'); +var locale = require('./locale.js').number; + +var _require = require('./util/_'); + +var isDate = _require.isDate; +var inherits = _require.inherits; + +module.exports = NumberSchema; + +function NumberSchema() { + 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; + + return isDate(value) ? +value : parseFloat(value); + }); +} + +inherits(NumberSchema, SchemaObject, { + + _typeCheck: function _typeCheck(v) { + return typeof v === 'number' && !(v !== +v) //isNaN check + ; + }, + + min: function min(_min, msg) { + return this.test({ + name: 'min', + exclusive: true, + params: { min: _min }, + message: msg || locale.min, + test: function test(value) { + return value == null || value >= _min; + } + }); + }, + + max: function max(_max, msg) { + return this.test({ + name: 'max', + exclusive: true, + params: { max: _max }, + message: msg || locale.max, + test: function test(value) { + return value == null || value <= _max; + } + }); + }, + + positive: function positive(msg) { + return this.min(0, msg || locale.positive); + }, + + negative: function negative(msg) { + return this.max(0, msg || locale.negative); + }, + + 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); + }); + }, + + round: function round(method) { + var avail = ['ceil', 'floor', 'round']; + method = method && method.toLowerCase() || 'round'; + + 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; + }); + } +}); \ No newline at end of file diff --git a/lib/object.js b/lib/object.js new file mode 100644 index 0000000..a0211d7 --- /dev/null +++ b/lib/object.js @@ -0,0 +1,255 @@ +'use strict'; + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +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; +var c = require('case'); + +var _require = require('./util/_'); + +var isObject = _require.isObject; +var transform = _require.transform; +var assign = _require.assign; +var inherits = _require.inherits; +var collectErrors = _require.collectErrors; +var has = _require.has; + +var isRecursive = function isRecursive(schema) { + return (schema._subType || schema) === '$this'; +}; + +var childSchema = function childSchema(field, parent) { + return isRecursive(field) ? field.of ? field.of(parent) : parent : field; +}; + +var scopeError = function scopeError(value) { + return function (err) { + err.value = value; + throw err; + }; +}; + +module.exports = ObjectSchema; + +function ObjectSchema(spec) { + if (!(this instanceof ObjectSchema)) return new ObjectSchema(spec); + + MixedSchema.call(this, { type: 'object', 'default': function _default() { + var _this = this; + + var dft = transform(this._nodes, function (obj, key) { + obj[key] = _this.fields[key]['default'] ? _this.fields[key]['default']() : undefined; + }, {}); + + return Object.keys(dft).length === 0 ? undefined : dft; + } + }); + + this.transforms.push(function coerce(value) { + if (typeof value === 'string') { + try { + value = JSON.parse(value); + } catch (err) { + value = null; + } + } + + if (this.isType(value)) return value; + + return null; + }); + + this.fields = Object.create(null); + this._nodes = []; + this._excludedEdges = []; + + if (spec) return this.shape(spec); +} + +inherits(ObjectSchema, MixedSchema, { + + _typeCheck: function _typeCheck(value) { + return isObject(value) || typeof value === 'function'; + }, + + _cast: function _cast(_value, _opts) { + var schema = this, + value = MixedSchema.prototype._cast.call(schema, _value); + + //should ignore nulls here + if (schema._typeCheck(value)) { + var fields = schema.fields, + strip = schema._option('stripUnknown', _opts) === true, + extra = Object.keys(value).filter(function (v) { + return schema._nodes.indexOf(v) === -1; + }), + props = schema._nodes.concat(extra); + + return transform(props, function (obj, prop) { + var exists = has(value, prop); + + if (exists && fields[prop]) { + var fieldSchema = childSchema(fields[prop], schema['default'](undefined)); + + obj[prop] = fieldSchema.cast(value[prop], { context: obj }); + } else if (exists && !strip) obj[prop] = cloneDeep(value[prop]);else if (fields[prop]) { + var fieldDefault = fields[prop]['default'] ? fields[prop]['default']() : undefined; + + if (fieldDefault !== undefined) obj[prop] = fieldDefault; + } + }, {}); + } + + return value; + }, + + _validate: function _validate(_value, _opts, _state) { + var errors = [], + context, + schema, + endEarly, + recursive; + + _state = _state || {}; + context = _state.parent || (_opts || {}).context; + schema = this._resolve(context); + endEarly = schema._option('abortEarly', _opts); + recursive = schema._option('recursive', _opts); + + return MixedSchema.prototype._validate.call(this, _value, _opts, _state)['catch'](endEarly ? null : function (err) { + errors.push(err); + return err.value; + }).then(function (value) { + if (!recursive || !isObject(value)) { + // only iterate though actual objects + if (errors.length) throw errors[0]; + return value; + } + + var result = schema._nodes.map(function (key) { + var path = (_state.path ? _state.path + '.' : '') + key, + field = childSchema(schema.fields[key], schema); + + return field._validate(value[key], _opts, _extends({}, _state, { key: key, path: path, parent: value })); + }); + + result = endEarly ? Promise.all(result)['catch'](scopeError(value)) : collectErrors(result, value, _state.path, errors); + + return result.then(function () { + return value; + }); + }); + }, + + concat: function concat(schema) { + var next = MixedSchema.prototype.concat.call(this, schema); + + next._nodes = sortFields(next.fields, next._excludedEdges); + + return next; + }, + + shape: function shape(schema) { + var excludes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; + + var next = this.clone(), + fields = assign(next.fields, schema); + + if (!Array.isArray(excludes[0])) excludes = [excludes]; + + next.fields = fields; + + if (excludes.length) next._excludedEdges = next._excludedEdges.concat(excludes.map(function (v) { + return v[0] + '-' + v[1]; + })); // 'node-othernode' + + next._nodes = sortFields(fields, next._excludedEdges); + + return next; + }, + + from: function from(_from, to, alias) { + return this.transform(function (obj) { + if (obj == null) return obj; + + var newObj = transform(obj, function (o, val, key) { + return key !== _from && (o[key] = val); + }, {}); + + newObj[to] = obj[_from]; + if (alias) newObj[_from] = obj[_from]; + + return newObj; + }); + }, + + noUnknown: function noUnknown(noAllow, message) { + if (typeof noAllow === 'string') message = noAllow, noAllow = true; + + var next = this.test({ + name: 'noUnknown', + exclusive: true, + message: message || locale.noUnknown, + test: function test(value) { + return value == null || !noAllow || unknown(this.schema, value).length === 0; + } + }); + + if (noAllow) this._options.stripUnknown = true; + + return next; + }, + + camelcase: function camelcase() { + return this.transform(function (obj) { + return obj == null ? obj : transform(obj, function (newobj, val, key) { + return newobj[c.camel(key)] = val; + }); + }); + }, + + constantcase: function constantcase() { + return this.transform(function (obj) { + return obj == null ? obj : transform(obj, function (newobj, val, key) { + return newobj[c.constant(key)] = val; + }); + }); + } +}); + +function unknown(ctx, value) { + var known = Object.keys(ctx.fields); + return Object.keys(value).filter(function (key) { + return known.indexOf(key) === -1; + }); +} + +function sortFields(fields) { + var excludes = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; + + var edges = [], + nodes = []; + + for (var key in fields) if (has(fields, key)) { + if (! ~nodes.indexOf(key)) nodes.push(key); + + fields[key]._deps && fields[key]._deps.forEach(function (dep) { + //eslint-disable-line no-loop-func + if (dep.isContext) return; + + var node = split(dep.key)[0]; + + if (! ~nodes.indexOf(node)) nodes.push(node); + + if (! ~excludes.indexOf(key + '-' + node)) edges.push([key, node]); + }); + } + + return toposort.array(nodes, edges).reverse(); +} \ No newline at end of file diff --git a/lib/string.js b/lib/string.js new file mode 100644 index 0000000..c37360b --- /dev/null +++ b/lib/string.js @@ -0,0 +1,116 @@ +'use strict'; +var MixedSchema = require('./mixed'); + +var _require = require('./locale.js'); + +var mixed = _require.mixed; +var locale = _require.string; +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; + +module.exports = StringSchema; + +function StringSchema() { + 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; + }); +} + +inherits(StringSchema, MixedSchema, { + + _typeCheck: function _typeCheck(value) { + return typeof value === 'string'; + }, + + required: function required(msg) { + var next = MixedSchema.prototype.required.call(this, msg || mixed.required); + + return next.min(1, msg || mixed.required); + }, + + min: function min(_min, msg) { + return this.test({ + name: 'min', + exclusive: true, + message: msg || locale.min, + params: { min: _min }, + test: function test(value) { + return value == null || value.length >= _min; + } + }); + }, + + max: function max(_max, msg) { + return this.test({ + name: 'max', + exclusive: true, + message: msg || locale.max, + params: { max: _max }, + test: function test(value) { + return value == null || value.length <= _max; + } + }); + }, + + matches: function matches(regex, msg) { + return this.test({ + message: msg || locale.matches, + params: { regex: regex }, + test: function test(value) { + return value == null || regex.test(value); + } + }); + }, + + email: function email(msg) { + return this.matches(rEmail, msg || locale.email); + }, + + url: function url(msg) { + return this.matches(rUrl, msg || locale.url); + }, + + //-- transforms -- + trim: function trim(msg) { + msg = msg || locale.trim; + + return this.transform(function (val) { + return val != null ? val.trim() : val; + }).test('trim', msg, function (val) { + return val == null || val === val.trim(); + }); + }, + + lowercase: function lowercase(msg) { + return this.transform(function (val) { + return val != null ? val.toLowerCase() : val; + }).test({ + name: 'string_case', + exclusive: true, + message: msg || locale.lowercase, + test: function test(val) { + return val == null || val === val.toLowerCase(); + } + }); + }, + + uppercase: function uppercase(msg) { + return this.transform(function (val) { + return val != null ? val.toUpperCase() : val; + }).test({ + name: 'string_case', + exclusive: true, + message: msg || locale.uppercase, + test: function test(val) { + return val == null || val === val.toUpperCase(); + } + }); + } +}); \ No newline at end of file diff --git a/lib/util/_.js b/lib/util/_.js new file mode 100644 index 0000000..3381f09 --- /dev/null +++ b/lib/util/_.js @@ -0,0 +1,121 @@ +'use strict'; + +var Promise = require('promise/lib/es6-extensions'), + ValidationError = require('./validation-error'); + +var toString = Object.prototype.toString; + +var isObject = function isObject(obj) { + return obj && toString.call(obj) === '[object Object]'; +}; + +var isPlainObject = function isPlainObject(obj) { + return isObject(obj) && Object.getPrototypeOf(obj) === Object.prototype; +}; + +var isDate = function isDate(obj) { + return Object.prototype.toString.call(obj) === '[object Date]'; +}; + +var isSchema = function isSchema(obj) { + return obj && obj.__isYupSchema__; +}; + +function settled(promises) { + var settle = function settle(promise) { + return promise.then(function (value) { + return { fulfilled: true, value: value }; + }, function (value) { + return { fulfilled: false, value: value }; + }); + }; + + return Promise.all(promises.map(settle)); +} + +function collectErrors(promises, value, path) { + var errors = arguments.length <= 3 || arguments[3] === undefined ? [] : arguments[3]; + + // unwrap aggregate errors + errors = errors.inner && errors.inner.length ? errors.inner : [].concat(errors); + + return settled(promises).then(function (results) { + errors = results.reduce(function (arr, r) { + return !r.fulfilled ? arr.concat(r.value) : arr; + }, errors); + + if (errors.length) throw new ValidationError(errors, value, path); + }); +} + +function assign(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) if (has(source, key)) target[key] = source[key]; + } + + return target; +} + +function uniq(arr, iter) { + var seen = {}; + + return arr.filter(function (item, idx) { + var key = iter(item, idx); + + if (has(seen, key)) return false; + return seen[key] = true; + }); +} + +function transform(obj, cb, seed) { + cb = cb.bind(null, seed = seed || (Array.isArray(obj) ? [] : {})); + + if (Array.isArray(obj)) obj.forEach(cb);else for (var key in obj) if (has(obj, key)) cb(obj[key], key, obj); + + return seed; +} + +function merge(target, source) { + for (var key in source) if (has(source, key)) { + var targetVal = target[key], + sourceVal = source[key]; + + if (sourceVal === undefined) continue; + + if (isSchema(sourceVal)) { + target[key] = isSchema(targetVal) ? targetVal.concat(sourceVal) : sourceVal; + } else if (isObject(sourceVal)) { + target[key] = isObject(targetVal) ? merge(targetVal, sourceVal) : sourceVal; + } else if (Array.isArray(sourceVal)) { + target[key] = Array.isArray(targetVal) ? targetVal.concat(sourceVal) : sourceVal; + } else target[key] = source[key]; + } + + return target; +} + +function has(o, k) { + return o ? Object.prototype.hasOwnProperty.call(o, k) : false; +} + +function inherits(ctor, superCtor, spec) { + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + + assign(ctor.prototype, spec); +} + +module.exports = { + inherits: inherits, uniq: uniq, has: has, + assign: assign, merge: merge, transform: transform, + isSchema: isSchema, isObject: isObject, isPlainObject: isPlainObject, isDate: isDate, + settled: settled, collectErrors: collectErrors +}; \ No newline at end of file diff --git a/lib/util/clone.js b/lib/util/clone.js new file mode 100644 index 0000000..ab26932 --- /dev/null +++ b/lib/util/clone.js @@ -0,0 +1,61 @@ +// Copyright (c) 2011-2014, Walmart and other contributors. +// Copyright (c) 2011, Yahoo Inc. +// All rights reserved. https://github.com/hapijs/hoek/blob/master/LICENSE + +'use strict'; + +module.exports = function clone(obj, seen) { + var isFirst = !seen; + + if (typeof obj !== 'object' || obj === null) return obj; + + seen = seen || { orig: [], copy: [] }; + + var lookup = seen.orig.indexOf(obj); + + if (lookup !== -1) return seen.copy[lookup]; + + var newObj; + var cloneDeep = false; + + if (!Array.isArray(obj)) { + if (obj instanceof Date) { + newObj = new Date(obj.getTime()); + } else if (obj instanceof RegExp) { + newObj = new RegExp(obj); + } else { + var proto = Object.getPrototypeOf(obj); + + if (proto !== null && (!proto || proto.__isYupSchema__ && !isFirst)) { + newObj = obj; + } else { + newObj = Object.create(proto); + cloneDeep = true; + } + } + } else { + newObj = []; + cloneDeep = true; + } + + seen.orig.push(obj); + seen.copy.push(newObj); + + if (cloneDeep) { + var keys = Object.getOwnPropertyNames(obj); + + for (var i = 0, il = keys.length; i < il; ++i) { + var key = keys[i]; + + var descriptor = Object.getOwnPropertyDescriptor(obj, key); + + if (descriptor.get || descriptor.set) { + Object.defineProperty(newObj, key, descriptor); + } else { + newObj[key] = clone(obj[key], seen); + } + } + } + + return newObj; +}; \ No newline at end of file diff --git a/lib/util/condition.js b/lib/util/condition.js new file mode 100644 index 0000000..4c4eba0 --- /dev/null +++ b/lib/util/condition.js @@ -0,0 +1,62 @@ +'use strict'; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _require = require('./_'); + +var has = _require.has; +var isSchema = _require.isSchema; +var getter = require('property-expr').getter; + +module.exports = Conditional; + +var Conditional = (function () { + function Conditional(key, type, options) { + _classCallCheck(this, Conditional); + + var is = options.is; + var then = options.then; + var otherwise = options.otherwise; + var prefix = options.contextPrefix || '$'; + + this.prefix = prefix; + this.key = key; + this.isContext = key.indexOf(prefix) === 0; + + if (typeof options === 'function') this.fn = options;else { + if (!has(options, 'is')) 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'); + + if (options.then && options.then._type !== type || options.otherwise && options.otherwise._type !== type) throw new TypeError('cannot create polymorphic conditionals, `then` and `otherwise` must be the same type: ' + type); + + is = typeof is === 'function' ? is : (function (is, value) { + return is === value; + }).bind(null, is); + + this.fn = function (value, ctx) { + return is(value) ? ctx.concat(then) : ctx.concat(otherwise); + }; + } + } + + Conditional.prototype.getValue = function getValue(parent, context) { + var path = this.isContext ? this.key.slice(this.prefix.length) : this.key; + + if (this.isContext && !context || !this.isContext && !context && !parent) throw new Error('missing the context necessary to cast this value'); + + return getter(path)(this.isContext ? context : parent || context); + }; + + Conditional.prototype.resolve = function resolve(ctx, value) { + var schema = this.fn.call(ctx, value, ctx); + + if (schema !== undefined && !isSchema(schema)) throw new TypeError('conditions must return a schema object'); + + return schema || ctx; + }; + + return Conditional; +})(); + +module.exports = Conditional; \ No newline at end of file diff --git a/lib/util/createValidation.js b/lib/util/createValidation.js new file mode 100644 index 0000000..415d941 --- /dev/null +++ b/lib/util/createValidation.js @@ -0,0 +1,60 @@ +'use strict'; + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +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('./_'); + +var formatError = ValidationError.formatError; + +function createErrorFactory(orginalMessage, orginalPath, value, params, originalType) { + return function createError() { + var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var _ref$path = _ref.path; + var path = _ref$path === undefined ? orginalPath : _ref$path; + var _ref$message = _ref.message; + var message = _ref$message === undefined ? orginalMessage : _ref$message; + var _ref$type = _ref.type; + var type = _ref$type === undefined ? originalType : _ref$type; + + return new ValidationError(formatError(message, _extends({ path: path, value: value }, 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; + + function validate(_ref3) { + var value = _ref3.value; + var path = _ref3.path; + var parent = _ref3.state.parent; + + var rest = _objectWithoutProperties(_ref3, ['value', 'path', 'state']); + + var createError = createErrorFactory(message, path, value, params, name); + var ctx = _extends({ path: path, parent: parent, createError: createError, type: name }, rest); + + return new Promise(function (resolve, reject) { + !useCallback ? resolve(test.call(ctx, value)) : test.call(ctx, value, function (err, valid) { + return err ? reject(err) : resolve(valid); + }); + }).then(function (validOrError) { + if (ValidationError.isError(validOrError)) throw validOrError;else if (!validOrError) throw createError(); + }); + } + + validate.test_name = name; + + return validate; +}; \ No newline at end of file diff --git a/lib/util/isodate.js b/lib/util/isodate.js new file mode 100644 index 0000000..312d106 --- /dev/null +++ b/lib/util/isodate.js @@ -0,0 +1,42 @@ +/** + * Date.parse with progressive enhancement for ISO 8601 + * NON-CONFORMANT EDITION. + * © 2011 Colin Snover + * Released under MIT license. + */ +// 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm +'use strict'; + +var isoReg = /^(\d{4}|[+\-]\d{6})(?:-?(\d{2})(?:-?(\d{2}))?)?(?:[ T]?(\d{2}):?(\d{2})(?::?(\d{2})(?:[,\.](\d{1,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?)?)?$/; + +module.exports = function parseIsoDate(date) { + var numericKeys = [1, 4, 5, 6, 7, 10, 11], + minutesOffset = 0, + timestamp, + struct; + + if (struct = isoReg.exec(date)) { + // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC + for (var i = 0, k; k = numericKeys[i]; ++i) struct[k] = +struct[k] || 0; + + // allow undefined days and months + struct[2] = (+struct[2] || 1) - 1; + struct[3] = +struct[3] || 1; + + // allow arbitrary sub-second precision beyond milliseconds + struct[7] = struct[7] ? +(struct[7] + '00').substr(0, 3) : 0; + + // timestamps without timezone identifiers should be considered local time + if ((struct[8] === undefined || struct[8] === '') && (struct[9] === undefined || struct[9] === '')) timestamp = +new Date(struct[1], struct[2], struct[3], struct[4], struct[5], struct[6], struct[7]);else { + if (struct[8] !== 'Z' && struct[9] !== undefined) { + minutesOffset = struct[10] * 60 + struct[11]; + + if (struct[9] === '+') minutesOffset = 0 - minutesOffset; + } + + timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); + } + } else timestamp = Date.parse ? Date.parse(date) : NaN; + + return timestamp; +}; \ No newline at end of file diff --git a/lib/util/reach.js b/lib/util/reach.js new file mode 100644 index 0000000..a2a7986 --- /dev/null +++ b/lib/util/reach.js @@ -0,0 +1,22 @@ +'use strict'; + +var _require = require('property-expr'); + +var forEach = _require.forEach; + +var trim = function trim(part) { + return part.substr(0, part.length - 1).substr(1); +}; + +module.exports = function (obj, path) { + forEach(path, function (part, isBracket, isArray) { + if (isArray) obj = obj._subType;else { + if (obj._subType) // we skipped an array + obj = obj._subType; + + obj = obj.fields[isBracket ? trim(part) : part]; + } + }); + + return obj; +}; \ No newline at end of file diff --git a/lib/util/reference.js b/lib/util/reference.js new file mode 100644 index 0000000..f2f88b7 --- /dev/null +++ b/lib/util/reference.js @@ -0,0 +1,19 @@ +"use strict"; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Reference = (function () { + function Reference(string) { + _classCallCheck(this, Reference); + + this._deps = []; + } + + Reference.prototype["default"] = function _default() {}; + + Reference.prototype.cast = function cast(value, parent, options) { + return parent["default"](undefined).cast(value, options); + }; + + return Reference; +})(); \ No newline at end of file diff --git a/lib/util/set.js b/lib/util/set.js new file mode 100644 index 0000000..7615073 --- /dev/null +++ b/lib/util/set.js @@ -0,0 +1,48 @@ +'use strict'; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var toString = Object.prototype.toString; +var isDate = function isDate(obj) { + return toString.call(obj) === '[object Date]'; +}; + +module.exports = (function () { + function BadSet() { + _classCallCheck(this, BadSet); + + this._array = []; + this.length = 0; + } + + BadSet.prototype.values = function values() { + return this._array; + }; + + BadSet.prototype.add = function add(item) { + if (!this.has(item)) this._array.push(item); + + this.length = this._array.length; + }; + + BadSet.prototype['delete'] = function _delete(item) { + var idx = indexOf(this._array, item); + if (idx !== -1) this._array.splice(idx, 1); + + this.length = this._array.length; + }; + + BadSet.prototype.has = function has(val) { + return indexOf(this._array, val) !== -1; + }; + + return BadSet; +})(); + +function indexOf(arr, val) { + for (var i = 0; i < arr.length; i++) { + var item = arr[i]; + if (item === val || isDate(item) && +val === +item) return i; + } + return -1; +} \ No newline at end of file diff --git a/lib/util/validation-error.js b/lib/util/validation-error.js new file mode 100644 index 0000000..340ba6e --- /dev/null +++ b/lib/util/validation-error.js @@ -0,0 +1,72 @@ +'use strict'; + +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 strReg = /\$\{\s*(\w+)\s*\}/g; + +var replace = function replace(str) { + return function (params) { + return str.replace(strReg, function (_, key) { + return params[key] || ''; + }); + }; +}; + +module.exports = ValidationError; + +function ValidationError(errors, value, field, type) { + var _this = this; + + this.name = 'ValidationError'; + this.value = value; + this.path = field; + this.type = type; + this.errors = []; + this.inner = []; + + if (errors) [].concat(errors).forEach(function (err) { + _this.errors = _this.errors.concat(err.errors || err); + + if (err.inner) _this.inner = _this.inner.concat(err.inner.length ? err.inner : err); + }); + + this.message = this.errors.length > 1 ? this.errors.length + ' errors occurred' : this.errors[0]; + + if (Error.captureStackTrace) Error.captureStackTrace(this, ValidationError); +} + +ValidationError.prototype = Object.create(Error.prototype); +ValidationError.prototype.constructor = ValidationError; + +ValidationError.isError = function (err) { + return err && err.name === 'ValidationError'; +}; + +ValidationError.formatError = function (message, params) { + if (typeof message === 'string') message = replace(message); + + var fn = function fn(_ref2) { + var path = _ref2.path; + + var params = _objectWithoutProperties(_ref2, ['path']); + + params.path = path || 'this'; + + return message(params); + }; + + return arguments.length === 1 ? fn : fn(params); +}; + +ValidationError.prototype.toJSON = function () { + var _ref; + + if (this.inner.length) return this.inner.reduce(function (list, e) { + list[e.path] = (list[e.path] || (list[e.path] = [])).concat({ errors: e.errors, path: e.path, type: e.type }); + return list; + }, {}); + + if (this.path) return (_ref = {}, _ref[this.path] = err.errors, _ref); + + return err.errors; +}; \ No newline at end of file diff --git a/src/util/validation-error.js b/src/util/validation-error.js index 110d6e8..e090026 100644 --- a/src/util/validation-error.js +++ b/src/util/validation-error.js @@ -53,12 +53,12 @@ ValidationError.formatError = function(message, params) { ValidationError.prototype.toJSON = function(){ if (this.inner.length) return this.inner.reduce((list, e) => { - list[e.path] = (list[e.path] || (list[e.path] = [])).concat({ errors: e.errors, path: e.path, type: e.type }) + list[e.path] = (list[e.path] || (list[e.path] = [])).concat(e.toJSON()) return list }, {}) if (this.path) - return { [this.path]: err.errors } + return { [this.path]: { errors: this.errors, path: this.path, type: this.type } } return err.errors }