├── doc ├── notes.txt ├── public │ ├── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── roboto-black.eot │ │ ├── roboto-black.ttf │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ ├── roboto-black.woff │ │ └── novecento-bold.woff │ └── stylesheets │ │ └── normalize.css ├── TODO.txt ├── examples │ ├── simple.js │ ├── http_request_options.js │ └── range.js └── docco.css ├── .travis.yml ├── test.sh ├── .gitignore ├── test ├── circle.js ├── jasmine-1.3.1 │ ├── MIT.LICENSE │ ├── jasmine.css │ └── jasmine-html.js ├── string-rulespec.js ├── jasmine.html ├── array.js ├── custom.js ├── default.js ├── ownparams.js ├── wildcard.js ├── format.js ├── run-jasmine.js ├── type.js ├── basic.js ├── parambulator-multi.spec.js └── value.js ├── LICENSE.txt ├── package.json ├── parambulator-min.js ├── parambulator-min.map ├── README.md └── parambulator.js /doc/notes.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 5 5 | - 4 6 | - 0.12 7 | - 0.10 8 | -------------------------------------------------------------------------------- /doc/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /doc/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /doc/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /doc/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /doc/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /doc/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /doc/public/fonts/roboto-black.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/roboto-black.eot -------------------------------------------------------------------------------- /doc/public/fonts/roboto-black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/roboto-black.ttf -------------------------------------------------------------------------------- /doc/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /doc/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /doc/public/fonts/roboto-black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/roboto-black.woff -------------------------------------------------------------------------------- /doc/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjrodger/parambulator/HEAD/doc/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | ./node_modules/.bin/jasmine-node ./test 2 | ./node_modules/.bin/phantomjs test/run-jasmine.js test/jasmine.html 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | 4 | lib-cov 5 | *.seed 6 | *.log 7 | *.csv 8 | *.dat 9 | *.out 10 | *.pid 11 | *.gz 12 | 13 | pids 14 | logs 15 | results 16 | 17 | node_modules 18 | npm-debug.log 19 | 20 | README.html 21 | -------------------------------------------------------------------------------- /test/circle.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | 9 | var parambulator = require('..') 10 | 11 | 12 | describe('circle', function() { 13 | 14 | var pb 15 | 16 | it('circle', function() { 17 | pb = parambulator({ 18 | string$: ['foo'] 19 | }, {msgs: { 20 | 'string$': 'circle: <%=json(point)%>' 21 | }}) 22 | 23 | 24 | var a = {} 25 | a.foo = a 26 | 27 | var res = pb.validate(a) 28 | assert.equal('circle: {"foo":"[CIRCULAR-REFERENCE]"}',res.message) 29 | }) 30 | 31 | }) 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/TODO.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | -- post 0.1 4 | 5 | - validate call should be optional - most common use case is non-async - need to be able to throw an exception 6 | 7 | - ctxt.util.proplist not good name - actually gets single or list of anything 8 | 9 | - test/document validate return value if no async calls 10 | - tests for type child rules 11 | - test for only$ rule 12 | - how to support required$: true 13 | - default$ rule 14 | - function$ rule - applies function as rule 15 | - deeper self-vaidation 16 | - list$, prop$ validation, error handling 17 | - http request example - agent - type should accept array (OR semantics) 18 | - check for truth failures when gex returns a 0 or '' - wild$ rule 19 | - extend docs for custom rules 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 Richard Rodger 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/jasmine-1.3.1/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parambulator", 3 | "description": "A simple way to generate nice error messages for named parameters.", 4 | "keywords": [ 5 | "json", 6 | "param", 7 | "arg", 8 | "parameter", 9 | "argument", 10 | "schema" 11 | ], 12 | "homepage": "https://github.com/rjrodger/parambulator", 13 | "author": "Richard Rodger (http://richardrodger.com/)", 14 | "contributors": [ 15 | "Richard Rodger (http://richardrodger.com/)" 16 | ], 17 | "version": "1.5.2", 18 | "main": "parambulator.js", 19 | "repository": { 20 | "url": "https://github.com/rjrodger/parambulator.git" 21 | }, 22 | "dependencies": { 23 | "gex": "0.2.2", 24 | "jsonic": "0.2.2", 25 | "lodash": "4.5.0" 26 | }, 27 | "files": [ 28 | "package.json", 29 | "LICENSE.txt", 30 | "README.md", 31 | "parambulator.js" 32 | ], 33 | "devDependencies": { 34 | "docco": "0.7.0", 35 | "jasmine-node": "1.14.5", 36 | "jshint": "2.8.0", 37 | "mocha": "~2.1.0", 38 | "phantomjs": "1.8.2-3", 39 | "uglify-js": "2.4.23" 40 | }, 41 | "scripts": { 42 | "test": "./test.sh", 43 | "build": "./build.sh" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/string-rulespec.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('assert') 7 | 8 | var parambulator = require('..') 9 | 10 | 11 | describe('string-rulespec', function() { 12 | 13 | it('single', function() { 14 | var pm, res 15 | 16 | pm = parambulator({foo:'required$'}) 17 | res = pm.validate({foo:'1'}) 18 | assert.ok( null == res ) 19 | 20 | res = pm.validate({bar:'1'}) 21 | assert.ok( null != res ) 22 | assert.equal('required$',res.parambulator.code) 23 | assert.ok(res.parambulator.point.bar) 24 | }) 25 | 26 | 27 | it('multiple', function() { 28 | var pm, res 29 | 30 | pm = parambulator({foo:'required$,integer$'}) 31 | res = pm.validate({foo:1}) 32 | assert.ok( null == res ) 33 | 34 | res = pm.validate({bar:1}) 35 | assert.ok( null != res ) 36 | assert.equal('required$',res.parambulator.code) 37 | assert.ok(res.parambulator.point.bar) 38 | 39 | res = pm.validate({foo:'zoo'}) 40 | assert.ok( null != res ) 41 | //console.log(res) 42 | assert.equal('integer$',res.parambulator.code) 43 | assert.ok(res.parambulator.point.foo) 44 | }) 45 | 46 | }) 47 | -------------------------------------------------------------------------------- /doc/examples/simple.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Create a Parameter Check 4 | ======================== 5 | 6 | Use parambulator to define a set of rules that input paramters must obey. 7 | 8 | */ 9 | 10 | // use require to load the parambulator module 11 | // a single function, parambulator, is returned 12 | var parambulator = require('parambulator') 13 | 14 | // create a new instance, providing a "param spec" as the first argument 15 | // the param spec defined the properties you expect to see, and the rules that should apply to them 16 | // in this case, you want your input parameters to have a price property, the value of which is a number 17 | var paramcheck = parambulator({ 18 | price: {type$:'number'} 19 | }) 20 | 21 | 22 | // print out any errors 23 | function printresult(err,res) { 24 | if(err) console.log(err.message); 25 | } 26 | 27 | 28 | 29 | // this passes - price is a number 30 | paramcheck.validate( { price: 10.99 }, printresult ) 31 | 32 | 33 | // this fails - price is a string 34 | paramcheck.validate( { price: 'free!' }, printresult ) 35 | // output: The value 'free!' is not of type 'number' (parent: price). 36 | 37 | 38 | // this also fails - price is an object 39 | paramcheck.validate( { price: {foo:'bar'} }, printresult ) 40 | // output: The value '{"foo":"bar"}' is not of type 'number' (parent: price). 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/jasmine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | parambulator jasmine test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /doc/examples/http_request_options.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Validate HTTP request options 4 | ============================= 5 | 6 | Validate the options accepted by http.request. 7 | See http://nodejs.org/api/http.html#http_http_request_options_callback 8 | 9 | 10 | */ 11 | 12 | 13 | var parambulator = require('parambulator') 14 | 15 | var paramcheck = parambulator({ 16 | host: {type$:'string'}, 17 | hostname: {type$:'string'}, 18 | port: {type$:'integer'}, 19 | localAddress: {type$:'string'}, 20 | socketPath: {type$:'string'}, 21 | method: {enum$:['DELETE','GET','HEAD','POST','PUT','CONNECT','OPTIONS','TRACE','COPY','LOCK','MKCOL','MOVE','PROPFIND','PROPPATCH','SEARCH','UNLOCK','REPORT','MKACTIVITY','CHECKOUT','MERGE','MSEARCH','M-SEARCH','NOTIFY','SUBSCRIBE','UNSUBSCRIBE','PATCH','PURGE']}, 22 | path: {type$:'string'}, 23 | headers: {type$:'object'}, 24 | auth: {wild$:'*:*'}, 25 | }) 26 | 27 | 28 | // print out any errors 29 | function printresult(err,res) { 30 | if(err) console.log(err.message); 31 | } 32 | 33 | 34 | 35 | // these pass 36 | paramcheck.validate( { 37 | host: 'www.google.com', 38 | port: 80, 39 | path: '/upload', 40 | method: 'POST' 41 | }, printresult ) 42 | 43 | paramcheck.validate( { 44 | auth: 'user1:pass1' 45 | }, printresult ) 46 | 47 | 48 | 49 | // these fail 50 | 51 | paramcheck.validate( { 52 | port: '80', 53 | }, printresult ) 54 | 55 | paramcheck.validate( { 56 | method: 'FOO' 57 | }, printresult ) 58 | 59 | paramcheck.validate( { 60 | headers: true 61 | }, printresult ) 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /test/array.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | 9 | var parambulator = require('..') 10 | 11 | 12 | 13 | describe('array', function() { 14 | 15 | var pb_array 16 | 17 | it('happy', function() { 18 | pb_array = new parambulator({ 19 | z: 'required$', 20 | foo: { 21 | 22 | '__1': { 23 | bar: 'required$' 24 | }, 25 | 26 | '__0': 'required$', 27 | }, 28 | }) 29 | }) 30 | 31 | 32 | it('z', function() { 33 | pb_array.validate({z:1},function(err,res){ 34 | assert.isNull(err) 35 | }) 36 | 37 | pb_array.validate({},function(err,res){ 38 | assert.isNotNull(err) 39 | assert.equal(err.parambulator.code,'required$') 40 | }) 41 | }) 42 | 43 | 44 | it('index0', function() { 45 | pb_array.validate({z:1},function(err,res){ 46 | assert.isNull(err) 47 | }) 48 | 49 | pb_array.validate({z:1,foo:[10]},function(err,res){ 50 | assert.isNull(err) 51 | }) 52 | 53 | pb_array.validate({z:1,foo:[]},function(err,res){ 54 | assert.isNotNull(err) 55 | assert.equal(err.parambulator.code,'required$') 56 | }) 57 | }) 58 | 59 | 60 | it('index1', function() { 61 | 62 | pb_array.validate({z:1,foo:[10]},function(err,res){ 63 | assert.isNull(err) 64 | }) 65 | 66 | pb_array.validate({z:1,foo:[{},{bar:1}]},function(err,res){ 67 | assert.isNull(err) 68 | }) 69 | 70 | pb_array.validate({z:1,foo:[{},{}]},function(err,res){ 71 | assert.isNotNull(err) 72 | assert.equal(err.parambulator.code,'required$') 73 | }) 74 | 75 | pb_array.validate({z:1,foo:[{},{barx:1}]},function(err,res){ 76 | assert.isNotNull(err) 77 | assert.equal(err.parambulator.code,'required$') 78 | }) 79 | }) 80 | 81 | }) 82 | 83 | -------------------------------------------------------------------------------- /test/custom.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | 9 | var parambulator = require('..') 10 | 11 | 12 | 13 | describe('custom', function() { 14 | 15 | var pb 16 | 17 | it('happy', function() { 18 | pb = parambulator({ 19 | required$: 'req', 20 | equalsbar$: 'foo', 21 | exactlyone$: ['a','b'] 22 | }, { 23 | rules: { 24 | equalsbar$: function(ctxt,cb) { 25 | var pn = ctxt.rule.spec 26 | var val = ctxt.point[pn] 27 | if( 'bar' == val ) { 28 | return cb(null) 29 | } 30 | else { 31 | return ctxt.util.fail(ctxt,cb) 32 | } 33 | } 34 | }, 35 | msgs: { 36 | required$:'Property %s is required, yo! At %s.', 37 | equalsbar$:'Property %s is not equal to "bar", man... At %s.', 38 | exactlyone$: function(inserts) { 39 | return 'my custom error msg for '+inserts.rule.spec+' at location '+inserts.parentpath 40 | } 41 | } 42 | }) 43 | }) 44 | 45 | 46 | it('equalsbar', function() { 47 | pb.validate({req:1,a:1,foo:'bar'},function(err,res){ 48 | assert.isNull(err) 49 | }) 50 | 51 | pb.validate({req:1,a:1,foo:'foo'},function(err,res){ 52 | assert.isNotNull(err) 53 | assert.equal(err.parambulator.code,'equalsbar$') 54 | }) 55 | 56 | pb.validate({req:1,a:1},function(err,res){ 57 | assert.isNotNull(err) 58 | assert.equal(err.parambulator.code,'equalsbar$') 59 | }) 60 | 61 | pb.validate({a:1},function(err,res){ 62 | assert.isNotNull(err) 63 | assert.equal(err.parambulator.code,'required$') 64 | }) 65 | 66 | pb.validate({req:1,foo:'bar', a:1,b:1},function(err,res){ 67 | assert.isNotNull(err) 68 | assert.equal(err.parambulator.code,'exactlyone$') 69 | assert.equal(err.message,'my custom error msg for a,b at location top level') 70 | }) 71 | }) 72 | }) 73 | 74 | -------------------------------------------------------------------------------- /test/default.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | var _ = require('underscore') 9 | 10 | var parambulator = require('..') 11 | 12 | 13 | 14 | describe('default', function() { 15 | 16 | var pb 17 | 18 | it('happy', function() { 19 | pb = parambulator({ 20 | a: {default$:123, type$:'number'}, 21 | b: { 22 | firstobj: {default$:23, type$:'number'}, 23 | secondobj: {innerobj: {default$:'test'}}, 24 | thirdobj: {type$:'array', __0: {default$:123}}, 25 | }, 26 | c: {default$:555, type$:'number'}, 27 | d: {type$: 'array', __0: {default$:'arraytest0'}, __1: {default$:'arraytest1'}}, 28 | e: {type$: 'array', default$:[]}, 29 | 30 | // TODO: handle this case 31 | //f: {default$:'aa', type$:'number'} 32 | }) 33 | }) 34 | 35 | 36 | it('firsttest', function() { 37 | var obj = {c: 2222} 38 | pb.validate(obj) 39 | 40 | assert.isTrue(_.has(obj, 'a')) 41 | assert.equal(obj['a'], 123) 42 | 43 | assert.isTrue(_.has(obj, 'b')) 44 | assert.isTrue(_.has(obj['b'], 'firstobj')) 45 | assert.equal(obj['b']['firstobj'], 23) 46 | 47 | assert.isTrue(_.has(obj['b'], 'secondobj')) 48 | assert.isTrue(_.has(obj['b']['secondobj'], 'innerobj')) 49 | assert.equal(obj['b']['secondobj']['innerobj'], 'test') 50 | 51 | assert.isTrue(_.has(obj['b'], 'thirdobj')) 52 | assert.isTrue(_.isArray(obj['b']['thirdobj'])) 53 | assert.equal(obj['b']['thirdobj'], '123') 54 | 55 | assert.isTrue(_.has(obj, 'c')) 56 | assert.equal(obj['c'], 2222) 57 | 58 | assert.isTrue(_.has(obj, 'd')) 59 | assert.isTrue(_.isArray(obj['d'])) 60 | assert.equal('arraytest0', obj['d'][0]) 61 | assert.equal('arraytest1', obj['d'][1]) 62 | 63 | assert.isTrue(_.has(obj, 'e')) 64 | assert.isTrue(_.isArray(obj['e'])) 65 | assert.equal(0, obj['e'].length) 66 | }) 67 | 68 | 69 | it('nice', function() { 70 | parambulator({ 71 | c: {default$:555, type$:'number'}, 72 | d: {type$: 'number', __0: {default$:'arraytest0'}, __1: {default$:'arraytest1'}}, 73 | }) 74 | }) 75 | 76 | 77 | 78 | }) 79 | 80 | 81 | -------------------------------------------------------------------------------- /test/ownparams.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | var _ = require('underscore') 9 | 10 | var parambulator = require('..') 11 | 12 | 13 | 14 | describe('ownparams', function() { 15 | 16 | var pb = parambulator.ownparams 17 | 18 | 19 | 20 | it('strings$', function() { 21 | 22 | for( var r in {required$:1,notempty$:1,atmostone$:1,exactlyone$:1,atleastone$:1} ) { 23 | var args = {} 24 | 25 | 26 | args[r]='foo' 27 | pb.validate(args,function(err,res){ 28 | assert.isNull(err) 29 | }) 30 | 31 | 32 | args[r]=['foo','bar'] 33 | pb.validate(args,function(err,res){ 34 | assert.isNull(err) 35 | }) 36 | 37 | args[r]=1 38 | pb.validate(args,function(err,res){ 39 | //console.dir(err) 40 | assert.isNotNull(err) 41 | assert.equal(err.parambulator.code,'strings$') 42 | }) 43 | 44 | 45 | args = {} 46 | 47 | args['foo']={} 48 | args['foo'][r]='bar' 49 | pb.validate(args,function(err,res){ 50 | assert.isNull(err) 51 | }) 52 | 53 | args['foo']={} 54 | args['foo'][r]=1 55 | pb.validate(args,function(err,res){ 56 | //console.dir(err) 57 | assert.isNotNull(err) 58 | assert.equal(err.parambulator.code,'strings$') 59 | }) 60 | } 61 | 62 | }) 63 | 64 | 65 | it('wild$', function() { 66 | pb.validate({a:{wild$:'b*'}},function(err,res){ 67 | assert.isNull(err) 68 | }) 69 | 70 | pb.validate({a:{wild$:1}},function(err,res){ 71 | //console.dir(err) 72 | //console.dir(res) 73 | assert.isNotNull(err) 74 | assert.equal(err.parambulator.code,'type$') 75 | }) 76 | }) 77 | 78 | 79 | it('type$', function() { 80 | pb.validate({a:{type$:'string'}},function(err,res){ 81 | assert.isNull(err) 82 | }) 83 | 84 | pb.validate({a:{type$:1}},function(err,res){ 85 | //console.dir(err) 86 | //console.dir(res) 87 | assert.isNotNull(err) 88 | assert.equal(err.parambulator.code,'type$') 89 | }) 90 | }) 91 | 92 | 93 | it('re$', function() { 94 | pb.validate({a:{re$:'/b/'}},function(err,res){ 95 | assert.isNull(err) 96 | }) 97 | 98 | pb.validate({a:{re$:1}},function(err,res){ 99 | //console.dir(err) 100 | //console.dir(res) 101 | assert.isNotNull(err) 102 | assert.equal(err.parambulator.code,'type$') 103 | }) 104 | }) 105 | 106 | 107 | it('enum$', function() { 108 | pb.validate({a:{enum$:[11,22]}},function(err,res){ 109 | assert.isNull(err) 110 | }) 111 | 112 | pb.validate({a:{enum$:1}},function(err,res){ 113 | //console.dir(err) 114 | //console.dir(res) 115 | assert.isNotNull(err) 116 | assert.equal(err.parambulator.code,'type$') 117 | }) 118 | }) 119 | }) 120 | 121 | 122 | -------------------------------------------------------------------------------- /test/wildcard.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | 9 | var parambulator = require('..') 10 | 11 | 12 | 13 | describe('array', function() { 14 | 15 | var pb = parambulator({ 16 | 17 | atmostone$: 'a*', 18 | 19 | '*': { 20 | a:{type$:'integer'} 21 | }, 22 | 23 | y: { 24 | '*': { 25 | a:{type$:'integer'} 26 | }, 27 | }, 28 | 29 | '**': { 30 | b:{type$:'integer'} 31 | }, 32 | 33 | 'z*': 'required$' 34 | }) 35 | 36 | 37 | //console.log(''+pb) 38 | 39 | 40 | it('a*', function() { 41 | pb.validate({z:1,},function(err,res){ 42 | assert.isNull(err) 43 | }) 44 | 45 | pb.validate({z:1,c:1},function(err,res){ 46 | assert.isNull(err) 47 | }) 48 | 49 | 50 | pb.validate({z:1,a:1},function(err,res){ 51 | assert.isNull(err) 52 | }) 53 | 54 | pb.validate({z:1,a:1,ax:1},function(err,res){ 55 | //console.log(err) 56 | assert.isNotNull(err) 57 | assert.equal(err.parambulator.code,'atmostone$') 58 | }) 59 | }) 60 | 61 | 62 | it('star', function() { 63 | pb.validate({z:1,x:{a:1},y:{a:2}},function(err,res){ 64 | assert.isNull(err) 65 | }) 66 | 67 | pb.validate({z:1,x:{a:'b'}},function(err,res){ 68 | assert.isNotNull(err) 69 | assert.equal(err.parambulator.code,'type$') 70 | }) 71 | 72 | pb.validate({z:1,x:[{a:1},{a:2}]},function(err,res){ 73 | assert.isNull(err) 74 | }) 75 | 76 | pb.validate({z:1,y:[{a:1},{a:2}]},function(err,res){ 77 | assert.isNull(err) 78 | }) 79 | 80 | 81 | pb.validate({z:1,y:[{a:'b'}]},function(err,res){ 82 | assert.isNotNull(err) 83 | assert.equal(err.parambulator.code,'type$') 84 | }) 85 | 86 | }) 87 | 88 | 89 | 90 | it('**', function() { 91 | 92 | pb.validate({z:1,b:1,x:{a:1,b:1,y:{b:1}}},function(err,res){ 93 | assert.isNull(err) 94 | }) 95 | 96 | pb.validate({z:1,b:'foo'},function(err,res){ 97 | //console.log(err) 98 | assert.isNotNull(err) 99 | assert.equal(err.parambulator.code,'type$') 100 | }) 101 | 102 | 103 | pb.validate({z:1,x:{b:'foo'}},function(err,res){ 104 | //console.log(err) 105 | assert.isNotNull(err) 106 | assert.equal(err.parambulator.code,'type$') 107 | }) 108 | 109 | pb.validate({z:1,x:{y:{b:'foo'}}},function(err,res){ 110 | //console.log(err) 111 | assert.isNotNull(err) 112 | assert.equal(err.parambulator.code,'type$') 113 | }) 114 | }) 115 | 116 | 117 | it('z*', function() { 118 | pb.validate({z:1},function(err,res){ 119 | assert.isNull(err) 120 | }) 121 | 122 | 123 | pb.validate({za:1},function(err,res){ 124 | assert.isNull(err) 125 | }) 126 | 127 | pb.validate({},function(err,res){ 128 | //console.log(err) 129 | assert.isNotNull(err) 130 | assert.equal(err.parambulator.code,'required$') 131 | }) 132 | }) 133 | 134 | }) 135 | 136 | -------------------------------------------------------------------------------- /test/format.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | var assert = require('chai').assert 6 | var gex = require('gex') 7 | 8 | var parambulator = require('..') 9 | 10 | 11 | 12 | describe('format', function() { 13 | 14 | var pb 15 | 16 | it('happy', function() { 17 | pb = parambulator({ 18 | a: {format$:'datetime'}, 19 | b: {format$:'date'}, 20 | c: {format$:'time'}, 21 | d: {format$:'utcmillisec'}, 22 | e: {format$:'re'}, 23 | f: {format$:['date', 'time']}, 24 | }) 25 | }) 26 | 27 | it('datetime', function() { 28 | pb.validate({},function(err,res){ 29 | assert.isNull(err) 30 | }) 31 | 32 | pb.validate({a:'2012-02-02T11:12:13Z'},function(err,res){ 33 | assert.isNull(err) 34 | }) 35 | 36 | pb.validate({a:'2012-02-02'},function(err,res){ 37 | assert.isNotNull(err) 38 | assert.equal(err.parambulator.code,'format$') 39 | }) 40 | }) 41 | 42 | it('date', function() { 43 | pb.validate({},function(err,res){ 44 | assert.isNull(err) 45 | }) 46 | 47 | pb.validate({b:'2012-02-02'},function(err,res){ 48 | assert.isNull(err) 49 | }) 50 | 51 | pb.validate({b:'2012-32-02'},function(err,res){ 52 | assert.isNotNull(err) 53 | assert.equal(err.parambulator.code,'format$') 54 | }) 55 | }) 56 | 57 | it('time', function() { 58 | pb.validate({},function(err,res){ 59 | assert.isNull(err) 60 | }) 61 | 62 | pb.validate({c:'11:12:13Z'},function(err,res){ 63 | assert.isNull(err) 64 | }) 65 | 66 | pb.validate({c:'51:12:13Z'},function(err,res){ 67 | assert.isNotNull(err) 68 | assert.equal(err.parambulator.code,'format$') 69 | }) 70 | }) 71 | 72 | it('utcmillisec', function() { 73 | pb.validate({},function(err,res){ 74 | assert.isNull(err) 75 | }) 76 | 77 | pb.validate({d:124578},function(err,res){ 78 | assert.isNull(err) 79 | }) 80 | 81 | pb.validate({d:'test'},function(err,res){ 82 | assert.isNotNull(err) 83 | assert.equal(err.parambulator.code,'format$') 84 | }) 85 | }) 86 | 87 | it('re', function() { 88 | pb.validate({},function(err,res){ 89 | assert.isNull(err) 90 | }) 91 | 92 | pb.validate({e:/([0-1]\d|2[0-4]):[0-5]\d:[0-5]\dZ/},function(err,res){ 93 | assert.isNull(err) 94 | }) 95 | 96 | pb.validate({e:124578},function(err,res){ 97 | assert.isNotNull(err) 98 | assert.equal(err.parambulator.code,'format$') 99 | }) 100 | }) 101 | 102 | 103 | it('date-time', function() { 104 | pb.validate({},function(err,res){ 105 | assert.isNull(err) 106 | }) 107 | 108 | pb.validate({f:'2012-02-02'},function(err,res){ 109 | assert.isNull(err) 110 | }) 111 | 112 | pb.validate({f:'2012-32-02'},function(err,res){ 113 | assert.isNotNull(err) 114 | assert.equal(err.parambulator.code,'format$') 115 | }) 116 | 117 | pb.validate({f:'11:12:13Z'},function(err,res){ 118 | assert.isNull(err) 119 | }) 120 | 121 | pb.validate({f:'51:12:13Z'},function(err,res){ 122 | assert.isNotNull(err) 123 | assert.equal(err.parambulator.code,'format$') 124 | }) 125 | 126 | pb.validate({f:124578},function(err,res){ 127 | assert.isNotNull(err) 128 | assert.equal(err.parambulator.code,'format$') 129 | }) 130 | 131 | pb.validate({f:/([0-1]\d|2[0-4]):[0-5]\d:[0-5]\dZ/},function(err,res){ 132 | assert.isNotNull(err) 133 | assert.equal(err.parambulator.code,'format$') 134 | }) 135 | 136 | pb.validate({f:124578},function(err,res){ 137 | assert.isNotNull(err) 138 | assert.equal(err.parambulator.code,'format$') 139 | }) 140 | 141 | pb.validate({f:'test'},function(err,res){ 142 | assert.isNotNull(err) 143 | assert.equal(err.parambulator.code,'format$') 144 | }) 145 | }) 146 | }) 147 | 148 | -------------------------------------------------------------------------------- /test/run-jasmine.js: -------------------------------------------------------------------------------- 1 | var system = require('system'); 2 | 3 | /** 4 | * Wait until the test condition is true or a timeout occurs. Useful for waiting 5 | * on a server response or for a ui change (fadeIn, etc.) to occur. 6 | * 7 | * @param testFx javascript condition that evaluates to a boolean, 8 | * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or 9 | * as a callback function. 10 | * @param onReady what to do when testFx condition is fulfilled, 11 | * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or 12 | * as a callback function. 13 | * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used. 14 | */ 15 | function waitFor(testFx, onReady, timeOutMillis) { 16 | var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timeout is 3s 17 | start = new Date().getTime(), 18 | condition = false, 19 | interval = setInterval(function() { 20 | if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) { 21 | // If not time-out yet and condition not yet fulfilled 22 | condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code 23 | } else { 24 | if(!condition) { 25 | // If condition still not fulfilled (timeout but condition is 'false') 26 | console.log("'waitFor()' timeout"); 27 | phantom.exit(1); 28 | } else { 29 | // Condition fulfilled (timeout and/or condition is 'true') 30 | console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms."); 31 | typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled 32 | clearInterval(interval); //< Stop this interval 33 | } 34 | } 35 | }, 100); //< repeat check every 100ms 36 | }; 37 | 38 | 39 | if (system.args.length !== 2) { 40 | console.log('Usage: run-jasmine.js URL'); 41 | phantom.exit(1); 42 | } 43 | 44 | var page = require('webpage').create(); 45 | 46 | // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") 47 | page.onConsoleMessage = function(msg) { 48 | console.log(msg); 49 | }; 50 | 51 | page.open(system.args[1], function(status){ 52 | if (status !== "success") { 53 | console.log("Unable to access network"); 54 | phantom.exit(); 55 | } else { 56 | waitFor(function(){ 57 | return page.evaluate(function(){ 58 | return document.body.querySelector('.symbolSummary .pending') === null 59 | }); 60 | }, function(){ 61 | var exitCode = page.evaluate(function(){ 62 | console.log(''); 63 | console.log(document.body.querySelector('.description').innerText); 64 | var list = document.body.querySelectorAll('.results > #details > .specDetail.failed'); 65 | if (list && list.length > 0) { 66 | console.log(''); 67 | console.log(list.length + ' test(s) FAILED:'); 68 | for (i = 0; i < list.length; ++i) { 69 | var el = list[i], 70 | desc = el.querySelector('.description'), 71 | msg = el.querySelector('.resultMessage.fail'); 72 | console.log(''); 73 | console.log(desc.innerText); 74 | console.log(msg.innerText); 75 | console.log(''); 76 | } 77 | return 1; 78 | } else { 79 | console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText); 80 | return 0; 81 | } 82 | }); 83 | phantom.exit(exitCode); 84 | }); 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /doc/examples/range.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Create a custom rule 4 | ==================== 5 | 6 | The parambulator function takes a second argument ('pref') that lets you define 7 | custom rules: 8 | 9 | var paramcheck = parambulator( {...}, { rules:{ mycustomrule$: function(){...} } } ) 10 | 11 | Here, a range$ rule is defined to check that property values fall 12 | within a given numerical range. A custom error message pattern is 13 | also defined - this is optional. And a validity check (also optional) 14 | is defined. The validity check lets you ensure that param specs 15 | specify the custom rule correctly. 16 | 17 | */ 18 | 19 | 20 | var _ = require('underscore') 21 | var parambulator = require('parambulator') 22 | 23 | 24 | // this is the pref argument, defined as a separate object so that it can be reused. 25 | var customrules = { 26 | 27 | // define the rules by name (the $ suffix is required) 28 | rules: { 29 | 30 | // each rule is a function that takes the current context (ctxt), and a callback (cb) 31 | range$: function(ctxt,cb) { 32 | 33 | // ctxt.rule provides you with the rule options as used in the param spec 34 | // in this case, rule.spec is an array with two elements, a min and max number 35 | var min = ctxt.rule.spec[0] 36 | var max = ctxt.rule.spec[1] 37 | 38 | // ctxt.point is the current value to be verified, and can be a primitive 39 | // value (e.g. a string), or a complex value (e.g. an object) 40 | var val = ctxt.point 41 | 42 | // if the value is a number ... (remember to check for NaN!) 43 | if( _.isNumber(val) && !_.isNaN(val) ) { 44 | 45 | // ... but the value is outside the range 46 | if( val < min || max < val ) { 47 | 48 | // generate an Error object, using the message pattern below 49 | return ctxt.util.fail(ctxt,cb) 50 | } 51 | } 52 | 53 | // if you get to here, the rule passes 54 | return cb() 55 | } 56 | }, 57 | 58 | // you can define custom error messages 59 | msgs: { 60 | 61 | // the template syntax is provided by the underscore library 62 | // see http://underscorejs.org/#template 63 | // available vars are value (the failing value), rule (.spec has the options), 64 | // and parentpath (dot-separated property path to the point) 65 | range$: 'The value <%=value%> is not within the range <%=rule.spec%> (property <%=parentpath%>).' 66 | }, 67 | 68 | // this is a param spec that is applied to param specs to ensure they are valid 69 | // use this to define the valid options for your new rule 70 | // in this case, range$ rule spec must be an array of length 2, containing only numbers 71 | // also, note that the prop$ rule is used, as a literal 'range$' would trigger a rule check 72 | // this is how you escape $ chars at the end of property names 73 | valid: { 74 | 75 | // can't use range$ directly, as that would trigger an actual range check! 76 | prop$:{ 77 | name:'range$', 78 | 79 | // these are the rules for range$ 80 | rules:{ 81 | 82 | // it must be an array - e.g. [10,20] 83 | type$:'array', 84 | 85 | // it must be an array of length 2 - [min, max] 86 | required$:['0','1'], 87 | 88 | // all property values must be numbers (array indexes are treated as properties: '0','1',...) 89 | '*':{type$:'number'} 90 | } 91 | } 92 | } 93 | } 94 | 95 | 96 | // create an instance to check the 'volume' parameter 97 | // http://www.youtube.com/watch?v=EbVKWCpNFhY 98 | var rangeparams = parambulator({ volume: {range$:[0,11]} }, customrules) 99 | 100 | 101 | // print out any errors 102 | function printresult(err,res) { 103 | if(err) console.log(err.message); 104 | } 105 | 106 | 107 | 108 | // this fails 109 | rangeparams.validate( { volume: -1 }, printresult ) 110 | // output: The value -1 is not within the range 0,11 (property volume). 111 | 112 | 113 | // these pass 114 | rangeparams.validate( { volume: 0 }, printresult ) 115 | rangeparams.validate( { volume: 5 }, printresult ) 116 | rangeparams.validate( { volume: 11 }, printresult ) 117 | 118 | 119 | // this fails 120 | rangeparams.validate( { volume: 12 }, printresult ) 121 | // output: The value 12 is not within the range 0,11 (property volume). 122 | 123 | 124 | 125 | // these all fail - range$ rule spec is invalid in each case 126 | // note the parent path - array indexes are treated the same as any other properties 127 | 128 | try { parambulator({ volume: {range$:1} }, customrules) } catch(e) { console.log(e.message) } 129 | // output: The value '1' is not of type 'array' (parent: volume.range$). 130 | 131 | try { parambulator({ volume: {range$:[]} }, customrules) } catch(e) { console.log(e.message) } 132 | // output: The property 0 is missing and is always required (parent: volume.range$). 133 | 134 | try { parambulator({ volume: {range$:['a',1]} }, customrules) } catch(e) { console.log(e.message) } 135 | // output: The value 'a' is not of type 'number' (parent: volume.range$.0). 136 | 137 | // the above tests are using parambulator to check it's own input parameters 138 | 139 | -------------------------------------------------------------------------------- /test/type.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | 9 | var parambulator = require('..') 10 | 11 | 12 | 13 | describe('type', function() { 14 | 15 | var pb = parambulator({ 16 | a: {type$:'string'}, 17 | b: {type$:'number'}, 18 | c: {type$:'integer'}, 19 | d: {type$:'boolean'}, 20 | e: {type$:'date'}, 21 | f: {type$:'array'}, 22 | g: {type$:'object'}, 23 | h: {type$:['date','string','array']}, 24 | i: {type$:['object']}, 25 | }) 26 | 27 | 28 | it('multi-types-1', function() { 29 | pb.validate({},function(err,res){ 30 | assert.isNull(err) 31 | }) 32 | 33 | pb.validate({h:'foo'},function(err,res){ 34 | assert.isNull(err) 35 | }) 36 | 37 | pb.validate({h:new Date()},function(err,res){ 38 | assert.isNull(err) 39 | }) 40 | 41 | pb.validate({h:[1, 2, '3']},function(err,res){ 42 | assert.isNull(err) 43 | }) 44 | 45 | pb.validate({h:11.1},function(err,res){ 46 | assert.isNotNull(err) 47 | assert.equal(err.parambulator.code,'type$') 48 | }) 49 | 50 | pb.validate({h:1},function(err,res){ 51 | assert.isNotNull(err) 52 | assert.equal(err.parambulator.code,'type$') 53 | }) 54 | 55 | pb.validate({h:true},function(err,res){ 56 | assert.isNotNull(err) 57 | assert.equal(err.parambulator.code,'type$') 58 | }) 59 | 60 | pb.validate({h:{a:1}},function(err,res){ 61 | assert.isNotNull(err) 62 | assert.equal(err.parambulator.code,'type$') 63 | }) 64 | }) 65 | 66 | it('multi-types-2', function() { 67 | pb.validate({},function(err,res){ 68 | assert.isNull(err) 69 | }) 70 | 71 | pb.validate({i:'foo'},function(err,res){ 72 | assert.isNotNull(err) 73 | assert.equal(err.parambulator.code,'type$') 74 | }) 75 | 76 | pb.validate({i:new Date()},function(err,res){ 77 | assert.isNotNull(err) 78 | assert.equal(err.parambulator.code,'type$') 79 | }) 80 | 81 | pb.validate({i:[1, 2, '3']},function(err,res){ 82 | assert.isNotNull(err) 83 | assert.equal(err.parambulator.code,'type$') 84 | }) 85 | 86 | pb.validate({i:11.1},function(err,res){ 87 | assert.isNotNull(err) 88 | assert.equal(err.parambulator.code,'type$') 89 | }) 90 | 91 | pb.validate({i:1},function(err,res){ 92 | assert.isNotNull(err) 93 | assert.equal(err.parambulator.code,'type$') 94 | }) 95 | 96 | pb.validate({i:true},function(err,res){ 97 | assert.isNotNull(err) 98 | assert.equal(err.parambulator.code,'type$') 99 | }) 100 | 101 | pb.validate({i:{a:1}},function(err,res){ 102 | assert.isNull(err) 103 | }) 104 | }) 105 | 106 | 107 | 108 | it('string', function() { 109 | pb.validate({},function(err,res){ 110 | assert.isNull(err) 111 | }) 112 | 113 | pb.validate({a:'foo'},function(err,res){ 114 | assert.isNull(err) 115 | }) 116 | 117 | pb.validate({a:1},function(err,res){ 118 | assert.isNotNull(err) 119 | assert.equal(err.parambulator.code,'type$') 120 | }) 121 | }) 122 | 123 | 124 | 125 | it('number', function() { 126 | pb.validate({b:1.1},function(err,res){ 127 | assert.isNull(err) 128 | }) 129 | 130 | pb.validate({b:'foo'},function(err,res){ 131 | assert.isNotNull(err) 132 | assert.equal(err.parambulator.code,'type$') 133 | }) 134 | }) 135 | 136 | 137 | it('integer', function() { 138 | pb.validate({c:1},function(err,res){ 139 | assert.isNull(err) 140 | }) 141 | 142 | pb.validate({c:1.1},function(err,res){ 143 | assert.isNotNull(err) 144 | assert.equal(err.parambulator.code,'type$') 145 | }) 146 | }) 147 | 148 | 149 | it('boolean', function() { 150 | pb.validate({d:true},function(err,res){ 151 | assert.isNull(err) 152 | }) 153 | 154 | pb.validate({d:false},function(err,res){ 155 | assert.isNull(err) 156 | }) 157 | 158 | pb.validate({d:'foo'},function(err,res){ 159 | assert.isNotNull(err) 160 | assert.equal(err.parambulator.code,'type$') 161 | }) 162 | }) 163 | 164 | 165 | it('date', function() { 166 | pb.validate({e:new Date()},function(err,res){ 167 | assert.isNull(err) 168 | }) 169 | 170 | pb.validate({e:'foo'},function(err,res){ 171 | assert.isNotNull(err) 172 | assert.equal(err.parambulator.code,'type$') 173 | }) 174 | 175 | pb.validate({e:{a:1}},function(err,res){ 176 | assert.isNotNull(err) 177 | assert.equal(err.parambulator.code,'type$') 178 | }) 179 | }) 180 | 181 | 182 | it('array', function() { 183 | pb.validate({f:[]},function(err,res){ 184 | assert.isNull(err) 185 | }) 186 | 187 | pb.validate({f:[11]},function(err,res){ 188 | assert.isNull(err) 189 | }) 190 | 191 | pb.validate({f:'foo'},function(err,res){ 192 | assert.isNotNull(err) 193 | assert.equal(err.parambulator.code,'type$') 194 | }) 195 | 196 | pb.validate({f:{a:1}},function(err,res){ 197 | assert.isNotNull(err) 198 | assert.equal(err.parambulator.code,'type$') 199 | }) 200 | }) 201 | 202 | 203 | it('object', function() { 204 | pb.validate({g:null},function(err,res){ 205 | assert.isNull(err) 206 | }) 207 | 208 | pb.validate({g:{}},function(err,res){ 209 | assert.isNull(err) 210 | }) 211 | 212 | pb.validate({g:{a:1}},function(err,res){ 213 | assert.isNull(err) 214 | }) 215 | 216 | pb.validate({g:new Date()},function(err,res){ 217 | assert.isNotNull(err) 218 | assert.equal(err.parambulator.code,'type$') 219 | }) 220 | 221 | pb.validate({g:[]},function(err,res){ 222 | assert.isNotNull(err) 223 | assert.equal(err.parambulator.code,'type$') 224 | }) 225 | }) 226 | }) 227 | -------------------------------------------------------------------------------- /test/jasmine-1.3.1/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 24 | #HTMLReporter .runningAlert { background-color: #666666; } 25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 28 | #HTMLReporter .passingAlert { background-color: #a6b779; } 29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 30 | #HTMLReporter .failingAlert { background-color: #cf867e; } 31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 32 | #HTMLReporter .results { margin-top: 14px; } 33 | #HTMLReporter #details { display: none; } 34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | #HTMLReporter.showDetails .summary { display: none; } 39 | #HTMLReporter.showDetails #details { display: block; } 40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | #HTMLReporter .summary { margin-top: 14px; } 42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 45 | #HTMLReporter .description + .suite { margin-top: 0; } 46 | #HTMLReporter .suite { margin-top: 14px; } 47 | #HTMLReporter .suite a { color: #333333; } 48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 51 | #HTMLReporter .resultMessage span.result { display: block; } 52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 53 | 54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 61 | #TrivialReporter .runner.running { background-color: yellow; } 62 | #TrivialReporter .options { text-align: right; font-size: .8em; } 63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 64 | #TrivialReporter .suite .suite { margin: 5px; } 65 | #TrivialReporter .suite.passed { background-color: #dfd; } 66 | #TrivialReporter .suite.failed { background-color: #fdd; } 67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 71 | #TrivialReporter .spec.skipped { background-color: #bbb; } 72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 73 | #TrivialReporter .passed { background-color: #cfc; display: none; } 74 | #TrivialReporter .failed { background-color: #fbb; } 75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 77 | #TrivialReporter .resultMessage .mismatch { color: black; } 78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 83 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | 9 | var parambulator = require('..') 10 | 11 | 12 | describe('basic', function() { 13 | 14 | var pb 15 | 16 | 17 | 18 | it('happy', function() { 19 | pb = parambulator({ 20 | atmostone$: ['path','from'], 21 | 22 | search: { 23 | required$: ['find','replace'] 24 | }, 25 | 26 | exactlyone$: ['red','blue'], 27 | atleastone$: ['a','b'], 28 | 29 | sub: { 30 | dub: { 31 | exactlyone$: ['x','y','z'], 32 | } 33 | }, 34 | 35 | required$: ['foo','bar'], 36 | notempty$: ['z'], 37 | }) 38 | 39 | 40 | }) 41 | 42 | 43 | 44 | it('required$', function() { 45 | pb.validate({a:1,z:1,red:1,foo:1,bar:1},function(err,res){ 46 | assert.isNull(err) 47 | }) 48 | 49 | pb.validate({a:1,z:1,red:1,foo:1},function(err,res){ 50 | assert.isNotNull(err) 51 | assert.equal('required$',err.parambulator.code) 52 | }) 53 | 54 | pb.validate({a:1,z:1,red:1,bar:1},function(err,res){ 55 | assert.isNotNull(err) 56 | assert.equal('required$',err.parambulator.code) 57 | }) 58 | 59 | pb.validate({a:1,z:1,red:1,},function(err,res){ 60 | //console.log(err) 61 | assert.isNotNull(err) 62 | assert.equal('required$',err.parambulator.code) 63 | }) 64 | }) 65 | 66 | 67 | it('exactlyone$', function() { 68 | pb.validate({a:1,z:1,red:1, foo:1,bar:1},function(err,res){ 69 | assert.isNull(err) 70 | }) 71 | 72 | pb.validate({a:1,z:1,blue:1, foo:1,bar:1},function(err,res){ 73 | assert.isNull(err) 74 | }) 75 | 76 | pb.validate({a:1,z:1,foo:1,bar:1},function(err,res){ 77 | //console.log(err) 78 | assert.isNotNull(err) 79 | assert.equal('exactlyone$',err.parambulator.code) 80 | }) 81 | 82 | pb.validate({a:1,z:1,red:1,blue:1, foo:1,bar:1},function(err,res){ 83 | assert.isNotNull(err) 84 | assert.equal('exactlyone$',err.parambulator.code) 85 | }) 86 | }) 87 | 88 | 89 | it('atmostone$', function() { 90 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, from:1},function(err,res){ 91 | assert.isNull(err) 92 | }) 93 | 94 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, path:1},function(err,res){ 95 | assert.isNull(err) 96 | }) 97 | 98 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, path:1,from:1},function(err,res){ 99 | //console.log(err) 100 | assert.isNotNull(err) 101 | assert.equal('atmostone$',err.parambulator.code) 102 | }) 103 | }) 104 | 105 | 106 | it('atleastone$', function() { 107 | pb.validate({z:1, red:1,foo:1,bar:1,from:1, a:1},function(err,res){ 108 | assert.isNull(err) 109 | }) 110 | 111 | pb.validate({z:1, red:1,foo:1,bar:1,from:1, b:1},function(err,res){ 112 | assert.isNull(err) 113 | }) 114 | 115 | pb.validate({red:1,foo:1,bar:1,from:1, a:1,z:1,b:1},function(err,res){ 116 | assert.isNull(err) 117 | }) 118 | 119 | pb.validate({z:1, red:1,foo:1,bar:1,from:1 },function(err,res){ 120 | //console.log(err) 121 | assert.isNotNull(err) 122 | assert.equal('atleastone$',err.parambulator.code) 123 | }) 124 | }) 125 | 126 | 127 | it('search', function() { 128 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, search:{find:1,replace:1}},function(err,res){ 129 | assert.isNull(err) 130 | }) 131 | 132 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, search:{find:1}},function(err,res){ 133 | assert.isNotNull(err) 134 | assert.equal('required$',err.parambulator.code) 135 | }) 136 | 137 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, search:{replace:1}},function(err,res){ 138 | assert.isNotNull(err) 139 | assert.equal('required$',err.parambulator.code) 140 | }) 141 | }) 142 | 143 | 144 | 145 | it('sublevels', function() { 146 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, sub:{dub:{x:1}}},function(err,res){ 147 | assert.isNull(err) 148 | }) 149 | 150 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, sub:{dub:{y:1}}},function(err,res){ 151 | assert.isNull(err) 152 | }) 153 | 154 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, sub:{dub:{z:1}}},function(err,res){ 155 | assert.isNull(err) 156 | }) 157 | 158 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, sub:{dub:{}}},function(err,res){ 159 | assert.isNotNull(err) 160 | assert.equal('exactlyone$',err.parambulator.code) 161 | }) 162 | 163 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, sub:{dub:{x:1,y:1}}},function(err,res){ 164 | assert.isNotNull(err) 165 | assert.equal('exactlyone$',err.parambulator.code) 166 | }) 167 | 168 | pb.validate({a:1,z:1,red:1,foo:1,bar:1, sub:{dub:{x:1,y:1,z:1}}},function(err,res){ 169 | assert.isNotNull(err) 170 | assert.equal('exactlyone$',err.parambulator.code) 171 | }) 172 | }) 173 | 174 | 175 | it('notempty$', function() { 176 | pb.validate({a:1,z:'',red:1,foo:1,bar:1},function(err,res){ 177 | //console.log(err) 178 | assert.isNotNull(err) 179 | assert.equal('notempty$',err.parambulator.code) 180 | }) 181 | 182 | pb.validate({a:1,z:null,red:1,foo:1,bar:1},function(err,res){ 183 | assert.isNotNull(err) 184 | assert.equal('notempty$',err.parambulator.code) 185 | }) 186 | 187 | pb.validate({a:1,z:undefined,red:1,foo:1,bar:1},function(err,res){ 188 | assert.isNotNull(err) 189 | assert.equal('notempty$',err.parambulator.code) 190 | }) 191 | }) 192 | 193 | 194 | it('pbeasy', function() { 195 | var pbeasy_one = parambulator({one:{string$:true,required$:true}}) 196 | 197 | pbeasy_one.validate({one:'a'},function(err,res){ 198 | assert.isNull(err) 199 | }) 200 | 201 | pbeasy_one.validate({},function(err,res){ 202 | assert.isNotNull(err) 203 | assert.equal('required$',err.parambulator.code) 204 | }) 205 | 206 | 207 | var pbeasy_deep = parambulator({one:{required$:true,two:{string$:true,required$:true}}}) 208 | 209 | pbeasy_deep.validate({one:{two:'a'}},function(err,res){ 210 | assert.isNull(err) 211 | }) 212 | 213 | pbeasy_deep.validate({one:{}},function(err,res){ 214 | assert.isNotNull(err) 215 | assert.equal('required$',err.parambulator.code) 216 | }) 217 | 218 | 219 | var pbeasy_two = parambulator({a:1,b:'q',c:{required$:true}}) 220 | 221 | pbeasy_two.validate({a:1,b:'q',c:'w'},function(err,res){ 222 | assert.isNull(err) 223 | }) 224 | 225 | pbeasy_two.validate({a:1,b:'q'},function(err,res){ 226 | //console.log(err) 227 | assert.isNotNull(err) 228 | assert.equal('required$',err.parambulator.code) 229 | }) 230 | 231 | 232 | }) 233 | 234 | }) 235 | 236 | 237 | -------------------------------------------------------------------------------- /doc/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /test/parambulator-multi.spec.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014-2015 Richard Rodger, MIT License */ 2 | "use strict"; 3 | 4 | 5 | if( 'undefined' === typeof parambulator ) { 6 | var parambulator = require('..') 7 | } 8 | 9 | 10 | if( 'undefined' === typeof _ ) { 11 | var _ = require('lodash') 12 | } 13 | 14 | 15 | function s(obj){ 16 | return JSON.stringify(obj) 17 | } 18 | 19 | 20 | var assert = { 21 | isNull: function(x){ 22 | expect(x).toBe(null) 23 | }, 24 | isNotNull: function(x){ 25 | expect(x).toNotBe(null) 26 | }, 27 | equal: function(x,y){ 28 | expect(x).toBe(y) 29 | }, 30 | isTrue: function(x){ 31 | expect(!!x).toBe(true) 32 | }, 33 | ok: function(x){ 34 | expect(!!x).toBe(true) 35 | }, 36 | } 37 | 38 | 39 | describe('parambulator-multi', function() { 40 | 41 | var pb_default = parambulator({ 42 | a: {default$:123, type$:'number'}, 43 | b: { 44 | firstobj: {default$:23, type$:'number'}, 45 | secondobj: {innerobj: {default$:'test'}}, 46 | thirdobj: {type$:'array', __0: {default$:123}}, 47 | }, 48 | c: {default$:555, type$:'number'}, 49 | d: {type$: 'array', __0: {default$:'arraytest0'}, __1: {default$:'arraytest1'}}, 50 | e: {type$: 'array', default$:[]}, 51 | 52 | // TODO: handle this case 53 | //f: {default$:'aa', type$:'number'} 54 | }, {multiErrors: true}) 55 | 56 | 57 | it('default-firsttest', function() { 58 | var obj = {c: 2222} 59 | pb_default.validate(obj) 60 | 61 | assert.isTrue(_.has(obj, 'a')) 62 | assert.equal(obj['a'], 123) 63 | 64 | assert.isTrue(_.has(obj, 'b')) 65 | assert.isTrue(_.has(obj['b'], 'firstobj')) 66 | assert.equal(obj['b']['firstobj'], 23) 67 | 68 | assert.isTrue(_.has(obj['b'], 'secondobj')) 69 | assert.isTrue(_.has(obj['b']['secondobj'], 'innerobj')) 70 | assert.equal(obj['b']['secondobj']['innerobj'], 'test') 71 | 72 | assert.isTrue(_.has(obj['b'], 'thirdobj')) 73 | assert.isTrue(_.isArray(obj['b']['thirdobj'])) 74 | assert.equal(obj['b']['thirdobj'][0], 123) 75 | 76 | assert.isTrue(_.has(obj, 'c')) 77 | assert.equal(obj['c'], 2222) 78 | 79 | assert.isTrue(_.has(obj, 'd')) 80 | assert.isTrue(_.isArray(obj['d'])) 81 | assert.equal('arraytest0', obj['d'][0]) 82 | assert.equal('arraytest1', obj['d'][1]) 83 | 84 | assert.isTrue(_.has(obj, 'e')) 85 | assert.isTrue(_.isArray(obj['e'])) 86 | assert.equal(0, obj['e'].length) 87 | }) 88 | 89 | 90 | var pb_format= parambulator({ 91 | a: {format$:'datetime'}, 92 | b: {format$:'date'}, 93 | c: {format$:'time'}, 94 | d: {format$:'utcmillisec'}, 95 | e: {format$:'re'}, 96 | f: {format$:['date', 'time']}, 97 | }, { 98 | multiErrors: true 99 | }) 100 | 101 | 102 | it('format-datetime', function() { 103 | var validationsCount = 0 104 | 105 | pb_format.validate({},function(err,res){ 106 | assert.isNull(err) 107 | validationsCount++ 108 | }) 109 | 110 | pb_format.validate({a:'2012-02-02T11:12:13Z'},function(err,res){ 111 | assert.isNull(err) 112 | validationsCount++ 113 | }) 114 | 115 | pb_format.validate({a:'2012-02-02'},function(err,res){ 116 | assert.isNotNull(err) 117 | assert.equal(err.length, 1) 118 | assert.equal(err[0].parambulator.code,'format$') 119 | validationsCount++ 120 | }) 121 | 122 | // if there is a bug in parambulator where the callback is never called 123 | // tests will succeed without going through the asserts 124 | assert.equal(validationsCount, 3) 125 | }) 126 | 127 | it('format-date', function() { 128 | pb_format.validate({},function(err,res){ 129 | assert.isNull(err) 130 | }) 131 | 132 | pb_format.validate({b:'2012-02-02'},function(err,res){ 133 | assert.isNull(err) 134 | }) 135 | 136 | pb_format.validate({b:'2012-32-02'},function(err,res){ 137 | assert.isNotNull(err) 138 | assert.equal(err.length, 1) 139 | assert.equal(err[0].parambulator.code,'format$') 140 | }) 141 | }) 142 | 143 | it('format-time', function() { 144 | pb_format.validate({},function(err,res){ 145 | assert.isNull(err) 146 | }) 147 | 148 | pb_format.validate({c:'11:12:13Z'},function(err,res){ 149 | assert.isNull(err) 150 | }) 151 | 152 | pb_format.validate({c:'51:12:13Z'},function(err,res){ 153 | assert.isNotNull(err) 154 | assert.equal(err.length, 1) 155 | assert.equal(err[0].parambulator.code,'format$') 156 | }) 157 | }) 158 | 159 | it('format-utcmillisec', function() { 160 | pb_format.validate({},function(err,res){ 161 | assert.isNull(err) 162 | }) 163 | 164 | pb_format.validate({d:124578},function(err,res){ 165 | assert.isNull(err) 166 | }) 167 | 168 | pb_format.validate({d:'test'},function(err,res){ 169 | assert.isNotNull(err) 170 | assert.equal(err.length, 1) 171 | assert.equal(err[0].parambulator.code,'format$') 172 | }) 173 | }) 174 | 175 | it('format-re', function() { 176 | pb_format.validate({},function(err,res){ 177 | assert.isNull(err) 178 | }) 179 | 180 | pb_format.validate({e:/([0-1]\d|2[0-4]):[0-5]\d:[0-5]\dZ/},function(err,res){ 181 | assert.isNull(err) 182 | }) 183 | 184 | pb_format.validate({e:124578},function(err,res){ 185 | assert.isNotNull(err) 186 | assert.equal(err.length, 1) 187 | assert.equal(err[0].parambulator.code,'format$') 188 | }) 189 | }) 190 | 191 | 192 | it('format-date-time', function() { 193 | pb_format.validate({},function(err,res){ 194 | assert.isNull(err) 195 | }) 196 | 197 | pb_format.validate({f:'2012-02-02'},function(err,res){ 198 | assert.isNull(err) 199 | }) 200 | 201 | pb_format.validate({f:'2012-32-02'},function(err,res){ 202 | assert.isNotNull(err) 203 | assert.equal(err.length, 1) 204 | assert.equal(err[0].parambulator.code,'format$') 205 | }) 206 | 207 | pb_format.validate({f:'11:12:13Z'},function(err,res){ 208 | assert.isNull(err) 209 | }) 210 | 211 | pb_format.validate({f:'51:12:13Z'},function(err,res){ 212 | assert.isNotNull(err) 213 | assert.equal(err.length, 1) 214 | assert.equal(err[0].parambulator.code,'format$') 215 | }) 216 | 217 | pb_format.validate({f:124578},function(err,res){ 218 | assert.isNotNull(err) 219 | assert.equal(err.length, 1) 220 | assert.equal(err[0].parambulator.code,'format$') 221 | }) 222 | 223 | pb_format.validate({f:/([0-1]\d|2[0-4]):[0-5]\d:[0-5]\dZ/},function(err,res){ 224 | assert.isNotNull(err) 225 | assert.equal(err.length, 1) 226 | assert.equal(err[0].parambulator.code,'format$') 227 | }) 228 | 229 | pb_format.validate({f:124578},function(err,res){ 230 | assert.isNotNull(err) 231 | assert.equal(err.length, 1) 232 | assert.equal(err[0].parambulator.code,'format$') 233 | }) 234 | 235 | pb_format.validate({f:'test'},function(err,res){ 236 | assert.isNotNull(err) 237 | assert.equal(err.length, 1) 238 | assert.equal(err[0].parambulator.code,'format$') 239 | }) 240 | }) 241 | 242 | it('format-all', function(done) { 243 | var ent = { 244 | a:'2012-02-02', 245 | b:'2012-32-02', 246 | c:'51:12:13Z', 247 | d:'test', 248 | e:124578, 249 | f:'51:12:13Z' 250 | } 251 | pb_format.validate(ent,function(err,res) { 252 | assert.isNotNull(err) 253 | assert.equal(err.length, 6) 254 | var i = 0 255 | _.each(ent, function(val, prop) { 256 | assert.equal(err[i].parambulator.property, prop) 257 | assert.equal(err[i].parambulator.code, 'format$') 258 | i++ 259 | }) 260 | done() 261 | }) 262 | }) 263 | 264 | var pb_multi_require = parambulator({ 265 | required$:['a', 'b'], 266 | c: 'required$' 267 | }, { 268 | multiErrors: true 269 | }) 270 | 271 | it('required all missing', function(done) { 272 | var props = ['a', 'b', 'c'] 273 | var ent = {} 274 | pb_multi_require.validate(ent,function(err,res) { 275 | assert.isNotNull(err) 276 | assert.equal(err.length, 3) 277 | var i = 0 278 | _.each(props, function(prop) { 279 | assert.equal(err[i].parambulator.property, prop) 280 | assert.equal(err[i].parambulator.code, 'required$') 281 | i++ 282 | }) 283 | done() 284 | }) 285 | }) 286 | 287 | it('required partially missing', function(done) { 288 | var props = ['b', 'c'] 289 | var ent = {a: 1} 290 | pb_multi_require.validate(ent,function(err,res) { 291 | assert.isNotNull(err) 292 | assert.equal(err.length, 2) 293 | var i = 0 294 | _.each(props, function(prop) { 295 | assert.equal(err[i].parambulator.property, prop) 296 | assert.equal(err[i].parambulator.code, 'required$') 297 | i++ 298 | }) 299 | done() 300 | }) 301 | }) 302 | 303 | it('required partially missing', function(done) { 304 | var props = ['a', 'b', 'c'] 305 | var ent = {a: 1, b: 2} 306 | pb_multi_require.validate(ent,function(err, res) { 307 | assert.isNotNull(err) 308 | assert.equal(err.length, 1) 309 | assert.equal(err[0].parambulator.property, 'c') 310 | assert.equal(err[0].parambulator.code, 'required$') 311 | done() 312 | }) 313 | }) 314 | 315 | 316 | }) 317 | -------------------------------------------------------------------------------- /test/value.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2013 Richard Rodger */ 2 | 3 | "use strict"; 4 | 5 | 6 | var assert = require('chai').assert 7 | var gex = require('gex') 8 | 9 | var parambulator = require('..') 10 | 11 | 12 | 13 | describe('value', function() { 14 | 15 | var pb = parambulator({ 16 | a: 'a', 17 | b: 'b*', 18 | c: {wild$:'*c'}, 19 | d: {eq$:'d*'}, 20 | e: {re$:'e[z]'}, 21 | f: {re$:'/f[z]/i'}, 22 | g: {enum$:['A','B','C']}, 23 | h: {minlen$:2}, 24 | i: {maxlen$:6}, 25 | j: {lt$: 2}, 26 | k: {lt$: new Date("2012-09-04")}, 27 | l: {lte$: 2}, 28 | m: {lte$: new Date("2012-09-04")}, 29 | n: {gt$: 2}, 30 | o: {gt$: new Date("2012-09-04")}, 31 | p: {gte$: 2}, 32 | q: {gte$: new Date("2012-09-04")}, 33 | r: {min$: 2}, 34 | s: {min$: new Date("2012-09-04")}, 35 | t: {max$: 2}, 36 | u: {max$: new Date("2012-09-04")}, 37 | v: {uniq$: [true]}, 38 | wild$:'top*', // does nothing 39 | }) 40 | 41 | 42 | it('wild$', function() { 43 | 44 | pb.validate({},function(err,res){ 45 | assert.isNull(err) 46 | }) 47 | 48 | pb.validate({a:'a'},function(err,res){ 49 | assert.isNull(err) 50 | }) 51 | 52 | pb.validate({b:'bx'},function(err,res){ 53 | assert.isNull(err) 54 | }) 55 | 56 | pb.validate({c:'xc'},function(err,res){ 57 | assert.isNull(err) 58 | }) 59 | 60 | pb.validate({a:'b'},function(err,res){ 61 | //console.log(err) 62 | assert.isNotNull(err) 63 | assert.equal(err.parambulator.code,'wild$') 64 | }) 65 | 66 | }) 67 | 68 | 69 | it('eq$', function() { 70 | pb.validate({d:'d*'},function(err,res){ 71 | assert.isNull(err) 72 | }) 73 | 74 | pb.validate({d:'dx'},function(err,res){ 75 | //console.log(err) 76 | assert.isNotNull(err) 77 | assert.equal(err.parambulator.code,'eq$') 78 | }) 79 | }) 80 | 81 | 82 | it('minlen$', function() { 83 | // test for string values 84 | pb.validate({h:'abcde'},function(err,res){ 85 | assert.isNull(err) 86 | }) 87 | 88 | pb.validate({h:'a'},function(err,res){ 89 | assert.isNotNull(err) 90 | assert.equal(err.parambulator.code,'minlen$') 91 | }) 92 | 93 | // test arrays 94 | pb.validate({h:[1,2,3,4]},function(err,res){ 95 | assert.isNull(err) 96 | }) 97 | 98 | pb.validate({h:[1]},function(err,res){ 99 | assert.isNotNull(err) 100 | assert.equal(err.parambulator.code,'minlen$') 101 | }) 102 | 103 | 104 | // test objects 105 | pb.validate({h:{1:1, 2:2, 3:3, 4:4}},function(err,res){ 106 | assert.isNull(err) 107 | }) 108 | 109 | pb.validate({h:{1:1}},function(err,res){ 110 | assert.isNotNull(err) 111 | assert.equal(err.parambulator.code,'minlen$') 112 | }) 113 | }) 114 | 115 | 116 | it('maxlen$', function() { 117 | // test string values 118 | pb.validate({i:'abcde'},function(err,res){ 119 | assert.isNull(err) 120 | }) 121 | 122 | pb.validate({i:'abcdefgh'},function(err,res){ 123 | assert.isNotNull(err) 124 | assert.equal(err.parambulator.code,'maxlen$') 125 | }) 126 | 127 | // test arrays 128 | pb.validate({i:[1,2,3,4,5]},function(err,res){ 129 | assert.isNull(err) 130 | }) 131 | 132 | pb.validate({i:[1,2,3,4,5,6,7]},function(err,res){ 133 | assert.isNotNull(err) 134 | assert.equal(err.parambulator.code,'maxlen$') 135 | }) 136 | 137 | // test objects 138 | pb.validate({i:{1:1, 2:2, 3:3, 4:4, 5:5}},function(err,res){ 139 | assert.isNull(err) 140 | }) 141 | 142 | pb.validate({i:{1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7}},function(err,res){ 143 | assert.isNotNull(err) 144 | assert.equal(err.parambulator.code,'maxlen$') 145 | }) 146 | 147 | }) 148 | 149 | 150 | it('lt$', function() { 151 | pb.validate({j: 1},function(err,lt){ 152 | assert.isNull(err) 153 | }) 154 | 155 | pb.validate({j: 3}, function(err,res){ 156 | assert.isNotNull(err) 157 | assert.equal(err.parambulator.code, 'lt$') 158 | }) 159 | 160 | pb.validate({k: new Date("2012-09-03")}, function(err,res) { 161 | assert.isNull(err) 162 | }) 163 | 164 | pb.validate({k: new Date("2012-09-04")}, function(err,res) { 165 | assert.isNotNull(err) 166 | assert.equal(err.parambulator.code, 'lt$') 167 | }) 168 | }) 169 | 170 | 171 | it('lte$', function() { 172 | pb.validate({l: 1},function(err,lt){ 173 | assert.isNull(err) 174 | }) 175 | 176 | pb.validate({l: 2},function(err,lt){ 177 | assert.isNull(err) 178 | }) 179 | 180 | pb.validate({l: 3}, function(err,res){ 181 | assert.isNotNull(err) 182 | assert.equal(err.parambulator.code, 'lte$') 183 | }) 184 | 185 | pb.validate({m: new Date("2012-09-04")}, function(err,res) { 186 | assert.isNull(err) 187 | }) 188 | 189 | pb.validate({m: new Date("2012-09-05")}, function(err,res) { 190 | assert.isNotNull(err) 191 | assert.equal(err.parambulator.code, 'lte$') 192 | }) 193 | }) 194 | 195 | 196 | it('gt$', function() { 197 | pb.validate({n: 3},function(err,lt){ 198 | assert.isNull(err) 199 | }) 200 | 201 | pb.validate({n: 2}, function(err,res){ 202 | assert.isNotNull(err) 203 | assert.equal(err.parambulator.code, 'gt$') 204 | }) 205 | 206 | pb.validate({o: new Date("2012-09-05")}, function(err,res) { 207 | assert.isNull(err) 208 | }) 209 | 210 | pb.validate({o: new Date("2012-09-04")}, function(err,res) { 211 | assert.isNotNull(err) 212 | assert.equal(err.parambulator.code, 'gt$') 213 | }) 214 | }) 215 | 216 | 217 | it('gte$', function() { 218 | pb.validate({p: 2},function(err,lt){ 219 | assert.isNull(err) 220 | }) 221 | 222 | pb.validate({p: 3},function(err,lt){ 223 | assert.isNull(err) 224 | }) 225 | 226 | pb.validate({p: 1}, function(err,res){ 227 | assert.isNotNull(err) 228 | assert.equal(err.parambulator.code, 'gte$') 229 | }) 230 | 231 | pb.validate({q: new Date("2012-09-04")},function(err,res) { 232 | assert.isNull(err) 233 | }) 234 | 235 | pb.validate({q: new Date("2012-09-03")},function(err,res) { 236 | assert.isNotNull(err) 237 | assert.equal(err.parambulator.code, 'gte$') 238 | }) 239 | }) 240 | 241 | 242 | it('min$', function() { 243 | pb.validate({r: 2},function(err,lt){ 244 | assert.isNull(err) 245 | }) 246 | 247 | pb.validate({r: 3},function(err,lt){ 248 | assert.isNull(err) 249 | }) 250 | 251 | pb.validate({r: 1}, function(err,res){ 252 | assert.isNotNull(err) 253 | assert.equal(err.parambulator.code, 'min$') 254 | }) 255 | 256 | pb.validate({s: new Date("2012-09-04")},function(err,res) { 257 | assert.isNull(err) 258 | }) 259 | 260 | pb.validate({s: new Date("2012-09-03")},function(err,res) { 261 | assert.isNotNull(err) 262 | assert.equal(err.parambulator.code, 'min$') 263 | }) 264 | }) 265 | 266 | 267 | it('max$', function() { 268 | pb.validate({t: 1},function(err,lt){ 269 | assert.isNull(err) 270 | }) 271 | 272 | pb.validate({t: 2},function(err,lt){ 273 | assert.isNull(err) 274 | }) 275 | 276 | pb.validate({t: 3},function(err,res){ 277 | assert.isNotNull(err) 278 | assert.equal(err.parambulator.code, 'max$') 279 | }) 280 | 281 | pb.validate({u: new Date("2012-09-04")},function(err,res){ 282 | assert.isNull(err) 283 | }) 284 | 285 | pb.validate({u: new Date("2012-09-05")},function(err,res){ 286 | assert.isNotNull(err) 287 | assert.equal(err.parambulator.code, 'max$') 288 | }) 289 | }) 290 | 291 | 292 | it('uniq$', function() { 293 | pb.validate({v: [1,2,3]},function(err,res){ 294 | assert.isNull(err) 295 | }) 296 | 297 | pb.validate({v: [1,2,3,1]},function(err,res){ 298 | assert.isNotNull(err) 299 | assert.equal(err.parambulator.code, 'uniq$') 300 | }) 301 | }) 302 | 303 | 304 | it('re$', function() { 305 | pb.validate({e:'ez'},function(err,res){ 306 | assert.isNull(err) 307 | }) 308 | 309 | pb.validate({e:'ex'},function(err,res){ 310 | //console.log(err) 311 | assert.isNotNull(err) 312 | assert.equal(err.parambulator.code,'re$') 313 | }) 314 | 315 | pb.validate({f:'FZ'},function(err,res){ 316 | assert.isNull(err) 317 | }) 318 | 319 | pb.validate({f:'fx'},function(err,res){ 320 | assert.isNotNull(err) 321 | assert.equal(err.parambulator.code,'re$') 322 | }) 323 | }) 324 | 325 | 326 | it('enum$', function() { 327 | pb.validate({g:'A'},function(err,res){ 328 | assert.isNull(err) 329 | }) 330 | 331 | pb.validate({g:'X'},function(err,res){ 332 | //console.log(err) 333 | assert.isNotNull(err) 334 | assert.equal(err.parambulator.code,'enum$') 335 | }) 336 | }) 337 | 338 | 339 | it('toplevel', function() { 340 | 341 | var pb = parambulator({ 342 | type$:'object' 343 | }) 344 | 345 | pb.validate({},function(err){ 346 | assert.isNull(err) 347 | }) 348 | 349 | pb.validate("foo",function(err){ 350 | assert.isNotNull(err) 351 | }) 352 | }) 353 | 354 | }) 355 | 356 | 357 | 358 | -------------------------------------------------------------------------------- /doc/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'roboto-black'; 25 | src: url('public/fonts/roboto-black.eot'); 26 | src: url('public/fonts/roboto-black.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/roboto-black.woff') format('woff'), 28 | url('public/fonts/roboto-black.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p { 55 | margin: 15px 0 0px; 56 | } 57 | .annotation ul, .annotation ol { 58 | margin: 25px 0; 59 | } 60 | .annotation ul li, .annotation ol li { 61 | font-size: 14px; 62 | line-height: 18px; 63 | margin: 10px 0; 64 | } 65 | 66 | h1, h2, h3, h4, h5, h6 { 67 | color: #112233; 68 | line-height: 1em; 69 | font-weight: normal; 70 | font-family: "roboto-black"; 71 | text-transform: uppercase; 72 | margin: 30px 0 15px 0; 73 | } 74 | 75 | h1 { 76 | margin-top: 40px; 77 | } 78 | h2 { 79 | font-size: 1.26em; 80 | } 81 | 82 | hr { 83 | border: 0; 84 | background: 1px #ddd; 85 | height: 1px; 86 | margin: 20px 0; 87 | } 88 | 89 | pre, tt, code { 90 | font-size: 12px; line-height: 16px; 91 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 92 | margin: 0; padding: 0; 93 | } 94 | .annotation pre { 95 | display: block; 96 | margin: 0; 97 | padding: 7px 10px; 98 | background: #fcfcfc; 99 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 100 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 101 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 102 | overflow-x: auto; 103 | } 104 | .annotation pre code { 105 | border: 0; 106 | padding: 0; 107 | background: transparent; 108 | } 109 | 110 | 111 | blockquote { 112 | border-left: 5px solid #ccc; 113 | margin: 0; 114 | padding: 1px 0 1px 1em; 115 | } 116 | .sections blockquote p { 117 | font-family: Menlo, Consolas, Monaco, monospace; 118 | font-size: 12px; line-height: 16px; 119 | color: #999; 120 | margin: 10px 0 0; 121 | white-space: pre-wrap; 122 | } 123 | 124 | ul.sections { 125 | list-style: none; 126 | padding:0 0 5px 0;; 127 | margin:0; 128 | } 129 | 130 | /* 131 | Force border-box so that % widths fit the parent 132 | container without overlap because of margin/padding. 133 | 134 | More Info : http://www.quirksmode.org/css/box.html 135 | */ 136 | ul.sections > li > div { 137 | -moz-box-sizing: border-box; /* firefox */ 138 | -ms-box-sizing: border-box; /* ie */ 139 | -webkit-box-sizing: border-box; /* webkit */ 140 | -khtml-box-sizing: border-box; /* konqueror */ 141 | box-sizing: border-box; /* css3 */ 142 | } 143 | 144 | 145 | /*---------------------- Jump Page -----------------------------*/ 146 | #jump_to, #jump_page { 147 | margin: 0; 148 | background: white; 149 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 150 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 151 | font: 16px Arial; 152 | cursor: pointer; 153 | text-align: right; 154 | list-style: none; 155 | } 156 | 157 | #jump_to a { 158 | text-decoration: none; 159 | } 160 | 161 | #jump_to a.large { 162 | display: none; 163 | } 164 | #jump_to a.small { 165 | font-size: 22px; 166 | font-weight: bold; 167 | color: #676767; 168 | } 169 | 170 | #jump_to, #jump_wrapper { 171 | position: fixed; 172 | right: 0; top: 0; 173 | padding: 10px 15px; 174 | margin:0; 175 | } 176 | 177 | #jump_wrapper { 178 | display: none; 179 | padding:0; 180 | } 181 | 182 | #jump_to:hover #jump_wrapper { 183 | display: block; 184 | } 185 | 186 | #jump_page_wrapper{ 187 | position: fixed; 188 | right: 0; 189 | top: 0; 190 | bottom: 0; 191 | } 192 | 193 | #jump_page { 194 | padding: 5px 0 3px; 195 | margin: 0 0 25px 25px; 196 | max-height: 100%; 197 | overflow: auto; 198 | } 199 | 200 | #jump_page .source { 201 | display: block; 202 | padding: 15px; 203 | text-decoration: none; 204 | border-top: 1px solid #eee; 205 | } 206 | 207 | #jump_page .source:hover { 208 | background: #f5f5ff; 209 | } 210 | 211 | #jump_page .source:first-child { 212 | } 213 | 214 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 215 | @media only screen and (min-width: 320px) { 216 | .pilwrap { display: none; } 217 | 218 | ul.sections > li > div { 219 | display: block; 220 | padding:5px 10px 0 10px; 221 | } 222 | 223 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 224 | padding-left: 30px; 225 | } 226 | 227 | ul.sections > li > div.content { 228 | overflow-x:auto; 229 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 230 | box-shadow: inset 0 0 5px #e5e5ee; 231 | border: 1px solid #dedede; 232 | margin:5px 10px 5px 10px; 233 | padding-bottom: 5px; 234 | } 235 | 236 | ul.sections > li > div.annotation pre { 237 | margin: 7px 0 7px; 238 | padding-left: 15px; 239 | } 240 | 241 | ul.sections > li > div.annotation p tt, .annotation code { 242 | background: #f8f8ff; 243 | border: 1px solid #dedede; 244 | font-size: 12px; 245 | padding: 0 0.2em; 246 | } 247 | } 248 | 249 | /*---------------------- (> 481px) ---------------------*/ 250 | @media only screen and (min-width: 481px) { 251 | #container { 252 | position: relative; 253 | } 254 | body { 255 | background-color: #F5F5FF; 256 | font-size: 15px; 257 | line-height: 21px; 258 | } 259 | pre, tt, code { 260 | line-height: 18px; 261 | } 262 | p, ul, ol { 263 | margin: 0 0 15px; 264 | } 265 | 266 | 267 | #jump_to { 268 | padding: 5px 10px; 269 | } 270 | #jump_wrapper { 271 | padding: 0; 272 | } 273 | #jump_to, #jump_page { 274 | font: 10px Arial; 275 | text-transform: uppercase; 276 | } 277 | #jump_page .source { 278 | padding: 5px 10px; 279 | } 280 | #jump_to a.large { 281 | display: inline-block; 282 | } 283 | #jump_to a.small { 284 | display: none; 285 | } 286 | 287 | 288 | 289 | #background { 290 | position: absolute; 291 | top: 0; bottom: 0; 292 | width: 350px; 293 | background: #fff; 294 | border-right: 1px solid #e5e5ee; 295 | z-index: -1; 296 | } 297 | 298 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 299 | padding-left: 40px; 300 | } 301 | 302 | ul.sections > li { 303 | white-space: nowrap; 304 | } 305 | 306 | ul.sections > li > div { 307 | display: inline-block; 308 | } 309 | 310 | ul.sections > li > div.annotation { 311 | max-width: 350px; 312 | min-width: 350px; 313 | min-height: 5px; 314 | padding: 13px; 315 | overflow-x: hidden; 316 | white-space: normal; 317 | vertical-align: top; 318 | text-align: left; 319 | } 320 | ul.sections > li > div.annotation pre { 321 | margin: 15px 0 15px; 322 | padding-left: 15px; 323 | } 324 | 325 | ul.sections > li > div.content { 326 | padding: 13px; 327 | vertical-align: top; 328 | border: none; 329 | -webkit-box-shadow: none; 330 | box-shadow: none; 331 | } 332 | 333 | .pilwrap { 334 | position: relative; 335 | display: inline; 336 | } 337 | 338 | .pilcrow { 339 | font: 12px Arial; 340 | text-decoration: none; 341 | color: #454545; 342 | position: absolute; 343 | top: 3px; left: -20px; 344 | padding: 1px 2px; 345 | opacity: 0; 346 | -webkit-transition: opacity 0.2s linear; 347 | } 348 | .for-h1 .pilcrow { 349 | top: 47px; 350 | } 351 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 352 | top: 35px; 353 | } 354 | 355 | ul.sections > li > div.annotation:hover .pilcrow { 356 | opacity: 1; 357 | } 358 | } 359 | 360 | /*---------------------- (> 1025px) ---------------------*/ 361 | @media only screen and (min-width: 1025px) { 362 | 363 | body { 364 | font-size: 16px; 365 | line-height: 24px; 366 | } 367 | 368 | #background { 369 | width: 525px; 370 | } 371 | ul.sections > li > div.annotation { 372 | max-width: 525px; 373 | min-width: 525px; 374 | padding: 10px 25px 1px 50px; 375 | } 376 | ul.sections > li > div.content { 377 | padding: 9px 15px 16px 25px; 378 | } 379 | } 380 | 381 | /*---------------------- Syntax Highlighting -----------------------------*/ 382 | 383 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 384 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 385 | /* 386 | 387 | github.com style (c) Vasily Polovnyov 388 | 389 | */ 390 | 391 | pre code { 392 | display: block; padding: 0.5em; 393 | color: #000; 394 | background: #f8f8ff 395 | } 396 | 397 | pre .hljs-comment, 398 | pre .hljs-template_comment, 399 | pre .hljs-diff .hljs-header, 400 | pre .hljs-javadoc { 401 | color: #408080; 402 | font-style: italic 403 | } 404 | 405 | pre .hljs-keyword, 406 | pre .hljs-assignment, 407 | pre .hljs-literal, 408 | pre .hljs-css .hljs-rule .hljs-keyword, 409 | pre .hljs-winutils, 410 | pre .hljs-javascript .hljs-title, 411 | pre .hljs-lisp .hljs-title, 412 | pre .hljs-subst { 413 | color: #954121; 414 | /*font-weight: bold*/ 415 | } 416 | 417 | pre .hljs-number, 418 | pre .hljs-hexcolor { 419 | color: #40a070 420 | } 421 | 422 | pre .hljs-string, 423 | pre .hljs-tag .hljs-value, 424 | pre .hljs-phpdoc, 425 | pre .hljs-tex .hljs-formula { 426 | color: #219161; 427 | } 428 | 429 | pre .hljs-title, 430 | pre .hljs-id { 431 | color: #19469D; 432 | } 433 | pre .hljs-params { 434 | color: #00F; 435 | } 436 | 437 | pre .hljs-javascript .hljs-title, 438 | pre .hljs-lisp .hljs-title, 439 | pre .hljs-subst { 440 | font-weight: normal 441 | } 442 | 443 | pre .hljs-class .hljs-title, 444 | pre .hljs-haskell .hljs-label, 445 | pre .hljs-tex .hljs-command { 446 | color: #458; 447 | font-weight: bold 448 | } 449 | 450 | pre .hljs-tag, 451 | pre .hljs-tag .hljs-title, 452 | pre .hljs-rules .hljs-property, 453 | pre .hljs-django .hljs-tag .hljs-keyword { 454 | color: #000080; 455 | font-weight: normal 456 | } 457 | 458 | pre .hljs-attribute, 459 | pre .hljs-variable, 460 | pre .hljs-instancevar, 461 | pre .hljs-lisp .hljs-body { 462 | color: #008080 463 | } 464 | 465 | pre .hljs-regexp { 466 | color: #B68 467 | } 468 | 469 | pre .hljs-class { 470 | color: #458; 471 | font-weight: bold 472 | } 473 | 474 | pre .hljs-symbol, 475 | pre .hljs-ruby .hljs-symbol .hljs-string, 476 | pre .hljs-ruby .hljs-symbol .hljs-keyword, 477 | pre .hljs-ruby .hljs-symbol .hljs-keymethods, 478 | pre .hljs-lisp .hljs-keyword, 479 | pre .hljs-tex .hljs-special, 480 | pre .hljs-input_number { 481 | color: #990073 482 | } 483 | 484 | pre .hljs-builtin, 485 | pre .hljs-constructor, 486 | pre .hljs-built_in, 487 | pre .hljs-lisp .hljs-title { 488 | color: #0086b3 489 | } 490 | 491 | pre .hljs-preprocessor, 492 | pre .hljs-pi, 493 | pre .hljs-doctype, 494 | pre .hljs-shebang, 495 | pre .hljs-cdata { 496 | color: #999; 497 | font-weight: bold 498 | } 499 | 500 | pre .hljs-deletion { 501 | background: #fdd 502 | } 503 | 504 | pre .hljs-addition { 505 | background: #dfd 506 | } 507 | 508 | pre .hljs-diff .hljs-change { 509 | background: #0086b3 510 | } 511 | 512 | pre .hljs-chunk { 513 | color: #aaa 514 | } 515 | 516 | pre .hljs-tex .hljs-formula { 517 | opacity: 0.5; 518 | } 519 | -------------------------------------------------------------------------------- /parambulator-min.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2015 Richard Rodger, MIT License */ 2 | (function(){"use strict";function e(e){var r=e.rule.spec;h.isArray(r)||(r=[""+r]);var t=[];return h.each(r,function(r){r.match(/[*?]/)?t=t.concat(h.keys(m(r).on(e.point))):t.push(r)}),t}function r(){return!0}function t(){return!1}function n(e){return h.isUndefined(e)||h.isNull(e)}function a(e){var r={rules:e.rules,point:e.point,msgs:e.msgs,log:e.log,parents:e.parents,util:e.util};return r}function u(e,r){var t=r||"top level";return 00&&t.multiErrors)n(v,{log:e.log});else{var l=v.length>0?v[0]:null;n(l,{log:e.log})}}if(h.isUndefined(r))return o("no_input$",e,n);var u=e.rules;a(0)}function l(e,r){for(var t in f){var n=f[t],a=e.point;for(var u in n.pathnames){var i=n.pathnames[u];if(h.has(a,i))a=a[i];else if(u==n.pathnames.length-1)a[i]=n.defaultvalue;else{var o,p=n.pathtypes[u];if("object"==p)o={};else{if("array"!=p)return void e.util.fail("default$",e,r);o=[]}a[i]=o,a=o}}}r()}var s,c=t.callbackmaker?t.callbackmaker(n):n||function(){},m=function(e){s=e,c.apply(null,arguments)},v=[],y={rules:d,point:r,msgs:g,log:[],parents:[]};return y.util={formatparents:function(){var e=$(arguments);return e[1]=t&&t.topname&&!e[1]?t.topname:e[1],u.apply(null,e)},msgmods:function(e){return(t.msgprefix||"")+e+(t.msgsuffix||"")},fail:o,proplist:e,execrules:p,clone:a},l(y,function(e){e?m(e,{log:y.log}):p(y,function(e){m(e,{log:y.log})})}),s},c}var s=this,c=s.parambulator,f="undefined"!=typeof require,h=s._,m=s.gex,v=s.jsonic;if("undefined"==typeof h){if(!f)throw new Error("parambulator requires underscore, see http://underscorejs.org");h=require("lodash")}if("undefined"==typeof m){if(!f)throw new Error("parambulator requires gex, see http://github.com/rjrodger/gex");m=require("gex")}if("undefined"==typeof v){if(!f)throw new Error("parambulator requires jsonic, see http://github.com/rjrodger/jsonic");v=require("jsonic")}var $=function(){return Array.prototype.slice.call(arguments[0],arguments[1])},d=function(e,r){return r=r||"quantrule",function(r,t){r.prop=null;var n=r.util.proplist(r),a=0;return h.each(n,function(e){a+=r.point[e]?1:0}),e(a)?t():(r.value=""+n,r.util.fail(r,t))}},g=function(e){return function(r,t){var n=r.rule.spec,a=r.point;if(!h.isUndefined(a)){var u=h.isObject(a)?Object.keys(a).length:a.length;if(!h.isUndefined(u)&&!e(u,n))return r.util.fail(r,t)}return t()}},y=function(e,r,t){return t=t||"childrule",function(t,n){for(var a=t.util.proplist(t),u=0;u=e},"atmostone$"),exactlyone$:d(function(e){return 1==e},"exactlyone$"),atleastone$:d(function(e){return e>=1},"atleastone$"),required$:y(function(e,r,t){return!h.isUndefined(t)},t,"required$"),notempty$:y(function(e,r,t){return!h.isUndefined(t)&&!h.isNull(t)&&""!==t},r,"notempty$"),string$:y(function(e,r,t){return n(t)||h.isString(t)},r,"string$"),integer$:y(function(e,r,t){return n(t)||h.isNumber(t)&&t===(0|t)},r,"integer$"),number$:y(function(e,r,t){return n(t)||h.isNumber(t)},r,"number$"),boolean$:y(function(e,r,t){return n(t)||h.isBoolean(t)},r,"boolean$"),date$:y(function(e,r,t){return n(t)||h.isDate(t)},r,"date$"),array$:y(function(e,r,t){return n(t)||h.isArray(t)},r,"array$"),object$:y(function(e,r,t){return n(t)||h.isObject(t)&&!h.isArray(t)},r,"object$"),function$:y(function(e,r,t){return n(t)||h.isFunction(t)},r,"function$"),lt$:b(function(e,r){return r>e}),lte$:b(function(e,r){return r>=e}),gt$:b(function(e,r){return e>r}),gte$:b(function(e,r){return e>=r}),min$:b(function(e,r){return e>=r}),max$:b(function(e,r){return r>=e}),uniq$:function(e,r){for(var t=e.point,n={},a=0;a=r}),maxlen$:g(function(e,r){return r>=e}),re$:function(e,r){var t=e.point;if(!h.isUndefined(t)){t=""+t;var n=e.rule.spec,a=void 0,u=/^\/(.*)\/(\w*)$/.exec(e.rule.spec);u&&(n=u[1],a=u[2]);var i=new RegExp(n,a);if(!i.exec(t))return e.util.fail(e,r)}return r()},type$:function(e,r){var t=e.util.proplist(e),n={string:h.isString,number:h.isNumber,integer:function(e){return h.isNumber(e)&&e===(0|e)},"boolean":h.isBoolean,date:h.isDate,array:h.isArray,object:function(e){return h.isObject(e)&&!h.isArray(e)&&!h.isDate(e)},"function":function(e){return h.isFunction(e)}},a=0;return h.each(t,function(r){var t=n[r.toLowerCase()];t&&(a+=t(e.point))}),a?r():e.util.fail(e,r)},format$:function(e,r){var t=e.util.proplist(e),n={datetime:function(e){return/\d{4}-(0[1-9]|1[1-2])-([0-2]\d|3[0-1])T([0-1]\d|2[0-4]):[0-5]\d:[0-5]\dZ/.test(e)},date:function(e){return/\d{4}-[0-1][0-2]-[0-2]\d/.test(e)},time:function(e){return/([0-1]\d|2[0-4]):[0-5]\d:[0-5]\dZ/.test(e)},utcmillisec:h.isNumber,re:h.isRegExp},a=0;return h.each(t,function(r){var t=n[r.toLowerCase()];t&&(a+=t(e.point))}),a?r():e.util.fail(e,r)},default$:function(e,r){return r()},enum$:function(e,r){var t=e.point,n=e.rule.spec,a=[];h.isArray(t)?a=t:a[0]=t;var u=0;return a&&h.each(a,function(e){u+=-1==n.indexOf(e)}),u?e.util.fail(e,r):r()},iterate$:function(e,r){function t(u){if(u' (parent: <%=parentpath%>).",exactlyone$:"Exactly one of these properties must be used: '<%=value%>' (parent: <%=parentpath%>).",atleastone$:"At least one of these properties is required: '<%=value%>' (parent: <%=parentpath%>).",required$:"The property '<%=property%>' is missing and is always required (parent: <%=parentpath%>).",notempty$:"The property '<%=property%>' requires a value (parent: <%=parentpath%>).",string$:"The property '<%=property%>', with current value: '<%=value%>', must be a string (parent: <%=parentpath%>).",integer$:"The property '<%=property%>', with current value: '<%=value%>', must be a integer (parent: <%=parentpath%>).",number$:"The property '<%=property%>', with current value: '<%=value%>', must be a number (parent: <%=parentpath%>).",boolean$:"The property '<%=property%>', with current value: '<%=value%>', must be a boolean (parent: <%=parentpath%>).",date$:"The property '<%=property%>', with current value: '<%=value%>', must be a date (parent: <%=parentpath%>).",array$:"The property '<%=property%>', with current value: '<%=value%>', must be a array (parent: <%=parentpath%>).",object$:"The property '<%=property%>', with current value: '<%=value%>', must be a object (parent: <%=parentpath%>).",function$:"The property '<%=property%>', with current value: '<%=value%>', must be a function (parent: <%=parentpath%>).",only$:"The property '<%=property%>' is not recognised here. Recognised properties are: <%=rule.spec%> (parent: <%=parentpath%>).",wild$:"The value <%=value%> does not match the expression '<%=rule.spec%>' (parent: <%=parentpath%>).",re$:"The value <%=value%> does not match the regular expression <%=rule.spec%> (parent: <%=parentpath%>).",type$:"The value <%=value%> is not of type '<%=rule.spec%>' (parent: <%=parentpath%>).",format$:"The value <%=value%> is not of format '<%=rule.spec%>' (parent: <%=parentpath%>).",minlen$:"The property '<%=property%>', with current value: '<%=value%>', must have minimum length '<%=rule.spec%>' (parent: <%=parentpath%>).",maxlen$:"The property '<%=property%>', with current value: '<%=value%>', must have maximum length '<%=rule.spec%>' (parent: <%=parentpath%>).",eq$:"The value <%=value%> does not equal '<%=rule.spec%>' (parent: <%=parentpath%>).",lt$:"The value <%=value%> is not less than '<%=rule.spec%>' (parent: <%=parentpath%>).",lte$:"The value <%=value%> is not less than or equal with '<%=rule.spec%>' (parent: <%=parentpath%>).",gt$:"The value <%=value%> is not greater than '<%=rule.spec%>' (parent: <%=parentpath%>).",gte$:"The value <%=value%> is not not greater than or equal with '<%=rule.spec%>' (parent: <%=parentpath%>).",min$:"The value <%=value%> is not not greater than or equal with '<%=rule.spec%>' (parent: <%=parentpath%>).",max$:"The value <%=value%> is not less than or equal with '<%=rule.spec%>' (parent: <%=parentpath%>).",uniq$:"The value <%=value%> has duplicate elements.",enum$:"The value <%=value%> must be one of '<%=rule.spec%>' (parent: <%=parentpath%>)."},T=function(){var e=$(arguments);return l.apply(this,e)};T.ownparams=new l({"**":{strings$:["required$","notempty$","atmostone$","exactlyone$","atleastone$"],list$:[["prop$",{name:"wild$",rules:{type$:"string"}}],["prop$",{name:"type$",rules:{type$:["string","array"]}}],["prop$",{name:"format$",rules:{type$:["string","array"]}}],["prop$",{name:"re$",rules:{type$:"string"}}],["prop$",{name:"type$",rules:{enum$:["string","number","integer","boolean","date","array","object"]}}],["prop$",{name:"format$",rules:{enum$:["datetime","date","time","utcmillisec","re"]}}],["prop$",{name:"minlen$",rules:{type$:"number"}}],["prop$",{name:"maxlen$",rules:{type$:"number"}}],["prop$",{name:"enum$",rules:{type$:"array"}}],["prop$",{name:"list$",rules:{type$:"array"}}],["prop$",{name:"uniq$",rules:{type$:"array"}}]]}},{__ownparams__:!0,rules:{strings$:function(e,r){var t=e.rule.spec;h.isArray(t)||(t=[""+t]);for(var n=0;n rule needs a string or array of strings (property: <%=parentpath%>)."},topname:"spec"}),T.ownprefs=new l({object$:["valid","rules","msgs"],string$:["topname","msgprefix","msgsuffix"],boolean$:["multiErrors"],function$:["callbackmaker"],only$:["valid","rules","msgs","topname","msgprefix","msgsuffix","multiErrors","callbackmaker"]},{topname:"prefs"}),T.Parambulator=l,s.parambulator=T,T.noConflict=function(){return s.previous_parambulator=c,self},"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=T),exports.parambulator=T):s.parambulator=T}).call(this); 3 | //# sourceMappingURL=parambulator-min.map -------------------------------------------------------------------------------- /parambulator-min.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"parambulator-min.js","sources":["parambulator.js"],"names":["proplist","ctxt","pn","rule","spec","_","isArray","all","each","n","match","concat","keys","gex","on","point","push","truefn","falsefn","noval","v","isUndefined","isNull","clone","newctxt","rules","msgs","log","parents","util","formatparents","topname","out","length","map","p","prop","join","killcircles","seen","k","contains","fail","code","arguments","cb","name","Error","inserts","property","value","JSON","stringify","parentpath","json","msg","isFunction","msgmods","template","err","parambulator","expected","proporder","obj","pc","indexOf","substring","Parambulator","pref","parsedefault","path","pathtypes","innerulenames","currentruletype","hasOwnProperty","defaultrule","pathnames","splice","defaultvalue","defaultrules","isObject","index","inner_rule","newpath","slice","newpathtypes","buildrule","rulespec","func","rulemap","parse","names","i","rs","subrules","multiErrors","item","item_rule","build_rule","starstar_subrules","starstar_rule","isBoolean","prop_subrules","prop_rule","isString","rulespecs","split","isNumber","self","jsonic","exp","ownprefs","validate","valid","prefparams","**","toString","extend","msgsmap","args","execrules","execrule","ruleI","specstr","errors","validatedefaults","ruleindex","location","has","newobj","type","reterr","callback","callbackmaker","wrapcb","apply","arrayify","msgprefix","msgsuffix","root","this","previous_parambulator","has_require","require","Array","prototype","call","quantrule","pass","rulename","found","lenrule","len","valuelen","Object","childrule","noneok","valrule","atmostone$","f","exactlyone$","atleastone$","required$","notempty$","string$","integer$","number$","boolean$","date$","isDate","array$","object$","function$","lt$","lte$","gt$","gte$","min$","max$","uniq$","o","only$","include","wild$","eq$","minlen$","conditionlen","maxlen$","re$","redef","reopt","m","exec","re","RegExp","type$","checkmap","string","number","integer","boolean","date","array","object","function","check","toLowerCase","format$","datetime","test","time","utcmillisec","isRegExp","default$","enum$","okvals","avalue","iserror","iterate$","eachprop","propI","psubctxt","subctxt","range","recurse$","recurse","eachpropctxt","recurctxt","$","descend$","no_input$","ownparams","strings$","list$","__ownparams__","pI","val","noConflict","exports","module"],"mappings":"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"} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parambulator - Node.js module 2 | 3 | 4 | A simple way to generate nice error messages for named parameters. 5 | 6 | This module is used by the Seneca framework for input validation. 7 | 8 | This module works on both Node.js and browsers. 9 | 10 | If you're using this module, feel free to contact me on twitter if you 11 | have any questions! :) [@rjrodger](http://twitter.com/rjrodger) 12 | 13 | [![Gitter chat](https://badges.gitter.im/rjrodger/parambulator.png)](https://gitter.im/rjrodger/parambulator) 14 | 15 | Current Version: 1.5.2 16 | 17 | [![Build Status](https://travis-ci.org/rjrodger/parambulator.png?branch=master)](https://travis-ci.org/rjrodger/parambulator) 18 | 19 | [Annotated Source](http://rjrodger.github.io/parambulator/doc/parambulator.html) 20 | 21 | 22 | # Usage 23 | 24 | Use this module to validate input or configuration parameters provided 25 | as JSON. You can ensure that the JSON structure and data types are 26 | what you need. You'll get friendly, useful error messages for your 27 | users, and that makes your API better! 28 | 29 | ```javascript 30 | var parambulator = require('parambulator') 31 | 32 | var paramcheck = parambulator({ 33 | price: {type$:'number'} 34 | }) 35 | 36 | // this passes 37 | paramcheck.validate( { price: 10.99 }, function(err) { console.log(err) } ) 38 | 39 | // this fails - price should be a number 40 | paramcheck.validate( { price: 'free!' }, function(err) { console.log(err) } ) 41 | // output: The value 'free!' is not of type 'number' (parent: price). 42 | ``` 43 | 44 | _Why?_ 45 | 46 | You're writing a module and you accept configuration as a structured 47 | JavaScript object. For example, opening a database connection: 48 | [MongoDB driver](http://mongodb.github.com/node-mongodb-native/api-generated/server.html). Or 49 | you want to have named parameters: 50 | [http.request](http://nodejs.org/api/http.html#http_http_request_options_callback). 51 | 52 | It's nice to be able to validate the input and provide useful error messages, without hand-coding the validation. 53 | 54 | 55 | _But What About JSONSchema!_ 56 | 57 | Yes, [JSONSchema](http://json-schema.org) would be the proper way to do this. But the syntax is too hard, and the error messages aren't friendly. This is a [Worse is Better!](http://www.jwz.org/doc/worse-is-better.html) approach. 58 | 59 | There's also a philosophical difference. JSONSchema defines a formal structure, so you need to be fairly precise and complete. Parambulator defines a list of rules that are tested in the order you specify, and you can be vague and incomplete. 60 | 61 | 62 | Key Features: 63 | 64 | * Easy syntax, rules are tested in order 65 | * Add your own rules 66 | * Customize the error messages 67 | 68 | And yes Virginia, it does [validate its own input](http://en.wikipedia.org/wiki/Self-hosting). 69 | 70 | 71 | ## Installation 72 | 73 | npm install parambulator 74 | 75 | or 76 | 77 | bower install parambulator 78 | 79 | 80 | And in your code: 81 | 82 | var parambulator = require('parambulator') 83 | 84 | or 85 | 86 | 87 | 88 | 89 | ## Usage 90 | 91 | Import the module using the standard _require_ syntax: 92 | 93 | ```javascript 94 | var parambulator = require('parambulator') 95 | ``` 96 | 97 | This is a function that creates _Parambulator_ object instances. This function accepts two arguments: 98 | 99 | * _spec_ - the rule specification to test against 100 | * _pref_ - your preferences, such as custom error messages and rules 101 | 102 | Example: 103 | 104 | ```javascript 105 | var paramcheck = parambulator({ price: {type$:'number'} }) 106 | ``` 107 | 108 | 109 | The _paramcheck_ variable is an instance of _Parambulator_. This object only has one method: _validate_, which accepts two arguments: 110 | 111 | * _args_: the object to validate 112 | * _cb_: a callback function, following the standard Node.js error convention (first arg is an Error) 113 | 114 | Example: 115 | 116 | ```javascript 117 | paramcheck.validate( { price: 10.99 }, function(err) { console.log(err) } ) 118 | ``` 119 | 120 | 121 | The callback function is called when the validation has completed. Processing of rules stops as soon as a rule fails. If validation fails, the first argument to the callback will be a standard JavaScript _Error_ object, with an error message in the _message_ property. 122 | 123 | 124 | ### Examples 125 | 126 | Heavily commented examples are provided in the _doc/examples_ folder: https://github.com/rjrodger/parambulator/tree/master/doc/examples 127 | 128 | You should probably read this rest of this first, though. 129 | 130 | 131 | ### Rules 132 | 133 | The validation rules are defined in the _spec_ argument to _parambulator_. The rules are specified as an object, the properties of which are the rule names, and the values the rule options, like so: `{required$:['foo','bar']}`. The rules are executed in the order that they appear (JavaScript preserves the order of object properties). 134 | 135 | Rule names always end with a `$` character. Properties that do not end with `$` are considered to be literal property names: 136 | 137 | ```javascript 138 | { 139 | required$: ['foo','bar'], 140 | foo: { 141 | type$: 'string' 142 | } 143 | } 144 | ``` 145 | 146 | This specification requires the input object to have two properties, _foo_ and _bar_, and for the _foo_ property to have a string value. For example, this is valid: 147 | 148 | ```javascript 149 | { foo:'hello', bar:1 } 150 | ``` 151 | 152 | But these are not: 153 | ```javascript 154 | { foo:1, bar:1 } // foo is not a string 155 | { foo:'hello' } // bar is missing 156 | ``` 157 | 158 | The rules are evaluated in the order they appear: 159 | 160 | 1. at the current property (i.e. the top level), check for properties _foo_ and _bar_, as per `required$: ['foo','bar']` 161 | 2. descend into the _foo_ property, and check that it's value is of `type$: 'string'` 162 | 163 | You can nest rules within other rules. They will be evaluated in the order they appear, depth first. 164 | 165 | For each input property, the rules apply to the value or values within that property. This means that your rule specification mirrors the structure of the input object. 166 | 167 | For example, the specification: 168 | 169 | ```javascript 170 | { 171 | foo: { 172 | bar: { type$: 'integer' } 173 | } 174 | } 175 | ``` 176 | 177 | matches 178 | 179 | ```javascript 180 | { foo: { bar: 1 } } 181 | ``` 182 | 183 | but does not match 184 | 185 | ```javascript 186 | { bar: { foo: 1 } } 187 | ``` 188 | 189 | In general, rules are permissive, in that they only apply if a given property is present. You need to use the _required$_ rule to require that a property is always present in the input. 190 | 191 | Each rule has a specific set of options relevant to that rule. For example, the _required$_ rule takes an array of property names. The type$ rule takes a string indicating the expected type: _string_, _number_, _boolean_, etc. For full details, see the rule descriptions below. 192 | 193 | Literal properties can also accept a wildcard string expression. For example: 194 | 195 | ```javascript 196 | { foo: "ba*" } 197 | ``` 198 | 199 | This matches: 200 | 201 | ```javascript 202 | { foo: "ba" } 203 | { foo: "bar" } 204 | { foo: "barx" } 205 | ``` 206 | 207 | but not 208 | 209 | ```javascript 210 | { foo: "b" } 211 | ``` 212 | 213 | 214 | 215 | ### Wildcards 216 | 217 | Sometimes you don't know the property names in advance. To handle this case, you can also use wildcard expressions in literal properties: 218 | 219 | ```javascript 220 | { 'a*': { type$: 'boolean' } } 221 | ``` 222 | 223 | This matches: 224 | 225 | ```javascript 226 | { 227 | a: true, 228 | ax: false, 229 | ayz: true 230 | } 231 | ``` 232 | 233 | In particular, `'*'` on its own will match any property (at the same level). Wildcard expressions have the usual syntax: `*` means match anything, and `?` means match a single character. 234 | 235 | 236 | What about repeatedly nested rules? In this situation, you want to apply the same set of rules at any depth. You can use the special literal property `'**'` to achieve this: 237 | 238 | ```javascript 239 | { '**': { a: {type$: 'boolean' } } } 240 | ``` 241 | 242 | This matches: 243 | 244 | ```javascript 245 | { a:true, x:{a:false, y:{a:true}}} 246 | ``` 247 | 248 | ensuring that any properties called _a_ will be an integer. The recursive descent starts from the current level. 249 | 250 | 251 | ### Arrays 252 | 253 | Arrays are treated as if they were objects. The property names are simply the string values of the integer array indexes. For example: 254 | 255 | ```javascript 256 | { a: {'0':'first'} } 257 | ``` 258 | 259 | This matches: 260 | 261 | ```javascript 262 | { a:['first'] } 263 | ``` 264 | 265 | Due to a quirk in the Chrome V8 engine, the order of integer properties is not preserved. Use the special prefix `__` as a workaround: 266 | 267 | ```javascript 268 | { a: {'__1':'first', '__0':'second'} } 269 | ``` 270 | 271 | This matches: 272 | 273 | ```javascript 274 | { a:['second','first'] } 275 | ``` 276 | 277 | but the rules are tested in order: 278 | 279 | 1. `'__1':'first'` 280 | 2. `'__0':'second'` 281 | 282 | 283 | 284 | ### Custom Errors 285 | 286 | Each rule has an associated error message. By default these explain the reason why a rule failed, and give the property path (in standard JavaScript dot syntax: `foo.bar.baz`) of the offending value. You can customize these error messages, by providing your own string templates, or by providing a function that returns the error message text. 287 | 288 | Use the _msgs_ property of the _pref_ argument (the second argument to _parambulator_) to define custom error messages: 289 | 290 | ```javascript 291 | var pm = parambulator({...},{ 292 | msgs: { 293 | required$: 'Property <%=property%> is required, yo!' 294 | } 295 | }) 296 | ``` 297 | 298 | The template syntax is provided by the _underscore_ module: http://underscorejs.org/#template 299 | 300 | The following properties are available: 301 | 302 | * _property_: the relevant property name 303 | * _value_: the string representation of the value that failed in some way 304 | * _point_: the actual value, which could be of any type, not just a string 305 | * _rule.name_: the name of the rule 306 | * _rule.spec_: the rule specification, e.g. `'boolean'` for rule `type$:'boolean'` 307 | * _parentpath_: a string locating the value in the input (properties in dot-syntax) 308 | * _json_: a reference to the JSON.stringify function, use like so: <%=json(rule.spec)%> 309 | 310 | The _parentpath_ will use the term _top level_ when the error concerns 311 | the top level of the input object. You can customize this term using the _topname_ option: 312 | 313 | ```javascript 314 | var pm = parambulator({...},{ 315 | topname: 'name_of_param_in_my_function_definition' 316 | }) 317 | ``` 318 | 319 | You can also modify the error message using the _msgprefix_ and 320 | _msgsuffix_ options, which are prepended and appended to the message 321 | body, respectively, and also support the template syntax. 322 | 323 | 324 | You can also specify a custom error message using a function. This 325 | lets you customize on the specific failing conditions, such as the 326 | property name: 327 | 328 | ```javascript 329 | var pm = parambulator({...},{ 330 | msgs: { 331 | required$: function(inserts){ 332 | if( 'voodoo' == inserts.property ) { 333 | return "don't dare do: "+inserts.value 334 | } 335 | else { 336 | return 'Property '+inserts.property+' is required, yo!' 337 | } 338 | } 339 | } 340 | }) 341 | ``` 342 | 343 | The _inserts_ parameter is an object containing the properties as above. 344 | 345 | 346 | ## Rules 347 | 348 | The following rules are provided out-of-the-box. To define your own rules, see below. 349 | 350 | Each rule operates at the current _point_. This is the current property location inside the input object. 351 | 352 | For example, with input: 353 | 354 | ```javascript 355 | { 356 | foo: { 357 | bar: { 358 | baz: 'zzz' 359 | } 360 | } 361 | } 362 | ``` 363 | 364 | the _point_ `foo.bar` is the object: 365 | 366 | ```javascript 367 | { baz: 'zzz'} 368 | ``` 369 | 370 | 371 | ### literal property 372 | 373 | Match an input property. You can use wildcards. Accepts a set of sub rules, or a wildcard string to match against. The property names match against property names in the current point. 374 | 375 | ```javascript 376 | { 377 | a: { ... } 378 | 'b*': { ... } 379 | c: 'z*' 380 | } 381 | ``` 382 | 383 | 384 | ### boolean rules 385 | 386 | As a convenience, rules that take a property name, such as _required$_, can be specified for a property using the form: 387 | 388 | ```javascript 389 | { 390 | foo: 'required$', 391 | bar: 'required$,string$' 392 | } 393 | ``` 394 | 395 | To use a _$_ symbol literally, use the form: 396 | 397 | ```javascript 398 | { 399 | foo: { eq$:'text containing $' } 400 | } 401 | ``` 402 | 403 | 404 | ### atmostone$ 405 | 406 | Accept at most one of a list of properties. Accepts an array of property name strings. At most one of them can be present in the current point. 407 | 408 | ```javascript 409 | { 410 | atmostone$: ['foo','bar'] 411 | } 412 | ``` 413 | 414 | 415 | ### exactlyone$ 416 | 417 | Accept exactly one of a list of properties. Accepts an array of property name strings. Exactly one of them must be present in the current point. 418 | 419 | ```javascript 420 | { 421 | exactlyone$: ['foo','bar'] 422 | } 423 | ``` 424 | 425 | 426 | ### atleastone$ 427 | 428 | Accept at least one of a list of properties. Accepts an array of property name strings. At least one of them must be present in the current point. 429 | 430 | ```javascript 431 | { 432 | atleastone$: ['foo','bar'] 433 | } 434 | ``` 435 | 436 | 437 | 438 | ### required$ 439 | 440 | Specify a set of required properties. Accepts an array of property name strings, or a single property name. Wildcards can be used. All properties must be present in the current point. Can also appear as a rule specification for literal properties. 441 | 442 | 443 | ```javascript 444 | { required$: ['foo','b*'] } // wildcards work too! 445 | { required$: 'bar' } // for convenience 446 | { bar: 'required$' } // for extra convenience 447 | { bar: {required$:true} } // for extra extra convenience 448 | { 'b*': 'required$' } // and that's just nice 449 | ``` 450 | 451 | 452 | ### notempty$ 453 | 454 | Specify a set of properties that cannot be empty, if they are present. Unlike _required$_, these properties can be absent altogether, so use _required$_ if they are also required! Accepts an array of property name strings, or a single property name. Wildcards can be used. All properties are relative to the current point. Can also appear as a rule specification for literal properties. 455 | 456 | ```javascript 457 | { notempty$: ['foo','b*'] } // wildcards work too! 458 | { notempty$: 'bar' } // for convenience 459 | { bar: 'notempty$' } // for extra convenience 460 | { 'b*': 'notempty$' } // and that's just nice, again 461 | ``` 462 | 463 | ### wild$ 464 | 465 | Specify a wildcard pattern that the property value must match. The property value does not need to be a string. See the [gex](https://github.com/rjrodger/gex) module documentation. 466 | 467 | ```javascript 468 | { foo: {wild$:'b*'} } 469 | ``` 470 | 471 | ### re$ 472 | 473 | Specify a regular expression that the property value must match. The property value is converted to a string. The regular epxression is given as-is, or can be in the format /.../X, where X is a modifier such as _i_. 474 | 475 | ```javascript 476 | { 477 | foo: {re$:'a.*'}, 478 | bar: {re$:'/b/i'} 479 | } 480 | ``` 481 | 482 | 483 | ### type$ 484 | 485 | Check that a property value is of a given JavaScript type. Does not require the property to be present (use _required$_ for that). Can only be used as a subrule of a literal property. 486 | 487 | ```javascript 488 | { 489 | a: {type$:'string'}, 490 | b: {type$:'number'}, 491 | c: {type$:'integer'}, // can't be decimal! 492 | d: {type$:'boolean'}, 493 | e: {type$:'date'}, 494 | f: {type$:'array'}, 495 | g: {type$:'object'}, 496 | h: {type$:'function'} 497 | } 498 | ``` 499 | 500 | As a convenience, the type rules can also be used in the form: 501 | 502 | ```javascript 503 | { 504 | $string: 'propname' 505 | } 506 | ``` 507 | 508 | 509 | ### eq$ 510 | 511 | Check that a property value is an exactly equal to the given value (must also match type). 512 | 513 | ```javascript 514 | { 515 | foo: {eq$:'bar'}, 516 | } 517 | ``` 518 | 519 | ### comparisons 520 | 521 | The following comparison rules can be used: 522 | 523 | * _lt$_: less than 524 | * _lte$_: less than or equal to (alias: max$) 525 | * _gt$_: greater than 526 | * _gte$_: greater than or equal to (alias: min$) 527 | * _gt$_: greater than 528 | 529 | For example: 530 | 531 | ```javascript 532 | { 533 | foo: {lt$:100}, 534 | } 535 | ``` 536 | 537 | Comparisons also work on alphabetically on strings. 538 | 539 | 540 | 541 | ### enum$ 542 | 543 | Check that a property value is one of an enumerated list of values (can be of any type). 544 | 545 | ```javascript 546 | { 547 | color: {enum$:['red','green','blue']}, 548 | } 549 | ``` 550 | 551 | 552 | ### uniq$ 553 | 554 | Check that a list contains only unique properties. 555 | 556 | ```javascript 557 | { 558 | rainbow: 'uniq$' 559 | } 560 | ``` 561 | 562 | The above specification validates: 563 | 564 | ```javascript 565 | { 566 | rainbow: ['red','orange','yellow','green','blue','indigo','violet'] 567 | } 568 | ``` 569 | 570 | But does not validate: 571 | 572 | ```javascript 573 | { 574 | rainbow: ['red','red','red','red','red','red','red'] 575 | } 576 | ``` 577 | 578 | 579 | ### only$ 580 | 581 | Check that a property _name_ is one of an enumerated list of names, at this point 582 | 583 | ```javascript 584 | { 585 | options: {only$:['folder','duration','limit']}, 586 | } 587 | ``` 588 | 589 | 590 | 591 | ### ** recursion 592 | 593 | Apply a set of subrules recursively to the current point and all it's children. 594 | 595 | ```javascript 596 | { 597 | a: { 598 | '**': { 599 | b: { type$:'integer' } 600 | }, 601 | } 602 | } 603 | ``` 604 | 605 | 606 | ## Custom Rules 607 | 608 | You can write your own rules if you need additional validation. The [range.js](https://github.com/rjrodger/parambulator/blob/master/doc/examples/range.js) example shows you how. 609 | 610 | Define your own rules inside the _rules_ property of the _prefs_ argument to _paramabulator_. Each rule is just a function, for example: 611 | 612 | ```javascript 613 | var pm = parambulator({...},{ 614 | rules: { 615 | mynewrule$: function(ctxt,cb){ 616 | ... 617 | } 618 | } 619 | }) 620 | ``` 621 | 622 | Dont forget the `$` suffix! 623 | 624 | The _ctxt_ parameter provides the same interface as the _inserts_ object for custom messages (as above). You can execute callback or evented code inside the rule function. Call the _cb_ callback without any arguments if the rule passes. 625 | 626 | If the rule fails, you can use a utility function to generate an error message: 627 | 628 | ```javascript 629 | return ctxt.util.fail(ctxt,cb) 630 | ``` 631 | 632 | Just ensure you have a custom message with the same name as the rule! 633 | 634 | The built-in rule definitions in [parambulator.js](https://github.com/rjrodger/parambulator/blob/master/parambulator.js) are also a good resource. 635 | 636 | Tweet me [@rjrodger](http://twitter.com/rjrodger) if you get stuck. 637 | 638 | By the way, if you have a cool new rule and you thing it should be built-in, send me a pull request! Just follow the pattern for, say, _wild$_ in [parambulator.js](https://github.com/rjrodger/parambulator/blob/master/parambulator.js). You'll need entries in _rulemap_, _msgsmap_, and _ownparams_. 639 | 640 | 641 | 642 | ### Validation of Custom Rules 643 | 644 | When you define a custom rule, you'll want to ensure that rule specifications using it are valid. You can do this by adding validation rules to the optional _valid_ property of the _prefs_ argument. 645 | 646 | The [range.js](https://github.com/rjrodger/parambulator/blob/master/doc/examples/range.js) example also shows you how to do this. 647 | 648 | There is a gotcha. You need to escape the rule names, so that they are treated as literal properties, and not rules. To do this, use the `prop$` pseudo-rule: 649 | 650 | ```javascript 651 | { prop$: {name:'foo', rules:{type$:'string'}} } 652 | ``` 653 | 654 | is equivalent to: 655 | 656 | ```javascript 657 | { foo: {type$:'string'} } 658 | ``` 659 | 660 | The other pseudo-rule that may come in handy is the `list$` rule. This lets you specify rules using an array. Each element is a sub array with two elements, the first is the rule name, and the second the rule specification 661 | 662 | ```javascript 663 | { 664 | list$: [ 665 | ['foo', {type$:'string'}], 666 | ['bar', {type$:'string'}], 667 | ] 668 | } 669 | ``` 670 | 671 | Take a look at the definition of `ownparams` in [parambulator.js](https://github.com/rjrodger/parambulator/blob/master/parambulator.js) to see how _parambulator_ validates its own input. 672 | 673 | 674 | ## Multiple validation errors 675 | 676 | When configuring a parambulator instance, it is possible to pass an option so 677 | that parambulator runs all the validation rules and return multiple errors: 678 | 679 | ```javascript 680 | var pm = parambulator({...},{ 681 | multiErrors: true 682 | }) 683 | ``` 684 | 685 | pm.validate({foo: 'bar}, function(errors) { 686 | // errors is null or errors.length > 0 687 | }) 688 | 689 | ## Testing 690 | 691 | Tests run on the command line, in a headless browser, and in a normal browser: 692 | 693 | ```sh 694 | $ npm build 695 | $ npm test 696 | $ npm run browser 697 | $ open test/jasmine.html 698 | ``` 699 | 700 | ## Releases 701 | 702 | Release numbers are strict [semver](http://semver.org/) as 1.x.x. All 703 | releases are tagged in github with release version. 704 | 705 | -------------------------------------------------------------------------------- /test/jasmine-1.3.1/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | jasmine.HtmlReporter = function(_doc) { 62 | var self = this; 63 | var doc = _doc || window.document; 64 | 65 | var reporterView; 66 | 67 | var dom = {}; 68 | 69 | // Jasmine Reporter Public Interface 70 | self.logRunningSpecs = false; 71 | 72 | self.reportRunnerStarting = function(runner) { 73 | var specs = runner.specs() || []; 74 | 75 | if (specs.length == 0) { 76 | return; 77 | } 78 | 79 | createReporterDom(runner.env.versionString()); 80 | doc.body.appendChild(dom.reporter); 81 | setExceptionHandling(); 82 | 83 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 84 | reporterView.addSpecs(specs, self.specFilter); 85 | }; 86 | 87 | self.reportRunnerResults = function(runner) { 88 | reporterView && reporterView.complete(); 89 | }; 90 | 91 | self.reportSuiteResults = function(suite) { 92 | reporterView.suiteComplete(suite); 93 | }; 94 | 95 | self.reportSpecStarting = function(spec) { 96 | if (self.logRunningSpecs) { 97 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 98 | } 99 | }; 100 | 101 | self.reportSpecResults = function(spec) { 102 | reporterView.specComplete(spec); 103 | }; 104 | 105 | self.log = function() { 106 | var console = jasmine.getGlobal().console; 107 | if (console && console.log) { 108 | if (console.log.apply) { 109 | console.log.apply(console, arguments); 110 | } else { 111 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 112 | } 113 | } 114 | }; 115 | 116 | self.specFilter = function(spec) { 117 | if (!focusedSpecName()) { 118 | return true; 119 | } 120 | 121 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 122 | }; 123 | 124 | return self; 125 | 126 | function focusedSpecName() { 127 | var specName; 128 | 129 | (function memoizeFocusedSpec() { 130 | if (specName) { 131 | return; 132 | } 133 | 134 | var paramMap = []; 135 | var params = jasmine.HtmlReporter.parameters(doc); 136 | 137 | for (var i = 0; i < params.length; i++) { 138 | var p = params[i].split('='); 139 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 140 | } 141 | 142 | specName = paramMap.spec; 143 | })(); 144 | 145 | return specName; 146 | } 147 | 148 | function createReporterDom(version) { 149 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 150 | dom.banner = self.createDom('div', { className: 'banner' }, 151 | self.createDom('span', { className: 'title' }, "Jasmine "), 152 | self.createDom('span', { className: 'version' }, version)), 153 | 154 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 155 | dom.alert = self.createDom('div', {className: 'alert'}, 156 | self.createDom('span', { className: 'exceptions' }, 157 | self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), 158 | self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), 159 | dom.results = self.createDom('div', {className: 'results'}, 160 | dom.summary = self.createDom('div', { className: 'summary' }), 161 | dom.details = self.createDom('div', { id: 'details' })) 162 | ); 163 | } 164 | 165 | function noTryCatch() { 166 | return window.location.search.match(/catch=false/); 167 | } 168 | 169 | function searchWithCatch() { 170 | var params = jasmine.HtmlReporter.parameters(window.document); 171 | var removed = false; 172 | var i = 0; 173 | 174 | while (!removed && i < params.length) { 175 | if (params[i].match(/catch=/)) { 176 | params.splice(i, 1); 177 | removed = true; 178 | } 179 | i++; 180 | } 181 | if (jasmine.CATCH_EXCEPTIONS) { 182 | params.push("catch=false"); 183 | } 184 | 185 | return params.join("&"); 186 | } 187 | 188 | function setExceptionHandling() { 189 | var chxCatch = document.getElementById('no_try_catch'); 190 | 191 | if (noTryCatch()) { 192 | chxCatch.setAttribute('checked', true); 193 | jasmine.CATCH_EXCEPTIONS = false; 194 | } 195 | chxCatch.onclick = function() { 196 | window.location.search = searchWithCatch(); 197 | }; 198 | } 199 | }; 200 | jasmine.HtmlReporter.parameters = function(doc) { 201 | var paramStr = doc.location.search.substring(1); 202 | var params = []; 203 | 204 | if (paramStr.length > 0) { 205 | params = paramStr.split('&'); 206 | } 207 | return params; 208 | } 209 | jasmine.HtmlReporter.sectionLink = function(sectionName) { 210 | var link = '?'; 211 | var params = []; 212 | 213 | if (sectionName) { 214 | params.push('spec=' + encodeURIComponent(sectionName)); 215 | } 216 | if (!jasmine.CATCH_EXCEPTIONS) { 217 | params.push("catch=false"); 218 | } 219 | if (params.length > 0) { 220 | link += params.join("&"); 221 | } 222 | 223 | return link; 224 | }; 225 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); 226 | jasmine.HtmlReporter.ReporterView = function(dom) { 227 | this.startedAt = new Date(); 228 | this.runningSpecCount = 0; 229 | this.completeSpecCount = 0; 230 | this.passedCount = 0; 231 | this.failedCount = 0; 232 | this.skippedCount = 0; 233 | 234 | this.createResultsMenu = function() { 235 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 236 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 237 | ' | ', 238 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 239 | 240 | this.summaryMenuItem.onclick = function() { 241 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 242 | }; 243 | 244 | this.detailsMenuItem.onclick = function() { 245 | showDetails(); 246 | }; 247 | }; 248 | 249 | this.addSpecs = function(specs, specFilter) { 250 | this.totalSpecCount = specs.length; 251 | 252 | this.views = { 253 | specs: {}, 254 | suites: {} 255 | }; 256 | 257 | for (var i = 0; i < specs.length; i++) { 258 | var spec = specs[i]; 259 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 260 | if (specFilter(spec)) { 261 | this.runningSpecCount++; 262 | } 263 | } 264 | }; 265 | 266 | this.specComplete = function(spec) { 267 | this.completeSpecCount++; 268 | 269 | if (isUndefined(this.views.specs[spec.id])) { 270 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 271 | } 272 | 273 | var specView = this.views.specs[spec.id]; 274 | 275 | switch (specView.status()) { 276 | case 'passed': 277 | this.passedCount++; 278 | break; 279 | 280 | case 'failed': 281 | this.failedCount++; 282 | break; 283 | 284 | case 'skipped': 285 | this.skippedCount++; 286 | break; 287 | } 288 | 289 | specView.refresh(); 290 | this.refresh(); 291 | }; 292 | 293 | this.suiteComplete = function(suite) { 294 | var suiteView = this.views.suites[suite.id]; 295 | if (isUndefined(suiteView)) { 296 | return; 297 | } 298 | suiteView.refresh(); 299 | }; 300 | 301 | this.refresh = function() { 302 | 303 | if (isUndefined(this.resultsMenu)) { 304 | this.createResultsMenu(); 305 | } 306 | 307 | // currently running UI 308 | if (isUndefined(this.runningAlert)) { 309 | this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); 310 | dom.alert.appendChild(this.runningAlert); 311 | } 312 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 313 | 314 | // skipped specs UI 315 | if (isUndefined(this.skippedAlert)) { 316 | this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); 317 | } 318 | 319 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 320 | 321 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 322 | dom.alert.appendChild(this.skippedAlert); 323 | } 324 | 325 | // passing specs UI 326 | if (isUndefined(this.passedAlert)) { 327 | this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); 328 | } 329 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 330 | 331 | // failing specs UI 332 | if (isUndefined(this.failedAlert)) { 333 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 334 | } 335 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 336 | 337 | if (this.failedCount === 1 && isDefined(dom.alert)) { 338 | dom.alert.appendChild(this.failedAlert); 339 | dom.alert.appendChild(this.resultsMenu); 340 | } 341 | 342 | // summary info 343 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 344 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 345 | }; 346 | 347 | this.complete = function() { 348 | dom.alert.removeChild(this.runningAlert); 349 | 350 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 351 | 352 | if (this.failedCount === 0) { 353 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 354 | } else { 355 | showDetails(); 356 | } 357 | 358 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 359 | }; 360 | 361 | return this; 362 | 363 | function showDetails() { 364 | if (dom.reporter.className.search(/showDetails/) === -1) { 365 | dom.reporter.className += " showDetails"; 366 | } 367 | } 368 | 369 | function isUndefined(obj) { 370 | return typeof obj === 'undefined'; 371 | } 372 | 373 | function isDefined(obj) { 374 | return !isUndefined(obj); 375 | } 376 | 377 | function specPluralizedFor(count) { 378 | var str = count + " spec"; 379 | if (count > 1) { 380 | str += "s" 381 | } 382 | return str; 383 | } 384 | 385 | }; 386 | 387 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 388 | 389 | 390 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 391 | this.spec = spec; 392 | this.dom = dom; 393 | this.views = views; 394 | 395 | this.symbol = this.createDom('li', { className: 'pending' }); 396 | this.dom.symbolSummary.appendChild(this.symbol); 397 | 398 | this.summary = this.createDom('div', { className: 'specSummary' }, 399 | this.createDom('a', { 400 | className: 'description', 401 | href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), 402 | title: this.spec.getFullName() 403 | }, this.spec.description) 404 | ); 405 | 406 | this.detail = this.createDom('div', { className: 'specDetail' }, 407 | this.createDom('a', { 408 | className: 'description', 409 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 410 | title: this.spec.getFullName() 411 | }, this.spec.getFullName()) 412 | ); 413 | }; 414 | 415 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 416 | return this.getSpecStatus(this.spec); 417 | }; 418 | 419 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 420 | this.symbol.className = this.status(); 421 | 422 | switch (this.status()) { 423 | case 'skipped': 424 | break; 425 | 426 | case 'passed': 427 | this.appendSummaryToSuiteDiv(); 428 | break; 429 | 430 | case 'failed': 431 | this.appendSummaryToSuiteDiv(); 432 | this.appendFailureDetail(); 433 | break; 434 | } 435 | }; 436 | 437 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 438 | this.summary.className += ' ' + this.status(); 439 | this.appendToSummary(this.spec, this.summary); 440 | }; 441 | 442 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 443 | this.detail.className += ' ' + this.status(); 444 | 445 | var resultItems = this.spec.results().getItems(); 446 | var messagesDiv = this.createDom('div', { className: 'messages' }); 447 | 448 | for (var i = 0; i < resultItems.length; i++) { 449 | var result = resultItems[i]; 450 | 451 | if (result.type == 'log') { 452 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 453 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 454 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 455 | 456 | if (result.trace.stack) { 457 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 458 | } 459 | } 460 | } 461 | 462 | if (messagesDiv.childNodes.length > 0) { 463 | this.detail.appendChild(messagesDiv); 464 | this.dom.details.appendChild(this.detail); 465 | } 466 | }; 467 | 468 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 469 | this.suite = suite; 470 | this.dom = dom; 471 | this.views = views; 472 | 473 | this.element = this.createDom('div', { className: 'suite' }, 474 | this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) 475 | ); 476 | 477 | this.appendToSummary(this.suite, this.element); 478 | }; 479 | 480 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 481 | return this.getSpecStatus(this.suite); 482 | }; 483 | 484 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 485 | this.element.className += " " + this.status(); 486 | }; 487 | 488 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 489 | 490 | /* @deprecated Use jasmine.HtmlReporter instead 491 | */ 492 | jasmine.TrivialReporter = function(doc) { 493 | this.document = doc || document; 494 | this.suiteDivs = {}; 495 | this.logRunningSpecs = false; 496 | }; 497 | 498 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 499 | var el = document.createElement(type); 500 | 501 | for (var i = 2; i < arguments.length; i++) { 502 | var child = arguments[i]; 503 | 504 | if (typeof child === 'string') { 505 | el.appendChild(document.createTextNode(child)); 506 | } else { 507 | if (child) { el.appendChild(child); } 508 | } 509 | } 510 | 511 | for (var attr in attrs) { 512 | if (attr == "className") { 513 | el[attr] = attrs[attr]; 514 | } else { 515 | el.setAttribute(attr, attrs[attr]); 516 | } 517 | } 518 | 519 | return el; 520 | }; 521 | 522 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 523 | var showPassed, showSkipped; 524 | 525 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 526 | this.createDom('div', { className: 'banner' }, 527 | this.createDom('div', { className: 'logo' }, 528 | this.createDom('span', { className: 'title' }, "Jasmine"), 529 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 530 | this.createDom('div', { className: 'options' }, 531 | "Show ", 532 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 533 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 534 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 535 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 536 | ) 537 | ), 538 | 539 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 540 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 541 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 542 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 543 | ); 544 | 545 | this.document.body.appendChild(this.outerDiv); 546 | 547 | var suites = runner.suites(); 548 | for (var i = 0; i < suites.length; i++) { 549 | var suite = suites[i]; 550 | var suiteDiv = this.createDom('div', { className: 'suite' }, 551 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 552 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 553 | this.suiteDivs[suite.id] = suiteDiv; 554 | var parentDiv = this.outerDiv; 555 | if (suite.parentSuite) { 556 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 557 | } 558 | parentDiv.appendChild(suiteDiv); 559 | } 560 | 561 | this.startedAt = new Date(); 562 | 563 | var self = this; 564 | showPassed.onclick = function(evt) { 565 | if (showPassed.checked) { 566 | self.outerDiv.className += ' show-passed'; 567 | } else { 568 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 569 | } 570 | }; 571 | 572 | showSkipped.onclick = function(evt) { 573 | if (showSkipped.checked) { 574 | self.outerDiv.className += ' show-skipped'; 575 | } else { 576 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 577 | } 578 | }; 579 | }; 580 | 581 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 582 | var results = runner.results(); 583 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 584 | this.runnerDiv.setAttribute("class", className); 585 | //do it twice for IE 586 | this.runnerDiv.setAttribute("className", className); 587 | var specs = runner.specs(); 588 | var specCount = 0; 589 | for (var i = 0; i < specs.length; i++) { 590 | if (this.specFilter(specs[i])) { 591 | specCount++; 592 | } 593 | } 594 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 595 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 596 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 597 | 598 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 599 | }; 600 | 601 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 602 | var results = suite.results(); 603 | var status = results.passed() ? 'passed' : 'failed'; 604 | if (results.totalCount === 0) { // todo: change this to check results.skipped 605 | status = 'skipped'; 606 | } 607 | this.suiteDivs[suite.id].className += " " + status; 608 | }; 609 | 610 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 611 | if (this.logRunningSpecs) { 612 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 613 | } 614 | }; 615 | 616 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 617 | var results = spec.results(); 618 | var status = results.passed() ? 'passed' : 'failed'; 619 | if (results.skipped) { 620 | status = 'skipped'; 621 | } 622 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 623 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 624 | this.createDom('a', { 625 | className: 'description', 626 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 627 | title: spec.getFullName() 628 | }, spec.description)); 629 | 630 | 631 | var resultItems = results.getItems(); 632 | var messagesDiv = this.createDom('div', { className: 'messages' }); 633 | for (var i = 0; i < resultItems.length; i++) { 634 | var result = resultItems[i]; 635 | 636 | if (result.type == 'log') { 637 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 638 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 639 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 640 | 641 | if (result.trace.stack) { 642 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 643 | } 644 | } 645 | } 646 | 647 | if (messagesDiv.childNodes.length > 0) { 648 | specDiv.appendChild(messagesDiv); 649 | } 650 | 651 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 652 | }; 653 | 654 | jasmine.TrivialReporter.prototype.log = function() { 655 | var console = jasmine.getGlobal().console; 656 | if (console && console.log) { 657 | if (console.log.apply) { 658 | console.log.apply(console, arguments); 659 | } else { 660 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 661 | } 662 | } 663 | }; 664 | 665 | jasmine.TrivialReporter.prototype.getLocation = function() { 666 | return this.document.location; 667 | }; 668 | 669 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 670 | var paramMap = {}; 671 | var params = this.getLocation().search.substring(1).split('&'); 672 | for (var i = 0; i < params.length; i++) { 673 | var p = params[i].split('='); 674 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 675 | } 676 | 677 | if (!paramMap.spec) { 678 | return true; 679 | } 680 | return spec.getFullName().indexOf(paramMap.spec) === 0; 681 | }; 682 | -------------------------------------------------------------------------------- /parambulator.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012-2015 Richard Rodger, MIT License */ 2 | /* jshint node:true, asi:true, eqnull:true */ 3 | 4 | (function() { 5 | "use strict"; 6 | 7 | var root = this 8 | var previous_parambulator = root.parambulator 9 | 10 | var has_require = typeof require !== 'undefined' 11 | 12 | 13 | var _ = root._ 14 | var gex = root.gex 15 | var jsonic = root.jsonic 16 | 17 | 18 | if( typeof _ === 'undefined' ) { 19 | if( has_require ) { 20 | _ = require('lodash') 21 | } 22 | 23 | // assume in web context, so still underscore, not lodash 24 | else throw new Error('parambulator requires underscore, see http://underscorejs.org'); 25 | } 26 | 27 | if( typeof gex === 'undefined' ) { 28 | if( has_require ) { 29 | gex = require('gex') 30 | } 31 | else throw new Error('parambulator requires gex, see http://github.com/rjrodger/gex'); 32 | } 33 | 34 | if( typeof jsonic === 'undefined' ) { 35 | if( has_require ) { 36 | jsonic = require('jsonic') 37 | } 38 | else throw new Error('parambulator requires jsonic, see http://github.com/rjrodger/jsonic'); 39 | } 40 | 41 | 42 | 43 | var arrayify = function(){ return Array.prototype.slice.call(arguments[0],arguments[1]) } 44 | 45 | 46 | var quantrule = function( pass, rulename ) { 47 | rulename = rulename || 'quantrule' 48 | 49 | return function(ctxt,cb) { 50 | ctxt.prop = null 51 | 52 | var pn = ctxt.util.proplist(ctxt) 53 | 54 | var found = 0 55 | _.each(pn, function(p){ 56 | found += ctxt.point[p]?1:0 57 | }) 58 | 59 | if( !pass(found) ) { 60 | ctxt.value = ''+pn 61 | return ctxt.util.fail(ctxt,cb) 62 | } 63 | else return cb(); 64 | } 65 | } 66 | 67 | var lenrule = function( pass ) { 68 | return function(ctxt,cb) { 69 | var len = ctxt.rule.spec 70 | var value = ctxt.point 71 | 72 | if( !_.isUndefined(value) ) { 73 | var valuelen = _.isObject(value) ? Object.keys(value).length : value.length 74 | if ( !_.isUndefined( valuelen ) ){ 75 | if ( !pass( valuelen, len ) ) { 76 | return ctxt.util.fail(ctxt,cb) 77 | } 78 | } 79 | } 80 | 81 | return cb() 82 | } 83 | }; 84 | 85 | 86 | var childrule = function( pass, noneok, rulename ) { 87 | rulename = rulename || 'childrule' 88 | 89 | return function(ctxt,cb) { 90 | var pn = ctxt.util.proplist(ctxt) 91 | 92 | for( var i = 0; i < pn.length; i++ ) { 93 | var p = pn[i] 94 | ctxt.prop = p 95 | 96 | var v = ctxt.point[p] 97 | 98 | if( !pass(ctxt,p,v) ) { 99 | ctxt.value = v 100 | return ctxt.util.fail(ctxt,cb) 101 | } 102 | } 103 | 104 | if( 0 === pn.length ) { 105 | if( !noneok() ) { 106 | // TODO needs a separate msg code 107 | ctxt.prop = JSON.stringify(ctxt.rule.spec,killcircles()) 108 | return ctxt.util.fail(ctxt,cb) 109 | } 110 | } 111 | cb(); 112 | } 113 | } 114 | 115 | function proplist(ctxt) { 116 | var pn = ctxt.rule.spec 117 | 118 | // TODO: handle comma separated strings 119 | // https://github.com/rjrodger/parambulator/issues/19 120 | 121 | if( !_.isArray(pn) ) { 122 | pn = [''+pn] 123 | } 124 | 125 | var all = [] 126 | _.each(pn, function(n){ 127 | 128 | if( n.match( /[*?]/ ) ) { 129 | all = all.concat( _.keys(gex(n).on(ctxt.point)) ) 130 | } 131 | else all.push(n); 132 | }) 133 | 134 | return all 135 | } 136 | 137 | 138 | function truefn(){return true} 139 | function falsefn(){return false} 140 | function noval(v){return _.isUndefined(v)||_.isNull(v)} 141 | 142 | var valrule = function( pass ) { 143 | return function(ctxt,cb) { 144 | var v = ctxt.point 145 | var p = ctxt.rule.spec 146 | if( !_.isUndefined(v) ) { 147 | if( !pass(v, p) ) { 148 | return ctxt.util.fail(ctxt,cb) 149 | } 150 | } 151 | 152 | return cb(); 153 | } 154 | } 155 | 156 | 157 | var rulemap = { 158 | 159 | atmostone$: quantrule( function(f){return f<=1}, 'atmostone$'), 160 | exactlyone$: quantrule( function(f){return 1==f}, 'exactlyone$'), 161 | atleastone$: quantrule( function(f){return 1<=f}, 'atleastone$'), 162 | 163 | 164 | required$: childrule( function(ctxt,p,v){return !_.isUndefined(v)}, falsefn, 'required$' ), 165 | 166 | notempty$: childrule( 167 | function(ctxt,p,v){ 168 | return !_.isUndefined(v) && !_.isNull(v) && '' !== v 169 | }, 170 | truefn, 171 | 'notempty$' 172 | ), 173 | 174 | string$: childrule( function(ctxt,p,v){return noval(v) || _.isString(v)}, truefn, 'string$' ), 175 | integer$: childrule( function(ctxt,p,v){return noval(v) || _.isNumber(v) && v===(v|0)}, truefn, 'integer$' ), 176 | number$: childrule( function(ctxt,p,v){return noval(v) || _.isNumber(v)}, truefn, 'number$' ), 177 | boolean$: childrule( function(ctxt,p,v){return noval(v) || _.isBoolean(v)}, truefn, 'boolean$' ), 178 | date$: childrule( function(ctxt,p,v){return noval(v) || _.isDate(v)}, truefn, 'date$' ), 179 | array$: childrule( function(ctxt,p,v){return noval(v) || _.isArray(v)}, truefn, 'array$' ), 180 | object$: childrule( function(ctxt,p,v){return noval(v) || _.isObject(v) && !_.isArray(v)}, truefn, 'object$' ), 181 | function$: childrule( function(ctxt,p,v){return noval(v) || _.isFunction(v)}, truefn, 'function$' ), 182 | 183 | lt$: valrule( function(p,v){return p < v} ), 184 | lte$: valrule( function(p,v){return p <= v} ), 185 | gt$: valrule( function(p,v){return p > v} ), 186 | gte$: valrule( function(p,v){return p >= v} ), 187 | 188 | min$: valrule( function(p,v){return p >= v}), 189 | max$: valrule( function(p,v){return p <= v}), 190 | 191 | uniq$: function(ctxt,cb){ 192 | var value = ctxt.point 193 | var o = {} 194 | 195 | for( var i=0; i= conditionlen} ), 246 | maxlen$: lenrule( function(valuelen, conditionlen){return valuelen <= conditionlen} ), 247 | 248 | re$: function(ctxt,cb) { 249 | var value = ctxt.point 250 | 251 | if( !_.isUndefined(value) ) { 252 | value = ''+value 253 | var redef = ctxt.rule.spec 254 | var reopt = void(0) 255 | 256 | var m = /^\/(.*)\/(\w*)$/.exec(ctxt.rule.spec) 257 | if( m ) { 258 | redef = m[1] 259 | reopt = m[2] 260 | } 261 | 262 | var re = new RegExp(redef,reopt) 263 | 264 | if( !re.exec(value) ) { 265 | return ctxt.util.fail(ctxt,cb) 266 | } 267 | } 268 | 269 | return cb(); 270 | }, 271 | 272 | 273 | type$: function(ctxt,cb) { 274 | var pn = ctxt.util.proplist(ctxt) 275 | 276 | var checkmap = { 277 | string:_.isString, 278 | number:_.isNumber, 279 | integer:function(v){return _.isNumber(v) && v===(v|0)}, 280 | boolean:_.isBoolean, 281 | date:_.isDate, 282 | array:_.isArray, 283 | object:function(v){return _.isObject(v) && !_.isArray(v) && !_.isDate(v)}, 284 | 'function':function(v){return _.isFunction(v)} 285 | } 286 | 287 | var found = 0; 288 | _.each(pn, function(p){ 289 | var check = checkmap[p.toLowerCase()] 290 | if( check ) { 291 | found += check(ctxt.point) 292 | } 293 | }) 294 | 295 | if( !found ) { 296 | return ctxt.util.fail(ctxt,cb) 297 | } 298 | 299 | return cb(); 300 | }, 301 | 302 | format$: function(ctxt,cb) { 303 | var pn = ctxt.util.proplist(ctxt) 304 | 305 | var checkmap = { 306 | datetime: function checkFormatRegex(v) { return /\d{4}-(0[1-9]|1[1-2])-([0-2]\d|3[0-1])T([0-1]\d|2[0-4]):[0-5]\d:[0-5]\dZ/.test(v) }, 307 | date: function checkFormatRegex(v) { return /\d{4}-[0-1][0-2]-[0-2]\d/.test(v) }, 308 | time: function checkFormatRegex(v) { return /([0-1]\d|2[0-4]):[0-5]\d:[0-5]\dZ/.test(v) }, 309 | utcmillisec: _.isNumber, 310 | re: _.isRegExp 311 | } 312 | 313 | var found = 0; 314 | _.each(pn, function(p){ 315 | var check = checkmap[p.toLowerCase()] 316 | if( check ) { 317 | found += check(ctxt.point) 318 | } 319 | }) 320 | 321 | if( !found ) { 322 | return ctxt.util.fail(ctxt,cb) 323 | } 324 | 325 | return cb(); 326 | }, 327 | 328 | default$: function(ctxt,cb) { 329 | return cb(); 330 | }, 331 | 332 | enum$: function(ctxt,cb) { 333 | var value = ctxt.point 334 | var okvals = ctxt.rule.spec 335 | 336 | var avalue = [] 337 | 338 | if ( _.isArray(value) ) { 339 | avalue = value 340 | } 341 | else { 342 | avalue[0] = value 343 | } 344 | 345 | var iserror = 0 346 | if( avalue ) { 347 | _.each(avalue, function(p){ 348 | iserror += (-1 == okvals.indexOf(p) ) 349 | }) 350 | } 351 | if ( iserror ){ 352 | return ctxt.util.fail(ctxt,cb) 353 | } 354 | 355 | return cb(); 356 | }, 357 | 358 | 359 | 360 | 361 | 362 | // internal rules 363 | 364 | 365 | iterate$: function(ctxt,cb) { 366 | var pn = [ctxt.rule.spec.prop] 367 | 368 | if( _.isObject(ctxt.point) ) { 369 | if( _.isArray(ctxt.point) ) { 370 | pn = gex( ctxt.rule.spec.prop ).on( _.range(ctxt.point.length)) 371 | } 372 | else { 373 | pn = _.keys(gex( ctxt.rule.spec.prop ).on(ctxt.point)) 374 | } 375 | } 376 | 377 | var subctxt = ctxt.util.clone(ctxt) 378 | subctxt.rules = ctxt.rule.spec.rules 379 | 380 | function eachprop(propI) { 381 | if( propI < pn.length ) { 382 | var p = pn[propI] 383 | 384 | var psubctxt = ctxt.util.clone(subctxt) 385 | 386 | psubctxt.parents = subctxt.parents.concat({prop:p,point:subctxt.point}) 387 | 388 | psubctxt.prop = p 389 | psubctxt.point = subctxt.point ? subctxt.point[p] : null 390 | 391 | psubctxt.util.execrules(psubctxt,function(err){ 392 | if( err ) return cb(err); 393 | eachprop(propI+1) 394 | }) 395 | } 396 | else cb() 397 | } 398 | eachprop(0) 399 | }, 400 | 401 | 402 | recurse$: function(ctxt,cb) { 403 | 404 | var recurctxt = ctxt.util.clone(ctxt) 405 | recurctxt.point = {$:ctxt.point} 406 | recurse('$',recurctxt.point,cb) 407 | 408 | function recurse(prop,point,cb) { 409 | if( !_.isObject(point) ) { 410 | return cb(null) 411 | } 412 | 413 | var pn = _.keys( point ) 414 | 415 | var subctxt = ctxt.util.clone(ctxt) 416 | subctxt.rules = ctxt.rule.spec.rules 417 | subctxt.parents = '$'!=prop?subctxt.parents.concat({prop:subctxt.prop,point:subctxt.point}):subctxt.parents 418 | 419 | function eachprop(propI,cb) { 420 | if( propI < pn.length ) { 421 | var p = pn[propI] 422 | var eachpropctxt = subctxt.util.clone(subctxt) 423 | eachpropctxt.prop = p 424 | eachpropctxt.point = point[p] 425 | eachpropctxt.parents = '$'!=p?eachpropctxt.parents.concat({prop:subctxt.prop,point:subctxt.point}):eachpropctxt.parents 426 | 427 | eachpropctxt.util.execrules(eachpropctxt,function(err){ 428 | if( err ) return cb(err); 429 | 430 | recurse(p,eachpropctxt.point,function(err){ 431 | if( err ) return cb(err); 432 | 433 | eachprop(propI+1,cb) 434 | }) 435 | }) 436 | } 437 | else { 438 | return cb(null) 439 | } 440 | } 441 | eachprop(0,cb) 442 | } 443 | }, 444 | 445 | 446 | descend$: function(ctxt,cb) { 447 | var subctxt = ctxt.util.clone(ctxt) 448 | var prop = ctxt.rule.spec.prop 449 | 450 | subctxt.rules = ctxt.rule.spec.rules 451 | subctxt.prop = prop 452 | subctxt.point = ctxt.point[prop] 453 | subctxt.parents = subctxt.parents.concat({prop:prop,point:ctxt.point}) 454 | 455 | subctxt.util.execrules(subctxt,function(err){ 456 | if( err ) return cb(err); 457 | cb(null) 458 | }) 459 | } 460 | } 461 | 462 | 463 | var msgsmap = { 464 | no_input$: "There is no input parameter", 465 | 466 | atmostone$: "At most one of these properties can be used at a time: '<%=value%>' (parent: <%=parentpath%>).", 467 | exactlyone$: "Exactly one of these properties must be used: '<%=value%>' (parent: <%=parentpath%>).", 468 | atleastone$: "At least one of these properties is required: '<%=value%>' (parent: <%=parentpath%>).", 469 | 470 | required$: "The property '<%=property%>' is missing and is always required (parent: <%=parentpath%>).", 471 | notempty$: "The property '<%=property%>' requires a value (parent: <%=parentpath%>).", 472 | 473 | string$: "The property '<%=property%>', with current value: '<%=value%>', must be a string (parent: <%=parentpath%>).", 474 | integer$: "The property '<%=property%>', with current value: '<%=value%>', must be a integer (parent: <%=parentpath%>).", 475 | number$: "The property '<%=property%>', with current value: '<%=value%>', must be a number (parent: <%=parentpath%>).", 476 | boolean$: "The property '<%=property%>', with current value: '<%=value%>', must be a boolean (parent: <%=parentpath%>).", 477 | date$: "The property '<%=property%>', with current value: '<%=value%>', must be a date (parent: <%=parentpath%>).", 478 | array$: "The property '<%=property%>', with current value: '<%=value%>', must be a array (parent: <%=parentpath%>).", 479 | object$: "The property '<%=property%>', with current value: '<%=value%>', must be a object (parent: <%=parentpath%>).", 480 | function$: "The property '<%=property%>', with current value: '<%=value%>', must be a function (parent: <%=parentpath%>).", 481 | 482 | only$: "The property '<%=property%>' is not recognised here. Recognised properties are: <%=rule.spec%> (parent: <%=parentpath%>).", 483 | 484 | wild$: "The value <%=value%> does not match the expression '<%=rule.spec%>' (parent: <%=parentpath%>).", 485 | re$: "The value <%=value%> does not match the regular expression <%=rule.spec%> (parent: <%=parentpath%>).", 486 | type$: "The value <%=value%> is not of type '<%=rule.spec%>' (parent: <%=parentpath%>).", 487 | format$: "The value <%=value%> is not of format '<%=rule.spec%>' (parent: <%=parentpath%>).", 488 | 489 | minlen$: "The property '<%=property%>', with current value: '<%=value%>', must have minimum length '<%=rule.spec%>' (parent: <%=parentpath%>).", 490 | maxlen$: "The property '<%=property%>', with current value: '<%=value%>', must have maximum length '<%=rule.spec%>' (parent: <%=parentpath%>).", 491 | 492 | eq$: "The value <%=value%> does not equal '<%=rule.spec%>' (parent: <%=parentpath%>).", 493 | lt$: "The value <%=value%> is not less than '<%=rule.spec%>' (parent: <%=parentpath%>).", 494 | lte$: "The value <%=value%> is not less than or equal with '<%=rule.spec%>' (parent: <%=parentpath%>).", 495 | gt$: "The value <%=value%> is not greater than '<%=rule.spec%>' (parent: <%=parentpath%>).", 496 | gte$: "The value <%=value%> is not not greater than or equal with '<%=rule.spec%>' (parent: <%=parentpath%>).", 497 | min$: "The value <%=value%> is not not greater than or equal with '<%=rule.spec%>' (parent: <%=parentpath%>).", 498 | max$: "The value <%=value%> is not less than or equal with '<%=rule.spec%>' (parent: <%=parentpath%>).", 499 | uniq$: "The value <%=value%> has duplicate elements.", 500 | enum$: "The value <%=value%> must be one of '<%=rule.spec%>' (parent: <%=parentpath%>)." 501 | 502 | } 503 | 504 | 505 | 506 | 507 | function clone(ctxt) { 508 | var newctxt = { 509 | rules:ctxt.rules, 510 | point:ctxt.point, 511 | msgs:ctxt.msgs, 512 | log:ctxt.log, 513 | parents:ctxt.parents, 514 | util:ctxt.util 515 | } 516 | return newctxt; 517 | } 518 | 519 | 520 | function formatparents(parents,topname) { 521 | var out = topname || 'top level' 522 | if( 0 < parents.length ) { 523 | out = _.map(parents,function(p){return p.prop}).join('.') 524 | if( topname ) { 525 | out = topname+'.'+out 526 | } 527 | } 528 | 529 | return out 530 | } 531 | 532 | 533 | function killcircles() { 534 | var seen = [] 535 | return function(k,v){ 536 | if( (_.includes||_.contains)(seen,v) ) return '[CIRCULAR-REFERENCE]'; 537 | seen.push(v) 538 | return v 539 | } 540 | } 541 | 542 | 543 | 544 | function fail() { 545 | var code = arguments[0] 546 | var ctxt = arguments[1] 547 | var cb = arguments[2] 548 | 549 | if( !cb ) { 550 | ctxt = arguments[0] 551 | cb = arguments[1] 552 | code = ctxt.rule.name 553 | } 554 | 555 | if(!cb) { 556 | throw new Error('Parambulator: ctxt.util.fail: callback undefined') 557 | } 558 | 559 | if(!ctxt) { 560 | return cb(new Error('Parambulator: ctxt.util.fail: ctxt undefined')) 561 | } 562 | 563 | var inserts = { 564 | property:ctxt.prop, 565 | value:ctxt.value||JSON.stringify(ctxt.point,killcircles()), 566 | point:ctxt.point, 567 | rule:ctxt.rule, 568 | parentpath:ctxt.util.formatparents(ctxt.parents), 569 | json:function(v){return JSON.stringify(v,killcircles())} 570 | } 571 | 572 | var msg = ctxt.msgs[code] || code 573 | 574 | if( _.isFunction(msg) ) { 575 | msg = msg(inserts,ctxt) 576 | } 577 | else { 578 | msg = ctxt.util.msgmods( msg ) 579 | msg = _.template(msg)(inserts) 580 | } 581 | 582 | var err = new Error( msg ) 583 | 584 | err.parambulator = { 585 | code: code, 586 | property: ctxt.prop, 587 | value: ctxt.point, 588 | expected: (ctxt.rule ? ctxt.rule.spec : void 0), 589 | parents: ctxt.parents, 590 | point: ctxt.point, 591 | rule: ctxt.rule} 592 | return cb(err) 593 | } 594 | 595 | 596 | // Return an ordered array of property names. The prefix __ is removed 597 | // from property names, both in the returned array, and the original 598 | // object. 599 | function proporder(obj) { 600 | var pn = [] 601 | for( var p in obj ) { 602 | var pc = p 603 | if( 0 === p.indexOf('__') ) { 604 | pc = p.substring(2) 605 | obj[pc] = obj[p] 606 | delete obj[p] 607 | } 608 | pn.push(pc) 609 | } 610 | return pn 611 | } 612 | 613 | 614 | /* 615 | 616 | name$ -> rule name 617 | name -> property names 618 | 619 | */ 620 | function Parambulator( spec, pref ) { 621 | var self = {} 622 | pref = pref || {} 623 | var defaultrules = [] 624 | 625 | if( _.isString(spec) ) { 626 | spec = jsonic(spec) 627 | } 628 | 629 | if( !spec || !_.isObject(spec) || _.isArray(spec) ) { 630 | throw new Error('Parambulator: spec argument is not an object') 631 | } 632 | 633 | if( _.cloneDeep ) { 634 | spec = _.cloneDeep(spec) 635 | } 636 | 637 | if( pref ) { 638 | if( exp.ownprefs ) { 639 | exp.ownprefs.validate(pref,function(err){ 640 | if( err ) throw err; 641 | }) 642 | } 643 | 644 | if( pref.valid && exp ) { 645 | var prefparams = exp({'**':pref.valid}) 646 | prefparams.validate(spec,function(err){ 647 | if( err ) throw err; 648 | }) 649 | } 650 | } 651 | 652 | 653 | //var rulenames = proporder(spec) 654 | var rules = parse(spec) 655 | parsedefault(spec, [], []) 656 | 657 | 658 | self.toString = function() { 659 | return JSON.stringify(rules) 660 | } 661 | 662 | 663 | /* 664 | * Example: 665 | * For a: {default$:123, type$:'number'} 666 | * creates {"pathnames":["a"],"pathtypes":[],"defaultvalue":123} 667 | * 668 | * for d: {type$: 'array', __0: {default$:'arraytest0'}} 669 | * creates {"pathnames":["d","0"],"pathtypes":["array"],"defaultvalue":"arraytest0"} 670 | */ 671 | function parsedefault(spec, path, pathtypes){ 672 | var innerulenames = [] 673 | var currentruletype = 'object' 674 | 675 | for(var name in spec){ 676 | if(spec.hasOwnProperty(name)) { 677 | var rule = spec[name] 678 | if ('default$' == name){ 679 | var defaultrule = {} 680 | defaultrule.pathnames = path 681 | 682 | defaultrule.pathtypes = pathtypes.splice(1,pathtypes.length) 683 | defaultrule.defaultvalue = rule 684 | defaultrules.push(defaultrule) 685 | } 686 | if ('type$' == name){ 687 | currentruletype = rule 688 | } 689 | if( _.isObject(rule) && !_.isArray(rule) ) { 690 | innerulenames.push(name) 691 | } 692 | } 693 | } 694 | 695 | for (var index in innerulenames){ 696 | if(innerulenames.hasOwnProperty(index)) { 697 | var inner_rule = spec[innerulenames[index]] 698 | 699 | var newpath = path.slice() 700 | newpath.push(innerulenames[index]) 701 | 702 | var newpathtypes = pathtypes.slice() 703 | newpathtypes.push(currentruletype) 704 | parsedefault(inner_rule, newpath, newpathtypes) 705 | } 706 | } 707 | } 708 | 709 | 710 | function buildrule(name,rulespec) { 711 | var func = (pref && pref.rules) ? pref.rules[name] : null 712 | if( !func ) { 713 | func = rulemap[name] 714 | } 715 | 716 | if( func ) { 717 | var rule = { 718 | func:func, 719 | name:name, 720 | spec:rulespec 721 | } 722 | return rule 723 | } 724 | else { 725 | throw new Error("Parambulator: Unknown rule: "+name) 726 | } 727 | } 728 | 729 | 730 | function parse(spec) { 731 | var rules = [] 732 | var names = proporder(spec) 733 | _.each(names, function(name){ 734 | var rulespec = spec[name] 735 | 736 | 737 | // enables multiple rules of same name 738 | if( 'list$' == name ) { 739 | for(var i = 0; i < rulespec.length; i++) { 740 | var rs = {} 741 | rs[rulespec[i][0]]=rulespec[i][1] 742 | rules.push(parse(rs)[0]) 743 | } 744 | } 745 | 746 | // enables quoting of property names that end in $ 747 | else if( 'prop$' == name ) { 748 | var subrules = parse( rulespec.rules ) 749 | var rule = buildrule('descend$',{prop:rulespec.name,rules:subrules}) 750 | rules.push(rule) 751 | } 752 | 753 | 754 | // it's a rule - name$ syntax 755 | else if( name.match(/\$$/) ) { 756 | if((name === 'required$' || name === 'notempty$') && 757 | pref.multiErrors && 758 | _.isArray(rulespec)) 759 | { 760 | _.each(rulespec, function(item) { 761 | var item_rule = buildrule(name,[item],spec) 762 | rules.push(item_rule) 763 | }) 764 | } 765 | else { 766 | var build_rule = buildrule(name,rulespec,spec) 767 | rules.push(build_rule) 768 | } 769 | } 770 | 771 | 772 | else if( '**' == name ) { 773 | var starstar_subrules = parse( rulespec ) 774 | var starstar_rule = 775 | buildrule('recurse$',{prop:name,rules:starstar_subrules}) 776 | rules.push(starstar_rule) 777 | } 778 | 779 | 780 | // it's a property 781 | else { 782 | if( _.isObject(rulespec) && !_.isArray(rulespec) ) { 783 | 784 | _.each( rulespec, function(v,p){ 785 | if( p.match(/\$$/) && _.isBoolean(v) && v ) { 786 | var rule = buildrule(p,name,spec) 787 | rules.push(rule) 788 | delete rulespec[p] 789 | } 790 | }) 791 | 792 | var prop_subrules = parse( rulespec ) 793 | var prop_rule = buildrule('iterate$',{prop:name,rules:prop_subrules}) 794 | rules.push(prop_rule) 795 | } 796 | 797 | 798 | else if( _.isString(rulespec) ) { 799 | 800 | // foo:'required$' 801 | if( rulespec.match(/\$/) ) { 802 | var rulespecs = rulespec.split(/\s*,\s*/) 803 | _.each( rulespecs, function( rulespec ) { 804 | rules.push( buildrule(rulespec,name) ) 805 | }) 806 | } 807 | 808 | // foo:'bar*' 809 | else { 810 | rules.push( buildrule('descend$',{ 811 | prop:name,rules:[buildrule('wild$',rulespec)] 812 | })) 813 | } 814 | } 815 | 816 | // TODO: else check for other types, and use eq$ !!! 817 | 818 | else if( _.isNumber(rulespec) ) { 819 | rules.push( buildrule('descend$',{ 820 | prop:name,rules:[buildrule('eq$',rulespec)] 821 | })) 822 | } 823 | 824 | else if( _.isBoolean(rulespec) ) { 825 | rules.push( buildrule('descend$',{ 826 | prop:name,rules:[buildrule('eq$',rulespec)] 827 | })) 828 | } 829 | 830 | else { 831 | throw new Error("Parambulator: Unrecognized rule specification: "+rulespec) 832 | } 833 | } 834 | 835 | }) 836 | 837 | return rules 838 | } 839 | 840 | 841 | var msgs = _.extend({},msgsmap,pref?pref.msgs:null) 842 | 843 | 844 | self.validate = function( args, cb ) { 845 | var reterr 846 | var callback = pref.callbackmaker ? pref.callbackmaker(cb) : (cb||function(){}) 847 | var wrapcb = function(err){ 848 | reterr=err 849 | // console.log('VALIDATE', JSON.stringify(arguments), arrayify(arguments)) 850 | callback.apply(null,arguments) 851 | } 852 | var errors = [] 853 | 854 | 855 | function execrules(ctxt,cb) { 856 | if( _.isUndefined(args) ) { 857 | return fail('no_input$',ctxt,cb) 858 | } 859 | 860 | var rules = ctxt.rules 861 | 862 | function execrule(ruleI) { 863 | if( ruleI < rules.length ) { 864 | var rule = rules[ruleI] 865 | 866 | if( !ctxt.point ) { 867 | return execrule(ruleI+1) 868 | } 869 | 870 | ctxt.rule = rule 871 | 872 | var specstr = JSON.stringify(rule.spec,killcircles()) 873 | 874 | ctxt.log.push('rule:'+rule.name+':exec:'+specstr) 875 | 876 | rule.func(ctxt, function(err) { 877 | if( err ) { 878 | if(_.isArray(err)) { 879 | // huh !? 880 | // errors = errors.concat(err) 881 | } else { 882 | errors.push(err) 883 | ctxt.log.push('rule:'+rule.name+':fail:'+specstr) 884 | } 885 | execrule(ruleI+1) 886 | } 887 | else { 888 | ctxt.log.push('rule:'+rule.name+':pass:'+specstr) 889 | execrule(ruleI+1) 890 | } 891 | }) 892 | } 893 | else { 894 | if(errors.length > 0 && pref.multiErrors) { 895 | cb(errors,{log:ctxt.log}) 896 | } else { 897 | var err = errors.length > 0 ? errors[0] : null; 898 | cb(err,{log:ctxt.log}) 899 | } 900 | 901 | } 902 | } 903 | 904 | execrule(0) 905 | } 906 | 907 | /* 908 | * Example: 909 | * For a: {"pathnames":["a"],"pathtypes":[],"defaultvalue":123} 910 | * creates {a:123} 911 | * 912 | * for d: {"pathnames":["d","0"],"pathtypes":["array"],"defaultvalue":"arraytest0"} 913 | * creates {d: ['arraytest0']} 914 | */ 915 | function validatedefaults(ctxt, cb){ 916 | for (var ruleindex in defaultrules){ 917 | var rule = defaultrules[ruleindex] 918 | var obj = ctxt.point 919 | 920 | for (var index in rule.pathnames){ 921 | var location = rule.pathnames[index] 922 | if ( !_.has(obj, location) ){ 923 | // if is last in path then just add default value 924 | if (index == rule.pathnames.length - 1){ 925 | obj[location] = rule.defaultvalue 926 | } 927 | // else create object or array and continue following path 928 | else{ 929 | var type = rule.pathtypes[index] 930 | var newobj 931 | if ('object' == type){ 932 | newobj = {} 933 | } 934 | else if ('array' == type){ 935 | newobj = [] 936 | } 937 | else{ 938 | // cannot continue, call cb and return false; 939 | ctxt.util.fail('default$',ctxt,cb) 940 | return; 941 | } 942 | obj[location] = newobj 943 | obj = newobj 944 | } 945 | } 946 | else{ 947 | obj = obj[location] 948 | } 949 | } 950 | } 951 | cb(); 952 | } 953 | 954 | var ctxt = {rules:rules,point:args,msgs:msgs,log:[],parents:[]} 955 | ctxt.util = { 956 | formatparents:function(){ 957 | var args = arrayify(arguments) 958 | args[1] = pref && pref.topname && !args[1] ? pref.topname : args[1] 959 | return formatparents.apply(null,args) 960 | }, 961 | msgmods:function(msg) { 962 | return (pref.msgprefix||'') + msg + (pref.msgsuffix||'') 963 | }, 964 | fail:fail,proplist:proplist,execrules:execrules,clone:clone} 965 | 966 | validatedefaults(ctxt,function(err){ 967 | if (err){ 968 | wrapcb(err,{log:ctxt.log}) 969 | } 970 | else { 971 | execrules(ctxt,function(err){ 972 | wrapcb(err,{log:ctxt.log}) 973 | }) 974 | } 975 | }) 976 | 977 | // only works if no async calls inside rules 978 | return reterr 979 | } 980 | 981 | 982 | return self 983 | } 984 | 985 | 986 | var exp = function(){ 987 | var args = arrayify(arguments) 988 | return Parambulator.apply(this,args) 989 | } 990 | 991 | // this is where Parambulator validates it's own input 992 | exp.ownparams = new Parambulator({ 993 | '**': 994 | { 995 | strings$: ['required$','notempty$','atmostone$','exactlyone$','atleastone$'], 996 | list$:[ 997 | ['prop$',{name:'wild$', rules:{type$:'string'}}], 998 | ['prop$',{name:'type$', rules:{type$:['string','array']}}], 999 | ['prop$',{name:'format$', rules:{type$:['string','array']}}], 1000 | ['prop$',{name:'re$', rules:{type$:'string'}}], 1001 | 1002 | ['prop$',{name:'type$', rules:{enum$:['string','number','integer','boolean','date','array','object',]}}], 1003 | ['prop$',{name:'format$', rules:{enum$:['datetime','date','time','utcmillisec', 're']}}], 1004 | 1005 | ['prop$',{name:'minlen$', rules:{type$:'number'}}], 1006 | ['prop$',{name:'maxlen$', rules:{type$:'number'}}], 1007 | 1008 | ['prop$',{name:'enum$', rules:{type$:'array'}}], 1009 | ['prop$',{name:'list$', rules:{type$:'array'}}], 1010 | 1011 | // This can work only after #1 is implemented 1012 | // these should also work for strings, right? 1013 | // ['prop$',{name:'lt$', rules:{type$:['number','date']}}], 1014 | // ['prop$',{name:'lte$', rules:{type$:['number','date']}}], 1015 | // ['prop$',{name:'gt$', rules:{type$:['number','date']}}], 1016 | // ['prop$',{name:'gte$', rules:{type$:['number','date']}}], 1017 | // ['prop$',{name:'min$', rules:{type$:['number','date']}}], 1018 | // ['prop$',{name:'max$', rules:{type$:['number','date']}}], 1019 | 1020 | ['prop$',{name:'uniq$', rules:{type$:'array'}}] 1021 | ] 1022 | } 1023 | }, { 1024 | __ownparams__:true, 1025 | rules: { 1026 | strings$: function(ctxt,cb){ 1027 | var pn = ctxt.rule.spec 1028 | 1029 | if( !_.isArray(pn) ) { 1030 | pn = [''+pn] 1031 | } 1032 | 1033 | for( var pI = 0; pI < pn.length; pI++ ) { 1034 | var p = pn[pI] 1035 | var val = ctxt.point[p] 1036 | 1037 | if( !_.isUndefined(val) ) { 1038 | if( _.isString(val) ) { 1039 | // ok 1040 | } 1041 | else if( _.isArray(val) ) { 1042 | for(var i = 0; i < val.length; i++ ) { 1043 | if( !_.isString(val[i]) ) { 1044 | ctxt.prop = p 1045 | return ctxt.util.fail(ctxt,cb) 1046 | } 1047 | } 1048 | } 1049 | else { 1050 | ctxt.prop = p 1051 | return ctxt.util.fail(ctxt,cb) 1052 | } 1053 | } 1054 | } 1055 | 1056 | cb(null) 1057 | } 1058 | }, 1059 | msgs: { 1060 | 'strings$': 'The <%=property%> rule needs a string or array of strings (property: <%=parentpath%>).' 1061 | }, 1062 | topname:'spec' 1063 | }) 1064 | 1065 | 1066 | // validate preferences 1067 | exp.ownprefs = new Parambulator({ 1068 | object$:['valid','rules','msgs'], 1069 | string$:['topname','msgprefix','msgsuffix'], 1070 | boolean$:['multiErrors'], 1071 | function$:['callbackmaker'], 1072 | only$:['valid','rules','msgs', 'topname','msgprefix','msgsuffix', 'multiErrors', 'callbackmaker'] 1073 | },{ 1074 | topname:'prefs' 1075 | }) 1076 | 1077 | exp.Parambulator = Parambulator 1078 | 1079 | 1080 | //module.exports = exp 1081 | 1082 | 1083 | 1084 | root.parambulator = exp 1085 | 1086 | exp.noConflict = function() { 1087 | root.parambulator = previous_parambulator; 1088 | return self; 1089 | } 1090 | 1091 | 1092 | if( typeof exports !== 'undefined' ) { 1093 | if( typeof module !== 'undefined' && module.exports ) { 1094 | exports = module.exports = exp 1095 | } 1096 | exports.parambulator = exp 1097 | } 1098 | else { 1099 | root.parambulator = exp 1100 | } 1101 | 1102 | }).call(this); 1103 | --------------------------------------------------------------------------------