Skip to content

Commit 30145e0

Browse files
committed
Merge pull request sequelize#3847 from johngiudici/unique-error-custom-message
Add support for unique constraint custom error message
2 parents bebad6e + 4fe70b4 commit 30145e0

8 files changed

Lines changed: 58 additions & 7 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Next
22
- [FIXED] Fix `Promise#nodeify()` and `Promise#done()` not passing CLS context
3+
- [ADDED] Unique constraints may now include custom error messages
34

45
# 3.2.0
56
- [FEATURE] Add support for new option `targetKey` in a belongs-to relationship for situations where the target key is not the id field.

lib/dialects/abstract/query.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,19 @@ AbstractQuery.prototype.findTableNameInAttribute = function(attribute) {
414414
}
415415
};
416416

417+
AbstractQuery.prototype.getUniqueConstraintErrorMessage = function(field) {
418+
var message = field + ' must be unique';
419+
var self = this;
420+
Object.keys(self.model.uniqueKeys).forEach(function(key) {
421+
if (self.model.uniqueKeys[key].fields.indexOf(field.replace(/"/g, '')) >= 0) {
422+
if (self.model.uniqueKeys[key].hasOwnProperty('msg')) {
423+
message = self.model.uniqueKeys[key].msg;
424+
}
425+
}
426+
});
427+
return message;
428+
};
429+
417430
AbstractQuery.prototype.isRawQuery = function () {
418431
return this.options.type === QueryTypes.RAW;
419432
};

lib/dialects/mssql/query.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,11 @@ Query.prototype.formatError = function (err) {
185185
}
186186

187187
var errors = [];
188+
var self = this;
188189
Utils._.forOwn(fields, function(value, field) {
189190
errors.push(new sequelizeErrors.ValidationErrorItem(
190-
field + ' must be unique', 'unique violation', field, value));
191+
self.getUniqueConstraintErrorMessage(field),
192+
'unique violation', field, value));
191193
});
192194

193195
return new sequelizeErrors.UniqueConstraintError({

lib/dialects/mysql/query.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,11 @@ Query.prototype.formatError = function (err) {
123123
}
124124

125125
var errors = [];
126+
var self = this;
126127
Utils._.forOwn(fields, function(value, field) {
127128
errors.push(new sequelizeErrors.ValidationErrorItem(
128-
field + ' must be unique', 'unique violation', field, value));
129+
self.getUniqueConstraintErrorMessage(field),
130+
'unique violation', field, value));
129131
});
130132

131133
return new sequelizeErrors.UniqueConstraintError({

lib/dialects/postgres/query.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,11 @@ Query.prototype.run = function(sql) {
134134

135135
if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
136136
if (rows[0].sequelize_caught_exception !== null) {
137-
throw new self.formatError({
137+
var err = self.formatError({
138138
code: '23505',
139139
detail: rows[0].sequelize_caught_exception
140140
});
141+
throw err;
141142
} else {
142143
rows = rows.map(function (row) {
143144
delete row.sequelize_caught_exception;
@@ -343,7 +344,8 @@ Query.prototype.formatError = function (err) {
343344
, index
344345
, fields
345346
, errors
346-
, message;
347+
, message
348+
, self = this;
347349

348350
var code = err.code || err.sqlState
349351
, errMessage = err.message || err.messagePrimary
@@ -374,7 +376,8 @@ Query.prototype.formatError = function (err) {
374376

375377
Utils._.forOwn(fields, function(value, field) {
376378
errors.push(new sequelizeErrors.ValidationErrorItem(
377-
field + ' must be unique', 'unique violation', field, value));
379+
self.getUniqueConstraintErrorMessage(field),
380+
'unique violation', field, value));
378381
});
379382

380383
if (this.model && this.model.uniqueKeys) {

lib/dialects/sqlite/query.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ Query.prototype.formatError = function (err) {
215215

216216
fields.forEach(function(field) {
217217
errors.push(new sequelizeErrors.ValidationErrorItem(
218-
field + ' must be unique', 'unique violation', field, self.instance && self.instance[field]));
218+
self.getUniqueConstraintErrorMessage(field),
219+
'unique violation', field, self.instance && self.instance[field]));
219220
});
220221

221222
if (this.model) {

lib/model.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,6 +1866,7 @@ Model.prototype.bulkCreate = function(records, options) {
18661866
}
18671867

18681868
// Insert all records at once
1869+
options.model = self;
18691870
return self.QueryInterface.bulkInsert(self.getTableName(options), records, options, attributes).then(function (results) {
18701871
if (Array.isArray(results)) {
18711872
results.forEach(function (result, i) {

test/integration/instance.validations.test.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), function() {
7979
});
8080
});
8181

82-
it('should enforce a unque constraint', function() {
82+
it('should enforce a unique constraint', function() {
8383
var Model = this.sequelize.define('model', {
8484
uniqueName: { type: Sequelize.STRING, unique: true }
8585
});
@@ -103,6 +103,34 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), function() {
103103
expect(err.errors[0].message).to.include('must be unique');
104104
});
105105
});
106+
107+
it('should allow a custom unique constraint error message', function() {
108+
var Model = this.sequelize.define('model', {
109+
uniqueName: {
110+
type: Sequelize.STRING,
111+
unique: { msg: 'custom unique error message' }
112+
}
113+
});
114+
var records = [
115+
{ uniqueName: 'unique name one' },
116+
{ uniqueName: 'unique name two' }
117+
];
118+
return Model.sync({ force: true })
119+
.then(function() {
120+
return Model.create(records[0]);
121+
}).then(function(instance) {
122+
expect(instance).to.be.ok;
123+
return Model.create(records[1]);
124+
}).then(function(instance) {
125+
expect(instance).to.be.ok;
126+
return expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected;
127+
}).then(function(err) {
128+
expect(err).to.be.an.instanceOf(Error);
129+
expect(err.errors).to.have.length(1);
130+
expect(err.errors[0].path).to.include('uniqueName');
131+
expect(err.errors[0].message).to.equal('custom unique error message');
132+
});
133+
});
106134
});
107135

108136
describe('#create', function() {

0 commit comments

Comments
 (0)