├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── README.md ├── api ├── controllers │ └── AuthenticationController.js └── models │ ├── Authentication.js │ └── User.js ├── config └── locales │ ├── en.json │ └── sw.json ├── doc.html ├── index.js ├── lib ├── WLValidationError.js ├── create.js ├── createEach.js ├── findOrCreate.js ├── findOrCreateEach.js ├── update.js ├── validate.js └── validateCustom.js ├── package.json └── test ├── bootstrap.spec.js ├── validation.spec.js ├── validation.zcreate.spec.js ├── validation.zcreateEach.spec.js ├── validation.zfindOrCreate.spec.js ├── validation.zi18n.spec.js └── validation.zupdate.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | #Temporary data 6 | .tmp 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # Deployed apps should consider commenting this line out: 27 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 28 | node_modules 29 | doc 30 | startup.sh -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "browser": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "esnext": true, 8 | "immed": true, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "node": true, 13 | "mocha": true, 14 | "quotmark": "single", 15 | "strict": true, 16 | "undef": true, 17 | "unused": true, 18 | "expr": true, 19 | "ignore": true, 20 | "globals": { 21 | "User": true, 22 | "Authentication": true, 23 | "sails": true, 24 | "_": true 25 | } 26 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | api 2 | config 3 | node_modules 4 | ssl 5 | .DS_STORE 6 | *~ 7 | .idea 8 | nbproject 9 | test 10 | .git 11 | .gitignore 12 | .tmp 13 | *.swo 14 | *.swp 15 | *.swn 16 | *.swm 17 | *.log 18 | .jshintrc 19 | .editorconfig 20 | doc.html 21 | .travis.yml 22 | Gruntfile.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_script: 5 | - npm install -g grunt-cli -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | // Add the grunt-mocha-test and jshint tasks. 6 | grunt.loadNpmTasks('grunt-mocha-test'); 7 | grunt.loadNpmTasks('grunt-contrib-jshint'); 8 | 9 | grunt.initConfig({ 10 | // Configure a mochaTest task 11 | mochaTest: { 12 | test: { 13 | options: { 14 | reporter: 'spec', 15 | timeout: 20000 16 | }, 17 | src: ['test/**/*.js'] 18 | } 19 | }, 20 | jshint: { 21 | options: { 22 | reporter: require('jshint-stylish'), 23 | jshintrc: '.jshintrc' 24 | }, 25 | all: [ 26 | 'Gruntfile.js', 27 | 'index.js', 28 | 'lib/**/*.js', 29 | 'test/**/*.js' 30 | ] 31 | } 32 | }); 33 | 34 | //custom tasks 35 | grunt.registerTask('default', ['jshint', 'mochaTest']); 36 | grunt.registerTask('test', ['jshint', 'mochaTest']); 37 | 38 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sails-hook-validation 2 | ===================== 3 | 4 | [![Build Status](https://travis-ci.org/lykmapipo/sails-hook-validation.svg?branch=master)](https://travis-ci.org/lykmapipo/sails-hook-validation) 5 | 6 | Custom validation error messages for sails model with i18n support. Its works with `callback`, `deferred` and `promise` style `model API` provided with sails. 7 | 8 | *Note:* 9 | - *This requires Sails v0.11.0+. If v0.11.0+ isn't published to NPM yet, you'll need to install it via Github.* 10 | - *`sails-hook-validation` work by patch model static `validate()`, `create()`, `createEach()`, `findOrCreate()`, `findOrCreateEach()` and `update()`.* 11 | - *To have custom error messages at model instance level consider using [sails-model-new](https://github.com/lykmapipo/sails-model-new).* 12 | - *`sails-hook-validation` opt to use `error.Errors` and not to re-create or remove any properties of error object so as to remain with sails legacy options* 13 | 14 | ## Installation 15 | ```sh 16 | $ npm install --save sails-hook-validation 17 | ``` 18 | 19 | ## Usage 20 | 21 | ### Use without i18n 22 | Add `validationMessages` static property in your sails model 23 | ```js 24 | //this is example 25 | module.exports = { 26 | attributes: { 27 | username: { 28 | type: 'string', 29 | required: true 30 | }, 31 | email: { 32 | type: 'email', 33 | required: true, 34 | unique: true 35 | } 36 | }, 37 | //model validation messages definitions 38 | validationMessages: { //hand for i18n & l10n 39 | email: { 40 | required: 'Email is required', 41 | email: 'Provide valid email address', 42 | unique: 'Email address is already taken' 43 | }, 44 | username: { 45 | required: 'Username is required' 46 | } 47 | } 48 | }; 49 | ``` 50 | 51 | ### Use with i18n 52 | Add validation messages into `config/locale` files following `modelId.attribute.rule`. If model contains any `validationMessages` definition, they will take precedence over locale defined messages. 53 | 54 | Example 55 | ```javascript 56 | //define your model in api/model/Authentication.js 57 | module.exports = { 58 | attributes: { 59 | username: { 60 | type: 'string', 61 | required: true 62 | }, 63 | email: { 64 | type: 'email', 65 | required: true, 66 | unique: true 67 | }, 68 | birthday: { 69 | type: 'date', 70 | required: true 71 | } 72 | } 73 | }; 74 | ``` 75 | 76 | Then define validation messages per locale 77 | 78 | ```javascript 79 | //in config/locale/en.json 80 | { 81 | "authentication.email.required": "Email is required", 82 | "authentication.email.email": "Provide valid email address", 83 | "authentication.email.unique": "Email address is already taken", 84 | "authentication.username.required": "Username is required", 85 | "authentication.username.string": "Username is must be a valid string", 86 | "authentication.birthday.required": "Birthday is required", 87 | "authentication.birthday.date": "Birthday is not a valid date" 88 | } 89 | 90 | //in config/locale/sw.json 91 | { 92 | "authentication.email.required": "Barua pepe yahitajika", 93 | "authentication.email.email": "Toa barua pepe iliyo halali", 94 | "authentication.email.unique": "Barua pepe tayari ishachukuliwa", 95 | "authentication.username.required": "Jina lahitajika", 96 | "authentication.username.string": "Jina lazima liwe ni mchanganyiko wa herufi", 97 | "authentication.birthday.required": "Siku ya kuzaliwa yahitajika", 98 | "authentication.birthday.date": "Siku ya kuzaliwa sio tarehe" 99 | } 100 | ``` 101 | 102 | Now you can call model static 103 | - `create()` 104 | - `createEach()` 105 | - `findOrCreate()` 106 | - `findOrCreateEach()` 107 | - `update()` 108 | - and other static model method that invoke `Model.validate()`. 109 | 110 | If there is any *validations or database errors* `sails-hook-validation` will put your custom errors message in `error.Errors` of the error object returned by those methods in your `callback` or `promise catch`. 111 | 112 | ### create() 113 | ```js 114 | //anywhere in your codes if 115 | //you invoke 116 | User 117 | .create({}, function(error, user) { 118 | //you will expect the following 119 | //error to exist on error.Errors based on 120 | //your custom validation messages 121 | 122 | expect(error.Errors.email).to.exist; 123 | 124 | expect(error.Errors.email[0].message) 125 | .to.equal(User.validationMessages.email.email); 126 | 127 | expect(error.Errors.email[1].message) 128 | .to.equal(User.validationMessages.email.required); 129 | 130 | 131 | expect(error.Errors.username).to.exist; 132 | expect(error.Errors.username[0].message) 133 | .to.equal(User.validationMessages.username.required); 134 | 135 | done(); 136 | }); 137 | ``` 138 | 139 | ### createEach() 140 | ```js 141 | User 142 | .createEach([{ 143 | email: faker.internet.email(), 144 | username: faker.internet.userName() 145 | }, { 146 | email: 'otherExistingModelEmail', 147 | username: username 148 | }]) 149 | .catch(function(error) { 150 | //you will expect the following 151 | //error to exist on error.Errors based on 152 | //your custom validation messages 153 | 154 | expect(error.Errors.email).to.exist; 155 | 156 | expect(error.Errors.email[0].message) 157 | .to.equal(User.validationMessages.email.unique); 158 | 159 | done(); 160 | }); 161 | ``` 162 | 163 | ### findOrCreate() 164 | ```js 165 | User 166 | .findOrCreate({ 167 | email: faker.internet.email() 168 | }, { 169 | email: 'otherExistingModelEmail', 170 | username: username 171 | }) 172 | .exec(function(error, user) { 173 | //you will expect the following 174 | //error to exist on error.Errors based on 175 | //your custom validation messages 176 | 177 | expect(error.Errors.email).to.exist; 178 | 179 | expect(error.Errors.email[0].message) 180 | .to.equal(User.validationMessages.email.unique); 181 | 182 | done(); 183 | }); 184 | ``` 185 | 186 | ### findOrCreateEach() 187 | ```js 188 | User 189 | .findOrCreateEach( 190 | [{ 191 | email: faker.internet.email() 192 | }], [{ 193 | email: 'otheExistingModelEmail', 194 | username: username 195 | }] 196 | ) 197 | .catch(function(error) { 198 | //you will expect the following 199 | //error to exist on error.Errors based on 200 | //your custom validation messages 201 | 202 | expect(error.Errors.email).to.exist; 203 | 204 | expect(error.Errors.email[0].message) 205 | .to.equal(User.validationMessages.email.unique); 206 | 207 | done(); 208 | }); 209 | ``` 210 | 211 | ### update() 212 | ```js 213 | User 214 | .update({ 215 | email: 'modelEmail' 216 | }, { 217 | email: 'otherExistingModelEmail' 218 | }, function(error, user) { 219 | //you will expect the following 220 | //error to exist on error.Errors based on 221 | //your custom validation messages 222 | 223 | expect(error.Errors.email).to.exist; 224 | 225 | expect(error.Errors.email[0].message) 226 | .to.equal(User.validationMessages.email.unique); 227 | 228 | done(); 229 | }); 230 | ``` 231 | 232 | ## Testing 233 | 234 | * Clone this repository 235 | 236 | * Install all development dependencies 237 | 238 | ```sh 239 | $ npm install 240 | ``` 241 | * Then run test 242 | 243 | ```sh 244 | $ npm test 245 | ``` 246 | 247 | ## Contribute 248 | 249 | Fork this repo and push in your ideas. 250 | Do not forget to add a bit of test(s) of what value you adding. 251 | 252 | ## Licence 253 | 254 | The MIT License (MIT) 255 | 256 | Copyright (c) 2015 lykmapipo & Contributors 257 | 258 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 259 | 260 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 261 | 262 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /api/controllers/AuthenticationController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @description Authentication Controller 5 | * @type {Object} 6 | */ 7 | module.exports = { 8 | create: function(request, response) { 9 | Authentication 10 | .create(request.body) 11 | .exec(function(error, authentication) { 12 | 13 | if (error) { 14 | response.status(400); 15 | response.json(error.Errors); 16 | } else { 17 | response.status(200); 18 | resposne.json(authentication); 19 | } 20 | }); 21 | } 22 | } -------------------------------------------------------------------------------- /api/models/Authentication.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sample model 3 | * @type {Object} 4 | */ 5 | module.exports = { 6 | attributes: { 7 | username: { 8 | type: 'string', 9 | required: true 10 | }, 11 | email: { 12 | type: 'email', 13 | required: true, 14 | unique: true 15 | }, 16 | birthday: { 17 | type: 'date', 18 | required: true 19 | } 20 | } 21 | 22 | //validation messages definitions 23 | //are taken from config/locales 24 | //based on request locale 25 | //otherwise using default locale 26 | 27 | }; -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sample model 3 | * @type {Object} 4 | */ 5 | module.exports = { 6 | attributes: { 7 | username: { 8 | required: true, 9 | type: 'string' 10 | }, 11 | email: { 12 | required: true, 13 | type: 'email', 14 | unique: true 15 | }, 16 | birthday: { 17 | required: true, 18 | type: 'date' 19 | }, 20 | nickname: { 21 | type: 'string', 22 | minLength: 2 23 | } 24 | }, 25 | 26 | //validation messages definitions 27 | validationMessages: { //hand for i18n & l10n 28 | email: { 29 | required: 'Email address is required', 30 | email: 'Provide valid email address', 31 | unique: 'Email address is already taken' 32 | }, 33 | username: { 34 | required: 'Username is required' 35 | }, 36 | birthday: { 37 | required: 'Your birthday is required', 38 | date: 'Birthday is not a valid date' 39 | } 40 | } 41 | 42 | }; -------------------------------------------------------------------------------- /config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "authentication.email.required": "Email is required", 3 | "authentication.email.email": "Provide valid email address", 4 | "authentication.email.unique": "Email address is already taken", 5 | "authentication.username.required": "Username is required", 6 | "authentication.username.string": "Username is must be a valid string", 7 | "authentication.birthday.required": "Birthday is required", 8 | "authentication.birthday.date": "Birthday is not a valid date" 9 | } -------------------------------------------------------------------------------- /config/locales/sw.json: -------------------------------------------------------------------------------- 1 | { 2 | "authentication.email.required": "Barua pepe yahitajika", 3 | "authentication.email.email": "Toa barua pepe iliyo halali", 4 | "authentication.email.unique": "Barua pepe tayari ishachukuliwa", 5 | "authentication.username.required": "Jina lahitajika", 6 | "authentication.username.string": "Jina lazima liwe ni mchanganyiko wa herufi", 7 | "authentication.birthday.required": "Siku ya kuzaliwa yahitajika", 8 | "authentication.birthday.date": "Siku ya kuzaliwa sio tarehe" 9 | } -------------------------------------------------------------------------------- /doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | sails-hook-validation | Custom error messages for sails model. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |

sails-hook-validation

38 | 44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 |
52 | 55 |
56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var path = require('path'); 5 | var libPath = path.join(__dirname, 'lib'); 6 | 7 | var validateCustom = require(path.join(libPath, 'validateCustom')); 8 | 9 | //patches 10 | var create = require(path.join(libPath, 'create')); 11 | var createEach = require(path.join(libPath, 'createEach')); 12 | var findOrCreate = require(path.join(libPath, 'findOrCreate')); 13 | var findOrCreateEach = require(path.join(libPath, 'findOrCreateEach')); 14 | var update = require(path.join(libPath, 'update')); 15 | var validate = require(path.join(libPath, 'validate')); 16 | 17 | //patch WLValidationError 18 | require(path.join(libPath, 'WLValidationError')); 19 | 20 | 21 | /** 22 | * @description allow model to define its custom validation error messages. 23 | * It hooks into model static methods that call `validate()`, 24 | * grab the `ValidationError` from the resulted error object, 25 | * process it and attach `Errors` as custom errors message into the 26 | * error object. 27 | * @param {Object} sails a sails application instance 28 | */ 29 | module.exports = function(sails) { 30 | //patch sails model 31 | //to add custom errors message 32 | //logic 33 | function patch() { 34 | (_ || sails.util._) 35 | .forEach(sails.models, function(model) { 36 | //bind path validate 37 | //on concrete models 38 | //and left derived model 39 | //build from associations 40 | if (model.globalId) { 41 | 42 | //patch sails `create()` method 43 | create(model, validateCustom); 44 | 45 | //patch sails `createEach()` method 46 | createEach(model, validateCustom); 47 | 48 | //patch sails `findOrCreate()` method 49 | findOrCreate(model, validateCustom); 50 | 51 | //patch sails `findOrCreateEach()` method 52 | findOrCreateEach(model, validateCustom); 53 | 54 | //patch sails `update()` method 55 | update(model, validateCustom); 56 | 57 | //patch sails `validate()` method 58 | validate(model, validateCustom); 59 | 60 | } 61 | }); 62 | } 63 | 64 | //export hook definition 65 | return { 66 | //intercent all request and current grab request locale 67 | routes: { 68 | before: { 69 | 'all /*': function grabLocale(request, response, next) { 70 | //configure i18n current request locale 71 | if(request && typeof request.getLocale === 'function'){ 72 | sails.config.i18n.requestLocale = request.getLocale(); 73 | } 74 | 75 | //continue 76 | next(); 77 | } 78 | } 79 | }, 80 | 81 | initialize: function(done) { 82 | var eventsToWaitFor = []; 83 | 84 | //wait for orm hook 85 | //to be loaded 86 | if (sails.hooks.orm) { 87 | eventsToWaitFor.push('hook:orm:loaded'); 88 | } 89 | 90 | //wait for pub sub hook 91 | //to be loaded 92 | if (sails.hooks.pubsub) { 93 | eventsToWaitFor.push('hook:pubsub:loaded'); 94 | } 95 | 96 | //apply validation hook 97 | sails 98 | .after(eventsToWaitFor, function() { 99 | //bind custom errors logic 100 | //and let sails to continue 101 | patch(); 102 | 103 | done(); 104 | }); 105 | } 106 | }; 107 | 108 | }; 109 | -------------------------------------------------------------------------------- /lib/WLValidationError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var WLError = require('waterline/lib/waterline/error/WLError'); 4 | 5 | module.exports.patch = function(error) { 6 | var toJSON = error.toJSON; 7 | 8 | error = new WLError(error); 9 | 10 | error.toJSON = 11 | error.toPOJO = function() { 12 | 13 | var errorKey = 'Errors'; 14 | 15 | if (sails && sails.config && sails.config.errors && 16 | typeof sails.config.errors.errorKey === 'string') { 17 | errorKey = sails.config.errors.errorKey; 18 | } 19 | 20 | if (!toJSON) { 21 | toJSON = WLError.prototype.toJSON; 22 | } 23 | 24 | var asJSON = toJSON.call(this); 25 | asJSON[errorKey] = this.Errors; 26 | return asJSON; 27 | }; 28 | 29 | return error; 30 | }; 31 | -------------------------------------------------------------------------------- /lib/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | //import sails waterline Deferred 5 | var Deferred = require('waterline/lib/waterline/query/deferred'); 6 | var WLValidationError = require('./WLValidationError'); 7 | 8 | /** 9 | * @description path sails `create()` method to allow 10 | * custom error message definitions 11 | * @param {Object} model a valid sails model 12 | * @param {Function} validateCustom a function to transform sails `invalidAttributes` 13 | * to custome `Errors` 14 | */ 15 | module.exports = function(model, validateCustom) { 16 | //remember sails defined create 17 | //method 18 | //See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/dql/create.js 19 | var sailsCreate = model.create; 20 | 21 | //prepare new create method 22 | //which wrap sailsCreate 23 | //with custom error message checking 24 | function create(values, callback) { 25 | 26 | // handle Deferred where 27 | // it passes criteria first 28 | // see https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/dql/create.js#L26 29 | if (arguments.length === 3) { 30 | var args = Array.prototype.slice.call(arguments); 31 | callback = args.pop(); 32 | values = args.pop(); 33 | } 34 | 35 | // return Deferred 36 | // if no callback passed 37 | // See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/dql/create.js#L54 38 | if (typeof callback !== 'function') { 39 | //this refer to the 40 | //model context 41 | return new Deferred(model, model.create, {}, values); 42 | } 43 | 44 | //otherwise 45 | //call sails create 46 | sailsCreate 47 | .call(model, values, function(error, result) { 48 | //any create error 49 | //found? 50 | if (error) { 51 | //process sails invalidAttributes and 52 | //attach Errors key in error object 53 | //as a place to lookup for our 54 | //custom errors messages 55 | if (error.invalidAttributes) { 56 | var customError = 57 | validateCustom(model, error.invalidAttributes); 58 | 59 | // will return and override with empty object 60 | // when using associations 61 | if (Object.keys(customError).length !== 0) { 62 | error.Errors = customError; 63 | } 64 | } 65 | 66 | callback(WLValidationError.patch(error)); 67 | } else { 68 | //no error 69 | //return 70 | callback(null, result); 71 | } 72 | }); 73 | } 74 | 75 | //bind our new create 76 | //to our models 77 | model.create = create; 78 | }; 79 | -------------------------------------------------------------------------------- /lib/createEach.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | //import sails waterline Deferred 5 | var Deferred = require('waterline/lib/waterline/query/deferred'); 6 | var WLValidationError = require('./WLValidationError'); 7 | 8 | /** 9 | * @description path sails `createEach()` method to allow 10 | * custom error message definitions 11 | * @param {Object} model a valid sails model 12 | * @param {Function} validateCustom a function to transform sails `invalidAttributes` 13 | * to custome `Errors` 14 | */ 15 | module.exports = function(model, validateCustom) { 16 | //remember sails defined createEach 17 | //method 18 | //See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/aggregate.js#L24 19 | var sailsCreate = model.createEach; 20 | 21 | //prepare new createEach method 22 | function createEach(values, callback) { 23 | // handle Deferred where 24 | // it passes criteria first 25 | // See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/aggregate.js#L27 26 | if (arguments.length === 3) { 27 | var args = Array.prototype.slice.call(arguments); 28 | callback = args.pop(); 29 | values = args.pop(); 30 | } 31 | 32 | // return Deferred 33 | // if no callback passed 34 | // See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/aggregate.js#L35 35 | if (typeof callback !== 'function') { 36 | //this refer to the 37 | //model context 38 | return new Deferred(model, model.createEach, {}, values); 39 | } 40 | 41 | //otherwise 42 | //call sails createEach 43 | sailsCreate 44 | .call(model, values, function(error, result) { 45 | //any createEach error 46 | //found? 47 | if (error) { 48 | //process sails invalidAttributes and 49 | //attach Errors key in error object 50 | //as a place to lookup for our 51 | //custom errors messages 52 | if (error.invalidAttributes) { 53 | var customError = 54 | validateCustom(model, error.invalidAttributes); 55 | 56 | // will return and override with empty object 57 | // when using associations 58 | if (Object.keys(customError).length !== 0) { 59 | error.Errors = customError; 60 | } 61 | } 62 | 63 | callback(WLValidationError.patch(error)); 64 | } else { 65 | //no error 66 | //return 67 | callback(null, result); 68 | } 69 | }); 70 | } 71 | 72 | //bind our new createEach 73 | //to our models 74 | model.createEach = createEach; 75 | }; 76 | -------------------------------------------------------------------------------- /lib/findOrCreate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | //import sails waterline Deferred 5 | var Deferred = require('waterline/lib/waterline/query/deferred'); 6 | var WLValidationError = require('./WLValidationError'); 7 | 8 | /** 9 | * @description path sails `findOrCreate()` method to allow 10 | * custom error message definitions 11 | * @param {Object} model a valid sails model 12 | * @param {Function} validateCustom a function to transform sails `invalidAttributes` 13 | * to custome `Errors` 14 | */ 15 | module.exports = function(model, validateCustom) { 16 | //remember sails defined findOrCreate 17 | //method 18 | //See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/composite.js#L24 19 | var sailsFindOrCreate = model.findOrCreate; 20 | 21 | //prepare new findOrCreate method 22 | function findOrCreate(criteria, values, callback) { 23 | // return Deferred 24 | // if no callback passed 25 | // See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/composite.js#L43 26 | if (typeof callback !== 'function') { 27 | //this refer to the 28 | //model context 29 | return new Deferred(model, model.findOrCreate, criteria, values); 30 | } 31 | 32 | //otherwise 33 | //call sails findOrCreate 34 | sailsFindOrCreate 35 | .call(model, criteria, values, function(error, result) { 36 | //any findOrCreate error 37 | //found? 38 | if (error) { 39 | //process sails invalidAttributes and 40 | //attach Errors key in error object 41 | //as a place to lookup for our 42 | //custom errors messages 43 | if (error.invalidAttributes) { 44 | var customError = 45 | validateCustom(model, error.invalidAttributes); 46 | 47 | // will return and override with empty object when using associations 48 | if (Object.keys(customError).length !== 0) { 49 | error.Errors = customError; 50 | } 51 | } 52 | 53 | callback(WLValidationError.patch(error)); 54 | } else { 55 | //no error 56 | //return 57 | callback(null, result); 58 | } 59 | }); 60 | } 61 | 62 | //bind our new findOrCreate 63 | //to our models 64 | model.findOrCreate = findOrCreate; 65 | }; 66 | -------------------------------------------------------------------------------- /lib/findOrCreateEach.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | //import sails waterline Deferred 5 | var Deferred = require('waterline/lib/waterline/query/deferred'); 6 | var WLValidationError = require('./WLValidationError'); 7 | 8 | /** 9 | * @deprecated See https://github.com/balderdashy/waterline/pull/843 10 | * @description path sails `findOrCreateEach()` method to allow 11 | * custom error message definitions 12 | * @param {Object} model a valid sails model 13 | * @param {Function} validateCustom a function to transform sails `invalidAttributes` 14 | * to custome `Errors` 15 | */ 16 | module.exports = function(model, validateCustom) { 17 | //remember sails defined findOrCreateEach 18 | //method 19 | //See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/aggregate.js#L84 20 | var sailsFindOrCreateEach = model.findOrCreateEach; 21 | 22 | //prepare new findOrCreateEach method 23 | function findOrCreateEach(criterias, values, callback) { 24 | // return Deferred 25 | // if no callback passed 26 | // See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/aggregate.js#L96 27 | if (typeof callback !== 'function') { 28 | //this refer to the 29 | //model context 30 | return new Deferred(model, model.findOrCreateEach, criterias, values); 31 | } 32 | 33 | //otherwise 34 | //call sails findOrCreateEach 35 | sailsFindOrCreateEach 36 | .call(model, criterias, values, function(error, result) { 37 | //any findOrCreateEach error 38 | //found? 39 | if (error) { 40 | //process sails invalidAttributes and 41 | //attach Errors key in error object 42 | //as a place to lookup for our 43 | //custom errors messages 44 | if (error.invalidAttributes) { 45 | var customError = 46 | validateCustom(model, error.invalidAttributes); 47 | 48 | // will return and override with empty object 49 | // when using associations 50 | if (Object.keys(customError).length !== 0) { 51 | error.Errors = customError; 52 | } 53 | } 54 | //hand over error 55 | //to user callback 56 | callback(WLValidationError.patch(error)); 57 | } else { 58 | //no error 59 | // 60 | //hand over result 61 | //to user callback 62 | callback(null, result); 63 | } 64 | }); 65 | } 66 | 67 | //bind our new findOrCreateEach 68 | //to our models 69 | model.findOrCreateEach = findOrCreateEach; 70 | }; 71 | -------------------------------------------------------------------------------- /lib/update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | //import sails waterline Deferred 5 | var Deferred = require('waterline/lib/waterline/query/deferred'); 6 | var WLValidationError = require('./WLValidationError'); 7 | 8 | /** 9 | * @description path sails `update()` method to allow 10 | * custom error message definitions 11 | * @param {Object} model a valid sails model 12 | * @param {Function} validateCustom a function to transform sails `invalidAttributes` 13 | * to custome `Errors` 14 | */ 15 | module.exports = function(model, validateCustom) { 16 | //remember sails defined update 17 | //method 18 | //See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/dql/update.js 19 | var sailsUpdate = model.update; 20 | 21 | //prepare new update method 22 | //which wrap sailsUpdate 23 | //with custom error message checking 24 | function update(criterias, values, callback) { 25 | 26 | // return Deferred 27 | // if no callback passed 28 | // See https://github.com/balderdashy/waterline/blob/master/lib/waterline/query/dql/update.js#L34 29 | if (typeof callback !== 'function') { 30 | //this refer to the 31 | //model context 32 | return new Deferred(model, model.update, criterias, values); 33 | } 34 | 35 | //otherwise 36 | //call sails update 37 | sailsUpdate 38 | .call(model, criterias, values, function(error, result) { 39 | //any update error 40 | //found? 41 | if (error) { 42 | //process sails invalidAttributes and 43 | //attach Errors key in error object 44 | //as a place to lookup for our 45 | //custom errors messages 46 | if (error.invalidAttributes) { 47 | var customError = 48 | validateCustom(model, error.invalidAttributes); 49 | 50 | // will return and override with empty object 51 | // when using associations 52 | if (Object.keys(customError).length !== 0) { 53 | error.Errors = customError; 54 | } 55 | } 56 | 57 | callback(WLValidationError.patch(error)); 58 | } else { 59 | //no error 60 | //return 61 | callback(null, result); 62 | } 63 | }); 64 | } 65 | 66 | //bind our new update 67 | //to our models 68 | model.update = update; 69 | }; 70 | -------------------------------------------------------------------------------- /lib/validate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var WLValidationError = require('./WLValidationError'); 5 | 6 | /** 7 | * @description path sails `validate()` method to allow 8 | * custom error message definitions 9 | * @param {Object} model a valid sails model 10 | * @param {Function} validateCustom a function to transform sails `invalidAttributes` 11 | * to custome `Errors` 12 | */ 13 | module.exports = function(model, validateCustom) { 14 | //remember sails defined validation 15 | //method 16 | var sailsValidate = model.validate; 17 | 18 | //prepare new validation method 19 | function validate(values, presentOnly, callback) { 20 | if(typeof presentOnly === 'function'){ 21 | callback = presentOnly; 22 | presentOnly = null; 23 | } 24 | //call sails validate 25 | sailsValidate 26 | .call(model, values, presentOnly, function(error) { 27 | //any validation error 28 | //found? 29 | if (error) { 30 | //process sails invalidAttributes and 31 | //attach Errors key in error object 32 | //as a place to lookup for our 33 | //custom errors messages 34 | if (error.invalidAttributes) { 35 | var customError = 36 | validateCustom(model, error.invalidAttributes); 37 | 38 | // will return and override with empty object 39 | // when using associations 40 | if (Object.keys(customError).length !== 0) { 41 | error.Errors = customError; 42 | } 43 | } 44 | 45 | callback(WLValidationError.patch(error)); 46 | } else { 47 | //no error 48 | //return 49 | callback(null); 50 | } 51 | }); 52 | } 53 | 54 | //bind our new validate 55 | //to our models 56 | model.validate = validate; 57 | }; 58 | -------------------------------------------------------------------------------- /lib/validateCustom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | 5 | /** 6 | * 7 | * @descriptions process validation error and mixin user defined errors 8 | * to produce friendly error messages. 9 | * 10 | * For this to work a model may either define `validationMessages` 11 | * hash as static property or adding validation messages into 12 | * locale files. 13 | * 14 | * @param {Object} model valid sails model definition 15 | * @param {Object} invalidAttributes a valid sails validation error object. 16 | * 17 | * @returns {Object} an object with friendly validation error conversions. 18 | */ 19 | module.exports = function(model, invalidAttributes) { 20 | 21 | //grab model validations definitions 22 | var validations = model._validator.validations || {}; 23 | 24 | //grab field names 25 | //from the messages 26 | var validationFields = Object.keys(validations); 27 | 28 | //custom validation error storage 29 | var customValidationMessages = {}; 30 | 31 | 32 | //iterate over all model 33 | //defined validations 34 | //and process thrown sails invalidAttributes 35 | //to model custom defined errors 36 | validationFields 37 | .forEach(function(validationField) { 38 | 39 | //grab field errors from the 40 | //sails validation error hash 41 | // 42 | //if sails is connected to a database a user can declare a column name 43 | //the valid sails validation error not use the column name as property anymore 44 | var fieldErrors = invalidAttributes[validationField]; 45 | 46 | //is there any field 47 | //error(s) found in invalidAttributes 48 | if (fieldErrors) { 49 | 50 | //iterate through each field of 51 | //sails validation error and 52 | //convert them 53 | //to custom model defined errors 54 | fieldErrors 55 | .forEach(function(fieldError) { 56 | //try 57 | //built custom error message from 58 | //from sails i18n 59 | 60 | //grab phrase to use to find custom message 61 | //from locales 62 | var phrase = [ 63 | model.globalId.toLowerCase(), 64 | validationField, 65 | fieldError.rule 66 | ].join('.'); 67 | 68 | var customMessage = phrase; 69 | var locale; 70 | 71 | if(sails.config.i18n){ 72 | //deduce locale from request else 73 | //use default locale 74 | locale = 75 | sails.config.i18n.requestLocale || 76 | sails.config.i18n.defaultLocale; 77 | 78 | if(locale){ 79 | //grab custom error 80 | //message from config/locales/`locale`.json 81 | customMessage = sails.__({ 82 | phrase: phrase, 83 | locale: locale 84 | }); 85 | } 86 | } 87 | 88 | //make sure custom error message from i18n exists 89 | var i18nMessageExist = 90 | customMessage !== phrase && 91 | sails.util._.isString(customMessage); 92 | 93 | //else 94 | //grab friedly error message of 95 | //the defined rule which has an error 96 | //from model defined 97 | //validation messages 98 | var messages = model.validationMessages; 99 | 100 | if (!i18nMessageExist && messages) { 101 | if (messages[validationField] && messages[validationField][fieldError.rule]) { 102 | customMessage = sails.__({ 103 | phrase: messages[validationField][fieldError.rule], 104 | locale: locale 105 | }); 106 | } else { 107 | //return default error message if user forgot to add custon validation message or not even specifying the field key in validationMessages 108 | customMessage = fieldError.message; 109 | } 110 | } 111 | 112 | if (customMessage) { 113 | //collect custom error messages 114 | if (!(customValidationMessages[validationField] instanceof Array)) { 115 | customValidationMessages[validationField] = []; 116 | } 117 | 118 | //build friendly error message 119 | var newMessage = { 120 | 'rule': fieldError.rule, 121 | 'message': customMessage 122 | }; 123 | 124 | customValidationMessages[validationField].push(newMessage); 125 | } 126 | }); 127 | } 128 | }); 129 | 130 | return customValidationMessages; 131 | }; 132 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sails-hook-validation", 3 | "version": "0.4.7", 4 | "description": "Custom validation error messages for sails model with i18n support", 5 | "main": "index.js", 6 | "sails": { 7 | "isHook": true 8 | }, 9 | "scripts": { 10 | "pretest": "npm link && npm link sails-hook-validation", 11 | "test": "grunt test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/lykmapipo/sails-hook-validation.git" 16 | }, 17 | "keywords": [ 18 | "sails", 19 | "sails-hook", 20 | "model", 21 | "validator", 22 | "validation", 23 | "validate", 24 | "custom", 25 | "messages", 26 | "define", 27 | "database", 28 | "exceptions", 29 | "constraints", 30 | "i18n", 31 | "internationalization", 32 | "hook", 33 | "plugin", 34 | "error", 35 | "module" 36 | ], 37 | "author": { 38 | "name": "lykmapipo", 39 | "email": "lallyelias87@gmail.com", 40 | "url": "https://github.com/lykmapipo" 41 | }, 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/lykmapipo/sails-hook-validation/issues" 45 | }, 46 | "homepage": "https://github.com/lykmapipo/sails-hook-validation", 47 | "contributors": [{ 48 | "name": "lykmapipo", 49 | "github": "https://github.com/lykmapipo" 50 | }, { 51 | "name": "isery", 52 | "github": "https://github.com/isery" 53 | }, { 54 | "name": "Alaneor", 55 | "github": "https://github.com/Alaneor" 56 | }, { 57 | "name": "VMBindraban", 58 | "github": "https://github.com/VMBindraban" 59 | }], 60 | "devDependencies": { 61 | "chai": "^2.3.0", 62 | "faker": "^2.1.5", 63 | "grunt": "^0.4.5", 64 | "grunt-contrib-jshint": "^0.11.3", 65 | "grunt-mocha-test": "^0.12.7", 66 | "jshint-stylish": "^1.0.2", 67 | "mocha": "^2.4.5", 68 | "sails": "^0.11.2", 69 | "sails-disk": "^0.10.8", 70 | "supertest": "^1.1.0" 71 | }, 72 | "dependencies": { 73 | "waterline": "^0.10.31" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/bootstrap.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var sails = require('sails'); 5 | 6 | /** 7 | * Lifting sails before all tests 8 | */ 9 | before(function(done) { 10 | sails 11 | .lift({ // configuration for testing purposes 12 | port: 7070, 13 | environment: 'test', 14 | log: { 15 | noShip: true 16 | }, 17 | models: { 18 | migrate: 'drop' 19 | }, 20 | hooks: { 21 | sockets: false, 22 | pubsub: false, 23 | grunt: false //we dont need grunt in test 24 | } 25 | }, function(error, sails) { 26 | if (error) { 27 | return done(error); 28 | } else { 29 | done(null, sails); 30 | } 31 | }); 32 | }); 33 | 34 | 35 | /** 36 | * Lowering sails after done testing 37 | */ 38 | after(function(done) { 39 | User 40 | .destroy() 41 | .then(function() { 42 | return Authentication.destroy(); 43 | }) 44 | .then(function( /*result*/ ) { 45 | sails.lower(done); 46 | }) 47 | .catch(function(error) { 48 | done(error); 49 | }); 50 | }); -------------------------------------------------------------------------------- /test/validation.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var expect = require('chai').expect; 5 | var faker = require('faker'); 6 | var email = faker.internet.email(); 7 | var username = faker.internet.userName(); 8 | 9 | describe('Hook#validation', function() { 10 | 11 | it('should be loaded as installable hook', function(done) { 12 | expect(sails.hooks.validation).to.not.be.null; 13 | done(); 14 | }); 15 | 16 | it('should be able to serialize custom errors when toJSON is invoked', function(done) { 17 | User 18 | .create({}, function(error, user) { 19 | 20 | var asJSON = error.toJSON(); 21 | 22 | expect(asJSON.Errors).to.exist; 23 | 24 | done(null, user); 25 | }); 26 | }); 27 | 28 | 29 | it('should be able to serialize custom errors when toPOJO is invoked', function(done) { 30 | User 31 | .create({}, function(error, user) { 32 | 33 | var asPOJO = error.toPOJO(); 34 | 35 | expect(asPOJO.Errors).to.exist; 36 | 37 | done(null, user); 38 | }); 39 | }); 40 | 41 | it('should be able to serialize custom errors when toPOJO is invoked and use custom Errors key', function(done) { 42 | sails.config.errors = { 43 | errorKey: 'customErrors' 44 | }; 45 | 46 | User 47 | .create({}, function(error, user) { 48 | 49 | var asPOJO = error.toPOJO(); 50 | 51 | expect(asPOJO.customErrors).to.exist; 52 | 53 | delete sails.config.errors; 54 | 55 | done(null, user); 56 | }); 57 | }); 58 | 59 | it('should throw custom errors', function(done) { 60 | User 61 | .create({}, function(error, user) { 62 | 63 | expect(error.Errors.email).to.exist; 64 | 65 | expect(error.Errors.email[0].message) 66 | .to.equal(User.validationMessages.email.required); 67 | expect(error.Errors.email[1].message) 68 | .to.equal(User.validationMessages.email.email); 69 | 70 | expect(error.Errors.username).to.exist; 71 | expect(error.Errors.username[0].message) 72 | .to.equal(User.validationMessages.username.required); 73 | 74 | expect(error.Errors.birthday).to.exist; 75 | 76 | expect(error.Errors.birthday[0].message) 77 | .to.equal(User.validationMessages.birthday.required); 78 | expect(error.Errors.birthday[1].message) 79 | .to.equal(User.validationMessages.birthday.date); 80 | 81 | done(null, user); 82 | }); 83 | }); 84 | 85 | it('should throw error when the error message or field key is not specificied', function(done) { 86 | 87 | User 88 | .create({ 89 | email: email, 90 | username: username, 91 | birthday: faker.date.past(), 92 | nickname: 'c' 93 | }, function(error, user) { 94 | expect(error.Errors.nickname).to.exist; 95 | done(null, user); 96 | }); 97 | }); 98 | 99 | it('should not throw error if all validation conditions passed', function(done) { 100 | 101 | User 102 | .create({ 103 | email: email, 104 | username: username, 105 | birthday: faker.date.past() 106 | }, function(error, user) { 107 | expect(error).to.be.null; 108 | expect(user).to.not.be.null; 109 | done(); 110 | }); 111 | }); 112 | 113 | }); -------------------------------------------------------------------------------- /test/validation.zcreate.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var expect = require('chai').expect; 5 | var faker = require('faker'); 6 | var email = faker.internet.email(); 7 | var username = faker.internet.userName(); 8 | 9 | 10 | describe('Hook#validation#create database errors', function() { 11 | 12 | before(function(done) { 13 | User 14 | .create({ 15 | email: email, 16 | username: username, 17 | birthday: faker.date.past() 18 | }) 19 | .exec(done); 20 | }); 21 | 22 | it('should throw unique error message using node callback style', function(done) { 23 | 24 | User 25 | .create({ 26 | email: email, 27 | username: username, 28 | birthday: faker.date.past() 29 | }, function(error, user) { 30 | expect(error.Errors.email).to.exist; 31 | 32 | expect(error.Errors.email[0].message) 33 | .to.equal(User.validationMessages.email.unique); 34 | 35 | done(null, user); 36 | }); 37 | }); 38 | 39 | it('should throw unique error message using deferred style', function(done) { 40 | 41 | User 42 | .create({ 43 | email: email, 44 | username: username, 45 | birthday: faker.date.past() 46 | }) 47 | .exec(function(error, user) { 48 | expect(error.Errors.email).to.exist; 49 | 50 | expect(error.Errors.email[0].message) 51 | .to.equal(User.validationMessages.email.unique); 52 | 53 | done(null, user); 54 | }); 55 | }); 56 | 57 | 58 | it('should throw unique error message using promise style', function(done) { 59 | 60 | User 61 | .create({ 62 | email: email, 63 | username: username, 64 | birthday: faker.date.past() 65 | }) 66 | .catch(function(error) { 67 | expect(error.Errors.email).to.exist; 68 | 69 | expect(error.Errors.email[0].message) 70 | .to.equal(User.validationMessages.email.unique); 71 | 72 | done(); 73 | }); 74 | }); 75 | 76 | }); -------------------------------------------------------------------------------- /test/validation.zcreateEach.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var expect = require('chai').expect; 5 | var faker = require('faker'); 6 | var email = faker.internet.email(); 7 | var username = faker.internet.userName(); 8 | 9 | describe('Hook#validation#createEach database errors', function() { 10 | 11 | before(function(done) { 12 | User 13 | .create({ 14 | email: email, 15 | username: username, 16 | birthday: faker.date.past() 17 | }) 18 | .exec(done); 19 | }); 20 | 21 | it('should throw unique error message using node callback style', function(done) { 22 | 23 | User 24 | .createEach([{ 25 | email: faker.internet.email(), 26 | username: faker.internet.userName(), 27 | birthday: faker.date.past() 28 | }, { 29 | email: email, 30 | username: username, 31 | birthday: faker.date.past() 32 | }], function(error, users) { 33 | expect(error.Errors.email).to.exist; 34 | 35 | expect(error.Errors.email[0].message) 36 | .to.equal(User.validationMessages.email.unique); 37 | 38 | done(null, users); 39 | }); 40 | }); 41 | 42 | it('should throw unique error message using deferred style', function(done) { 43 | 44 | User 45 | .createEach([{ 46 | email: faker.internet.email(), 47 | username: faker.internet.userName(), 48 | birthday: faker.date.past() 49 | }, { 50 | email: email, 51 | username: username, 52 | birthday: faker.date.past() 53 | }]) 54 | .exec(function(error, users) { 55 | expect(error.Errors.email).to.exist; 56 | 57 | expect(error.Errors.email[0].message) 58 | .to.equal(User.validationMessages.email.unique); 59 | 60 | done(null, users); 61 | }); 62 | }); 63 | 64 | it('should throw unique error message using promise style', function(done) { 65 | 66 | User 67 | .createEach([{ 68 | email: faker.internet.email(), 69 | username: faker.internet.userName(), 70 | birthday: faker.date.past() 71 | }, { 72 | email: email, 73 | username: username, 74 | birthday: faker.date.past() 75 | }]) 76 | .catch(function(error) { 77 | expect(error.Errors.email).to.exist; 78 | 79 | expect(error.Errors.email[0].message) 80 | .to.equal(User.validationMessages.email.unique); 81 | 82 | done(); 83 | }); 84 | }); 85 | 86 | }); -------------------------------------------------------------------------------- /test/validation.zfindOrCreate.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var expect = require('chai').expect; 5 | var faker = require('faker'); 6 | var email = faker.internet.email(); 7 | var username = faker.internet.userName(); 8 | 9 | describe('Hook#validation#findOrCreate database errors', function() { 10 | 11 | before(function(done) { 12 | User 13 | .create({ 14 | email: email, 15 | username: username, 16 | birthday: faker.date.past() 17 | }) 18 | .exec(done); 19 | }); 20 | 21 | it('should throw unique error message using node callback style', function(done) { 22 | 23 | User 24 | .findOrCreate({ 25 | email: faker.internet.email() 26 | }, { 27 | email: email, 28 | username: username, 29 | birthday: faker.date.past() 30 | }, function(error, user) { 31 | expect(error.Errors.email).to.exist; 32 | 33 | expect(error.Errors.email[0].message) 34 | .to.equal(User.validationMessages.email.unique); 35 | 36 | done(null, user); 37 | }); 38 | }); 39 | 40 | 41 | it('should throw unique error message using deferred style', function(done) { 42 | 43 | User 44 | .findOrCreate({ 45 | email: faker.internet.email() 46 | }, { 47 | email: email, 48 | username: username, 49 | birthday: faker.date.past() 50 | }) 51 | .exec(function(error, user) { 52 | expect(error.Errors.email).to.exist; 53 | 54 | expect(error.Errors.email[0].message) 55 | .to.equal(User.validationMessages.email.unique); 56 | 57 | done(null, user); 58 | }); 59 | }); 60 | 61 | it('should throw unique error message using promise style', function(done) { 62 | 63 | User 64 | .findOrCreate({ 65 | email: faker.internet.email() 66 | }, { 67 | email: email, 68 | username: username, 69 | birthday: faker.date.past() 70 | }) 71 | .catch(function(error) { 72 | expect(error.Errors.email).to.exist; 73 | 74 | expect(error.Errors.email[0].message) 75 | .to.equal(User.validationMessages.email.unique); 76 | 77 | done(); 78 | }); 79 | }); 80 | 81 | 82 | }); -------------------------------------------------------------------------------- /test/validation.zi18n.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var path = require('path'); 5 | var expect = require('chai').expect; 6 | var request = require('supertest'); 7 | 8 | //load messages from locales 9 | var enMessages = require(path.join(__dirname, '..', 'config', 'locales', 'en.json')); 10 | var swMessages = require(path.join(__dirname, '..', 'config', 'locales', 'sw.json')); 11 | 12 | describe('Hook#validation#i18n', function() { 13 | 14 | it('should throw custom errors messages from default locales', function(done) { 15 | Authentication 16 | .create({}, function(error, user) { 17 | 18 | expect(error.Errors.email).to.exist; 19 | 20 | expect(error.Errors.email[0].message) 21 | .to.equal(enMessages['authentication.email.email']); 22 | 23 | expect(error.Errors.email[1].message) 24 | .to.equal(enMessages['authentication.email.required']); 25 | 26 | 27 | expect(error.Errors.username).to.exist; 28 | 29 | expect(error.Errors.username[0].message) 30 | .to.equal(enMessages['authentication.username.string']); 31 | 32 | expect(error.Errors.username[1].message) 33 | .to.equal(enMessages['authentication.username.required']); 34 | 35 | expect(error.Errors.birthday).to.exist; 36 | 37 | expect(error.Errors.birthday[0].message) 38 | .to.equal(enMessages['authentication.birthday.date']); 39 | expect(error.Errors.birthday[1].message) 40 | .to.equal(enMessages['authentication.birthday.required']); 41 | 42 | done(null, user); 43 | }); 44 | }); 45 | 46 | 47 | it('should throw custom errors messages from request locales', function(done) { 48 | sails.config.i18n.requestLocale = 'sw'; 49 | 50 | Authentication 51 | .create({}, function(error, user) { 52 | 53 | expect(error.Errors.email).to.exist; 54 | 55 | expect(error.Errors.email[0].message) 56 | .to.equal(swMessages['authentication.email.email']); 57 | 58 | expect(error.Errors.email[1].message) 59 | .to.equal(swMessages['authentication.email.required']); 60 | 61 | 62 | expect(error.Errors.username).to.exist; 63 | 64 | expect(error.Errors.username[0].message) 65 | .to.equal(swMessages['authentication.username.string']); 66 | 67 | expect(error.Errors.username[1].message) 68 | .to.equal(swMessages['authentication.username.required']); 69 | 70 | expect(error.Errors.birthday).to.exist; 71 | 72 | expect(error.Errors.birthday[0].message) 73 | .to.equal(swMessages['authentication.birthday.date']); 74 | expect(error.Errors.birthday[1].message) 75 | .to.equal(swMessages['authentication.birthday.required']); 76 | 77 | done(null, user); 78 | }); 79 | }); 80 | 81 | 82 | it('should respond with appropriate error using default locale', function(done) { 83 | var authentication = {}; 84 | request(sails.hooks.http.app) 85 | .post('/authentication/create') 86 | .set('Content-Type', 'application/json') 87 | .set('Accept', 'application/json') 88 | .send(authentication) 89 | .end(function(error, response) { 90 | 91 | var _error = JSON.parse(response.text); 92 | 93 | expect(response.status).to.equal(400); 94 | 95 | expect(_error.email).to.exist; 96 | 97 | expect(_error.email[0].message) 98 | .to.equal(enMessages['authentication.email.email']); 99 | 100 | expect(_error.email[1].message) 101 | .to.equal(enMessages['authentication.email.required']); 102 | 103 | 104 | expect(_error.username).to.exist; 105 | 106 | expect(_error.username[0].message) 107 | .to.equal(enMessages['authentication.username.string']); 108 | 109 | expect(_error.username[1].message) 110 | .to.equal(enMessages['authentication.username.required']); 111 | 112 | expect(_error.birthday).to.exist; 113 | 114 | expect(_error.birthday[0].message) 115 | .to.equal(enMessages['authentication.birthday.date']); 116 | expect(_error.birthday[1].message) 117 | .to.equal(enMessages['authentication.birthday.required']); 118 | 119 | done(); 120 | }); 121 | }); 122 | 123 | 124 | it('should respond with appropriate error using request locale', function(done) { 125 | var authentication = {}; 126 | request(sails.hooks.http.app) 127 | .post('/authentication/create') 128 | .set('Content-Type', 'application/json') 129 | .set('Accept', 'application/json') 130 | .set('Accept-Language', 'sw;q=0.8') 131 | .send(authentication) 132 | .end(function(error, response) { 133 | 134 | var _error = JSON.parse(response.text); 135 | 136 | expect(response.status).to.equal(400); 137 | 138 | expect(_error.email).to.exist; 139 | 140 | expect(_error.email[0].message) 141 | .to.equal(swMessages['authentication.email.email']); 142 | 143 | expect(_error.email[1].message) 144 | .to.equal(swMessages['authentication.email.required']); 145 | 146 | 147 | expect(_error.username).to.exist; 148 | 149 | expect(_error.username[0].message) 150 | .to.equal(swMessages['authentication.username.string']); 151 | 152 | expect(_error.username[1].message) 153 | .to.equal(swMessages['authentication.username.required']); 154 | 155 | expect(_error.birthday).to.exist; 156 | 157 | expect(_error.birthday[0].message) 158 | .to.equal(swMessages['authentication.birthday.date']); 159 | expect(_error.birthday[1].message) 160 | .to.equal(swMessages['authentication.birthday.required']); 161 | 162 | done(); 163 | }); 164 | }); 165 | 166 | }); -------------------------------------------------------------------------------- /test/validation.zupdate.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //dependencies 4 | var expect = require('chai').expect; 5 | var faker = require('faker'); 6 | 7 | describe('Hook#validation#update database errors', function() { 8 | var _email = faker.internet.email(); 9 | var __email = faker.internet.email(); 10 | 11 | before(function(done) { 12 | User 13 | .createEach([{ 14 | email: _email, 15 | username: faker.internet.userName(), 16 | birthday: faker.date.past() 17 | }, { 18 | email: __email, 19 | username: faker.internet.userName(), 20 | birthday: faker.date.past() 21 | }]) 22 | .exec(done); 23 | }); 24 | 25 | it('should throw unique error message using node callback style', function(done) { 26 | 27 | User 28 | .update({ 29 | email: __email 30 | }, { 31 | email: _email 32 | }, function(error, user) { 33 | expect(error.Errors.email).to.exist; 34 | 35 | expect(error.Errors.email[0].message) 36 | .to.equal(User.validationMessages.email.unique); 37 | 38 | done(null, user); 39 | }); 40 | }); 41 | 42 | it('should throw unique error message using deferred style', function(done) { 43 | 44 | User 45 | .update({ 46 | email: __email 47 | }, { 48 | email: _email 49 | }) 50 | .exec(function(error, user) { 51 | expect(error.Errors.email).to.exist; 52 | 53 | expect(error.Errors.email[0].message) 54 | .to.equal(User.validationMessages.email.unique); 55 | 56 | done(null, user); 57 | }); 58 | }); 59 | 60 | it('should throw unique error message using promise style', function(done) { 61 | 62 | User 63 | .update({ 64 | email: __email 65 | }, { 66 | email: _email 67 | }) 68 | .catch(function(error) { 69 | expect(error.Errors.email).to.exist; 70 | 71 | expect(error.Errors.email[0].message) 72 | .to.equal(User.validationMessages.email.unique); 73 | 74 | done(); 75 | }); 76 | }); 77 | 78 | }); --------------------------------------------------------------------------------