├── .gitignore ├── .jshintrc ├── CONTRIBUTE.md ├── LICENSE ├── Makefile ├── README.md ├── index.js ├── lib ├── types.js └── validate.js ├── package.json └── test ├── lib └── random.js └── validate.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | *.komodoproject 14 | node_modules 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "quotmark": "single", 4 | "undef": true, 5 | "unused": true, 6 | "strict": true, 7 | "globals": { 8 | "test": true, 9 | "suite": true, 10 | "setup": true, 11 | "teardown": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Any contributions are welcome, be it documentation, bugfixes or new features. 4 | Please use pull request to send me patches. Check the bugtracker first, feature 5 | requests are welcome, as wel as bugreports. 6 | 7 | When sending a bug report or a feature request, be sure to give me some context. 8 | How did the bug occur, what platform are you on (Windows? Linux? Mac?), why do 9 | you need this feature, so I can decide on the serverity of the problem or provide 10 | additional pointers, etcetera. 11 | 12 | Please be patient, it may take a day or two for me to respond. 13 | 14 | ## Code guidelines 15 | 16 | No tabs, four spaces. Clean up trailing whitespace. This can all be achieved 17 | by simply running: 18 | 19 | make tidy 20 | make lint 21 | 22 | before each commit. Oh, and run `npm test` too. 23 | 24 | New features come with a small unit test. I prefer tdd style, not bdd. See the 25 | files in the `test` directory for a start. 26 | 27 | Hope you're enjoying my software :) 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Matthijs van Henten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GIT_MODIFIED_UPDATED = $(shell git status --porcelain | grep -E '.?[AM].+[.]js(on)?$$' | sed -e "s/^...//g") 2 | 3 | tidy: 4 | @./node_modules/js-beautify/js/bin/js-beautify.js -p -k -w120 -r -f $(GIT_MODIFIED_UPDATED) 5 | 6 | lint: 7 | @./node_modules/jshint/bin/jshint --verbose $(GIT_MODIFIED_UPDATED) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | validate-arguments-js 2 | ===================== 3 | 4 | Validate arguments, declarative. 5 | 6 | [![Build Status](https://drone.io/github.com/mvhenten/validate-arguments-js/status.png)](https://drone.io/github.com/mvhenten/validate-arguments-js/latest) 7 | 8 | ## Installation 9 | 10 | ```bash 11 | npm install validate-arguments 12 | ``` 13 | 14 | The code itself depends on `lodash`, and will propably run just fine in the browser using `require.js`. 15 | 16 | ## Documentation 17 | 18 | This module levarages some of the validation methods from `lodash`, but by offering you a declaritive syntax and nice error strings: 19 | 20 | * array 21 | * boolean 22 | * date 23 | * element 24 | * empty 25 | * finite 26 | * function 27 | * null 28 | * number 29 | * object 30 | * plainObject 31 | * regexp 32 | * string 33 | 34 | And adds the following additional ones: 35 | 36 | * whole ( An integer ) 37 | * real ( A real number that isn't NaN ) 38 | * natural ( Positive integer ) 39 | * primitive ( 'number', 'boolean', 'string' ) 40 | 41 | When passing a constructor (function) as an `isa`, an `instanceof` check is done. 42 | 43 | ```javascript 44 | var Validate = require('validate-arguments'); 45 | 46 | function doSomething(withNamedArguments) { 47 | var args = Validate.named(arguments, { 48 | number: 'whole', 49 | name: 'string' 50 | callback: 'function' 51 | options: { 52 | isa: 'plainObject', 53 | optional: true 54 | }, 55 | validation: Validate // performs an instanceof 56 | }); 57 | 58 | if (!args.isValid()) { 59 | throw args.errorString(); 60 | } 61 | 62 | // continue safely 63 | } 64 | ``` 65 | Look at the [test cases](https://github.com/mvhenten/validate-arguments-js/blob/master/test/validate.js) for more examples. 66 | 67 | Node that validations may be nested: 68 | 69 | ```javascript 70 | var args = Validate.named(arguments, { 71 | address: { 72 | primary: { 73 | street: 'string', 74 | housenumber: 'number' 75 | } 76 | }, 77 | }); 78 | ``` 79 | 80 | ## Methods 81 | 82 | #### `named( named, validationSpec )` 83 | 84 | Returns a `validationObject` for further inspection. `named` should be a non-empty plain `Object`, containing all the keys documented in the `validationSpec`. 85 | The `validationSpec` should be an object, where the keys match the desired input. You may use the form `{ thing: 'string' }` over `{ thing: { isa: 'string' } }`. 86 | 87 | When passed an `arguments` object instead of a plain object, the first key of the arguments is used. 88 | 89 | #### `positional( arguments, ... )` 90 | 91 | Validate positional arguments, either an array or arguments object. Spec may be provided as an array in the second argument, or a variable number of arguments. 92 | 93 | #### `validate( arguments, ... )` 94 | 95 | Validate arguments, freeform. 96 | 97 | If the second argument contains a string, it is treated as a positional argument with one element. 98 | 99 | ### Return values 100 | 101 | Both `validateObject` and `validatePositional` return a `validationObject` with the following methods: 102 | 103 | * isValid: A boolean indication the validness 104 | * errors: An array containing positions or keys of invalid arguments 105 | * get: Retrieve values from the original input, array index or key 106 | * values: Return an array of values ( not that usable ) 107 | * errorString: An error string explaining what went wrong (verbosely) 108 | 109 | ## Testing 110 | 111 | ```bash 112 | cd validate-arguments-js 113 | npm install 114 | npm test 115 | ``` 116 | 117 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/validate'); 2 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | var LODASH_HELPERS = [ 6 | 'isArray', 7 | 'isBoolean', 8 | 'isDate', 9 | 'isElement', 10 | 'isEmpty', 11 | 'isFinite', 12 | 'isFunction', 13 | 'isNull', 14 | 'isNumber', 15 | 'isObject', 16 | 'isRegexp', 17 | 'isString', 18 | ]; 19 | 20 | var Types = { 21 | isWhole: function(value) { 22 | // a whole nunmber ( int ) 23 | return _.isNumber(value) && value % 1 === 0; 24 | }, 25 | isReal: function(value) { 26 | // a real number ( float ) 27 | return _.isNumber(value) && !isNaN(value); 28 | }, 29 | isNatural: function(value) { 30 | // e.g. positive int 31 | return Types.isWhole(value) && 0 <= value; 32 | }, 33 | 34 | isPlainObject: function(value) { 35 | return _.isObject(value) && !_.isArray(value) && !_.isFunction(value); 36 | }, 37 | 38 | isPrimitive: function(value) { 39 | return _.indexOf(['number', 'boolean', 'string'], value) !== -1; 40 | } 41 | }; 42 | 43 | // expose lodash type checks 44 | _.each(LODASH_HELPERS, function(name) { 45 | Types[name] = _[name]; 46 | }); 47 | 48 | module.exports = Types; 49 | -------------------------------------------------------------------------------- /lib/validate.js: -------------------------------------------------------------------------------- 1 | /** Validate arguments: validate your args. 2 | * @module validate-arguments 3 | */ 4 | 'use strict'; 5 | 6 | var _ = require('lodash'), 7 | sliced = require('sliced'), 8 | ucfirst = function(str) { 9 | return str.charAt(0).toUpperCase() + str.slice(1); 10 | }; 11 | 12 | var Types = require('./types'); 13 | 14 | function _normalizeSpec(spec) { 15 | return _.reduce(spec, function(spec, value, key) { 16 | if (!value.isa) { 17 | if (Types.isPlainObject(value)) { 18 | value = _normalizeSpec(value); 19 | } 20 | 21 | value = { 22 | isa: value 23 | }; 24 | } 25 | 26 | spec[key] = value; 27 | return spec; 28 | }, {}); 29 | } 30 | 31 | function _formatError(prefix, value, key, type) { 32 | if (_.isEmpty(value)) 33 | return 'missing ' + prefix + ' argument ' + key; 34 | 35 | return prefix + ' argument ' + key + ' is not a "' + type + '"'; 36 | } 37 | 38 | 39 | var validationObject = function(invalidKeys, values, spec) { 40 | return { 41 | isValid: function() { 42 | return invalidKeys.length === 0; 43 | }, 44 | 45 | errors: function() { 46 | return invalidKeys; 47 | }, 48 | 49 | get: function(name) { 50 | if (values) return values[name]; 51 | return null; 52 | }, 53 | 54 | values: function() { 55 | if (_.isObject(values)) return _.values(values); 56 | if (_.isArray(values)) return values; 57 | 58 | return []; 59 | }, 60 | 61 | errorString: function() { 62 | var prefix = _.isObject(values) ? 'named' : 'positional'; 63 | 64 | if (_.isEmpty(spec)) return invalidKeys[0]; 65 | 66 | return _.map(invalidKeys, function(key) { 67 | return _formatError(prefix, values[key], key, spec[key].isa); 68 | }).join(', '); 69 | } 70 | }; 71 | }; 72 | 73 | var Validate = { 74 | /** 75 | * Perform validation against one of the builtin types: whole, real, natural 76 | * 77 | * @param {number} value A number to check 78 | * @param {string} name Type name: whole, real, natural 79 | * @return {bool} True or false, false if name is not one of "whole, real, natural" 80 | */ 81 | isValidOfType: function(value, typeName) { 82 | var method = 'is' + ucfirst(typeName); 83 | 84 | if (Types[method] === undefined) return false; 85 | return Types[method](value); 86 | }, 87 | 88 | /** 89 | * Validate a single value against a given validation spec 90 | * 91 | * @param {any} value Value to validate 92 | * @param {object} Validation spec { isa: 'what', optional: bool } 93 | * @return {bool} true or false 94 | */ 95 | isValid: function(value, spec) { 96 | if (_.isUndefined(value) || _.isNull(value)) 97 | return spec.optional; 98 | 99 | if (_.isString(spec.isa)) 100 | return Validate.isValidOfType(value, spec.isa); 101 | 102 | if (Types.isPlainObject(spec.isa)) 103 | return Validate.validate(value, spec.isa).isValid(); 104 | 105 | if (typeof spec.isa === 'function') 106 | return (value instanceof spec.isa); 107 | 108 | return false; 109 | }, 110 | 111 | /** 112 | * Validate positional arguments, either an array or arguments object. 113 | * 114 | * @deprecated This method is now replaced by Validate.positional 115 | */ 116 | validatePositional: function(values, spec) { 117 | return Validate.positional(values, spec); 118 | }, 119 | 120 | /** 121 | * Validate named arguments, a plain object of key => value pairs 122 | * 123 | * @deprecated This method is now replaced by Validate.named 124 | */ 125 | validateObject: function(values, spec) { 126 | return Validate.named(values, spec); 127 | }, 128 | 129 | /** 130 | * Validate arguments, freeform. 131 | * 132 | * If the second argument contains a string, it is treated as a positional argument 133 | * with one element. 134 | * 135 | * @param {object|array|arguments} named Object of key => value pairs, or an arguments object 136 | * @param {object} spec Simplified validation spec 137 | * @return {validationObject} A validation object telling you what happened 138 | */ 139 | validate: function(values, spec) { 140 | var errors = []; 141 | 142 | if (_.isEmpty(spec)) return validationObject(['missing validation spec']); 143 | if (!values instanceof Object) return validationObject(['invalid values']); 144 | if (Types.isPrimitive(spec)) return Validate.positional(values, [spec]); 145 | 146 | spec = _normalizeSpec(spec); 147 | 148 | errors = _.reduce(spec, function(errors, validation, key) { 149 | if (!Validate.isValid(values[key], validation)) { 150 | errors.push(key); 151 | } 152 | 153 | return errors; 154 | }, []); 155 | 156 | return validationObject(errors, values, spec); 157 | }, 158 | 159 | /** 160 | * Validate named arguments, allows omitting the "isa" key when not needed 161 | * 162 | * @param {object} named Object of key => value pairs, or an arguments object 163 | * @param {object} spec Simplified validation spec 164 | * @return {validationObject} A validation object telling you what happened 165 | */ 166 | named: function(named, spec) { 167 | if (!Types.isPlainObject(named)) 168 | return validationObject(['missing named arguments']); 169 | 170 | if (_.isArguments(named)) named = named[0]; 171 | 172 | return Validate.validate(named, spec); 173 | }, 174 | 175 | /** 176 | * Validate positional arguments, either an array or arguments object. 177 | * Spec may be provided as an array in the second argument, or a variable number 178 | * of arguments. 179 | * 180 | * @param {array|arguments} values Array or arguments object to validate 181 | * @param {array|spec|...} spec Validation spec (array of objects) to validate against 182 | * @return {validationObject} A validation object telling you what happened 183 | */ 184 | positional: function(values, spec) { 185 | if (!(_.isArray(values) || _.isArguments(values))) 186 | return validationObject(['missing positional arguments']); 187 | 188 | if (!_.isArray(spec)) { 189 | spec = sliced(arguments, 1); 190 | } 191 | 192 | return Validate.validate(values, spec); 193 | } 194 | }; 195 | 196 | module.exports = Validate; 197 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "validate-arguments", 3 | "version": "0.0.8", 4 | "description": "Validate arguments, declaritive", 5 | "main": "./index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:mvhenten/validate-arguments-js.git" 9 | }, 10 | "keywords": [ 11 | "validation", 12 | "declarative" 13 | ], 14 | "dependencies": { 15 | "lodash": ">=1.2.1", 16 | "sliced": "~0.0.5" 17 | }, 18 | "devDependencies": { 19 | "mocha": ">=1.9.0", 20 | "js-beautify": "~1.4.2", 21 | "jshint": "*" 22 | }, 23 | "scripts": { 24 | "test": "./node_modules/mocha/bin/mocha -u tdd -R tap test" 25 | }, 26 | "author": "Matthijs van Henten", 27 | "license": "MIT", 28 | "readmeFilename": "README.md" 29 | } 30 | -------------------------------------------------------------------------------- /test/lib/random.js: -------------------------------------------------------------------------------- 1 | var Random = { 2 | string: function(length) { 3 | length = length || 32; 4 | return Math.random().toString(36).substr(2, length); 5 | }, 6 | 7 | number: function() { 8 | return Math.random() * 1000000; 9 | }, 10 | 11 | 12 | integer: function() { 13 | return parseInt(Random.number(), 10); 14 | } 15 | }; 16 | 17 | module.exports = Random; 18 | -------------------------------------------------------------------------------- /test/validate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'), 4 | assert = require('assert'), 5 | random = require('./lib/random'), 6 | Validate = require('../'); 7 | 8 | 9 | suite('validate-arguments', function() { 10 | test('Validate must be gracefull about bad input', function(done) { 11 | var cases = [ 12 | { 13 | 'functions': [ 14 | 15 | function() {}, 16 | 17 | function() {}] 18 | }, 19 | { 20 | 'null and null': [null, null] 21 | }, 22 | { 23 | 'empty objects': [{}, {}] 24 | }, 25 | { 26 | 'empty arrays': [[], []] 27 | }, 28 | { 29 | 'undefs': [] 30 | }, 31 | ]; 32 | 33 | cases.forEach(function(testCase) { 34 | var label = _.first(_.keys(testCase)), 35 | values = testCase[label]; 36 | 37 | assert.ok(label && values, 'we have a case'); 38 | 39 | assert.doesNotThrow(function() { 40 | var valid = Validate.validateObject(values[0], values[1]); 41 | 42 | assert.equal(valid.isValid(), false, 'invalid input is not valid: ' + label); 43 | }); 44 | 45 | assert.doesNotThrow(function() { 46 | var valid = Validate.validatePositional(values[0], values[1]); 47 | 48 | assert.equal(valid.isValid(), false, 'invalid input is not valid: ' + label); 49 | }); 50 | }); 51 | 52 | done(); 53 | }); 54 | 55 | test('Validate returns a validationObject', function(done) { 56 | var value = random.string(), 57 | valid = Validate.validateObject({ 58 | str: value 59 | }, { 60 | str: { 61 | isa: 'string' 62 | } 63 | }); 64 | 65 | assert.ok(valid.isValid(), 'isValid returns a true value'); 66 | assert.equal(valid.get('str'), value, 'get returns expected value'); 67 | assert.equal(valid.errorString(), '', 'Error string is an empty string'); 68 | assert.deepEqual(valid.errors(), [], 'Errors is an emtpy array'); 69 | assert.deepEqual(valid.values(), [value], 'Values returns the values of the object'); 70 | 71 | valid = Validate.validatePositional([value], [{ 72 | isa: 'string' 73 | }]); 74 | 75 | assert.ok(valid.isValid(), 'isValid returns a true value'); 76 | assert.equal(valid.get(0), value, 'get returns expected value'); 77 | assert.equal(valid.errorString(), '', 'Error string is an empty string'); 78 | assert.deepEqual(valid.errors(), [], 'Errors is an emtpy array'); 79 | assert.deepEqual(valid.values(), [value], 'Values returns the values of the object'); 80 | 81 | done(); 82 | }); 83 | 84 | test('Validate returns a meaning ful errorString', function(done) { 85 | var value = random.string(); 86 | 87 | var cases = [ 88 | { 89 | label: 'Validation spec missing', 90 | args: {}, 91 | spec: {}, 92 | expect: 'missing validation spec' 93 | }, 94 | { 95 | label: 'Missing arguments', 96 | args: { 97 | bar: 1 98 | }, 99 | spec: { 100 | foo: { 101 | isa: 'string' 102 | } 103 | }, 104 | expect: 'missing named argument foo' 105 | }, 106 | { 107 | label: 'Multiple missing arguments', 108 | args: { 109 | bar: 1 110 | }, 111 | spec: { 112 | foo: { 113 | isa: 'string' 114 | }, 115 | biz: { 116 | isa: 'number' 117 | } 118 | }, 119 | expect: 'missing named argument foo, missing named argument biz' 120 | }, 121 | { 122 | label: 'Named argument is not a', 123 | args: { 124 | foo: value 125 | }, 126 | spec: { 127 | foo: { 128 | isa: 'number' 129 | } 130 | }, 131 | expect: 'named argument foo is not a "number"' 132 | }, 133 | { 134 | label: 'Mixing errors', 135 | args: { 136 | foo: value 137 | }, 138 | spec: { 139 | foo: { 140 | isa: 'number' 141 | }, 142 | biz: { 143 | isa: 'string' 144 | } 145 | }, 146 | expect: 'named argument foo is not a "number", missing named argument biz' 147 | } 148 | ]; 149 | 150 | cases.forEach(function(testCase) { 151 | var valid = Validate.validateObject(testCase.args, testCase.spec); 152 | 153 | assert.equal(valid.isValid(), false, 'not valid: ' + testCase.label); 154 | assert.equal(valid.errorString(), testCase.expect, 'errors: ' + testCase.label); 155 | }); 156 | 157 | done(); 158 | }); 159 | 160 | test('validate basic types', function(done) { 161 | // todo generalize test cases for types 162 | var cases = [{ 163 | label: 'boolean true is a boolean', 164 | value: true, 165 | spec: { 166 | isa: 'boolean' 167 | }, 168 | expect: true 169 | }, { 170 | label: 'boolean false is a boolean', 171 | value: false, 172 | spec: { 173 | isa: 'boolean' 174 | }, 175 | expect: true 176 | }, { 177 | label: 'stringy true is not a boolean', 178 | value: 'true', 179 | spec: { 180 | isa: 'boolean' 181 | }, 182 | expect: false 183 | }, { 184 | label: 'a random int number is a number', 185 | value: random.integer(), 186 | spec: { 187 | isa: 'number' 188 | }, 189 | expect: true 190 | }, { 191 | label: 'a random float number is a number', 192 | value: random.number(), 193 | spec: { 194 | isa: 'number' 195 | }, 196 | expect: true 197 | }, { 198 | label: 'a random int number is a finite number', 199 | value: random.integer(), 200 | spec: { 201 | isa: 'finite' 202 | }, 203 | expect: true 204 | }, { 205 | label: 'a random float number is not a whole number', 206 | value: random.number(), 207 | spec: { 208 | isa: 'whole' 209 | }, 210 | expect: false 211 | }, { 212 | label: 'a string is not a whole number', 213 | value: random.string(), 214 | spec: { 215 | isa: 'whole' 216 | }, 217 | expect: false 218 | }, { 219 | label: 'a random int is a whole number', 220 | value: random.integer(), 221 | spec: { 222 | isa: 'whole' 223 | }, 224 | expect: true 225 | }, { 226 | label: 'a random negative int is not a natural number', 227 | value: random.integer() * -1, 228 | spec: { 229 | isa: 'natural' 230 | }, 231 | expect: false 232 | }, { 233 | label: 'a zero is a natural number', 234 | value: 0, 235 | spec: { 236 | isa: 'whole' 237 | }, 238 | expect: true 239 | } 240 | ]; 241 | 242 | cases.forEach(function(testCase) { 243 | var value = { 244 | key: testCase.value 245 | }, spec = { 246 | key: testCase.spec 247 | }; 248 | 249 | var validated = Validate.validateObject(value, spec); 250 | assert.equal(validated.isValid(), testCase.expect, testCase.label); 251 | 252 | }); 253 | 254 | assert.ok(true, 'all is well'); 255 | done(); 256 | }); 257 | 258 | test('optionals can be emtpy', function(done) { 259 | var undef, values = [{ 260 | thing: null 261 | }, { 262 | thing: undef 263 | }, {}]; 264 | 265 | var spec = { 266 | thing: { 267 | isa: 'plainObject', 268 | optional: true 269 | } 270 | }; 271 | 272 | values.map(function(value) { 273 | var validated = Validate.validateObject({}, spec); 274 | 275 | assert.equal(validated.isValid(), true, 'optional arguments may be omitted: ' + value.thing); 276 | }); 277 | 278 | done(); 279 | 280 | 281 | }); 282 | 283 | test('validation specs can be nested', function() { 284 | // TODO add more testcases, both invalid and valid! 285 | 286 | var spec = { 287 | thing: { 288 | isa: { 289 | nestedThing: { 290 | isa: { 291 | childOfNested: { 292 | isa: 'string' 293 | } 294 | } 295 | }, 296 | optionalThing: { 297 | optional: true, 298 | isa: 'string' 299 | } 300 | }, 301 | optional: true 302 | } 303 | }; 304 | 305 | var cases = [ 306 | { 307 | label: 'deep nested object is validated', 308 | isValid: true, 309 | value: { 310 | thing: { 311 | nestedThing: { 312 | childOfNested: random.string() 313 | } 314 | } 315 | } 316 | }, 317 | { 318 | label: 'deep nested value must be of valid type', 319 | isValid: false, 320 | value: { 321 | thing: { 322 | nestedThing: { 323 | childOfNested: random.integer() 324 | } 325 | } 326 | } 327 | } 328 | ]; 329 | 330 | cases.map(function(testCase) { 331 | var validated = Validate.validateObject(testCase.value, spec); 332 | 333 | assert.equal(validated.isValid(), testCase.isValid, testCase.label); 334 | }); 335 | }); 336 | 337 | 338 | test('Validate.named is sugar for validateObject', function() { 339 | var cases = [ 340 | { 341 | label: 'Validate.named simple', 342 | spec: { 343 | name: 'string' 344 | }, 345 | input: { 346 | name: random.string() 347 | }, 348 | expect: true, 349 | }, 350 | { 351 | label: 'Validate.named validates', 352 | spec: { 353 | name: 'string' 354 | }, 355 | input: { 356 | name: random.integer() 357 | }, 358 | expect: false, 359 | }, 360 | { 361 | label: 'Validate.named allows isa', 362 | spec: { 363 | name: { 364 | isa: 'string' 365 | } 366 | }, 367 | input: { 368 | name: random.string() 369 | }, 370 | expect: true, 371 | }, 372 | { 373 | label: 'Validate.named allows nesting', 374 | spec: { 375 | name: { 376 | foo: { 377 | bar: 'string' 378 | } 379 | } 380 | }, 381 | input: { 382 | name: { 383 | foo: { 384 | bar: random.string() 385 | } 386 | } 387 | }, 388 | expect: true, 389 | }, 390 | { 391 | label: 'Validate.named allows nesting complexer objects', 392 | spec: { 393 | name: { 394 | biz: { 395 | bar: 'string' 396 | }, 397 | foo: { 398 | bar: 'string' 399 | } 400 | } 401 | }, 402 | input: { 403 | name: { 404 | biz: { 405 | bar: random.string() 406 | }, 407 | foo: { 408 | bar: random.string() 409 | } 410 | } 411 | }, 412 | expect: true, 413 | }, 414 | { 415 | label: 'Validate.named validates nested complexer objects', 416 | spec: { 417 | name: { 418 | biz: { 419 | bar: 'string' 420 | }, 421 | foo: { 422 | bar: 'number' 423 | } 424 | } 425 | }, 426 | input: { 427 | name: { 428 | biz: { 429 | bar: random.string() 430 | }, 431 | foo: { 432 | bar: random.string() 433 | } 434 | } 435 | }, 436 | expect: false, 437 | } 438 | ]; 439 | 440 | _.each(cases, function(testCase) { 441 | var validated = Validate.named(testCase.input, testCase.spec); 442 | 443 | assert.equal(validated.isValid(), testCase.expect); 444 | 445 | }); 446 | 447 | }); 448 | }); 449 | --------------------------------------------------------------------------------