diff --git a/src/error.js b/src/error.js index 2bfaf42..308463e 100644 --- a/src/error.js +++ b/src/error.js @@ -5,7 +5,7 @@ */ export default class ValidationError extends Error { - constructor(message, path) { + constructor(message, path, type) { super(message); Object.defineProperty(this, 'path', { @@ -14,6 +14,12 @@ export default class ValidationError extends Error { writable: true, value: path }); + Object.defineProperty(this, 'type', { + enumerable: false, + configurable: true, + writable: true, + value: type + }); if (Error.captureStackTrace) { Error.captureStackTrace(this, ValidationError); diff --git a/src/property.js b/src/property.js index a097266..0b5ef3e 100644 --- a/src/property.js +++ b/src/property.js @@ -313,6 +313,51 @@ export default class Property { return null; } + /** + * Validate given `value` + * + * @example + * prop.type(Number) + * assert(prop.validate(2) == null) + * assert(prop.validate('hello world') instanceof Error) + * + * @param {Mixed} value - value to validate + * @param {Object} ctx - the object containing the value + * @param {String} [path] - path of the value being validated + * @return {ValidationError} + */ + + validateAll(value, ctx, path = this.name) { + const types = Object.keys(this.registry); + const done = {}; + const errors = [] + let err; + + // Required first + err = this._run('required', value, ctx, path); + if (err) errors.push(err); + + // No need to continue if value is null-ish + if (value == null) return null; + + // Run type second + err = this._run('type', value, ctx, path); + if (err) errors.push(err); + + // Make sure required and run are not executed again + done.required = true; + done.type = true; + + // Run the rest + for (let type of types) { + if (done[type]) continue; + err = this._run(type, value, ctx, path); + if (err) errors.push(err); + } + + return errors.length ? errors : null; + } + /** * Run validator of given `type` * @@ -327,7 +372,7 @@ export default class Property { _run(type, value, ctx, path) { if (!this.registry[type]) return; const schema = this._schema; - const {args, fn} = this.registry[type]; + const { args, fn } = this.registry[type]; let validator = fn || schema.validators[type]; let valid = validator(value, ctx, ...args, path); if (!valid) return this._error(type, ctx, args, path); @@ -371,6 +416,6 @@ export default class Property { message = message(path, ctx, ...args); } - return new ValidationError(message, path); + return new ValidationError(message, path, type); } } diff --git a/src/schema.js b/src/schema.js index f58a79c..a3018ec 100644 --- a/src/schema.js +++ b/src/schema.js @@ -249,6 +249,44 @@ export default class Schema { return errors; } + /** + * Validate given `obj`. + * + * @example + * const schema = new Schema({ name: { required: true }}) + * const errors = schema.validate({}) + * assert(errors.length == 1) + * assert(errors[0].message == 'name is required') + * assert(errors[0].path == 'name') + * + * @param {Object} obj - the object to validate + * @param {Object} [opts] - options, see [Schema](#schema-1) + * @return {Array} + */ + + validateAll(obj, opts = {}) { + opts = Object.assign(this.opts, opts); + + let errors = []; + + if (opts.typecast) { + this.typecast(obj); + } + + if (opts.strip !== false) { + this.strip(obj); + } + + for (let [path, prop] of Object.entries(this.props)) { + walk(path, obj, (key, value) => { + const err = prop.validateAll(value, obj, key); + if (err) errors = errors.concat(err); + }); + } + + return errors; + } + /** * Assert that given `obj` is valid. *