instance-validator.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. 'use strict';
  2. const _ = require('lodash');
  3. const Utils = require('./utils');
  4. const sequelizeError = require('./errors');
  5. const Promise = require('./promise');
  6. const DataTypes = require('./data-types');
  7. const BelongsTo = require('./associations/belongs-to');
  8. const validator = require('./utils/validator-extras').validator;
  9. /**
  10. * Instance Validator.
  11. *
  12. * @param {Instance} modelInstance The model instance.
  13. * @param {Object} options A dictionary with options.
  14. *
  15. * @private
  16. */
  17. class InstanceValidator {
  18. constructor(modelInstance, options) {
  19. options = _.clone(options) || {};
  20. if (options.fields && !options.skip) {
  21. options.skip = _.difference(Object.keys(modelInstance.constructor.rawAttributes), options.fields);
  22. }
  23. // assign defined and default options
  24. this.options = _.defaults(options, {
  25. skip: [],
  26. hooks: true
  27. });
  28. this.modelInstance = modelInstance;
  29. /**
  30. * Exposes a reference to validator.js. This allows you to add custom validations using `validator.extend`
  31. * @name validator
  32. * @private
  33. */
  34. this.validator = validator;
  35. /**
  36. * All errors will be stored here from the validations.
  37. *
  38. * @type {Array} Will contain keys that correspond to attributes which will
  39. * be Arrays of Errors.
  40. * @private
  41. */
  42. this.errors = [];
  43. /**
  44. * @type {boolean} Indicates if validations are in progress
  45. * @private
  46. */
  47. this.inProgress = false;
  48. }
  49. /**
  50. * The main entry point for the Validation module, invoke to start the dance.
  51. *
  52. * @returns {Promise}
  53. * @private
  54. */
  55. _validate() {
  56. if (this.inProgress) throw new Error('Validations already in progress.');
  57. this.inProgress = true;
  58. return Promise.all([
  59. this._perAttributeValidators().reflect(),
  60. this._customValidators().reflect()
  61. ]).then(() => {
  62. if (this.errors.length) {
  63. throw new sequelizeError.ValidationError(null, this.errors);
  64. }
  65. });
  66. }
  67. /**
  68. * Invoke the Validation sequence and run validation hooks if defined
  69. * - Before Validation Model Hooks
  70. * - Validation
  71. * - On validation success: After Validation Model Hooks
  72. * - On validation failure: Validation Failed Model Hooks
  73. *
  74. * @returns {Promise}
  75. * @private
  76. */
  77. validate() {
  78. return this.options.hooks ? this._validateAndRunHooks() : this._validate();
  79. }
  80. /**
  81. * Invoke the Validation sequence and run hooks
  82. * - Before Validation Model Hooks
  83. * - Validation
  84. * - On validation success: After Validation Model Hooks
  85. * - On validation failure: Validation Failed Model Hooks
  86. *
  87. * @returns {Promise}
  88. * @private
  89. */
  90. _validateAndRunHooks() {
  91. const runHooks = this.modelInstance.constructor.runHooks.bind(this.modelInstance.constructor);
  92. return runHooks('beforeValidate', this.modelInstance, this.options)
  93. .then(() =>
  94. this._validate()
  95. .catch(error => runHooks('validationFailed', this.modelInstance, this.options, error)
  96. .then(newError => { throw newError || error; }))
  97. )
  98. .then(() => runHooks('afterValidate', this.modelInstance, this.options))
  99. .return(this.modelInstance);
  100. }
  101. /**
  102. * Will run all the validators defined per attribute (built-in validators and custom validators)
  103. *
  104. * @returns {Promise<Array.<Promise.PromiseInspection>>} A promise from .reflect().
  105. * @private
  106. */
  107. _perAttributeValidators() {
  108. // promisify all attribute invocations
  109. const validators = [];
  110. _.forIn(this.modelInstance.rawAttributes, (rawAttribute, field) => {
  111. if (this.options.skip.includes(field)) {
  112. return;
  113. }
  114. const value = this.modelInstance.dataValues[field];
  115. if (value instanceof Utils.SequelizeMethod) {
  116. return;
  117. }
  118. if (!rawAttribute._autoGenerated && !rawAttribute.autoIncrement) {
  119. // perform validations based on schema
  120. this._validateSchema(rawAttribute, field, value);
  121. }
  122. if (Object.prototype.hasOwnProperty.call(this.modelInstance.validators, field)) {
  123. validators.push(this._singleAttrValidate(value, field, rawAttribute.allowNull).reflect());
  124. }
  125. });
  126. return Promise.all(validators);
  127. }
  128. /**
  129. * Will run all the custom validators defined in the model's options.
  130. *
  131. * @returns {Promise<Array.<Promise.PromiseInspection>>} A promise from .reflect().
  132. * @private
  133. */
  134. _customValidators() {
  135. const validators = [];
  136. _.each(this.modelInstance._modelOptions.validate, (validator, validatorType) => {
  137. if (this.options.skip.includes(validatorType)) {
  138. return;
  139. }
  140. const valprom = this._invokeCustomValidator(validator, validatorType)
  141. // errors are handled in settling, stub this
  142. .catch(() => {})
  143. .reflect();
  144. validators.push(valprom);
  145. });
  146. return Promise.all(validators);
  147. }
  148. /**
  149. * Validate a single attribute with all the defined built-in validators and custom validators.
  150. *
  151. * @private
  152. *
  153. * @param {*} value Anything.
  154. * @param {string} field The field name.
  155. * @param {boolean} allowNull Whether or not the schema allows null values
  156. *
  157. * @returns {Promise} A promise, will always resolve, auto populates error on this.error local object.
  158. */
  159. _singleAttrValidate(value, field, allowNull) {
  160. // If value is null and allowNull is false, no validators should run (see #9143)
  161. if ((value === null || value === undefined) && !allowNull) {
  162. // The schema validator (_validateSchema) has already generated the validation error. Nothing to do here.
  163. return Promise.resolve();
  164. }
  165. // Promisify each validator
  166. const validators = [];
  167. _.forIn(this.modelInstance.validators[field], (test, validatorType) => {
  168. if (validatorType === 'isUrl' || validatorType === 'isURL' || validatorType === 'isEmail') {
  169. // Preserve backwards compat. Validator.js now expects the second param to isURL and isEmail to be an object
  170. if (typeof test === 'object' && test !== null && test.msg) {
  171. test = {
  172. msg: test.msg
  173. };
  174. } else if (test === true) {
  175. test = {};
  176. }
  177. }
  178. // Custom validators should always run, except if value is null and allowNull is false (see #9143)
  179. if (typeof test === 'function') {
  180. validators.push(this._invokeCustomValidator(test, validatorType, true, value, field).reflect());
  181. return;
  182. }
  183. // If value is null, built-in validators should not run (only custom validators have to run) (see #9134).
  184. if (value === null || value === undefined) {
  185. return;
  186. }
  187. const validatorPromise = this._invokeBuiltinValidator(value, test, validatorType, field);
  188. // errors are handled in settling, stub this
  189. validatorPromise.catch(() => {});
  190. validators.push(validatorPromise.reflect());
  191. });
  192. return Promise
  193. .all(validators)
  194. .then(results => this._handleReflectedResult(field, value, results));
  195. }
  196. /**
  197. * Prepare and invoke a custom validator.
  198. *
  199. * @private
  200. *
  201. * @param {Function} validator The custom validator.
  202. * @param {string} validatorType the custom validator type (name).
  203. * @param {boolean} optAttrDefined Set to true if custom validator was defined from the attribute
  204. * @param {*} optValue value for attribute
  205. * @param {string} optField field for attribute
  206. *
  207. * @returns {Promise} A promise.
  208. */
  209. _invokeCustomValidator(validator, validatorType, optAttrDefined, optValue, optField) {
  210. let validatorFunction = null; // the validation function to call
  211. let isAsync = false;
  212. const validatorArity = validator.length;
  213. // check if validator is async and requires a callback
  214. let asyncArity = 1;
  215. let errorKey = validatorType;
  216. let invokeArgs;
  217. if (optAttrDefined) {
  218. asyncArity = 2;
  219. invokeArgs = optValue;
  220. errorKey = optField;
  221. }
  222. if (validatorArity === asyncArity) {
  223. isAsync = true;
  224. }
  225. if (isAsync) {
  226. if (optAttrDefined) {
  227. validatorFunction = Promise.promisify(validator.bind(this.modelInstance, invokeArgs));
  228. } else {
  229. validatorFunction = Promise.promisify(validator.bind(this.modelInstance));
  230. }
  231. return validatorFunction()
  232. .catch(e => this._pushError(false, errorKey, e, optValue, validatorType));
  233. }
  234. return Promise
  235. .try(() => validator.call(this.modelInstance, invokeArgs))
  236. .catch(e => this._pushError(false, errorKey, e, optValue, validatorType));
  237. }
  238. /**
  239. * Prepare and invoke a build-in validator.
  240. *
  241. * @private
  242. *
  243. * @param {*} value Anything.
  244. * @param {*} test The test case.
  245. * @param {string} validatorType One of known to Sequelize validators.
  246. * @param {string} field The field that is being validated
  247. *
  248. * @returns {Object} An object with specific keys to invoke the validator.
  249. */
  250. _invokeBuiltinValidator(value, test, validatorType, field) {
  251. return Promise.try(() => {
  252. // Cast value as string to pass new Validator.js string requirement
  253. const valueString = String(value);
  254. // check if Validator knows that kind of validation test
  255. if (typeof validator[validatorType] !== 'function') {
  256. throw new Error(`Invalid validator function: ${validatorType}`);
  257. }
  258. const validatorArgs = this._extractValidatorArgs(test, validatorType, field);
  259. if (!validator[validatorType](valueString, ...validatorArgs)) {
  260. throw Object.assign(new Error(test.msg || `Validation ${validatorType} on ${field} failed`), { validatorName: validatorType, validatorArgs });
  261. }
  262. });
  263. }
  264. /**
  265. * Will extract arguments for the validator.
  266. *
  267. * @param {*} test The test case.
  268. * @param {string} validatorType One of known to Sequelize validators.
  269. * @param {string} field The field that is being validated.
  270. *
  271. * @private
  272. */
  273. _extractValidatorArgs(test, validatorType, field) {
  274. let validatorArgs = test.args || test;
  275. const isLocalizedValidator = typeof validatorArgs !== 'string' && (validatorType === 'isAlpha' || validatorType === 'isAlphanumeric' || validatorType === 'isMobilePhone');
  276. if (!Array.isArray(validatorArgs)) {
  277. if (validatorType === 'isImmutable') {
  278. validatorArgs = [validatorArgs, field, this.modelInstance];
  279. } else if (isLocalizedValidator || validatorType === 'isIP') {
  280. validatorArgs = [];
  281. } else {
  282. validatorArgs = [validatorArgs];
  283. }
  284. } else {
  285. validatorArgs = validatorArgs.slice(0);
  286. }
  287. return validatorArgs;
  288. }
  289. /**
  290. * Will validate a single field against its schema definition (isnull).
  291. *
  292. * @param {Object} rawAttribute As defined in the Schema.
  293. * @param {string} field The field name.
  294. * @param {*} value anything.
  295. *
  296. * @private
  297. */
  298. _validateSchema(rawAttribute, field, value) {
  299. if (rawAttribute.allowNull === false && (value === null || value === undefined)) {
  300. const association = _.values(this.modelInstance.constructor.associations).find(association => association instanceof BelongsTo && association.foreignKey === rawAttribute.fieldName);
  301. if (!association || !this.modelInstance.get(association.associationAccessor)) {
  302. const validators = this.modelInstance.validators[field];
  303. const errMsg = _.get(validators, 'notNull.msg', `${this.modelInstance.constructor.name}.${field} cannot be null`);
  304. this.errors.push(new sequelizeError.ValidationErrorItem(
  305. errMsg,
  306. 'notNull Violation', // sequelizeError.ValidationErrorItem.Origins.CORE,
  307. field,
  308. value,
  309. this.modelInstance,
  310. 'is_null'
  311. ));
  312. }
  313. }
  314. if (rawAttribute.type instanceof DataTypes.STRING || rawAttribute.type instanceof DataTypes.TEXT || rawAttribute.type instanceof DataTypes.CITEXT) {
  315. if (Array.isArray(value) || _.isObject(value) && !(value instanceof Utils.SequelizeMethod) && !Buffer.isBuffer(value)) {
  316. this.errors.push(new sequelizeError.ValidationErrorItem(
  317. `${field} cannot be an array or an object`,
  318. 'string violation', // sequelizeError.ValidationErrorItem.Origins.CORE,
  319. field,
  320. value,
  321. this.modelInstance,
  322. 'not_a_string'
  323. ));
  324. }
  325. }
  326. }
  327. /**
  328. * Handles the returned result of a Promise.reflect.
  329. *
  330. * If errors are found it populates this.error.
  331. *
  332. * @param {string} field The attribute name.
  333. * @param {string|number} value The data value.
  334. * @param {Array<Promise.PromiseInspection>} promiseInspections objects.
  335. *
  336. * @private
  337. */
  338. _handleReflectedResult(field, value, promiseInspections) {
  339. for (const promiseInspection of promiseInspections) {
  340. if (promiseInspection.isRejected()) {
  341. const rejection = promiseInspection.error();
  342. const isBuiltIn = !!rejection.validatorName;
  343. this._pushError(isBuiltIn, field, rejection, value, rejection.validatorName, rejection.validatorArgs);
  344. }
  345. }
  346. }
  347. /**
  348. * Signs all errors retaining the original.
  349. *
  350. * @param {boolean} isBuiltin - Determines if error is from builtin validator.
  351. * @param {string} errorKey - name of invalid attribute.
  352. * @param {Error|string} rawError - The original error.
  353. * @param {string|number} value - The data that triggered the error.
  354. * @param {string} fnName - Name of the validator, if any
  355. * @param {Array} fnArgs - Arguments for the validator [function], if any
  356. *
  357. * @private
  358. */
  359. _pushError(isBuiltin, errorKey, rawError, value, fnName, fnArgs) {
  360. const message = rawError.message || rawError || 'Validation error';
  361. const error = new sequelizeError.ValidationErrorItem(
  362. message,
  363. 'Validation error', // sequelizeError.ValidationErrorItem.Origins.FUNCTION,
  364. errorKey,
  365. value,
  366. this.modelInstance,
  367. fnName,
  368. isBuiltin ? fnName : undefined,
  369. isBuiltin ? fnArgs : undefined
  370. );
  371. error[InstanceValidator.RAW_KEY_NAME] = rawError;
  372. this.errors.push(error);
  373. }
  374. }
  375. /**
  376. * @define {string} The error key for arguments as passed by custom validators
  377. * @private
  378. */
  379. InstanceValidator.RAW_KEY_NAME = 'original';
  380. module.exports = InstanceValidator;
  381. module.exports.InstanceValidator = InstanceValidator;
  382. module.exports.default = InstanceValidator;