不要怂,就是干,撸起袖子干!

major refactoring of dao-validator module

1 parent 84509752
Showing with 182 additions and 99 deletions
...@@ -3,6 +3,8 @@ var Validator = require("validator") ...@@ -3,6 +3,8 @@ var Validator = require("validator")
, sequelizeError = require("./errors") , sequelizeError = require("./errors")
, Promise = require("bluebird") , Promise = require("bluebird")
function noop() {}
// Backwards compat for people using old validation function // Backwards compat for people using old validation function
// We cannot use .extend, since it coerces the first arg to string // We cannot use .extend, since it coerces the first arg to string
Validator.notNull = function (val) { Validator.notNull = function (val) {
...@@ -81,16 +83,19 @@ Validator.extend('is', function(str, pattern, modifiers) { ...@@ -81,16 +83,19 @@ Validator.extend('is', function(str, pattern, modifiers) {
* The Main DAO Validator. * The Main DAO Validator.
* *
* @param {sequelize.Model} modelInstance The model instance. * @param {sequelize.Model} modelInstance The model instance.
* @param {Object} options A dict with options. * @param {Object=} optOptions A dict with options.
* @constructor * @constructor
*/ */
var DaoValidator = module.exports = function(modelInstance, options) { var DaoValidator = module.exports = function(modelInstance, optOptions) {
options = options || {} var options = optOptions || {}
options.skip = options.skip || []
// assign defined and default options
this.options = Utils._.defaults(options, {
skip: [],
});
this.modelInstance = modelInstance this.modelInstance = modelInstance
this.chainer = new Utils.QueryChainer() this.chainer = new Utils.QueryChainer()
this.options = options
/** /**
* Expose validator.js to allow users to extend * Expose validator.js to allow users to extend
...@@ -104,7 +109,7 @@ var DaoValidator = module.exports = function(modelInstance, options) { ...@@ -104,7 +109,7 @@ var DaoValidator = module.exports = function(modelInstance, options) {
* @type {Object} Will contain keys that correspond to attributes which will * @type {Object} Will contain keys that correspond to attributes which will
* be Arrays of Errors. * be Arrays of Errors.
*/ */
this.errors = {}; this.errors = {}
/** @type {boolean} Indicates if validations are in progress */ /** @type {boolean} Indicates if validations are in progress */
this.inProgress = false; this.inProgress = false;
...@@ -123,27 +128,20 @@ DaoValidator.prototype.validate = function() { ...@@ -123,27 +128,20 @@ DaoValidator.prototype.validate = function() {
throw new Error('Validations already in progress.'); throw new Error('Validations already in progress.');
} }
this.inProgress = true; this.inProgress = true;
this.errors = []; this.errors = {}
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
Promise.settle([ Promise.settle([
self._validateAttributes(), self._builtinValidators(),
self._validateSchema(), self._customValidators(),
]).then(function () { ]).then(function () {
emitter.emit('success') if (Object.keys(self.errors).length) {
}).catch(function(err) { emitter.emit('success', self.errors)
var error = new sequelizeError.ValidationError('Validation error') } else {
error[DaoValidator.RAW_KEY_NAME] = [] emitter.emit('success')
}
Utils._.each(err, function (value) { })
error[DaoValidator.RAW_KEY_NAME].push(value[DaoValidator.RAW_KEY_NAME]);
delete value[DaoValidator.RAW_KEY_NAME]
Utils._.extend(error, value)
})
emitter.emit('success', error)
})
}).run() }).run()
} }
...@@ -182,22 +180,30 @@ DaoValidator.prototype.hookValidate = function() { ...@@ -182,22 +180,30 @@ DaoValidator.prototype.hookValidate = function() {
} }
/** /**
* Will validate all attributes based on their schema rules and defined validators. * Will run all the built-in validators.
* *
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .settle(). * @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .settle().
* @private * @private
*/ */
DaoValidator.prototype._validateAttributes = function() { DaoValidator.prototype._builtinValidators = function() {
var self = this var self = this
// promisify all attribute invocations // promisify all attribute invocations
var validators = []; var validators = [];
_.forIn(this.modelInstance.rawAttributes, function(rawAttribute, field) { Utils._.forIn(this.modelInstance.rawAttributes, function(rawAttribute, field) {
if (self.options.skip.indexOf(field) >= 0) {
return
}
var value = self.modelInstance.dataValues[field] var value = self.modelInstance.dataValues[field]
if (self.modelInstance.validators.hasOwnProperty(field)) {
validators.push(self._validateAttribute(value, field)) if (!rawAttribute._autoGenerated) {
// perform validations based on schema
self._validateSchema(rawAttribute, field, value)
}
if (self.modelInstance.validators.hasOwnProperty(field)) {
validators.push(self._builtinAttrValidate.call(self, value, field))
} }
}) })
...@@ -205,42 +211,30 @@ DaoValidator.prototype._validateAttributes = function() { ...@@ -205,42 +211,30 @@ DaoValidator.prototype._validateAttributes = function() {
} }
/** /**
* Will validate a single field against its schema definition (isnull). * Will run all the custom validators.
* *
* @param {string} field The field name. * @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .settle().
* @param {*} value anything.
throw error;
* @return {Promise} A promise, will always resolve,
* auto populates error on this.error local object.
* @private * @private
*/ */
DaoValidator.prototype._validateSchema = function(field, value) { DaoValidator.prototype._customValidators = function() {
var validators = [];
var self = this; var self = this;
return new Promise(function(resolve) { Utils._.each(this.modelInstance.__options.validate, function(validator,
validatorType) {
var hasAllowedNull = ((rawAttribute === undefined ||
rawAttribute.allowNull === true) && ((value === null) ||
(value === undefined)))
var isSkipped = self.options.skip.length > 0 &&
self.options.skip.indexOf(field) !== -1
if (!hasAllowedNull && !isSkipped) {
var error = new sequelizeError.ValidationError(field + ' cannot be null')
error.path = field
error.value = value
error.type = 'notNull Violation'
if (!self.errors[field]) {
self.errors[field] = [];
}
self.errors[field].push(error);
}
resolve(); var valprom = self._invokeCustomValidator(validator, validatorType)
}); // errors are handled in settling, stub this
}; .catch(noop)
validators.push(valprom)
})
return Promise.settle(validators)
}
/** /**
* Validate a single attribute with all the defined validators. * Validate a single attribute with all the defined built-in validators.
* *
* @param {*} value Anything. * @param {*} value Anything.
* @param {string} field The field name. * @param {string} field The field name.
...@@ -248,86 +242,175 @@ DaoValidator.prototype._validateSchema = function(field, value) { ...@@ -248,86 +242,175 @@ DaoValidator.prototype._validateSchema = function(field, value) {
* auto populates error on this.error local object. * auto populates error on this.error local object.
* @private * @private
*/ */
DaoValidator.prototype._validateAttribute = function(value, field) { DaoValidator.prototype._builtinAttrValidate = function(value, field) {
var self = this; var self = this;
// Promisify each validator // Promisify each validator
var validators = []; var validators = [];
Utils._.forIn(this.modelInstance.validators[field], function(details, Utils._.forIn(this.modelInstance.validators[field], function(test,
validatorType) { validatorType) {
var validator = self._prepareValidationOfAttribute.call(self, value, details, // Check for custom validator.
validatorType); if (typeof test === 'function') {
return validators.push(self._invokeCustomValidator(test, validatorType,
true, value, field))
}
validators.push(Promise.nodeify(validator)); var validatorPromise = self._invokeBuiltinValidator(value, test, validatorType);
// errors are handled in settling, stub this
validatorPromise.catch(noop)
validators.push(validatorPromise)
}); });
return Promise.settle(validators) return Promise.settle(validators)
.then(this._handleSettledResult.bind(this, field)); .then(this._handleSettledResult.bind(this, field))
}; };
/** /**
* Prepare Attribute for validation. * Prepare and invoke a custom validator.
*
* @param {Function} validator The custom validator.
* @param {string} validatorType the custom validator type (name).
* @param {boolean=} optAttrDefined Set to true if custom validator was defined
* from the Attribute
* @return {Promise} A promise.
* @private
*/
DaoValidator.prototype._invokeCustomValidator = Promise.method(function(validator, validatorType,
optAttrDefined, optValue, optField) {
var validatorFunction = null // the validation function to call
var isAsync = false
var validatorArity = validator.length;
// check if validator is async and requires a callback
var asyncArity = 1;
var errorKey = validatorType;
var invokeArgs;
if (optAttrDefined) {
asyncArity = 2;
invokeArgs = optValue;
errorKey = optField;
}
if (validatorArity === asyncArity) {
isAsync = true;
}
if (isAsync) {
if (optAttrDefined) {
validatorFunction = Promise.promisify(validator.bind(this.modelInstance, invokeArgs))
} else {
validatorFunction = Promise.promisify(validator.bind(this.modelInstance))
}
return validatorFunction()
.catch(this._pushError.bind(this, false, errorKey))
} else {
return Promise.try(validator.bind(this.modelInstance, invokeArgs))
.catch(this._pushError.bind(this, false, errorKey))
}
})
/**
* Prepare and invoke a build-in validator.
* *
* @param {*} value Anything * @param {*} value Anything.
* @param {Function} validator The validator. * @param {*} test The test case.
* @param {string} validatorType One of known to Sequelize validators. * @param {string} validatorType One of known to Sequelize validators.
* @param {Object=} optOptions Options
* @return {Object} An object with specific keys to invoke the validator. * @return {Object} An object with specific keys to invoke the validator.
* @private * @private
*/ */
DaoValidator.prototype._prepareValidationOfAttribute = function(value, validator, DaoValidator.prototype._invokeBuiltinValidator = Promise.method(function(value,
validatorType, optOptions) { test, validatorType) {
var isCustomValidator = false // if true then it's a custom validation method
, validatorFunction = null // the validation function to call
// it is a custom validator function? // check if Validator knows that kind of validation test
isCustomValidator = true if (typeof Validator[validatorType] !== 'function') {
throw new Error('Invalid validator function: ' + validatorType)
}
var validatorArity = validator.length; // extract extra arguments for the validator
var callArgs = [] var validatorArgs = test.hasOwnProperty('args') ? test.args : test;
var options = optOptions || {} // extract the error msg
var omitValue = !!options.omitValue var errorMessage = test.hasOwnProperty('msg') ? test.msg :
'Validation ' + validatorType + ' failed'
if (!omitValue) {
callArgs.push(value)
}
// check if validator is async and requires a callback if (!Array.isArray(validatorArgs)) {
var isAsync = false validatorArgs = [validatorArgs]
if (omitValue && validatorArity === 1 || !omitValue && validatorArity === 2) { } else {
isAsync = true; validatorArgs = validatorArgs.slice(0);
}
if (!Validator[validatorType].apply(Validator, [value].concat(validatorArgs))) {
throw errorMessage
} }
});
validatorFunction = Promise.nodeify(validator.bind(this.modelInstance))
return { /**
fn: validatorFunction, * Will validate a single field against its schema definition (isnull).
msg: errorMessage, *
args: validatorArgs, * @param {Object} rawAttribute As defined in the Schema.
isCustom: isCustomValidator * @param {string} field The field name.
* @param {*} value anything.
* @private
*/
DaoValidator.prototype._validateSchema = function(rawAttribute,
field, value) {
if (rawAttribute.allowNull === false && ((value === null) ||
(value === undefined))) {
var error = new sequelizeError.ValidationError(field + ' cannot be null')
error.path = field
error.value = value
error.type = 'notNull Violation'
if (!this.errors.hasOwnProperty(field)) {
this.errors[field] = [];
}
this.errors[field].push(error);
} }
} };
/** /**
* Handles the returned result of a Promise.settle. * Handles the returned result of a Promise.settle.
* *
* If errors are found it populates this.error and throws an Array of the errors. * If errors are found it populates this.error.
* *
* @param {string} field The attribute name. * @param {string} field The attribute name.
* @param {Array.<Promise.PromiseInspection>} Promise inspection objects. * @param {Array.<Promise.PromiseInspection>} Promise inspection objects.
* @private * @private
*/ */
DaoValidator.prototype._handleSettleResult = function(field, promiseInspections) { DaoValidator.prototype._handleSettledResult = function(field, promiseInspections) {
var self = this; var self = this;
promiseInspections.forEach(function(promiseInspection) { promiseInspections.forEach(function(promiseInspection) {
if (promiseInspection.isRejected) { if (promiseInspection.isRejected()) {
var rejection = promiseInspection.error(); var rejection = promiseInspection.error();
var error = new sequelizeError.ValidationError('Validation error') self._pushError(true, field, rejection);
error[DaoValidator.RAW_KEY_NAME] = rejection
if (!self.errors[field]) {
self.errors[field] = [];
}
self.errors[field].push(error);
} }
}); });
}; };
/**
* Signs all errors retaining the original.
*
* @param {boolean} isBuiltin Determines if error is from builtin validator.
* @param {string} errorKey The error key to assign on this.errors object.
* @param {Error|string} rawError The original error.
* @private
*/
DaoValidator.prototype._pushError = function(isBuiltin, errorKey, rawError) {
if (!this.errors.hasOwnProperty(errorKey)) {
this.errors[errorKey] = [];
}
if (isBuiltin) {
this.errors[errorKey].push(rawError);
return;
}
var error = new sequelizeError.ValidationError()
error[DaoValidator.RAW_KEY_NAME] = rawError
error.message = rawError.message || rawError || 'Validation error'
this.errors[errorKey].push(error);
};
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!