BE-SPAN/node_modules/sequelize-oracle/lib/instance-validator.js

420 lines
12 KiB
JavaScript
Raw Permalink Normal View History

2024-10-20 15:04:16 +00:00
'use strict';
var Validator = require('validator')
, Utils = require('./utils')
, sequelizeError = require('./errors')
, Promise = require('./promise')
, DataTypes = require('./data-types')
, _ = require('lodash');
function noop() {}
// Deprecate this.
Validator.notNull = function() {
throw new Error('Warning "notNull" validation has been deprecated in favor of Schema based "allowNull"');
};
// https://github.com/chriso/validator.js/blob/1.5.0/lib/validators.js
Validator.extend('notEmpty', function(str) {
return !str.match(/^[\s\t\r\n]*$/);
});
Validator.extend('len', function(str, min, max) {
return this.isLength(str, min, max);
});
Validator.extend('isUrl', function(str) {
return this.isURL(str);
});
Validator.extend('isIPv6', function(str) {
return this.isIP(str, 6);
});
Validator.extend('isIPv4', function(str) {
return this.isIP(str, 4);
});
Validator.extend('notIn', function(str, values) {
return !this.isIn(str, values);
});
Validator.extend('regex', function(str, pattern, modifiers) {
str += '';
if (Object.prototype.toString.call(pattern).slice(8, -1) !== 'RegExp') {
pattern = new RegExp(pattern, modifiers);
}
return str.match(pattern);
});
Validator.extend('notRegex', function(str, pattern, modifiers) {
return !this.regex(str, pattern, modifiers);
});
Validator.extend('isDecimal', function(str) {
return str !== '' && str.match(/^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/);
});
Validator.extend('min', function(str, val) {
var number = parseFloat(str);
return isNaN(number) || number >= val;
});
Validator.extend('max', function(str, val) {
var number = parseFloat(str);
return isNaN(number) || number <= val;
});
Validator.extend('not', function(str, pattern, modifiers) {
return this.notRegex(str, pattern, modifiers);
});
Validator.extend('contains', function(str, elem) {
return str.indexOf(elem) >= 0 && !!elem;
});
Validator.extend('notContains', function(str, elem) {
return !this.contains(str, elem);
});
Validator.extend('is', function(str, pattern, modifiers) {
return this.regex(str, pattern, modifiers);
});
function extendModelValidations(modelInstance) {
Validator.extend('isImmutable', function(str, param, field) {
return (modelInstance.isNewRecord || modelInstance.dataValues[field] === modelInstance._previousDataValues[field]);
});
}
/**
* The Main Instance Validator.
*
* @param {Instance} modelInstance The model instance.
* @param {Object} options A dict with options.
* @constructor
*/
var InstanceValidator = module.exports = function(modelInstance, options) {
options = options || {};
if (options.fields && !options.skip) {
options.skip = Utils._.difference(Object.keys(modelInstance.Model.attributes), options.fields);
}
// 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.
*
* @type {Array} Will contain keys that correspond to attributes which will
* be Arrays of Errors.
*/
this.errors = [];
/** @type {boolean} Indicates if validations are in progress */
this.inProgress = false;
extendModelValidations(modelInstance);
};
/** @define {string} The error key for arguments as passed by custom validators */
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;
return Promise.settle([
self._builtinValidators(),
self._customValidators()
]).then(function() {
if (self.errors.length) {
return new sequelizeError.ValidationError(null, self.errors);
}
return new Promise(function(resolve) {
resolve();
});
});
};
/**
* Invoke the Validation sequence:
* - Before Validation Model Hooks
* - Validation
* - After Validation Model Hooks
*
* @return {Promise}
*/
InstanceValidator.prototype.hookValidate = function() {
var self = this;
return self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance, self.options).then(function() {
return self.validate().then(function(error) {
if (error) {
throw error;
}
});
}).then(function() {
return self.modelInstance.Model.runHooks('afterValidate', self.modelInstance, self.options);
}).return(self.modelInstance);
};
/**
* Will run all the built-in validators.
*
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .settle().
* @private
*/
InstanceValidator.prototype._builtinValidators = function() {
var self = this;
// promisify all attribute invocations
var validators = [];
Utils._.forIn(this.modelInstance.rawAttributes, function(rawAttribute, field) {
if (self.options.skip.indexOf(field) >= 0) {
return;
}
var value = self.modelInstance.dataValues[field];
if (!rawAttribute._autoGenerated && !rawAttribute.autoIncrement) {
// perform validations based on schema
self._validateSchema(rawAttribute, field, value);
}
if (self.modelInstance.validators.hasOwnProperty(field)) {
validators.push(self._builtinAttrValidate.call(self, value, field));
}
});
return Promise.settle(validators);
};
/**
* Will run all the custom validators.
*
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .settle().
* @private
*/
InstanceValidator.prototype._customValidators = function() {
var validators = [];
var self = this;
Utils._.each(this.modelInstance.__options.validate, function(validator,
validatorType) {
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 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 = [];
Utils._.forIn(this.modelInstance.validators[field], function(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 = {};
}
}
// Check for custom validator.
if (typeof test === 'function') {
return validators.push(self._invokeCustomValidator(test, validatorType, true, value, field));
}
var validatorPromise = self._invokeBuiltinValidator(value, test, validatorType, field);
// errors are handled in settling, stub this
validatorPromise.catch(noop);
validators.push(validatorPromise);
});
return Promise.settle(validators).then(this._handleSettledResult.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) {
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 {string} field The field that is being validated
* @return {Object} An object with specific keys to invoke the validator.
* @private
*/
InstanceValidator.prototype._invokeBuiltinValidator = Promise.method(function(value, test, validatorType, field) {
// check if Validator knows that kind of validation test
if (typeof Validator[validatorType] !== 'function') {
throw new Error('Invalid validator function: ' + validatorType);
}
// extract extra arguments for the validator
var validatorArgs = test.args || test;
if (!Array.isArray(validatorArgs)) {
if (validatorType === 'isImmutable') {
validatorArgs = [validatorArgs, field];
} else if (validatorType === 'isIP') {
validatorArgs = [];
} else {
validatorArgs = [validatorArgs];
}
} else {
validatorArgs = validatorArgs.slice(0);
}
if (!Validator[validatorType].apply(Validator, [value].concat(validatorArgs))) {
// extract the error msg
throw test.msg || 'Validation ' + validatorType + ' failed';
}
});
/**
* 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);
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.settle.
*
* If errors are found it populates this.error.
*
* @param {string} field The attribute name.
* @param {Array.<Promise.PromiseInspection>} Promise inspection objects.
* @private
*/
InstanceValidator.prototype._handleSettledResult = function(field, promiseInspections) {
var self = this;
promiseInspections.forEach(function(promiseInspection) {
if (promiseInspection.isRejected()) {
var rejection = promiseInspection.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
*/
InstanceValidator.prototype._pushError = function(isBuiltin, errorKey, rawError) {
var message = rawError.message || rawError || 'Validation error';
var error = new sequelizeError.ValidationErrorItem(message, 'Validation error', errorKey, rawError);
error[InstanceValidator.RAW_KEY_NAME] = rawError;
this.errors.push(error);
};