From 84ad05a90dc19d281be5dbc90f6429162675bb4a Mon Sep 17 00:00:00 2001 From: darc Date: Wed, 18 Mar 2015 23:06:12 -0300 Subject: [PATCH] feat: String maxLength validator --- .gitignore | 2 + .jshintrc | 42 +++++++++++ .travis.yml | 4 ++ gulpfile.js | 89 +++++++++++++++++++++++ index.js | 4 ++ lib/string-ext/max-length.js | 55 +++++++++++++++ package.json | 40 +++++++++++ test/max-length.string.test.js | 124 +++++++++++++++++++++++++++++++++ 8 files changed, 360 insertions(+) create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100644 .travis.yml create mode 100644 gulpfile.js create mode 100644 index.js create mode 100644 lib/string-ext/max-length.js create mode 100644 package.json create mode 100644 test/max-length.string.test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62562b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +coverage +node_modules diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..b1bf401 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,42 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": false, + "camelcase": false, + "curly": false, + "eqeqeq": true, + "immed": true, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": false, + "trailing": true, + "smarttabs": false, + "expr": true, + "globals": { + "angular": true + }, + "predef": [ + "define", + "require", + "exports", + "module", + "describe", + "before", + "beforeEach", + "after", + "afterEach", + "it", + "StringMask", + "BrV" + ], + "indent": 4, + "maxlen": 120, + "devel": false, + "noempty": true +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..18ae2d8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.11" + - "0.10" diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..40e0378 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,89 @@ +var gulp = require('gulp'), + path = require('path'), + jshintReporter = require('jshint-stylish'), + plugins = require('gulp-load-plugins')({ + config: path.join(__dirname, 'package.json') + }), + istanbul = require('gulp-istanbul'); + +var filePatterns = { + src: ['./lib/**/*.js', './index.js'], + test: ['./test/**/*.test.js'] +}; + +var config = { + files: filePatterns.test, + reporter: 'progress', + + target: 'node', + framework: 'mocha', + coverage: filePatterns.src +}; + +function handleError(err) { + console.log(err.toString()); + this.emit('end'); +} + +function mochaRunnerFactory() { + var mocha = require('gulp-mocha'); + + return mocha({ + reporter: config.reporter || 'progress' + }); +} + +function unitTestFactory(watch) { + function mochaRunner() { + return gulp.src(config.files, { + cwd: process.env.PWD, + read: false + }) + .pipe(mochaRunnerFactory(config.reporter)) + .on('error', handleError); + } + + if (!watch) { + return mochaRunner; + } + + var filesToWatch = config.coverage.concat(config.files); + + return function() { + gulp.watch(filesToWatch, mochaRunner); + }; +} + +gulp.task('jshint', function() { + gulp.src(config.coverage.concat(config.files)) + .pipe(plugins.jshint('.jshintrc')) + .pipe(plugins.jshint.reporter(jshintReporter)); +}); + +gulp.task('default', ['jshint'], function() { + gulp.watch(config.files.concat(config.coverage), ['jshint']); +}); + +gulp.task('test', ['jshint'], function(done) { + unitTestFactory(false)(); +}); + +gulp.task('test-watch', function(done) { + config.reporter = 'spec'; + unitTestFactory(true)(); +}); + +gulp.task('test-coverage', function(done) { + gulp.src(config.coverage) + .pipe(istanbul()) + .pipe(istanbul.hookRequire()) + .on('finish', function() { + gulp.src(config.files, { + cwd: process.env.PWD, + read: false + }) + .pipe(mochaRunnerFactory('progress')) + .pipe(istanbul.writeReports()) + .on('end', done); + }); +}); diff --git a/index.js b/index.js new file mode 100644 index 0000000..cf511fd --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +module.exports = function(mongoose) { + require('./lib/string-ext/max-length.js')(mongoose); + require('./lib/string-ext/min-length.js')(mongoose); +}; diff --git a/lib/string-ext/max-length.js b/lib/string-ext/max-length.js new file mode 100644 index 0000000..bfd04be --- /dev/null +++ b/lib/string-ext/max-length.js @@ -0,0 +1,55 @@ +module.exports = function(mongoose) { + var SchemaString = mongoose.SchemaTypes.String, + errorMessages = mongoose.Error.messages; + + /** + * Sets a maxlength string validator. + * + * ####Example: + * + * var s = new Schema({ n: { type: String, maxlength: 4 }) + * var M = db.model('M', s) + * var m = new M({ n: 'teste' }) + * m.save(function (err) { + * console.error(err) // validator error + * m.n = 'test; + * m.save() // success + * }) + * + * // custom error messages + * // We can also use the special {MAX_LENGTH} token which will be replaced with the invalid value + * var max = [4, 'The length of path `{PATH}` ({VALUE}) is beneath the limit ({MAX_LENGTH}).']; + * var schema = new Schema({ n: { type: String, maxlength: max }) + * var M = mongoose.model('Measurement', schema); + * var s= new M({ n: 'teste' }); + * s.validate(function (err) { + * console.log(String(err)) // ValidationError: The length of path `n` (5) is beneath the limit (4). + * }) + * + * @param {Number} max length value + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + + SchemaString.prototype.maxlength = function(value, message) { + if (this.maxlengthValidator) { + this.validators = this.validators.filter(function (v) { + return v[0] !== this.maxlengthValidator; + }, this); + } + + if (null !== value) { + var msg = message || errorMessages.String.maxlength; + msg = msg.replace(/{MAX_LENGTH}/, value); + this.validators.push([this.maxlengthValidator = function (v) { + return !v || v.length <= value; + }, msg, 'maxlength']); + } + + return this; + }; + + errorMessages.String.maxlength = 'Path `{PATH}` ({VALUE}) exceeds the maximum allowed length ({MAX_LENGTH}).'; +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..666c84e --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "mongoose-types-ext", + "version": "0.1.0", + "description": "A package of mongoose types extensions", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/the-darc/mongoose-types-ext" + }, + "keywords": [ + "mongoose", + "mongodb", + "minLength", + "maxLength" + ], + "author": "darc.tec@gmail.com", + "license": "MIT", + "bugs": { + "url": "https://github.com/the-darc/mongoose-types-ext/issues" + }, + "homepage": "https://github.com/the-darc/mongoose-types-ext", + "devDependencies": { + "gulp": "^3.8.11", + "gulp-istanbul": "^0.6.0", + "gulp-jshint": "^1.9.4", + "gulp-load-plugins": "^0.8.1", + "gulp-mocha": "^2.0.0", + "jasmine-core": "^2.2.0", + "jshint-stylish": "^1.0.1", + "mocha": "^2.2.1", + "mongoose": "^3.8.25", + "should": "^5.2.0" + } +} diff --git a/test/max-length.string.test.js b/test/max-length.string.test.js new file mode 100644 index 0000000..97a7fc5 --- /dev/null +++ b/test/max-length.string.test.js @@ -0,0 +1,124 @@ +describe('String.maxLength:', function() { + var db = require('db'), + mongoose = db.mongoose, + should = require('should'); + + require('../index'); + require('../lib/string-ext/max-length'); + + var TestDoc; + + before('init db', db.init); + before('load extensions', db.loadExtensions); + before('load test model', function(done) { + var TestDocSchema = new mongoose.Schema({ + field01: String, + field02: { + type: String, + maxlength: 5 + }, + field03: { + type: String, + maxlength: [10, 'Invalid text length'] + }, + field04: { + type: String, + maxlength: 10 + } + }); + TestDocSchema.path('field04', { + type: String, + maxlength: [1, 'Path {PATH} ({VALUE}) has length greater than {MAX_LENGTH}'] + }); + TestDoc = mongoose.model('TestDoc', TestDocSchema); + done(); + }); + + it('should not impact normal string types', function(done) { + var tst = new TestDoc({field01: '12345678'}); + tst.save(function(err, tst) { + if(err) { + return done(err); + } + should(tst.field01).be.eql('12345678'); + done(); + }); + }); + it('should not throw maxLength error for shorter strings', function(done) { + var tst = new TestDoc({field02: '1234'}); + tst.save(function(err, tst) { + if(err) { + return done(err); + } + should(tst.field02).be.eql('1234'); + done(); + }); + }); + it('should not throw maxLength error for exact length strings', function(done) { + var tst = new TestDoc({field02: '12345'}); + tst.save(function(err, tst) { + if(err) { + return done(err); + } + should(tst.field02).be.eql('12345'); + done(); + }); + }); + it('should not throw maxLength error for empty values', function(done) { + var tst = new TestDoc({field01: 'some value'}); + tst.save(function(err, tst) { + if(err) { + return done(err); + } + should(tst.field01).be.eql('some value'); + should(tst.field02).be.not.ok; + done(); + }); + }); + it('should throw maxLength default error message', function(done) { + var tst = new TestDoc({field02: '123456'}); + tst.save(function(err) { + should(err).be.ok; + should(err.message).be.eql('Validation failed'); + should(err.name).be.eql('ValidationError'); + should(err.errors.field02).be.ok; + should(err.errors.field02.message).be.eql( + 'Path `field02` (123456) exceeds the maximum allowed length (5).' + ); + done(); + }); + }); + it('should throw maxLength custom error message', function(done) { + var tst = new TestDoc({ + field02: '123456', + field03: '123456789112345' + }); + tst.save(function(err) { + should(err).be.ok; + should(err.message).be.eql('Validation failed'); + should(err.name).be.eql('ValidationError'); + should(err.errors.field02).be.ok; + should(err.errors.field02.message).be.eql( + 'Path `field02` (123456) exceeds the maximum allowed length (5).' + ); + should(err.errors.field03).be.ok; + should(err.errors.field03.message).be.eql('Invalid text length'); + done(); + }); + }); + it('should throw maxLength custom error with special tokens replaced', function(done) { + var tst = new TestDoc({field04: 'test'}); + tst.save(function(err) { + should(err).be.ok; + should(err.message).be.eql('Validation failed'); + should(err.name).be.eql('ValidationError'); + should(err.errors.field04).be.ok; + should(err.errors.field04.message).be.eql( + 'Path field04 (test) has length greater than 1' + ); + done(); + }); + }); + + after('finish db', db.finish); +});