Skip to content

Commit 68a94d0

Browse files
committed
feat(validation): include failed paths in error message and inspect output
Fix #3064
1 parent 67bcfd3 commit 68a94d0

6 files changed

+51
-22
lines changed

lib/error/validation.js

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ var MongooseError = require('../error.js');
1414

1515
function ValidationError(instance) {
1616
this.errors = {};
17+
this._message = '';
1718
if (instance && instance.constructor.name === 'model') {
18-
MongooseError.call(this, instance.constructor.modelName + ' validation failed');
19+
this._message = instance.constructor.modelName + ' validation failed';
20+
MongooseError.call(this, this._message);
1921
} else {
20-
MongooseError.call(this, 'Validation failed');
22+
this._message = 'Validation failed';
23+
MongooseError.call(this, this._message);
2124
}
2225
if (Error.captureStackTrace) {
2326
Error.captureStackTrace(this);
@@ -37,24 +40,49 @@ function ValidationError(instance) {
3740
ValidationError.prototype = Object.create(MongooseError.prototype);
3841
ValidationError.prototype.constructor = MongooseError;
3942

43+
Object.defineProperty(ValidationError.prototype, 'message', {
44+
get: function() {
45+
return this._message + ': ' + _generateMessage(this);
46+
},
47+
enumerable: true
48+
});
4049

4150
/**
4251
* Console.log helper
4352
*/
4453

4554
ValidationError.prototype.toString = function() {
46-
var ret = this.name + ': ';
55+
return this.name + ': ' + _generateMessage(this);
56+
};
57+
58+
/*!
59+
* inspect helper
60+
*/
61+
62+
ValidationError.prototype.inspect = function() {
63+
return Object.assign(new Error(this.message), this);
64+
};
65+
66+
/*!
67+
* ignore
68+
*/
69+
70+
function _generateMessage(err) {
71+
var keys = Object.keys(err.errors || {});
72+
var len = keys.length;
4773
var msgs = [];
74+
var key;
4875

49-
Object.keys(this.errors || {}).forEach(function(key) {
50-
if (this === this.errors[key]) {
51-
return;
76+
for (var i = 0; i < len; ++i) {
77+
key = keys[i];
78+
if (err === err.errors[key]) {
79+
continue;
5280
}
53-
msgs.push(String(this.errors[key]));
54-
}, this);
81+
msgs.push(key + ': ' + err.errors[key].message);
82+
}
5583

56-
return ret + msgs.join(', ');
57-
};
84+
return msgs.join(', ');
85+
}
5886

5987
/*!
6088
* Module exports

test/document.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,7 +1272,7 @@ describe('document', function() {
12721272
var m = new M({name: 'gh1109-2', arr: [1]});
12731273
assert.equal(called, false);
12741274
m.save(function(err) {
1275-
assert.equal(String(err), 'ValidationError: BAM');
1275+
assert.equal(String(err), 'ValidationError: arr: BAM');
12761276
assert.equal(called, true);
12771277
m.arr.push(2);
12781278
called = false;
@@ -1301,7 +1301,7 @@ describe('document', function() {
13011301
assert.equal(err.errors.arr.message, 'Path `arr` is required.');
13021302
m.arr.push({nice: true});
13031303
m.save(function(err) {
1304-
assert.equal(String(err), 'ValidationError: BAM');
1304+
assert.equal(String(err), 'ValidationError: arr: BAM');
13051305
m.arr.push(95);
13061306
m.save(function(err) {
13071307
assert.ifError(err);

test/model.populate.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2023,7 +2023,8 @@ describe('model: populate:', function() {
20232023
});
20242024

20252025
comment.save(function(err) {
2026-
assert.equal('CommentWithRequiredField validation failed', err && err.message);
2026+
assert.ok(err);
2027+
assert.ok(err.message.indexOf('CommentWithRequiredField validation failed') === 0, err.message);
20272028
assert.ok('num' in err.errors);
20282029
assert.ok('str' in err.errors);
20292030
assert.ok('user' in err.errors);

test/model.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ describe('Model', function() {
11391139
db.close();
11401140
assert.equal(err.errors.name.message, 'Name cannot be greater than 1 character for path "name" with value `hi`');
11411141
assert.equal(err.name, 'ValidationError');
1142-
assert.equal(err.message, 'IntrospectionValidation validation failed');
1142+
assert.ok(err.message.indexOf('IntrospectionValidation validation failed') !== -1, err.message);
11431143
done();
11441144
});
11451145
});

test/schema.validation.test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,7 @@ describe('schema', function() {
10311031
bad.foods = 'waffles';
10321032
bad.validate(function(error) {
10331033
assert.ok(error);
1034-
var errorMessage = 'CastError: Cast to Object failed for value ' +
1034+
var errorMessage = 'foods: Cast to Object failed for value ' +
10351035
'"waffles" at path "foods"';
10361036
assert.ok(error.toString().indexOf(errorMessage) !== -1, error.toString());
10371037
done();
@@ -1045,7 +1045,7 @@ describe('schema', function() {
10451045
var bad = new Breakfast({});
10461046
bad.validate(function(error) {
10471047
assert.ok(error);
1048-
var errorMessage = 'ValidationError: Path `description` is required.';
1048+
var errorMessage = 'ValidationError: description: Path `description` is required.';
10491049
assert.equal(errorMessage, error.toString());
10501050
done();
10511051
});
@@ -1059,7 +1059,7 @@ describe('schema', function() {
10591059
var error = bad.validateSync();
10601060

10611061
assert.ok(error);
1062-
var errorMessage = 'ValidationError: Path `description` is required.';
1062+
var errorMessage = 'ValidationError: description: Path `description` is required.';
10631063
assert.equal(errorMessage, error.toString());
10641064
done();
10651065
});
@@ -1088,7 +1088,7 @@ describe('schema', function() {
10881088
var bad = new Breakfast({});
10891089
bad.validate(function(error) {
10901090
assert.ok(error);
1091-
var errorMessage = 'ValidationError: Path `description` is required.';
1091+
var errorMessage = 'ValidationError: description: Path `description` is required.';
10921092
assert.equal(errorMessage, error.toString());
10931093
done();
10941094
});
@@ -1106,7 +1106,7 @@ describe('schema', function() {
11061106
var Breakfast = mongoose.model('gh2832', breakfast, 'gh2832');
11071107
Breakfast.create({description: undefined}, function(error) {
11081108
assert.ok(error);
1109-
var errorMessage = 'ValidationError: CastError: Cast to String failed for value "undefined" at path "description"';
1109+
var errorMessage = 'ValidationError: description: Cast to String failed for value "undefined" at path "description"';
11101110
assert.equal(errorMessage, error.toString());
11111111
assert.ok(error.errors.description);
11121112
assert.equal(error.errors.description.reason.toString(), 'Error: oops');

test/types.buffer.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe('types.buffer', function() {
6969
});
7070

7171
t.validate(function(err) {
72-
assert.equal(err.message, 'UserBuffer validation failed');
72+
assert.ok(err.message.indexOf('UserBuffer validation failed') === 0, err.message);
7373
assert.equal(err.errors.required.kind, 'required');
7474
t.required = {x: [20]};
7575
t.save(function(err) {
@@ -83,11 +83,11 @@ describe('types.buffer', function() {
8383

8484
t.sub.push({name: 'Friday Friday'});
8585
t.save(function(err) {
86-
assert.equal(err.message, 'UserBuffer validation failed');
86+
assert.ok(err.message.indexOf('UserBuffer validation failed') === 0, err.message);
8787
assert.equal(err.errors['sub.0.buf'].kind, 'required');
8888
t.sub[0].buf = new Buffer('well well');
8989
t.save(function(err) {
90-
assert.equal(err.message, 'UserBuffer validation failed');
90+
assert.ok(err.message.indexOf('UserBuffer validation failed') === 0, err.message);
9191
assert.equal(err.errors['sub.0.buf'].kind, 'user defined');
9292
assert.equal(err.errors['sub.0.buf'].message, 'valid failed');
9393

0 commit comments

Comments
 (0)