diff --git a/index.js b/index.js index 720e3d6..c340282 100644 --- a/index.js +++ b/index.js @@ -1,46 +1,37 @@ /* jshint node: true */ 'use strict'; -var i18nError = module.exports = function(options) { - options = options || {}; - this.prefix = options.prefix ||  this.prefix; -}; - -i18nError.prototype = { - prefix: 'error', +module.exports = function(options) { + this.prefix = (options || {}).prefix ||  'error.'; - handler: function(handler) { + this.handler = function(handler) { var self = this; return function(err, req, res, next) { if (!err) return next(err); + if (typeof res.__ !== 'function') { + console.error('no i18n.__ function found.'); + return next(err); + } - var i18n; - if (typeof res.__ === 'function') i18n = res; - else if (req.i18n && typeof req.i18n.__ === 'function') i18n = req.i18n; - else throw 'no i18n.__ function found.'; + var i18nErr = self.parseValidationError(err, res.__) ||  self.parseUniqueError(err, res.__); - var mongooseErr = self.parseValidationError(err, i18n); - if (!mongooseErr) mongooseErr = self.parseUniqueError(err, i18n); - if (mongooseErr) { - return handler(mongooseErr, req, res, next); - } else return next(err); + if (i18nErr) return handler(i18nErr, req, res, next); + else return next(err); }; - }, + }; - parseValidationError: function(err, i18n) { - if (!err ||  err.name !== 'ValidationError') return null; + this.parseValidationError = function(err, __) { + if (!err) return null; + if (err.name !== 'ValidationError') return null; - var self = this; var result = {}; - var errors = err.errors; - Object.keys(errors).forEach(function(key) { - var error = errors[key]; + for (var key in err.errors) { + var error = err.errors[key]; var type = error.kind; var condition; - var message = self.prefix + '.'; - var value = error.value; + var message = this.prefix; // cast error if (error.name === 'CastError') { @@ -50,14 +41,13 @@ i18nError.prototype = { // match or enum error else if (type === 'regexp' ||  type === 'enum') { - var model = err.message.substring(0, err.message.lastIndexOf(' validation failed')).toLowerCase(); + var model = /(.*)\svalidation\sfailed/.exec(err.message)[1].toLowerCase(); message += model + '.' + key + '.' + type; } // custom validation error else if (type.match(/user defined/)) { - var array = error.message.split(/\./g); - type = array[array.length - 1]; + type = error.message.split(/\./g).reverse()[0]; message += error.message; } @@ -69,30 +59,27 @@ i18nError.prototype = { result[key] = { type: type, - message: i18n.__(message, condition), - value: value + message: __(message, condition), + value: error.value }; - }); + } return result; - }, + }; - parseUniqueError: function(err, i18n) { - if (!err ||  err.name !== 'MongoError' || !(err.code === 11000 ||  err.code === 11001)) return null; + this.parseUniqueError = function(err, __) { + if (!err) return null; + if (err.name !== 'MongoError') return null; + if (err.code !== 11000 && err.code !== 11001) return null; - var result = {}; - - var regex = /index:\s*.+?\.\$(\S*)_.+\s*dup key:\s*\{.*?:\s*"(.*)"\s*\}/; - var matches = regex.exec(err.message); - var key = matches[1]; - var value = matches[2]; + var matches = /index:\s.*\.\$(.*)_1\sdup key:\s\{\s:\s"(.*)"\s\}/.exec(err.message); - result[key] = { + var result = {}; + result[matches[1]] = { type: 'unique', - message: i18n.__(this.prefix + '.unique'), - value: value + message: __(this.prefix + 'unique'), + value: matches[2] }; - return result; - } + }; }; diff --git a/locales/de.json b/locales/de.json deleted file mode 100644 index 9e26dfe..0000000 --- a/locales/de.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/locales/en.js b/locales/en.js deleted file mode 100644 index fcc5289..0000000 --- a/locales/en.js +++ /dev/null @@ -1,10 +0,0 @@ -{ - "error.required": "is required", - "error.minlength": "needs a minimum length of %s", - "error.min": "needs a number greater than or equal to %s", - "error.model.value.regexp": "error.model.value.regexp", - "error.model.value.enum": "error.model.value.enum", - "error.model.value.custom": "error.model.value.custom", - "error.unique": "error.unique", - "err.required": "err.required" -} \ No newline at end of file diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 0000000..1aa984e --- /dev/null +++ b/locales/en.json @@ -0,0 +1,13 @@ +{ + "error.required": "is required", + "error.minlength": "needs a minimum length of %s", + "error.maxlength": "has a length greater than %s", + "error.model.value.regexp": "must consist of only letters", + "error.model.value.enum": "must be either \"true\" or \"false\"", + "error.min": "needs a minimum value of %s", + "error.max": "has a value greater than %s", + "error.model.value.custom": "must consist of only letters", + "error.unique": "already exists", + "err.required": "is not allowed to left blank", + "error.cast": "is not valid" +} diff --git a/package.json b/package.json index 6c8d449..adbc9f2 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "devDependencies": { "mongoose": "^4.2.8", "i18n": "^0.5.0", - "i18n-2": "^0.6.0", "express": "^4.13.3", "should": "^7.1.1", "supertest": "1.1.0" diff --git a/test/index.js b/test/index.js index 0f953c6..f94a239 100644 --- a/test/index.js +++ b/test/index.js @@ -1,21 +1,28 @@ /* jshint node: true, mocha: true */ 'use strict'; -var express = require('express'); var mongoose = require('mongoose'); - -var app = express(); -var server = app.listen(3000); +var express = require('express'); +var i18n = require('i18n'); mongoose.connect('mongodb://localhost/mongoose-i18n-error'); mongoose.connection.on('error', function() { throw new Error('Unable to connect to database.'); }); +var app = express(); +var server = app.listen(3000); + +i18n.configure({ + locales: ['en'], + directory: './locales' +}); + +app.use(i18n.init); + describe('Mongoose I18n Error', function() { - require('./tests/message')(); - require('./tests/i18n')(app); - //require('./tests/i18n-2')(app); + require('./tests/message')(i18n); + require('./tests/express')(app); }); server.close(); diff --git a/test/tests/express.js b/test/tests/express.js new file mode 100644 index 0000000..4333259 --- /dev/null +++ b/test/tests/express.js @@ -0,0 +1,65 @@ +/* jshint node: true, mocha: true */ +'use strict'; + +var mongoose = require('mongoose'); +var request = require('supertest'); +var should = require('should'); + +var helper = require('../helper'); +var i18nMongooseError = new(require('../../index'))(); + +module.exports = function(app) { + + describe('Express', function() { + afterEach(helper.afterEach); + + it('should print a localized validation error message', function(done) { + var Model = mongoose.model('Model', helper.createStringSchema()); + app.post('/1', function(req, res, next) { + new Model().save(function(err) { + if (err) return next(err); + res.sendStatus(200); + }); + }); + + app.use(i18nMongooseError.handler(function(err, req, res) { + res.status(422).json(err); + })); + + request(app).post('/1').expect(422).end(function(err, res) { + res.body.value.message.should.equal('is required'); + + done(); + }); + }); + + it('should print a localized unique error message', function(done) { + var Model = mongoose.model('Model', helper.createUniqueSchema()); + + app.post('/2', function(req, res, next) { + new Model({ + value: 'ab' + }).save(function(err) { + if (err) return next(err); + res.sendStatus(200); + }); + }); + + app.use(i18nMongooseError.handler(function(err, req, res) { + res.status(422).json(err); + })); + + new Model({ + value: 'ab' + }).save(function(err) { + should.not.exist(err); + + request(app).post('/2').expect(422).end(function(err, res) { + res.body.value.message.should.equal('already exists'); + + done(); + }); + }); + }); + }); +}; diff --git a/test/tests/i18n-2.js b/test/tests/i18n-2.js deleted file mode 100644 index ec1a22e..0000000 --- a/test/tests/i18n-2.js +++ /dev/null @@ -1,85 +0,0 @@ -/* jshint node: true, mocha: true */ -'use strict'; - -var mongoose = require('mongoose'); -var i18n2 = require('i18n-2'); -var request = require('supertest'); - -var helper = require('../helper'); -var i18nMongooseError = new(require('../../index'))(); - -module.exports = function(app) { - - describe('I18n-2', function() { - afterEach(helper.afterEach); - - i18n2.expressBind(app, { - locales: ['en'], - session: false - }); - - it('should print a localized error message', function(done) { - var Model = mongoose.model('Model', helper.createStringSchema()); - app.post('/i18n-2/1', function(req, res, next) { - new Model().save(function(err) { - if (err) return next(err); - res.sendStatus(200); - }); - }); - - app.use(i18nMongooseError.handler(function(err, req, res) { - res.status(422).json(err); - })); - - request(app).post('/i18n-2/1').expect(422).end(function(err, res) { - res.body.value.message.should.equal('is required'); - - done(); - }); - }); - - it('should print a localized minlength error message', function(done) { - var Model = mongoose.model('Model', helper.createStringSchema()); - app.post('/i18n-2/2', function(req, res, next) { - new Model({ - value: 'ab' - }).save(function(err) { - if (err) return next(err); - res.sendStatus(200); - }); - }); - - app.use(i18nMongooseError.handler(function(err, req, res) { - res.status(422).json(err); - })); - - request(app).post('/i18n-2/2').expect(422).end(function(err, res) { - res.body.value.message.should.equal('needs a minimum length of 3'); - - done(); - }); - }); - - it('should print a localized min error message', function(done) { - var Model = mongoose.model('Model', helper.createNumberSchema()); - app.post('/i18n-2/3', function(req, res, next) { - new Model({ - value: -1 - }).save(function(err) { - if (err) return next(err); - res.sendStatus(200); - }); - }); - - app.use(i18nMongooseError.handler(function(err, req, res) { - res.status(422).json(err); - })); - - request(app).post('/i18n-2/3').expect(422).end(function(err, res) { - res.body.value.message.should.equal('needs a number greater than or equal to 0'); - - done(); - }); - }); - }); -}; diff --git a/test/tests/i18n.js b/test/tests/i18n.js deleted file mode 100644 index aa2424a..0000000 --- a/test/tests/i18n.js +++ /dev/null @@ -1,87 +0,0 @@ -/* jshint node: true, mocha: true */ -'use strict'; - -var mongoose = require('mongoose'); -var i18n = require('i18n'); -var request = require('supertest'); - -var helper = require('../helper'); -var i18nMongooseError = new(require('../../index'))(); - -module.exports = function(app) { - - describe('I18n', function() { - afterEach(helper.afterEach); - - i18n.configure({ - locales: ['en'], - directory: './locales' - }); - - app.use(i18n.init); - - it('should print a localized error message', function(done) { - var Model = mongoose.model('Model', helper.createStringSchema()); - app.post('/i18n/1', function(req, res, next) { - new Model().save(function(err) { - if (err) return next(err); - res.sendStatus(200); - }); - }); - - app.use(i18nMongooseError.handler(function(err, req, res) { - res.status(422).json(err); - })); - - request(app).post('/i18n/1').expect(422).end(function(err, res) { - res.body.value.message.should.equal('is required'); - - done(); - }); - }); - - it('should print a localized minlength error message', function(done) { - var Model = mongoose.model('Model', helper.createStringSchema()); - app.post('/i18n/2', function(req, res, next) { - new Model({ - value: 'ab' - }).save(function(err) { - if (err) return next(err); - res.sendStatus(200); - }); - }); - - app.use(i18nMongooseError.handler(function(err, req, res) { - res.status(422).json(err); - })); - - request(app).post('/i18n/2').expect(422).end(function(err, res) { - res.body.value.message.should.equal('needs a minimum length of 3'); - - done(); - }); - }); - - it('should print a localized min error message', function(done) { - var Model = mongoose.model('Model', helper.createNumberSchema()); - app.post('/i18n/3', function(req, res, next) { - new Model({ - value: -1 - }).save(function(err) { - if (err) return next(err); - res.sendStatus(200); - }); - }); - - app.use(i18nMongooseError.handler(function(err, req, res) { - res.status(422).json(err); - })); - - request(app).post('/i18n/3').expect(422).end(function(err, res) { - res.body.value.message.should.equal('needs a number greater than or equal to 0'); - - done(); - }); - }); - }); -}; diff --git a/test/tests/message.js b/test/tests/message.js index 309b329..2286a5f 100644 --- a/test/tests/message.js +++ b/test/tests/message.js @@ -2,21 +2,12 @@ 'use strict'; var mongoose = require('mongoose'); -var i18n = require('i18n'); var should = require('should'); var helper = require('../helper'); var i18nMongooseError = new(require('../../index'))(); -i18n.configure({ - locales: ['de'], - defaultLocale: 'de', - directory: './locales' -}); - -i18n.setLocale('de'); - -module.exports = function() { +module.exports = function(i18n) { describe('Message', function() { afterEach(helper.afterEach); @@ -25,48 +16,48 @@ module.exports = function() { // required error var Model = mongoose.model('Model', helper.createStringSchema()); new Model().save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('required'); - err.value.message.should.equal('error.required'); + err.value.message.should.equal('is required'); should.equal(err.value.value, undefined); // minlength error new Model({ value: 'ab' }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('minlength'); - err.value.message.should.equal('error.minlength.3'); + err.value.message.should.equal('needs a minimum length of 3'); err.value.value.should.equal('ab'); // maxlength error new Model({ value: 'abcdefghijk' }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('maxlength'); - err.value.message.should.equal('error.maxlength.10'); + err.value.message.should.equal('has a length greater than 10'); err.value.value.should.equal('abcdefghijk'); // cast error new Model({ value: {} }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('cast'); - err.value.message.should.equal('error.cast'); + err.value.message.should.equal('is not valid'); done(); }); @@ -81,12 +72,12 @@ module.exports = function() { new Model({ value: '1234' }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('regexp'); - err.value.message.should.equal('error.model.value.regexp'); + err.value.message.should.equal('must consist of only letters'); err.value.value.should.equal('1234'); done(); @@ -99,12 +90,12 @@ module.exports = function() { new Model({ value: 'invalid' }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('enum'); - err.value.message.should.equal('error.model.value.enum'); + err.value.message.should.equal('must be either "true" or "false"'); err.value.value.should.equal('invalid'); done(); @@ -116,48 +107,48 @@ module.exports = function() { // required error var Model = mongoose.model('Model', helper.createNumberSchema()); new Model().save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('required'); - err.value.message.should.equal('error.required'); + err.value.message.should.equal('is required'); should.equal(err.value.value, undefined); // min error new Model({ value: -1 }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('min'); - err.value.message.should.equal('error.min.0'); + err.value.message.should.equal('needs a minimum value of 0'); err.value.value.should.equal(-1); // max error new Model({ value: 11 }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('max'); - err.value.message.should.equal('error.max.10'); + err.value.message.should.equal('has a value greater than 10'); err.value.value.should.equal(11); // cast error new Model({ value: {} }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('cast'); - err.value.message.should.equal('error.cast'); + err.value.message.should.equal('is not valid'); done(); }); @@ -171,12 +162,12 @@ module.exports = function() { new Model({ value: '1234' }).save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('custom'); - err.value.message.should.equal('error.model.value.custom'); + err.value.message.should.equal('must consist of only letters'); err.value.value.should.equal('1234'); done(); @@ -186,16 +177,16 @@ module.exports = function() { it('should parse multiple errors correctly', function(done) { var Model = mongoose.model('Model', helper.createMultipleValidatorSchema()); new Model().save(function(err) { - err = i18nMongooseError.parseValidationError(err, i18n); + err = i18nMongooseError.parseValidationError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(2); should.exist(err.string); err.string.type.should.equal('required'); - err.string.message.should.equal('error.required'); + err.string.message.should.equal('is required'); should.equal(err.string.value, undefined); should.exist(err.number); err.number.type.should.equal('required'); - err.number.message.should.equal('error.required'); + err.number.message.should.equal('is required'); should.equal(err.number.value, undefined); done(); @@ -212,12 +203,12 @@ module.exports = function() { new Model({ value: 'test' }).save(function(err) { - err = i18nMongooseError.parseUniqueError(err, i18n); + err = i18nMongooseError.parseUniqueError(err, i18n.__); should.exist(err); Object.keys(err).length.should.be.exactly(1); should.exist(err.value); err.value.type.should.equal('unique'); - err.value.message.should.equal('error.unique'); + err.value.message.should.equal('already exists'); err.value.value.should.equal('test'); done(); @@ -225,13 +216,23 @@ module.exports = function() { }); }); - it('should have the correct prefix', function(done) { + it('uses custom prefix', function(done) { var Model = mongoose.model('Model', helper.createStringSchema()); new Model().save(function(err) { err = new(require('../../index'))({ - prefix: 'err' - }).parseValidationError(err, i18n); - err.value.message.should.equal('err.required'); + prefix: 'err.' + }).parseValidationError(err, i18n.__); + err.value.message.should.equal('is not allowed to left blank'); + + done(); + }); + }); + + it('uses default prefix', function(done) { + var Model = mongoose.model('Model', helper.createStringSchema()); + new Model().save(function(err) { + err = new(require('../../index'))({}).parseValidationError(err, i18n.__); + err.value.message.should.equal('is required'); done(); });