instance-validator.js 13 KB

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