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

Commit a243a82c by Tiago Ribeiro

Add default validation based on attribute type

1 parent a56a1882
......@@ -3,7 +3,9 @@
var util = require('util')
, _ = require('lodash')
, Wkt = require('wellknown')
, warnings = {};
, sequelizeErrors = require('./errors')
, warnings = {}
, Validator = require('validator');
/**
* A convenience class holding commonly used data types. The datatypes are used when definining a new model using `Sequelize.define`, like this:
......@@ -80,6 +82,13 @@ STRING.prototype.key = STRING.key = 'STRING';
STRING.prototype.toSql = function() {
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '');
};
STRING.prototype.validate = function(value) {
if (typeof value !== 'string') {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value));
}
return true;
};
Object.defineProperty(STRING.prototype, 'BINARY', {
get: function() {
this._binary = true;
......@@ -137,6 +146,13 @@ TEXT.prototype.toSql = function() {
return this.key;
}
};
TEXT.prototype.validate = function(value) {
if (typeof value !== 'string') {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value));
}
return true;
};
var NUMBER = function(options) {
this.options = options;
......@@ -167,6 +183,13 @@ NUMBER.prototype.toSql = function() {
}
return result;
};
NUMBER.prototype.validate = function(value) {
if (!_.isNumber(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid number', value));
}
return true;
};
Object.defineProperty(NUMBER.prototype, 'UNSIGNED', {
get: function() {
......@@ -200,6 +223,13 @@ var INTEGER = function(length) {
util.inherits(INTEGER, NUMBER);
INTEGER.prototype.key = INTEGER.key = 'INTEGER';
INTEGER.prototype.validate = function(value) {
if (!Validator.isInt(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid integer', value));
}
return true;
};
/**
* A 64 bit integer.
......@@ -219,6 +249,13 @@ var BIGINT = function(length) {
util.inherits(BIGINT, NUMBER);
BIGINT.prototype.key = BIGINT.key = 'BIGINT';
BIGINT.prototype.validate = function(value) {
if (!Validator.isInt(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid bigint', value));
}
return true;
};
/**
* Floating point number (4-byte precision). Accepts one or two arguments for precision
......@@ -238,6 +275,13 @@ var FLOAT = function(length, decimals) {
util.inherits(FLOAT, NUMBER);
FLOAT.prototype.key = FLOAT.key = 'FLOAT';
FLOAT.prototype.validate = function(value) {
if (!Validator.isFloat(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid float', value));
}
return true;
};
/**
* Floating point number (4-byte precision). Accepts one or two arguments for precision
......@@ -302,6 +346,13 @@ DECIMAL.prototype.toSql = function() {
return 'DECIMAL';
};
DECIMAL.prototype.validate = function(value) {
if (!_.isNumber(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid decimal', value));
}
return true;
};
/**
* A boolean / tinyint column, depending on dialect
......@@ -317,6 +368,13 @@ BOOLEAN.prototype.key = BOOLEAN.key = 'BOOLEAN';
BOOLEAN.prototype.toSql = function() {
return 'TINYINT(1)';
};
BOOLEAN.prototype.validate = function(value) {
if (!_.isBoolean(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid boolean', value));
}
return true;
};
/**
* A time column
......@@ -348,7 +406,13 @@ DATE.prototype.key = DATE.key = 'DATE';
DATE.prototype.toSql = function() {
return 'DATETIME';
};
DATE.prototype.validate = function(value) {
if (!Validator.isDate(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid date', value));
}
return true;
};
/**
* A date only column
* @property DATEONLY
......@@ -377,6 +441,13 @@ var HSTORE = function() {
util.inherits(HSTORE, ABSTRACT);
HSTORE.prototype.key = HSTORE.key = 'HSTORE';
HSTORE.prototype.validate = function(value) {
if (!_.isPlainObject(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid hstore', value));
}
return true;
};
/**
* A JSON string column. Only available in postgres.
......@@ -389,6 +460,9 @@ var JSONTYPE = function() {
util.inherits(JSONTYPE, ABSTRACT);
JSONTYPE.prototype.key = JSONTYPE.key = 'JSON';
JSONTYPE.prototype.validate = function(value) {
return true;
};
/**
* A pre-processed JSON data column. Only available in postgres.
......@@ -442,6 +516,13 @@ BLOB.prototype.toSql = function() {
return this.key;
}
};
BLOB.prototype.validate = function(value) {
if (!_.isString(value) && !Buffer.isBuffer(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid blob', value));
}
return true;
};
/**
* Range types are data types representing a range of values of some element type (called the range's subtype).
......@@ -474,6 +555,21 @@ RANGE.prototype.key = RANGE.key = 'RANGE';
RANGE.prototype.toSql = function() {
return pgRangeSubtypes[this._subtype.toLowerCase()];
};
RANGE.prototype.validate = function(value) {
if (_.isPlainObject(value) && value.inclusive) {
value = value.inclusive;
}
if (!_.isArray(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid range', value));
}
if (value.length !== 2) {
throw new sequelizeErrors.ValidationError('A range must be an array with two elements');
}
return true;
};
/**
* A column storing a unique univeral identifier. Use with `UUIDV1` or `UUIDV4` for default values.
......@@ -486,6 +582,13 @@ var UUID = function() {
util.inherits(UUID, ABSTRACT);
UUID.prototype.key = UUID.key = 'UUID';
UUID.prototype.validate = function(value) {
if (!Validator.isUUID(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuid', value));
}
return true;
};
/**
* A default unique universal identifier generated following the UUID v1 standard
......@@ -499,6 +602,13 @@ var UUIDV1 = function() {
util.inherits(UUIDV1, ABSTRACT);
UUIDV1.prototype.key = UUIDV1.key = 'UUIDV1';
UUIDV1.prototype.validate = function(value) {
if (!Validator.isUUID(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuid', value));
}
return true;
};
/**
* A default unique universal identifier generated following the UUID v2 standard
......@@ -512,6 +622,13 @@ var UUIDV4 = function() {
util.inherits(UUIDV4, ABSTRACT);
UUIDV4.prototype.key = UUIDV4.key = 'UUIDV4';
UUIDV4.prototype.validate = function(value) {
if (!Validator.isUUID(value, 4)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuidv4', value));
}
return true;
};
/**
* A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model that is returned to the user but not stored in the DB.
......@@ -565,6 +682,13 @@ var ENUM = function(value) {
util.inherits(ENUM, ABSTRACT);
ENUM.prototype.key = ENUM.key = 'ENUM';
ENUM.prototype.validate = function(value) {
if (!_.contains(this.values, value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid choice in %j', value, this.values));
}
return true;
};
/**
* An array of `type`, e.g. `DataTypes.ARRAY(DataTypes.DECIMAL)`. Only available in postgres.
......@@ -583,6 +707,13 @@ ARRAY.prototype.key = ARRAY.key = 'ARRAY';
ARRAY.prototype.toSql = function() {
return this.type.toSql() + '[]';
};
ARRAY.prototype.validate = function(value) {
if (!Array.isArray(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid array', value));
}
return true;
};
ARRAY.is = function(obj, type) {
return obj instanceof ARRAY && obj.type instanceof type;
};
......
......@@ -877,6 +877,12 @@ var QueryGenerator = {
return this.handleSequelizeMethod(value);
}
if (field && field.type && value) {
if (field.type.validate) {
field.type.validate(value);
}
}
if (Utils._.isObject(value) && field && (field.type instanceof DataTypes.HSTORE || DataTypes.ARRAY.is(field.type, DataTypes.HSTORE))) {
if (field.type instanceof DataTypes.HSTORE){
return "'" + hstore.stringify(value) + "'";
......
......@@ -91,26 +91,8 @@ var Model = function(name, attributes, options) {
throw new Error('Values for ENUM have not been defined.');
}
attribute.validate = attribute.validate || {
_checkEnum: function(value, next) {
var hasValue = value !== undefined
, isMySQL = ['mysql', 'mariadb'].indexOf(options.sequelize.options.dialect) !== -1
, ciCollation = !!options.collate && options.collate.match(/_ci$/i) !== null
, valueOutOfScope;
if (isMySQL && ciCollation && hasValue) {
var scopeIndex = (attributes[name].values || []).map(function(d) { return d.toLowerCase(); }).indexOf(value.toLowerCase());
valueOutOfScope = scopeIndex === -1;
} else {
valueOutOfScope = ((attributes[name].values || []).indexOf(value) === -1);
}
if (hasValue && valueOutOfScope && attributes[name].allowNull !== true) {
return next('Value "' + value + '" for ENUM ' + name + ' is out of allowed scope. Allowed values: ' + attributes[name].values.join(', '));
}
next();
}
};
// BC compatible.
attribute.type.values = attribute.values;
}
return attribute;
......
......@@ -406,7 +406,7 @@ QueryInterface.prototype.renameColumn = function(tableName, attrNameBefore, attr
_options[attrNameAfter] = {
attribute: attrNameAfter,
type: data.type,
type: DataTypes[data.type],
allowNull: data.allowNull,
defaultValue: data.defaultValue
};
......
......@@ -16,9 +16,9 @@ if (dialect.match(/^postgres/)) {
beforeEach(function() {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
numbers: {type: DataTypes.ARRAY(DataTypes.FLOAT)},
document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
email: { type: DataTypes.ARRAY(DataTypes.TEXT) },
numbers: { type: DataTypes.ARRAY(DataTypes.FLOAT) },
document: { type: DataTypes.HSTORE, defaultValue: { default: '"value"' } }
});
return this.User.sync({ force: true });
});
......
......@@ -718,4 +718,4 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), function() {
expect(errors.get('name')[0].message).to.equal('Validation isExactly7Characters failed');
});
});
});
\ No newline at end of file
});
......@@ -927,7 +927,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), function() {
it("doesn't save an instance if value is not in the range of enums", function() {
return this.Review.create({status: 'fnord'}).catch(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.get('status')[0].message).to.equal('Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished');
expect(err.message).to.equal('"fnord" is not a valid choice in ["scheduled","active","finished"]');
});
});
});
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!