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

major refactoring of dao-validator module

1 parent 84509752
Showing with 178 additions and 95 deletions
......@@ -3,6 +3,8 @@ var Validator = require("validator")
, sequelizeError = require("./errors")
, Promise = require("bluebird")
function noop() {}
// Backwards compat for people using old validation function
// We cannot use .extend, since it coerces the first arg to string
Validator.notNull = function (val) {
......@@ -81,16 +83,19 @@ Validator.extend('is', function(str, pattern, modifiers) {
* The Main DAO Validator.
*
* @param {sequelize.Model} modelInstance The model instance.
* @param {Object} options A dict with options.
* @param {Object=} optOptions A dict with options.
* @constructor
*/
var DaoValidator = module.exports = function(modelInstance, options) {
options = options || {}
options.skip = options.skip || []
var DaoValidator = module.exports = function(modelInstance, optOptions) {
var options = optOptions || {}
// assign defined and default options
this.options = Utils._.defaults(options, {
skip: [],
});
this.modelInstance = modelInstance
this.chainer = new Utils.QueryChainer()
this.options = options
/**
* Expose validator.js to allow users to extend
......@@ -104,7 +109,7 @@ var DaoValidator = module.exports = function(modelInstance, options) {
* @type {Object} Will contain keys that correspond to attributes which will
* be Arrays of Errors.
*/
this.errors = {};
this.errors = {}
/** @type {boolean} Indicates if validations are in progress */
this.inProgress = false;
......@@ -123,26 +128,19 @@ DaoValidator.prototype.validate = function() {
throw new Error('Validations already in progress.');
}
this.inProgress = true;
this.errors = [];
this.errors = {}
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
Promise.settle([
self._validateAttributes(),
self._validateSchema(),
self._builtinValidators(),
self._customValidators(),
]).then(function () {
if (Object.keys(self.errors).length) {
emitter.emit('success', self.errors)
} else {
emitter.emit('success')
}).catch(function(err) {
var error = new sequelizeError.ValidationError('Validation error')
error[DaoValidator.RAW_KEY_NAME] = []
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()
}
......@@ -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().
* @private
*/
DaoValidator.prototype._validateAttributes = function() {
DaoValidator.prototype._builtinValidators = function() {
var self = this
// promisify all attribute invocations
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]
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() {
}
/**
* Will validate a single field against its schema definition (isnull).
* Will run all the custom validators.
*
* @param {string} field The field name.
* @param {*} value anything.
throw error;
* @return {Promise} A promise, will always resolve,
* auto populates error on this.error local object.
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .settle().
* @private
*/
DaoValidator.prototype._validateSchema = function(field, value) {
DaoValidator.prototype._customValidators = function() {
var validators = [];
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
var valprom = self._invokeCustomValidator(validator, validatorType)
// errors are handled in settling, stub this
.catch(noop)
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);
}
validators.push(valprom)
})
resolve();
});
};
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 {string} field The field name.
......@@ -248,86 +242,175 @@ DaoValidator.prototype._validateSchema = function(field, value) {
* auto populates error on this.error local object.
* @private
*/
DaoValidator.prototype._validateAttribute = function(value, field) {
DaoValidator.prototype._builtinAttrValidate = function(value, field) {
var self = this;
// Promisify each validator
var validators = [];
Utils._.forIn(this.modelInstance.validators[field], function(details,
Utils._.forIn(this.modelInstance.validators[field], function(test,
validatorType) {
var validator = self._prepareValidationOfAttribute.call(self, value, details,
validatorType);
// Check for custom validator.
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)
.then(this._handleSettledResult.bind(this, field));
.then(this._handleSettledResult.bind(this, field))
};
/**
* Prepare Attribute for validation.
* Prepare and invoke a custom validator.
*
* @param {*} value Anything
* @param {Function} validator The 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 {*} test The test case.
* @param {string} validatorType One of known to Sequelize validators.
* @param {Object=} optOptions Options
* @return {Object} An object with specific keys to invoke the validator.
* @private
*/
DaoValidator.prototype._prepareValidationOfAttribute = function(value, validator,
validatorType, optOptions) {
var isCustomValidator = false // if true then it's a custom validation method
, validatorFunction = null // the validation function to call
DaoValidator.prototype._invokeBuiltinValidator = Promise.method(function(value,
test, validatorType) {
// it is a custom validator function?
isCustomValidator = true
// check if Validator knows that kind of validation test
if (typeof Validator[validatorType] !== 'function') {
throw new Error('Invalid validator function: ' + validatorType)
}
var validatorArity = validator.length;
var callArgs = []
var options = optOptions || {}
var omitValue = !!options.omitValue
// extract extra arguments for the validator
var validatorArgs = test.hasOwnProperty('args') ? test.args : test;
// extract the error msg
var errorMessage = test.hasOwnProperty('msg') ? test.msg :
'Validation ' + validatorType + ' failed'
if (!omitValue) {
callArgs.push(value)
}
// check if validator is async and requires a callback
var isAsync = false
if (omitValue && validatorArity === 1 || !omitValue && validatorArity === 2) {
isAsync = true;
if (!Array.isArray(validatorArgs)) {
validatorArgs = [validatorArgs]
} else {
validatorArgs = validatorArgs.slice(0);
}
if (!Validator[validatorType].apply(Validator, [value].concat(validatorArgs))) {
throw errorMessage
}
});
validatorFunction = Promise.nodeify(validator.bind(this.modelInstance))
/**
* Will validate a single field against its schema definition (isnull).
*
* @param {Object} rawAttribute As defined in the Schema.
* @param {string} field The field name.
* @param {*} value anything.
* @private
*/
DaoValidator.prototype._validateSchema = function(rawAttribute,
field, value) {
return {
fn: validatorFunction,
msg: errorMessage,
args: validatorArgs,
isCustom: isCustomValidator
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.
*
* 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 {Array.<Promise.PromiseInspection>} Promise inspection objects.
* @private
*/
DaoValidator.prototype._handleSettleResult = function(field, promiseInspections) {
DaoValidator.prototype._handleSettledResult = function(field, promiseInspections) {
var self = this;
promiseInspections.forEach(function(promiseInspection) {
if (promiseInspection.isRejected) {
if (promiseInspection.isRejected()) {
var rejection = promiseInspection.error();
var error = new sequelizeError.ValidationError('Validation error')
error[DaoValidator.RAW_KEY_NAME] = rejection
if (!self.errors[field]) {
self.errors[field] = [];
}
self.errors[field].push(error);
self._pushError(true, field, rejection);
}
});
};
/**
* 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!