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

Commit 7525ef06 by Sushant Committed by GitHub

fix: customize allowNull message with notNull validator (#9549)

1 parent 0fbda716
...@@ -460,7 +460,25 @@ See [the validator.js project][3] for more details on the built in validation me ...@@ -460,7 +460,25 @@ See [the validator.js project][3] for more details on the built in validation me
### Validators and `allowNull` ### Validators and `allowNull`
If a particular field of a model is set to allow null (with `allowNull: true`) and that value has been set to `null` , its validators do not run. This means you can, for instance, have a string field which validates its length to be at least 5 characters, but which also allows`null`. If a particular field of a model is set to allow null (with `allowNull: true`) and that value has been set to `null` , its validators do not run.
This means you can, for instance, have a string field which validates its length to be at least 5 characters, but which also allows `null`.
You can customize `allowNull` error message by setting `notNull` validator, like this
```js
const User = sequelize.define('user', {
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
notNull: {
msg: 'Please enter your name'
}
}
}
});
```
### Model validations ### Model validations
......
...@@ -13,12 +13,11 @@ const _ = require('lodash'); ...@@ -13,12 +13,11 @@ const _ = require('lodash');
* The Main Instance Validator. * The Main Instance Validator.
* *
* @param {Instance} modelInstance The model instance. * @param {Instance} modelInstance The model instance.
* @param {Object} options A dict with options. * @param {Object} options A dictionary with options.
* @constructor * @constructor
* @private * @private
*/ */
class InstanceValidator { class InstanceValidator {
constructor(modelInstance, options) { constructor(modelInstance, options) {
options = _.clone(options) || {}; options = _.clone(options) || {};
...@@ -66,14 +65,14 @@ class InstanceValidator { ...@@ -66,14 +65,14 @@ class InstanceValidator {
* @private * @private
*/ */
_validate() { _validate() {
if (this.inProgress) { if (this.inProgress) throw new Error('Validations already in progress.');
throw new Error('Validations already in progress.');
}
this.inProgress = true; this.inProgress = true;
return Promise.all( return Promise.all([
[this._builtinValidators(), this._customValidators()].map(promise => promise.reflect()) this._builtinValidators().reflect(),
).then(() => { this._customValidators().reflect()
]).then(() => {
if (this.errors.length) { if (this.errors.length) {
throw new sequelizeError.ValidationError(null, this.errors); throw new sequelizeError.ValidationError(null, this.errors);
} }
...@@ -125,6 +124,7 @@ class InstanceValidator { ...@@ -125,6 +124,7 @@ class InstanceValidator {
_builtinValidators() { _builtinValidators() {
// promisify all attribute invocations // promisify all attribute invocations
const validators = []; const validators = [];
_.forIn(this.modelInstance.rawAttributes, (rawAttribute, field) => { _.forIn(this.modelInstance.rawAttributes, (rawAttribute, field) => {
if (this.options.skip.indexOf(field) >= 0) { if (this.options.skip.indexOf(field) >= 0) {
return; return;
...@@ -337,7 +337,7 @@ class InstanceValidator { ...@@ -337,7 +337,7 @@ class InstanceValidator {
} }
} }
if (rawAttribute.type === DataTypes.STRING || rawAttribute.type instanceof DataTypes.STRING || rawAttribute.type === DataTypes.TEXT || rawAttribute.type instanceof DataTypes.TEXT) { if (rawAttribute.type instanceof DataTypes.STRING || rawAttribute.type instanceof DataTypes.TEXT) {
if (Array.isArray(value) || _.isObject(value) && !(value instanceof Utils.SequelizeMethod) && !Buffer.isBuffer(value)) { if (Array.isArray(value) || _.isObject(value) && !(value instanceof Utils.SequelizeMethod) && !Buffer.isBuffer(value)) {
this.errors.push(new sequelizeError.ValidationErrorItem( this.errors.push(new sequelizeError.ValidationErrorItem(
`${field} cannot be an array or an object`, `${field} cannot be an array or an object`,
......
...@@ -89,7 +89,6 @@ class Model { ...@@ -89,7 +89,6 @@ class Model {
this._changed = {}; this._changed = {};
this._modelOptions = this.constructor.options; this._modelOptions = this.constructor.options;
this._options = options || {}; this._options = options || {};
this.__eagerlyLoadedAssociations = [];
/** /**
* Returns true if this instance has not yet been persisted to the database * Returns true if this instance has not yet been persisted to the database
...@@ -906,7 +905,11 @@ class Model { ...@@ -906,7 +905,11 @@ class Model {
attribute = this.sequelize.normalizeAttribute(attribute); attribute = this.sequelize.normalizeAttribute(attribute);
if (attribute.type === undefined) { if (attribute.type === undefined) {
throw new Error('Unrecognized data type for field ' + name); throw new Error(`Unrecognized datatype for attribute "${this.name}.${name}"`);
}
if (attribute.allowNull !== false && _.get(attribute, 'validate.notNull')) {
throw new Error(`Invalid definition for "${this.name}.${name}", "notNull" validator is only allowed with "allowNull:false"`);
} }
if (_.get(attribute, 'references.model.prototype') instanceof Model) { if (_.get(attribute, 'references.model.prototype') instanceof Model) {
......
...@@ -77,9 +77,8 @@ function extendModelValidations(modelInstance) { ...@@ -77,9 +77,8 @@ function extendModelValidations(modelInstance) {
} }
exports.extendModelValidations = extendModelValidations; exports.extendModelValidations = extendModelValidations;
// Deprecate this. validator.notNull = function(val) {
validator.notNull = function() { return ![null, undefined].includes(val);
throw new Error('Warning "notNull" validation has been deprecated in favor of Schema based "allowNull"');
}; };
// https://github.com/chriso/validator.js/blob/6.2.0/validator.js // https://github.com/chriso/validator.js/blob/6.2.0/validator.js
......
...@@ -1063,13 +1063,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -1063,13 +1063,13 @@ describe(Support.getTestDialectTeaser('Model'), () => {
self.sequelize.define('UserBadDataType', { self.sequelize.define('UserBadDataType', {
activity_date: Sequelize.DATe activity_date: Sequelize.DATe
}); });
}).to.throw(Error, 'Unrecognized data type for field activity_date'); }).to.throw(Error, 'Unrecognized datatype for attribute "UserBadDataType.activity_date"');
expect(() => { expect(() => {
self.sequelize.define('UserBadDataType', { self.sequelize.define('UserBadDataType', {
activity_date: {type: Sequelize.DATe} activity_date: {type: Sequelize.DATe}
}); });
}).to.throw(Error, 'Unrecognized data type for field activity_date'); }).to.throw(Error, 'Unrecognized datatype for attribute "UserBadDataType.activity_date"');
}); });
it('sets a 64 bit int in bigint', function() { it('sets a 64 bit int in bigint', function() {
......
...@@ -41,6 +41,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -41,6 +41,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}); });
}).to.throw("A column called 'id' was added to the attributes of 'bars' but not marked with 'primaryKey: true'"); }).to.throw("A column called 'id' was added to the attributes of 'bars' but not marked with 'primaryKey: true'");
}); });
it('should defend against null or undefined "unique" attributes', () => { it('should defend against null or undefined "unique" attributes', () => {
expect(() => { expect(() => {
current.define('baz', { current.define('baz', {
...@@ -58,5 +59,44 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -58,5 +59,44 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}); });
}).not.to.throw(); }).not.to.throw();
}); });
it('should throw for unknown data type', () => {
expect(() => {
current.define('bar', {
name: {
type: DataTypes.MY_UNKNOWN_TYPE
}
});
}).to.throw('Unrecognized datatype for attribute "bar.name"');
});
it('should throw for notNull validator without allowNull', () => {
expect(() => {
current.define('user', {
name: {
type: DataTypes.STRING,
allowNull: true,
validate: {
notNull: {
msg: 'Please enter the name'
}
}
}
});
}).to.throw('Invalid definition for "user.name", "notNull" validator is only allowed with "allowNull:false"');
expect(() => {
current.define('part', {
name: {
type: DataTypes.STRING,
validate: {
notNull: {
msg: 'Please enter the part name'
}
}
}
});
}).to.throw('Invalid definition for "part.name", "notNull" validator is only allowed with "allowNull:false"');
});
}); });
}); });
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!