Skip to content

Commit

Permalink
added; value at time of validation error
Browse files Browse the repository at this point in the history
Invalid values are now included in the error message as
well as included in the ValidatorError constructor.
  • Loading branch information
aheckmann committed Feb 20, 2013
1 parent 862c452 commit 6c7ba30
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 79 deletions.
15 changes: 11 additions & 4 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -956,8 +956,9 @@ Document.prototype.validate = function (cb) {
var p = self.schema.path(path);
if (!p) return --total || complete();

p.doValidate(self.getValue(path), function (err) {
if (err) self.invalidate(path, err, true);
var val = self.getValue(path);
p.doValidate(val, function (err) {
if (err) self.invalidate(path, err, undefined, /* embedded docs */true);
--total || complete();
}, self);
});
Expand All @@ -976,16 +977,22 @@ Document.prototype.validate = function (cb) {
*
* @param {String} path the field to invalidate
* @param {String|Error} err the error which states the reason `path` was invalid
* @param {Object|String|Number|any} value optional invalid value
* @api public
*/

Document.prototype.invalidate = function (path, err) {
Document.prototype.invalidate = function (path, err, val) {
if (!this.$__.validationError) {
this.$__.validationError = new ValidationError(this);
}

if (!err || 'string' === typeof err) {
err = new ValidatorError(path, err);
// sniffing arguments:
// need to handle case where user does not pass value
// so our error message is cleaner
err = 2 < arguments.length
? new ValidatorError(path, err, val)
: new ValidatorError(path, err)
}

this.$__.validationError.errors[path] = err;
Expand Down
10 changes: 8 additions & 2 deletions lib/errors/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@ var MongooseError = require('../error');
*
* @param {String} path
* @param {String} msg
* @param {String|Number|any} val
* @inherits MongooseError
* @api private
*/

function ValidatorError (path, type) {
function ValidatorError (path, type, val) {
var msg = type
? '"' + type + '" '
: '';
MongooseError.call(this, 'Validator ' + msg + 'failed for path ' + path);

var message = 'Validator ' + msg + 'failed for path ' + path
if (2 < arguments.length) message += ' with value `' + String(val) + '`';

MongooseError.call(this, message);
Error.captureStackTrace(this, arguments.callee);
this.name = 'ValidatorError';
this.path = path;
this.type = type;
this.value = val;
};

/*!
Expand Down
14 changes: 7 additions & 7 deletions lib/schematype.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,12 +559,12 @@ SchemaType.prototype.doValidate = function (value, fn, scope) {

if (!count) return fn(null);

function validate (val, msg) {
function validate (ok, msg, val) {
if (err) return;
if (val === undefined || val) {
if (ok === undefined || ok) {
--count || fn(null);
} else {
fn(err = new ValidatorError(path, msg));
fn(err = new ValidatorError(path, msg, val));
}
}

Expand All @@ -573,14 +573,14 @@ SchemaType.prototype.doValidate = function (value, fn, scope) {
, message = v[1];

if (validator instanceof RegExp) {
validate(validator.test(value), message);
validate(validator.test(value), message, value);
} else if ('function' === typeof validator) {
if (2 === validator.length) {
validator.call(scope, value, function (val) {
validate(val, message);
validator.call(scope, value, function (ok) {
validate(ok, message, value);
});
} else {
validate(validator.call(scope, value), message);
validate(validator.call(scope, value), message, value);
}
}
});
Expand Down
14 changes: 12 additions & 2 deletions lib/types/embedded.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,22 @@ EmbeddedDocument.prototype.inspect = function () {
* @api public
*/

EmbeddedDocument.prototype.invalidate = function (path, err, first) {
EmbeddedDocument.prototype.invalidate = function (path, err, val, first) {
if (!this.__parent) return false;

var index = this.__parentArray.indexOf(this);
var parentPath = this.__parentArray._path;
var fullPath = [parentPath, index, path].join('.');
this.__parent.invalidate(fullPath, err);

// sniffing arguments:
// need to check if user passed a value to keep
// our error message clean.
if (2 < arguments.length) {
this.__parent.invalidate(fullPath, err, val);
} else {
this.__parent.invalidate(fullPath, err);
}

if (first)
this.$__.validationError = ownerDocument(this).$__.validationError;
return true;
Expand Down
32 changes: 16 additions & 16 deletions test/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -924,8 +924,8 @@ describe('model', function(){
assert.ok(err instanceof MongooseError);
assert.ok(err instanceof ValidationError);
assert.ok(err.errors.simple instanceof ValidatorError);
assert.equal(err.errors.simple.message,'Validator "must be abc" failed for path simple');
assert.equal(post.errors.simple.message,'Validator "must be abc" failed for path simple');
assert.equal(err.errors.simple.message,'Validator "must be abc" failed for path simple with value ``');
assert.equal(post.errors.simple.message,'Validator "must be abc" failed for path simple with value ``');

post.set('simple', 'abc');
post.save(function(err){
Expand All @@ -948,7 +948,7 @@ describe('model', function(){
var doc = new IntrospectionValidation({name: 'hi'});
doc.save( function (err) {
db.close();
assert.equal(err.errors.name.message,"Validator \"Name cannot be greater than 1 character\" failed for path name");
assert.equal(err.errors.name.message, "Validator \"Name cannot be greater than 1 character\" failed for path name with value `hi`");
assert.equal(err.name,"ValidationError");
assert.equal(err.message,"Validation failed");
done();
Expand Down Expand Up @@ -1006,17 +1006,17 @@ describe('model', function(){
assert.ok(err.errors.password instanceof ValidatorError);
assert.ok(err.errors.email instanceof ValidatorError);
assert.ok(err.errors.username instanceof ValidatorError);
assert.equal(err.errors.password.message,'Validator failed for path password');
assert.equal(err.errors.email.message,'Validator failed for path email');
assert.equal(err.errors.username.message,'Validator failed for path username');
assert.equal(err.errors.password.message,'Validator failed for path password with value `short`');
assert.equal(err.errors.email.message,'Validator failed for path email with value `too`');
assert.equal(err.errors.username.message,'Validator failed for path username with value `nope`');

assert.equal(Object.keys(post.errors).length, 3);
assert.ok(post.errors.password instanceof ValidatorError);
assert.ok(post.errors.email instanceof ValidatorError);
assert.ok(post.errors.username instanceof ValidatorError);
assert.equal(post.errors.password.message,'Validator failed for path password');
assert.equal(post.errors.email.message,'Validator failed for path email');
assert.equal(post.errors.username.message,'Validator failed for path username');
assert.equal(post.errors.password.message,'Validator failed for path password with value `short`');
assert.equal(post.errors.email.message,'Validator failed for path email with value `too`');
assert.equal(post.errors.username.message,'Validator failed for path username with value `nope`');
done();
});
});
Expand Down Expand Up @@ -1132,9 +1132,9 @@ describe('model', function(){
assert.ok(err instanceof MongooseError);
assert.ok(err instanceof ValidationError);
assert.ok(err.errors['items.0.subs.0.required'] instanceof ValidatorError);
assert.equal(err.errors['items.0.subs.0.required'].message,'Validator "required" failed for path required');
assert.equal(err.errors['items.0.subs.0.required'].message,'Validator "required" failed for path required with value ``');
assert.ok(post.errors['items.0.subs.0.required'] instanceof ValidatorError);
assert.equal(post.errors['items.0.subs.0.required'].message,'Validator "required" failed for path required');
assert.equal(post.errors['items.0.subs.0.required'].message,'Validator "required" failed for path required with value ``');

assert.ok(!err.errors['items.0.required']);
assert.ok(!err.errors['items.0.required']);
Expand All @@ -1148,7 +1148,7 @@ describe('model', function(){
assert.ok(err);
assert.ok(err.errors);
assert.ok(err.errors['items.0.required'] instanceof ValidatorError);
assert.equal(err.errors['items.0.required'].message,'Validator "required" failed for path required');
assert.equal(err.errors['items.0.required'].message,'Validator "required" failed for path required with value ``');

assert.ok(!err.errors['items.0.subs.0.required']);
assert.ok(!err.errors['items.0.subs.0.required']);
Expand Down Expand Up @@ -1190,7 +1190,7 @@ describe('model', function(){
assert.ok(err instanceof MongooseError);
assert.ok(err instanceof ValidationError);
assert.ok(err.errors.async instanceof ValidatorError);
assert.equal(err.errors.async.message,'Validator "async validator" failed for path async');
assert.equal(err.errors.async.message,'Validator "async validator" failed for path async with value `test`');
assert.equal(true, executed);
executed = false;

Expand Down Expand Up @@ -3498,9 +3498,9 @@ describe('model', function(){
query.exec(function (err, found) {
db.close();
assert.ifError(err);
assert.equal(found.length,2);
assert.equal(found[0]._id.id,createdOne._id.id);
assert.equal(found[1]._id.id,createdTwo._id.id);
assert.equal(found.length, 2);
assert.equal(found[0]._id.id, createdOne._id.id);
assert.equal(found[1]._id.id, createdTwo._id.id);
done();
});
});
Expand Down
3 changes: 3 additions & 0 deletions test/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ describe('schema', function(){

Tobi.path('friends').doValidate(100, function(err){
assert.ok(err instanceof ValidatorError);
assert.equal('friends', err.path);
assert.equal('max', err.type);
assert.equal(100, err.value);
});

Tobi.path('friends').doValidate(1, function(err){
Expand Down
2 changes: 1 addition & 1 deletion test/types.document.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('types.document', function(){
a.save(function(err){
assert.ok(a.__parent.$__.validationError instanceof ValidationError);
assert.equal(a.__parent.errors['jsconf.ar.0.work'].name, 'ValidatorError');
assert.equal(a.__parent.$__.validationError.toString(), 'ValidationError: Validator "required" failed for path test, Validator failed for path work');
assert.equal(a.__parent.$__.validationError.toString(), 'ValidationError: Validator "required" failed for path test with value ``, Validator failed for path work with value `nope`');
done();
});
});
Expand Down
126 changes: 79 additions & 47 deletions test/types.documentarray.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,63 +179,61 @@ describe('types.documentarray', function(){
})
})

describe('EmbeddedDocumentArray', function(){
describe('create()', function(){
it('works', function(done){
var a = new MongooseDocumentArray([]);
assert.equal('function', typeof a.create);

var schema = new Schema({ docs: [new Schema({ name: 'string' })] });
var T = mongoose.model('embeddedDocument#create_test', schema, 'asdfasdfa'+ random());
var t = new T;
assert.equal('function', typeof t.docs.create);
var subdoc = t.docs.create({ name: 100 });
assert.ok(subdoc._id);
assert.equal(subdoc.name, '100');
assert.ok(subdoc instanceof EmbeddedDocument);
done();
})
describe('create()', function(){
it('works', function(done){
var a = new MongooseDocumentArray([]);
assert.equal('function', typeof a.create);

var schema = new Schema({ docs: [new Schema({ name: 'string' })] });
var T = mongoose.model('embeddedDocument#create_test', schema, 'asdfasdfa'+ random());
var t = new T;
assert.equal('function', typeof t.docs.create);
var subdoc = t.docs.create({ name: 100 });
assert.ok(subdoc._id);
assert.equal(subdoc.name, '100');
assert.ok(subdoc instanceof EmbeddedDocument);
done();
})
})

describe('push()', function(){
it('does not re-cast instances of its embedded doc xxxxxx', function(done){
var db = start();
describe('push()', function(){
it('does not re-cast instances of its embedded doc', function(done){
var db = start();

var child = new Schema({ name: String, date: Date });
child.pre('save', function (next) {
this.date = new Date;
next();
});
var schema = Schema({ children: [child] });
var M = db.model('embeddedDocArray-push-re-cast', schema, 'edarecast-'+random());
var m = new M;
m.save(function (err) {
var child = new Schema({ name: String, date: Date });
child.pre('save', function (next) {
this.date = new Date;
next();
});
var schema = Schema({ children: [child] });
var M = db.model('embeddedDocArray-push-re-cast', schema, 'edarecast-'+random());
var m = new M;
m.save(function (err) {
assert.ifError(err);
M.findById(m._id, function (err, doc) {
assert.ifError(err);
M.findById(m._id, function (err, doc) {
var c = doc.children.create({ name: 'first' })
assert.equal(undefined, c.date);
doc.children.push(c);
assert.equal(undefined, c.date);
doc.save(function (err) {
assert.ifError(err);
var c = doc.children.create({ name: 'first' })
assert.equal(undefined, c.date);
assert.ok(doc.children[doc.children.length-1].date);
assert.equal(c.date, doc.children[doc.children.length-1].date);

doc.children.push(c);
assert.equal(undefined, c.date);
doc.children.push(c);

doc.save(function (err) {
assert.ifError(err);
assert.ok(doc.children[doc.children.length-1].date);
assert.equal(c.date, doc.children[doc.children.length-1].date);

doc.children.push(c);
doc.children.push(c);

doc.save(function (err) {
M.findById(m._id, function (err, doc) {
db.close()
assert.ifError(err);
M.findById(m._id, function (err, doc) {
db.close()
assert.ifError(err);
assert.equal(3, doc.children.length);
doc.children.forEach(function (child) {
assert.equal(doc.children[0].id, child.id);
})
done();
assert.equal(3, doc.children.length);
doc.children.forEach(function (child) {
assert.equal(doc.children[0].id, child.id);
})
done();
})
})
})
Expand Down Expand Up @@ -289,4 +287,38 @@ describe('types.documentarray', function(){
})
});

describe('invalidate()', function(){
it('works', function(done){
var schema = Schema({ docs: [{ name: 'string' }] });
var T = mongoose.model('embeddedDocument#invalidate_test', schema, 'asdfasdfa'+ random());
var t = new T;
t.docs.push({ name: 100 });
var subdoc = t.docs[t.docs.length-1];
subdoc.invalidate('name', 'boo boo', '%');

t.validate(function (err) {
var e = t.errors['docs.0.name'];
assert.ok(e);
assert.equal(e.path, 'docs.0.name');
assert.equal(e.type, 'boo boo');
assert.equal(e.value, '%');
done();
})
})

it('handles validation failures', function(done){
var db = start();
var nested = Schema({ v: { type: Number, max: 30 }});
var schema = Schema({
docs: [nested]
}, { collection: 'embedded-invalidate-'+random() });
var M = db.model('embedded-invalidate', schema);
var m = new M({ docs: [{ v: 900 }] });
m.save(function (err) {
db.close();
assert.equal(900, err.errors['docs.0.v'].value);
done();
});
})
})
})

0 comments on commit 6c7ba30

Please sign in to comment.