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

Commit bf19d9a9 by Felix Becker Committed by Jan Aagaard Meier

ES6 refactor of instance-validator.js (#6044)

1 parent 797720d9
Showing with 290 additions and 293 deletions
'use strict'; 'use strict';
var validator = require('./utils/validator-extras').validator const validator = require('./utils/validator-extras').validator;
, extendModelValidations = require('./utils/validator-extras').extendModelValidations const extendModelValidations = require('./utils/validator-extras').extendModelValidations;
, Utils = require('./utils') const Utils = require('./utils');
, sequelizeError = require('./errors') const sequelizeError = require('./errors');
, Promise = require('./promise') const Promise = require('./promise');
, DataTypes = require('./data-types') const DataTypes = require('./data-types');
, _ = require('lodash'); const _ = require('lodash');
/** /**
* The Main Instance Validator. * The Main Instance Validator.
...@@ -15,341 +15,338 @@ var validator = require('./utils/validator-extras').validator ...@@ -15,341 +15,338 @@ var validator = require('./utils/validator-extras').validator
* @param {Object} options A dict with options. * @param {Object} options A dict with options.
* @constructor * @constructor
*/ */
var InstanceValidator = module.exports = function(modelInstance, options) { class InstanceValidator {
options = _.clone(options) || {};
if (options.fields && !options.skip) { constructor(modelInstance, options) {
options.skip = Utils._.difference(Object.keys(modelInstance.constructor.attributes), options.fields); options = _.clone(options) || {};
}
// assign defined and default options if (options.fields && !options.skip) {
this.options = Utils._.defaults(options, { options.skip = Utils._.difference(Object.keys(modelInstance.constructor.attributes), options.fields);
skip: [] }
});
this.modelInstance = modelInstance; // assign defined and default options
this.options = Utils._.defaults(options, {
skip: []
});
/** this.modelInstance = modelInstance;
* Exposes a reference to validator.js. This allows you to add custom validations using `validator.extend`
* @name validator
*/
this.validator = validator;
/** /**
* All errors will be stored here from the validations. * Exposes a reference to validator.js. This allows you to add custom validations using `validator.extend`
* * @name validator
* @type {Array} Will contain keys that correspond to attributes which will */
* be Arrays of Errors. this.validator = validator;
*/
this.errors = [];
/** @type {boolean} Indicates if validations are in progress */ /**
this.inProgress = false; * All errors will be stored here from the validations.
*
* @type {Array} Will contain keys that correspond to attributes which will
* be Arrays of Errors.
*/
this.errors = [];
extendModelValidations(modelInstance); /** @type {boolean} Indicates if validations are in progress */
}; this.inProgress = false;
/** @define {string} The error key for arguments as passed by custom validators */ extendModelValidations(modelInstance);
InstanceValidator.RAW_KEY_NAME = '__raw';
/**
* The main entry point for the Validation module, invoke to start the dance.
*
* @return {Promise}
*/
InstanceValidator.prototype._validate = function() {
if (this.inProgress) {
throw new Error('Validations already in progress.');
} }
this.inProgress = true;
/**
var self = this; * The main entry point for the Validation module, invoke to start the dance.
return Promise.all([ *
self._builtinValidators(), * @return {Promise}
self._customValidators() */
].map(function(promise) { _validate() {
return promise.reflect(); if (this.inProgress) {
})).then(function() { throw new Error('Validations already in progress.');
if (self.errors.length) {
throw new sequelizeError.ValidationError(null, self.errors);
} }
}); this.inProgress = true;
};
/** return Promise.all(
* Invoke the Validation sequence: [this._builtinValidators(), this._customValidators()].map(promise => promise.reflect())
* - Before Validation Model Hooks ).then(() => {
* - Validation if (this.errors.length) {
* - On validation success: After Validation Model Hooks throw new sequelizeError.ValidationError(null, this.errors);
* - On validation failure: Validation Failed Model Hooks }
* });
* @return {Promise}
*/
InstanceValidator.prototype.validate = function() {
if (this.options.hooks) {
return this.modelInstance.constructor.runHooks('beforeValidate', this.modelInstance, this.options).bind(this).then(function() {
return this._validate().bind(this).catch(function(error) {
return this.modelInstance.constructor.runHooks('validationFailed', this.modelInstance, this.options, error).then(function(newError) {
throw newError || error;
});
});
}).then(function() {
return this.modelInstance.constructor.runHooks('afterValidate', this.modelInstance, this.options);
}).return(this.modelInstance);
} }
return this._validate();
}; /**
* Invoke the Validation sequence:
/** * - Before Validation Model Hooks
* Will run all the built-in validators. * - Validation
* * - On validation success: After Validation Model Hooks
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .reflect(). * - On validation failure: Validation Failed Model Hooks
* @private *
*/ * @return {Promise}
InstanceValidator.prototype._builtinValidators = function() { */
var self = this; validate() {
if (this.options.hooks) {
// promisify all attribute invocations return this.modelInstance.constructor.runHooks('beforeValidate', this.modelInstance, this.options)
var validators = []; .then(() =>
Utils._.forIn(this.modelInstance.rawAttributes, function(rawAttribute, field) { this._validate().catch(error =>
if (self.options.skip.indexOf(field) >= 0) { this.modelInstance.constructor.runHooks('validationFailed', this.modelInstance, this.options, error).then(newError => {
return; throw newError || error;
})
)
)
.then(() => this.modelInstance.constructor.runHooks('afterValidate', this.modelInstance, this.options))
.return(this.modelInstance);
} }
return this._validate();
}
var value = self.modelInstance.dataValues[field]; /**
* Will run all the built-in validators.
*
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .reflect().
* @private
*/
_builtinValidators() {
// promisify all attribute invocations
const validators = [];
Utils._.forIn(this.modelInstance.rawAttributes, (rawAttribute, field) => {
if (this.options.skip.indexOf(field) >= 0) {
return;
}
if (!rawAttribute._autoGenerated && !rawAttribute.autoIncrement) { const value = this.modelInstance.dataValues[field];
// perform validations based on schema
self._validateSchema(rawAttribute, field, value);
}
if (self.modelInstance.validators.hasOwnProperty(field)) { if (!rawAttribute._autoGenerated && !rawAttribute.autoIncrement) {
validators.push(self._builtinAttrValidate.call(self, value, field).reflect()); // perform validations based on schema
} this._validateSchema(rawAttribute, field, value);
}); }
return Promise.all(validators); if (this.modelInstance.validators.hasOwnProperty(field)) {
}; validators.push(this._builtinAttrValidate.call(this, value, field).reflect());
}
});
/** return Promise.all(validators);
* Will run all the custom validators. }
*
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .reflect().
* @private
*/
InstanceValidator.prototype._customValidators = function() {
var validators = [];
var self = this;
Utils._.each(this.modelInstance.$modelOptions.validate, function(validator, validatorType) {
if (self.options.skip.indexOf(validatorType) >= 0) {
return;
}
var valprom = self._invokeCustomValidator(validator, validatorType) /**
// errors are handled in settling, stub this * Will run all the custom validators.
.catch(function() {}) *
.reflect(); * @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .reflect().
* @private
*/
_customValidators() {
const validators = [];
Utils._.each(this.modelInstance.$modelOptions.validate, (validator, validatorType) => {
if (this.options.skip.indexOf(validatorType) >= 0) {
return;
}
validators.push(valprom); const valprom = this._invokeCustomValidator(validator, validatorType)
}); // errors are handled in settling, stub this
.catch(() => {})
.reflect();
return Promise.all(validators); validators.push(valprom);
}; });
/** return Promise.all(validators);
* Validate a single attribute with all the defined built-in validators.
*
* @param {*} value Anything.
* @param {string} field The field name.
* @return {Promise} A promise, will always resolve,
* auto populates error on this.error local object.
* @private
*/
InstanceValidator.prototype._builtinAttrValidate = function(value, field) {
var self = this;
// check if value is null (if null not allowed the Schema pass will capture it)
if (value === null || typeof value === 'undefined') {
return Promise.resolve();
} }
// Promisify each validator /**
var validators = []; * Validate a single attribute with all the defined built-in validators.
Utils._.forIn(this.modelInstance.validators[field], function(test, *
validatorType) { * @param {*} value Anything.
* @param {string} field The field name.
if (['isUrl', 'isURL', 'isEmail'].indexOf(validatorType) !== -1) { * @return {Promise} A promise, will always resolve,
// Preserve backwards compat. Validator.js now expects the second param to isURL and isEmail to be an object * auto populates error on this.error local object.
if (typeof test === 'object' && test !== null && test.msg) { * @private
test = { */
msg: test.msg _builtinAttrValidate(value, field) {
}; // check if value is null (if null not allowed the Schema pass will capture it)
} else if (test === true) { if (value === null || typeof value === 'undefined') {
test = {}; return Promise.resolve();
}
} }
// Check for custom validator. // Promisify each validator
if (typeof test === 'function') { const validators = [];
return validators.push(self._invokeCustomValidator(test, validatorType, true, value, field).reflect()); Utils._.forIn(this.modelInstance.validators[field], (test, validatorType) => {
}
if (['isUrl', 'isURL', 'isEmail'].indexOf(validatorType) !== -1) {
// Preserve backwards compat. Validator.js now expects the second param to isURL and isEmail to be an object
if (typeof test === 'object' && test !== null && test.msg) {
test = {
msg: test.msg
};
} else if (test === true) {
test = {};
}
}
var validatorPromise = self._invokeBuiltinValidator(value, test, validatorType, field); // Check for custom validator.
// errors are handled in settling, stub this if (typeof test === 'function') {
validatorPromise.catch(function() {}); return validators.push(this._invokeCustomValidator(test, validatorType, true, value, field).reflect());
validators.push(validatorPromise.reflect()); }
});
return Promise.all(validators).then(this._handleReflectedResult.bind(this, field)); const validatorPromise = this._invokeBuiltinValidator(value, test, validatorType, field);
}; // errors are handled in settling, stub this
validatorPromise.catch(() => {});
validators.push(validatorPromise.reflect());
});
/** return Promise.all(validators).then(this._handleReflectedResult.bind(this, field));
* 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
*/
InstanceValidator.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) { /**
* 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
*/
_invokeCustomValidator(validator, validatorType, optAttrDefined, optValue, optField) {
let validatorFunction = null; // the validation function to call
let isAsync = false;
const validatorArity = validator.length;
// check if validator is async and requires a callback
let asyncArity = 1;
let errorKey = validatorType;
let invokeArgs;
if (optAttrDefined) { if (optAttrDefined) {
validatorFunction = Promise.promisify(validator.bind(this.modelInstance, invokeArgs)); 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 { } else {
validatorFunction = Promise.promisify(validator.bind(this.modelInstance)); return Promise.try(validator.bind(this.modelInstance, invokeArgs)).catch(this._pushError.bind(this, false, errorKey));
} }
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. * Prepare and invoke a build-in validator.
* *
* @param {*} value Anything. * @param {*} value Anything.
* @param {*} test The test case. * @param {*} test The test case.
* @param {string} validatorType One of known to Sequelize validators. * @param {string} validatorType One of known to Sequelize validators.
* @param {string} field The field that is being validated * @param {string} field The field that is being validated
* @return {Object} An object with specific keys to invoke the validator. * @return {Object} An object with specific keys to invoke the validator.
* @private * @private
*/ */
InstanceValidator.prototype._invokeBuiltinValidator = Promise.method(function(value, test, validatorType, field) { _invokeBuiltinValidator(value, test, validatorType, field) {
var self = this; return Promise.try(() => {
// Cast value as string to pass new Validator.js string requirement // Cast value as string to pass new Validator.js string requirement
var valueString = String(value); const valueString = String(value);
// check if Validator knows that kind of validation test // check if Validator knows that kind of validation test
if (typeof validator[validatorType] !== 'function') { if (typeof validator[validatorType] !== 'function') {
throw new Error('Invalid validator function: ' + validatorType); throw new Error('Invalid validator function: ' + validatorType);
} }
var validatorArgs = self._extractValidatorArgs(test, validatorType, field); const validatorArgs = this._extractValidatorArgs(test, validatorType, field);
if (!validator[validatorType].apply(validator, [valueString].concat(validatorArgs))) { if (!validator[validatorType].apply(validator, [valueString].concat(validatorArgs))) {
// extract the error msg // extract the error msg
throw new Error(test.msg || 'Validation ' + validatorType + ' failed'); throw new Error(test.msg || 'Validation ' + validatorType + ' failed');
}
});
} }
});
/** /**
* Will extract arguments for the validator. * Will extract arguments for the validator.
* *
* @param {*} test The test case. * @param {*} test The test case.
* @param {string} validatorType One of known to Sequelize validators. * @param {string} validatorType One of known to Sequelize validators.
* @param {string} field The field that is being validated. * @param {string} field The field that is being validated.
* @private * @private
*/ */
InstanceValidator.prototype._extractValidatorArgs = function(test, validatorType, field) { _extractValidatorArgs(test, validatorType, field) {
var validatorArgs = test.args || test; let validatorArgs = test.args || test;
var isLocalizedValidator = typeof(validatorArgs) !== 'string' && (validatorType === 'isAlpha' || validatorType === 'isAlphanumeric' || validatorType === 'isMobilePhone'); const isLocalizedValidator = typeof(validatorArgs) !== 'string' && (validatorType === 'isAlpha' || validatorType === 'isAlphanumeric' || validatorType === 'isMobilePhone');
if (!Array.isArray(validatorArgs)) { if (!Array.isArray(validatorArgs)) {
if (validatorType === 'isImmutable') { if (validatorType === 'isImmutable') {
validatorArgs = [validatorArgs, field]; validatorArgs = [validatorArgs, field];
} else if (isLocalizedValidator || validatorType === 'isIP') { } else if (isLocalizedValidator || validatorType === 'isIP') {
validatorArgs = []; validatorArgs = [];
} else {
validatorArgs = [validatorArgs];
}
} else { } else {
validatorArgs = [validatorArgs]; validatorArgs = validatorArgs.slice(0);
} }
} else { return validatorArgs;
validatorArgs = validatorArgs.slice(0);
} }
return validatorArgs;
};
/**
* 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
*/
InstanceValidator.prototype._validateSchema = function(rawAttribute, field, value) {
var error;
if (rawAttribute.allowNull === false && ((value === null) || (value === undefined))) { /**
error = new sequelizeError.ValidationErrorItem(field + ' cannot be null', 'notNull Violation', field, value); * Will validate a single field against its schema definition (isnull).
this.errors.push(error); *
} * @param {Object} rawAttribute As defined in the Schema.
* @param {string} field The field name.
* @param {*} value anything.
* @private
*/
_validateSchema(rawAttribute, field, value) {
let error;
if (rawAttribute.type === DataTypes.STRING || rawAttribute.type instanceof DataTypes.STRING || rawAttribute.type === DataTypes.TEXT || rawAttribute.type instanceof DataTypes.TEXT) { if (rawAttribute.allowNull === false && ((value === null) || (value === undefined))) {
if (Array.isArray(value) || (_.isObject(value) && !value._isSequelizeMethod) && !Buffer.isBuffer(value)) { error = new sequelizeError.ValidationErrorItem(field + ' cannot be null', 'notNull Violation', field, value);
error = new sequelizeError.ValidationErrorItem(field + ' cannot be an array or an object', 'string violation', field, value);
this.errors.push(error); this.errors.push(error);
} }
if (rawAttribute.type === DataTypes.STRING || rawAttribute.type instanceof DataTypes.STRING || rawAttribute.type === DataTypes.TEXT || rawAttribute.type instanceof DataTypes.TEXT) {
if (Array.isArray(value) || (_.isObject(value) && !value._isSequelizeMethod) && !Buffer.isBuffer(value)) {
error = new sequelizeError.ValidationErrorItem(field + ' cannot be an array or an object', 'string violation', field, value);
this.errors.push(error);
}
}
} }
};
/** /**
* Handles the returned result of a Promise.reflect. * Handles the returned result of a Promise.reflect.
* *
* If errors are found it populates this.error. * 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
*/ */
InstanceValidator.prototype._handleReflectedResult = function(field, promiseInspections) { _handleReflectedResult(field, promiseInspections) {
var self = this; for (const promiseInspection of promiseInspections) {
promiseInspections.forEach(function(promiseInspection) { if (promiseInspection.isRejected()) {
if (promiseInspection.isRejected()) { const rejection = promiseInspection.error();
var rejection = promiseInspection.error(); this._pushError(true, field, rejection);
self._pushError(true, field, rejection); }
} }
}); }
};
/** /**
* Signs all errors retaining the original. * Signs all errors retaining the original.
* *
* @param {boolean} isBuiltin Determines if error is from builtin validator. * @param {boolean} isBuiltin Determines if error is from builtin validator.
* @param {string} errorKey The error key to assign on this.errors object. * @param {string} errorKey The error key to assign on this.errors object.
* @param {Error|string} rawError The original error. * @param {Error|string} rawError The original error.
* @private * @private
*/ */
InstanceValidator.prototype._pushError = function(isBuiltin, errorKey, rawError) { _pushError(isBuiltin, errorKey, rawError) {
var message = rawError.message || rawError || 'Validation error'; const message = rawError.message || rawError || 'Validation error';
var error = new sequelizeError.ValidationErrorItem(message, 'Validation error', errorKey, rawError); const error = new sequelizeError.ValidationErrorItem(message, 'Validation error', errorKey, rawError);
error[InstanceValidator.RAW_KEY_NAME] = rawError; error[InstanceValidator.RAW_KEY_NAME] = rawError;
this.errors.push(error);
}
}
/** @define {string} The error key for arguments as passed by custom validators */
InstanceValidator.RAW_KEY_NAME = '__raw';
this.errors.push(error); module.exports = InstanceValidator;
}; module.exports.InstanceValidator = InstanceValidator;
module.exports.default = InstanceValidator;
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!