├── .gitignore ├── .travis.yml ├── test ├── json-schema-draft4 │ ├── nullAndObject.json │ ├── nullAndFormat.json │ ├── pattern.json │ ├── minItems.json │ ├── maxLength.json │ ├── minLength.json │ ├── maxItems.json │ ├── minProperties.json │ ├── maxProperties.json │ ├── definitions.json │ ├── required.json │ ├── maximum.json │ ├── minimum.json │ ├── items.json │ ├── default.json │ ├── anyOf.json │ ├── oneOf.json │ ├── enum.json │ ├── refRemote.json │ ├── additionalItems.json │ ├── not.json │ ├── uniqueItems.json │ ├── multipleOf.json │ ├── additionalProperties.json │ ├── properties.json │ ├── allOf.json │ ├── bignum.json │ ├── dependencies.json │ ├── patternProperties.json │ ├── ref.json │ ├── format.json │ └── type.json ├── json-schema.js ├── fixtures │ └── cosmic.js └── misc.js ├── require.js ├── example.js ├── package.json ├── LICENSE ├── formats.js ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | cosmicrealms.com 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /test/json-schema-draft4/nullAndObject.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "multiple types of null and object containing properties", 4 | "schema": { 5 | "type": ["null", "object"], 6 | "properties": { 7 | "foo": {} 8 | } 9 | }, 10 | "tests": [ 11 | { 12 | "description": "null is valid", 13 | "data": null, 14 | "valid": true 15 | } 16 | ] 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /test/json-schema-draft4/nullAndFormat.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "validation of null and format", 4 | "schema": {"type": ["null", "string"], "format": "date-time"}, 5 | "tests": [ 6 | { 7 | "description": "a valid date-time string", 8 | "data": "1963-06-19T08:30:06.283185Z", 9 | "valid": true 10 | }, 11 | { 12 | "description": "allow null", 13 | "data": null, 14 | "valid": true 15 | } 16 | ] 17 | } 18 | ] -------------------------------------------------------------------------------- /require.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var compile = require('./') 4 | 5 | delete require.cache[require.resolve(__filename)] 6 | 7 | module.exports = function(file, opts) { 8 | file = path.join(path.dirname(module.parent.filename), file) 9 | if (!fs.existsSync(file) && fs.existsSync(file+'.schema')) file += '.schema' 10 | if (!fs.existsSync(file) && fs.existsSync(file+'.json')) file += '.json' 11 | return compile(fs.readFileSync(file, 'utf-8'), opts) 12 | } 13 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var validator = require('./') 2 | 3 | var validate = validator({ 4 | type: 'object', 5 | properties: { 6 | hello: { 7 | required: true, 8 | type: 'string' 9 | } 10 | } 11 | }) 12 | 13 | console.log('should be valid', validate({hello: 'world'})) 14 | console.log('should not be valid', validate({})) 15 | 16 | // get the last error message by checking validate.error 17 | // the following will print "data.hello is required" 18 | console.log('the errors were:', validate.errors) 19 | -------------------------------------------------------------------------------- /test/json-schema-draft4/pattern.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "pattern validation", 4 | "schema": {"pattern": "^a*$"}, 5 | "tests": [ 6 | { 7 | "description": "a matching pattern is valid", 8 | "data": "aaa", 9 | "valid": true 10 | }, 11 | { 12 | "description": "a non-matching pattern is invalid", 13 | "data": "abc", 14 | "valid": false 15 | }, 16 | { 17 | "description": "ignores non-strings", 18 | "data": true, 19 | "valid": true 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /test/json-schema.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var fs = require('fs') 3 | var validator = require('../') 4 | 5 | var files = fs.readdirSync(__dirname+'/json-schema-draft4') 6 | .map(function(file) { 7 | if (file === 'definitions.json') return null 8 | if (file === 'refRemote.json') return null 9 | return require('./json-schema-draft4/'+file) 10 | }) 11 | .filter(Boolean) 12 | 13 | files.forEach(function(file) { 14 | file.forEach(function(f) { 15 | tape('json-schema-test-suite '+f.description, function(t) { 16 | var validate = validator(f.schema) 17 | f.tests.forEach(function(test) { 18 | t.same(validate(test.data), test.valid, test.description) 19 | }) 20 | t.end() 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /test/json-schema-draft4/minItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minItems validation", 4 | "schema": {"minItems": 1}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": [1, 2], 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": [1], 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": [], 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-arrays", 23 | "data": "", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /test/json-schema-draft4/maxLength.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxLength validation", 4 | "schema": {"maxLength": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": "f", 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": "fo", 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": "foo", 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-strings", 23 | "data": 100, 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /test/json-schema-draft4/minLength.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minLength validation", 4 | "schema": {"minLength": 2}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": "foo", 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": "fo", 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": "f", 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-strings", 23 | "data": 1, 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /test/json-schema-draft4/maxItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxItems validation", 4 | "schema": {"maxItems": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": [1], 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": [1, 2], 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": [1, 2, 3], 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-arrays", 23 | "data": "foobar", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /test/json-schema-draft4/minProperties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minProperties validation", 4 | "schema": {"minProperties": 1}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": {"foo": 1, "bar": 2}, 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": {"foo": 1}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": {}, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-objects", 23 | "data": "", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /test/json-schema-draft4/maxProperties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxProperties validation", 4 | "schema": {"maxProperties": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": {"foo": 1}, 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": {"foo": 1, "bar": 2}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": {"foo": 1, "bar": 2, "baz": 3}, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-objects", 23 | "data": "foobar", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@postman/is-my-json-valid", 3 | "version": "2.18.0", 4 | "description": "A JSONSchema validator that uses code generation to be extremely fast", 5 | "main": "index.js", 6 | "dependencies": { 7 | "generate-function": "^2.0.0", 8 | "generate-object-property": "^1.1.0", 9 | "jsonpointer": "^4.0.0", 10 | "xtend": "^4.0.0" 11 | }, 12 | "devDependencies": { 13 | "tape": "^4.0.0" 14 | }, 15 | "scripts": { 16 | "test": "tape test/*.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/postmanlabs/is-my-json-valid" 21 | }, 22 | "keywords": [ 23 | "json", 24 | "schema", 25 | "orderly", 26 | "jsonschema" 27 | ], 28 | "author": "Mathias Buus", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/postmanlabs/is-my-json-valid/issues" 32 | }, 33 | "homepage": "https://github.com/postmanlabs/is-my-json-valid" 34 | } 35 | -------------------------------------------------------------------------------- /test/json-schema-draft4/definitions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "valid definition", 4 | "schema": {"$ref": "http://json-schema.org/draft-04/schema#"}, 5 | "tests": [ 6 | { 7 | "description": "valid definition schema", 8 | "data": { 9 | "definitions": { 10 | "foo": {"type": "integer"} 11 | } 12 | }, 13 | "valid": true 14 | } 15 | ] 16 | }, 17 | { 18 | "description": "invalid definition", 19 | "schema": {"$ref": "http://json-schema.org/draft-04/schema#"}, 20 | "tests": [ 21 | { 22 | "description": "invalid definition schema", 23 | "data": { 24 | "definitions": { 25 | "foo": {"type": 1} 26 | } 27 | }, 28 | "valid": false 29 | } 30 | ] 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /test/json-schema-draft4/required.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "required validation", 4 | "schema": { 5 | "properties": { 6 | "foo": {}, 7 | "bar": {} 8 | }, 9 | "required": ["foo"] 10 | }, 11 | "tests": [ 12 | { 13 | "description": "present required property is valid", 14 | "data": {"foo": 1}, 15 | "valid": true 16 | }, 17 | { 18 | "description": "non-present required property is invalid", 19 | "data": {"bar": 1}, 20 | "valid": false 21 | } 22 | ] 23 | }, 24 | { 25 | "description": "required default validation", 26 | "schema": { 27 | "properties": { 28 | "foo": {} 29 | } 30 | }, 31 | "tests": [ 32 | { 33 | "description": "not required by default", 34 | "data": {}, 35 | "valid": true 36 | } 37 | ] 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mathias Buus 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. -------------------------------------------------------------------------------- /test/json-schema-draft4/maximum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maximum validation", 4 | "schema": {"maximum": 3.0}, 5 | "tests": [ 6 | { 7 | "description": "below the maximum is valid", 8 | "data": 2.6, 9 | "valid": true 10 | }, 11 | { 12 | "description": "above the maximum is invalid", 13 | "data": 3.5, 14 | "valid": false 15 | }, 16 | { 17 | "description": "ignores non-numbers", 18 | "data": "x", 19 | "valid": true 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "exclusiveMaximum validation", 25 | "schema": { 26 | "maximum": 3.0, 27 | "exclusiveMaximum": true 28 | }, 29 | "tests": [ 30 | { 31 | "description": "below the maximum is still valid", 32 | "data": 2.2, 33 | "valid": true 34 | }, 35 | { 36 | "description": "boundary point is invalid", 37 | "data": 3.0, 38 | "valid": false 39 | } 40 | ] 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /test/json-schema-draft4/minimum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minimum validation", 4 | "schema": {"minimum": 1.1}, 5 | "tests": [ 6 | { 7 | "description": "above the minimum is valid", 8 | "data": 2.6, 9 | "valid": true 10 | }, 11 | { 12 | "description": "below the minimum is invalid", 13 | "data": 0.6, 14 | "valid": false 15 | }, 16 | { 17 | "description": "ignores non-numbers", 18 | "data": "x", 19 | "valid": true 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "exclusiveMinimum validation", 25 | "schema": { 26 | "minimum": 1.1, 27 | "exclusiveMinimum": true 28 | }, 29 | "tests": [ 30 | { 31 | "description": "above the minimum is still valid", 32 | "data": 1.2, 33 | "valid": true 34 | }, 35 | { 36 | "description": "boundary point is invalid", 37 | "data": 1.1, 38 | "valid": false 39 | } 40 | ] 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /test/json-schema-draft4/items.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "a schema given for items", 4 | "schema": { 5 | "items": {"type": "integer"} 6 | }, 7 | "tests": [ 8 | { 9 | "description": "valid items", 10 | "data": [ 1, 2, 3 ], 11 | "valid": true 12 | }, 13 | { 14 | "description": "wrong type of items", 15 | "data": [1, "x"], 16 | "valid": false 17 | }, 18 | { 19 | "description": "ignores non-arrays", 20 | "data": {"foo" : "bar"}, 21 | "valid": true 22 | } 23 | ] 24 | }, 25 | { 26 | "description": "an array of schemas for items", 27 | "schema": { 28 | "items": [ 29 | {"type": "integer"}, 30 | {"type": "string"} 31 | ] 32 | }, 33 | "tests": [ 34 | { 35 | "description": "correct types", 36 | "data": [ 1, "foo" ], 37 | "valid": true 38 | }, 39 | { 40 | "description": "wrong types", 41 | "data": [ "foo", 1 ], 42 | "valid": false 43 | } 44 | ] 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /test/json-schema-draft4/default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "invalid type for default", 4 | "schema": { 5 | "properties": { 6 | "foo": { 7 | "type": "integer", 8 | "default": [] 9 | } 10 | } 11 | }, 12 | "tests": [ 13 | { 14 | "description": "valid when property is specified", 15 | "data": {"foo": 13}, 16 | "valid": true 17 | }, 18 | { 19 | "description": "still valid when the invalid default is used", 20 | "data": {}, 21 | "valid": true 22 | } 23 | ] 24 | }, 25 | { 26 | "description": "invalid string value for default", 27 | "schema": { 28 | "properties": { 29 | "bar": { 30 | "type": "string", 31 | "minLength": 4, 32 | "default": "bad" 33 | } 34 | } 35 | }, 36 | "tests": [ 37 | { 38 | "description": "valid when property is specified", 39 | "data": {"bar": "good"}, 40 | "valid": true 41 | }, 42 | { 43 | "description": "still valid when the invalid default is used", 44 | "data": {}, 45 | "valid": true 46 | } 47 | ] 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /test/json-schema-draft4/anyOf.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "anyOf", 4 | "schema": { 5 | "anyOf": [ 6 | { 7 | "type": "integer" 8 | }, 9 | { 10 | "minimum": 2 11 | } 12 | ] 13 | }, 14 | "tests": [ 15 | { 16 | "description": "first anyOf valid", 17 | "data": 1, 18 | "valid": true 19 | }, 20 | { 21 | "description": "second anyOf valid", 22 | "data": 2.5, 23 | "valid": true 24 | }, 25 | { 26 | "description": "both anyOf valid", 27 | "data": 3, 28 | "valid": true 29 | }, 30 | { 31 | "description": "neither anyOf valid", 32 | "data": 1.5, 33 | "valid": false 34 | } 35 | ] 36 | }, 37 | { 38 | "description": "anyOf with base schema", 39 | "schema": { 40 | "type": "string", 41 | "anyOf" : [ 42 | { 43 | "maxLength": 2 44 | }, 45 | { 46 | "minLength": 4 47 | } 48 | ] 49 | }, 50 | "tests": [ 51 | { 52 | "description": "mismatch base schema", 53 | "data": 3, 54 | "valid": false 55 | }, 56 | { 57 | "description": "one anyOf valid", 58 | "data": "foobar", 59 | "valid": true 60 | }, 61 | { 62 | "description": "both anyOf invalid", 63 | "data": "foo", 64 | "valid": false 65 | } 66 | ] 67 | } 68 | ] 69 | -------------------------------------------------------------------------------- /test/json-schema-draft4/oneOf.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "oneOf", 4 | "schema": { 5 | "oneOf": [ 6 | { 7 | "type": "integer" 8 | }, 9 | { 10 | "minimum": 2 11 | } 12 | ] 13 | }, 14 | "tests": [ 15 | { 16 | "description": "first oneOf valid", 17 | "data": 1, 18 | "valid": true 19 | }, 20 | { 21 | "description": "second oneOf valid", 22 | "data": 2.5, 23 | "valid": true 24 | }, 25 | { 26 | "description": "both oneOf valid", 27 | "data": 3, 28 | "valid": false 29 | }, 30 | { 31 | "description": "neither oneOf valid", 32 | "data": 1.5, 33 | "valid": false 34 | } 35 | ] 36 | }, 37 | { 38 | "description": "oneOf with base schema", 39 | "schema": { 40 | "type": "string", 41 | "oneOf" : [ 42 | { 43 | "minLength": 2 44 | }, 45 | { 46 | "maxLength": 4 47 | } 48 | ] 49 | }, 50 | "tests": [ 51 | { 52 | "description": "mismatch base schema", 53 | "data": 3, 54 | "valid": false 55 | }, 56 | { 57 | "description": "one oneOf valid", 58 | "data": "foobar", 59 | "valid": true 60 | }, 61 | { 62 | "description": "both oneOf valid", 63 | "data": "foo", 64 | "valid": false 65 | } 66 | ] 67 | } 68 | ] 69 | -------------------------------------------------------------------------------- /test/json-schema-draft4/enum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "simple enum validation", 4 | "schema": {"enum": [1, 2, 3]}, 5 | "tests": [ 6 | { 7 | "description": "one of the enum is valid", 8 | "data": 1, 9 | "valid": true 10 | }, 11 | { 12 | "description": "something else is invalid", 13 | "data": 4, 14 | "valid": false 15 | } 16 | ] 17 | }, 18 | { 19 | "description": "heterogeneous enum validation", 20 | "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, 21 | "tests": [ 22 | { 23 | "description": "one of the enum is valid", 24 | "data": [], 25 | "valid": true 26 | }, 27 | { 28 | "description": "something else is invalid", 29 | "data": null, 30 | "valid": false 31 | }, 32 | { 33 | "description": "objects are deep compared", 34 | "data": {"foo": false}, 35 | "valid": false 36 | } 37 | ] 38 | }, 39 | { 40 | "description": "enums in properties", 41 | "schema": { 42 | "type":"object", 43 | "properties": { 44 | "foo": {"enum":["foo"]}, 45 | "bar": {"enum":["bar"]} 46 | }, 47 | "required": ["bar"] 48 | }, 49 | "tests": [ 50 | { 51 | "description": "both properties are valid", 52 | "data": {"foo":"foo", "bar":"bar"}, 53 | "valid": true 54 | }, 55 | { 56 | "description": "missing optional property is valid", 57 | "data": {"bar":"bar"}, 58 | "valid": true 59 | }, 60 | { 61 | "description": "missing required property is invalid", 62 | "data": {"foo":"foo"}, 63 | "valid": false 64 | }, 65 | { 66 | "description": "missing all properties is invalid", 67 | "data": {}, 68 | "valid": false 69 | } 70 | ] 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /test/json-schema-draft4/refRemote.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "remote ref", 4 | "schema": {"$ref": "http://localhost:1234/integer.json"}, 5 | "tests": [ 6 | { 7 | "description": "remote ref valid", 8 | "data": 1, 9 | "valid": true 10 | }, 11 | { 12 | "description": "remote ref invalid", 13 | "data": "a", 14 | "valid": false 15 | } 16 | ] 17 | }, 18 | { 19 | "description": "fragment within remote ref", 20 | "schema": {"$ref": "http://localhost:1234/subSchemas.json#/integer"}, 21 | "tests": [ 22 | { 23 | "description": "remote fragment valid", 24 | "data": 1, 25 | "valid": true 26 | }, 27 | { 28 | "description": "remote fragment invalid", 29 | "data": "a", 30 | "valid": false 31 | } 32 | ] 33 | }, 34 | { 35 | "description": "ref within remote ref", 36 | "schema": { 37 | "$ref": "http://localhost:1234/subSchemas.json#/refToInteger" 38 | }, 39 | "tests": [ 40 | { 41 | "description": "ref within ref valid", 42 | "data": 1, 43 | "valid": true 44 | }, 45 | { 46 | "description": "ref within ref invalid", 47 | "data": "a", 48 | "valid": false 49 | } 50 | ] 51 | }, 52 | { 53 | "description": "change resolution scope", 54 | "schema": { 55 | "id": "http://localhost:1234/", 56 | "items": { 57 | "id": "folder/", 58 | "items": {"$ref": "folderInteger.json"} 59 | } 60 | }, 61 | "tests": [ 62 | { 63 | "description": "changed scope ref valid", 64 | "data": [[1]], 65 | "valid": true 66 | }, 67 | { 68 | "description": "changed scope ref invalid", 69 | "data": [["a"]], 70 | "valid": false 71 | } 72 | ] 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /formats.js: -------------------------------------------------------------------------------- 1 | exports['date-time'] = /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}[tT ]\d{2}:\d{2}:\d{2}(\.\d+)?([zZ]|[+-]\d{2}:\d{2})$/ 2 | exports['date'] = /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/ 3 | exports['time'] = /^\d{2}:\d{2}:\d{2}$/ 4 | exports['email'] = /^\S+@\S+$/ 5 | exports['ip-address'] = exports['ipv4'] = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ 6 | exports['ipv6'] = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/ 7 | exports['uri'] = /^[a-zA-Z][a-zA-Z0-9+-.]*:[^\s]*$/ 8 | exports['uri-template'] = /^[a-zA-Z{][a-zA-Z0-9+-.{}]*:[^\s]*$/ 9 | exports['color'] = /(#?([0-9A-Fa-f]{3,6})\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\))/ 10 | exports['hostname'] = /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$/ 11 | exports['alpha'] = /^[a-zA-Z]+$/ 12 | exports['alphanumeric'] = /^[a-zA-Z0-9]+$/ 13 | exports['style'] = /\s*(.+?):\s*([^;]+);?/g 14 | exports['phone'] = /^\+(?:[0-9] ?){6,14}[0-9]$/ 15 | exports['utc-millisec'] = /^[0-9]{1,15}\.?[0-9]{0,15}$/ 16 | -------------------------------------------------------------------------------- /test/json-schema-draft4/additionalItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "additionalItems as schema", 4 | "schema": { 5 | "items": [{}], 6 | "additionalItems": {"type": "integer"} 7 | }, 8 | "tests": [ 9 | { 10 | "description": "additional items match schema", 11 | "data": [ null, 2, 3, 4 ], 12 | "valid": true 13 | }, 14 | { 15 | "description": "additional items do not match schema", 16 | "data": [ null, 2, 3, "foo" ], 17 | "valid": false 18 | } 19 | ] 20 | }, 21 | { 22 | "description": "items is schema, no additionalItems", 23 | "schema": { 24 | "items": {}, 25 | "additionalItems": false 26 | }, 27 | "tests": [ 28 | { 29 | "description": "all items match schema", 30 | "data": [ 1, 2, 3, 4, 5 ], 31 | "valid": true 32 | } 33 | ] 34 | }, 35 | { 36 | "description": "array of items with no additionalItems", 37 | "schema": { 38 | "items": [{}, {}, {}], 39 | "additionalItems": false 40 | }, 41 | "tests": [ 42 | { 43 | "description": "no additional items present", 44 | "data": [ 1, 2, 3 ], 45 | "valid": true 46 | }, 47 | { 48 | "description": "additional items are not permitted", 49 | "data": [ 1, 2, 3, 4 ], 50 | "valid": false 51 | } 52 | ] 53 | }, 54 | { 55 | "description": "additionalItems as false without items", 56 | "schema": {"additionalItems": false}, 57 | "tests": [ 58 | { 59 | "description": 60 | "items defaults to empty schema so everything is valid", 61 | "data": [ 1, 2, 3, 4, 5 ], 62 | "valid": true 63 | }, 64 | { 65 | "description": "ignores non-arrays", 66 | "data": {"foo" : "bar"}, 67 | "valid": true 68 | } 69 | ] 70 | }, 71 | { 72 | "description": "additionalItems are allowed by default", 73 | "schema": {"items": [{"type": "integer"}]}, 74 | "tests": [ 75 | { 76 | "description": "only the first item is validated", 77 | "data": [1, "foo", false], 78 | "valid": true 79 | } 80 | ] 81 | } 82 | ] 83 | -------------------------------------------------------------------------------- /test/json-schema-draft4/not.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "not", 4 | "schema": { 5 | "not": {"type": "integer"} 6 | }, 7 | "tests": [ 8 | { 9 | "description": "allowed", 10 | "data": "foo", 11 | "valid": true 12 | }, 13 | { 14 | "description": "disallowed", 15 | "data": 1, 16 | "valid": false 17 | } 18 | ] 19 | }, 20 | { 21 | "description": "not multiple types", 22 | "schema": { 23 | "not": {"type": ["integer", "boolean"]} 24 | }, 25 | "tests": [ 26 | { 27 | "description": "valid", 28 | "data": "foo", 29 | "valid": true 30 | }, 31 | { 32 | "description": "mismatch", 33 | "data": 1, 34 | "valid": false 35 | }, 36 | { 37 | "description": "other mismatch", 38 | "data": true, 39 | "valid": false 40 | } 41 | ] 42 | }, 43 | { 44 | "description": "not more complex schema", 45 | "schema": { 46 | "not": { 47 | "type": "object", 48 | "properties": { 49 | "foo": { 50 | "type": "string" 51 | } 52 | } 53 | } 54 | }, 55 | "tests": [ 56 | { 57 | "description": "match", 58 | "data": 1, 59 | "valid": true 60 | }, 61 | { 62 | "description": "other match", 63 | "data": {"foo": 1}, 64 | "valid": true 65 | }, 66 | { 67 | "description": "mismatch", 68 | "data": {"foo": "bar"}, 69 | "valid": false 70 | } 71 | ] 72 | }, 73 | { 74 | "description": "forbidden property", 75 | "schema": { 76 | "properties": { 77 | "foo": { 78 | "not": {} 79 | } 80 | } 81 | }, 82 | "tests": [ 83 | { 84 | "description": "property present", 85 | "data": {"foo": 1, "bar": 2}, 86 | "valid": false 87 | }, 88 | { 89 | "description": "property absent", 90 | "data": {"bar": 1, "baz": 2}, 91 | "valid": true 92 | } 93 | ] 94 | } 95 | 96 | ] 97 | -------------------------------------------------------------------------------- /test/json-schema-draft4/uniqueItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "uniqueItems validation", 4 | "schema": {"uniqueItems": true}, 5 | "tests": [ 6 | { 7 | "description": "unique array of integers is valid", 8 | "data": [1, 2], 9 | "valid": true 10 | }, 11 | { 12 | "description": "non-unique array of integers is invalid", 13 | "data": [1, 1], 14 | "valid": false 15 | }, 16 | { 17 | "description": "numbers are unique if mathematically unequal", 18 | "data": [1.0, 1.00, 1], 19 | "valid": false 20 | }, 21 | { 22 | "description": "unique array of objects is valid", 23 | "data": [{"foo": "bar"}, {"foo": "baz"}], 24 | "valid": true 25 | }, 26 | { 27 | "description": "non-unique array of objects is invalid", 28 | "data": [{"foo": "bar"}, {"foo": "bar"}], 29 | "valid": false 30 | }, 31 | { 32 | "description": "unique array of nested objects is valid", 33 | "data": [ 34 | {"foo": {"bar" : {"baz" : true}}}, 35 | {"foo": {"bar" : {"baz" : false}}} 36 | ], 37 | "valid": true 38 | }, 39 | { 40 | "description": "non-unique array of nested objects is invalid", 41 | "data": [ 42 | {"foo": {"bar" : {"baz" : true}}}, 43 | {"foo": {"bar" : {"baz" : true}}} 44 | ], 45 | "valid": false 46 | }, 47 | { 48 | "description": "unique array of arrays is valid", 49 | "data": [["foo"], ["bar"]], 50 | "valid": true 51 | }, 52 | { 53 | "description": "non-unique array of arrays is invalid", 54 | "data": [["foo"], ["foo"]], 55 | "valid": false 56 | }, 57 | { 58 | "description": "1 and true are unique", 59 | "data": [1, true], 60 | "valid": true 61 | }, 62 | { 63 | "description": "0 and false are unique", 64 | "data": [0, false], 65 | "valid": true 66 | }, 67 | { 68 | "description": "unique heterogeneous types are valid", 69 | "data": [{}, [1], true, null, 1], 70 | "valid": true 71 | }, 72 | { 73 | "description": "non-unique heterogeneous types are invalid", 74 | "data": [{}, [1], true, null, {}, 1], 75 | "valid": false 76 | } 77 | ] 78 | } 79 | ] 80 | -------------------------------------------------------------------------------- /test/json-schema-draft4/multipleOf.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "by int", 4 | "schema": {"multipleOf": 2}, 5 | "tests": [ 6 | { 7 | "description": "int by int", 8 | "data": 10, 9 | "valid": true 10 | }, 11 | { 12 | "description": "int by int fail", 13 | "data": 7, 14 | "valid": false 15 | }, 16 | { 17 | "description": "ignores non-numbers", 18 | "data": "foo", 19 | "valid": true 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "by number", 25 | "schema": {"multipleOf": 1.5}, 26 | "tests": [ 27 | { 28 | "description": "zero is multiple of anything", 29 | "data": 0, 30 | "valid": true 31 | }, 32 | { 33 | "description": "4.5 is multiple of 1.5", 34 | "data": 4.5, 35 | "valid": true 36 | }, 37 | { 38 | "description": "35 is not multiple of 1.5", 39 | "data": 35, 40 | "valid": false 41 | } 42 | ] 43 | }, 44 | { 45 | "description": "by small number", 46 | "schema": {"multipleOf": 0.0001}, 47 | "tests": [ 48 | { 49 | "description": "0.0075 is multiple of 0.0001", 50 | "data": 0.0075, 51 | "valid": true 52 | }, 53 | { 54 | "description": "0.00751 is not multiple of 0.0001", 55 | "data": 0.00751, 56 | "valid": false 57 | } 58 | ] 59 | }, 60 | { 61 | "description": "by decimal number where floating point precision is wrong", 62 | "schema": {"multipleOf": 0.01}, 63 | "tests": [ 64 | { 65 | "description": "Number 2 is multiple of 0.01", 66 | "data": 2, 67 | "valid": true 68 | }, 69 | { 70 | "description": "Number 2.1 is multiple of 0.01", 71 | "data": 2.1, 72 | "valid": true 73 | }, 74 | { 75 | "description": "Number 2.2 is multiple of 0.01", 76 | "data": 2.2, 77 | "valid": true 78 | }, 79 | { 80 | "description": "Number 2.3 is multiple of 0.01", 81 | "data": 2.3, 82 | "valid": true 83 | }, 84 | { 85 | "description": "Number 2.4 is multiple of 0.01", 86 | "data": 2.4, 87 | "valid": true 88 | }, 89 | { 90 | "description": "Number 1.211 is not multiple of 0.01", 91 | "data": 1.211, 92 | "valid": false 93 | } 94 | ] 95 | } 96 | ] 97 | -------------------------------------------------------------------------------- /test/json-schema-draft4/additionalProperties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": 4 | "additionalProperties being false does not allow other properties", 5 | "schema": { 6 | "properties": {"foo": {}, "bar": {}}, 7 | "patternProperties": { "^v": {} }, 8 | "additionalProperties": false 9 | }, 10 | "tests": [ 11 | { 12 | "description": "no additional properties is valid", 13 | "data": {"foo": 1}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "an additional property is invalid", 18 | "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-objects", 23 | "data": [1, 2, 3], 24 | "valid": true 25 | }, 26 | { 27 | "description": "patternProperties are not additional properties", 28 | "data": {"foo":1, "vroom": 2}, 29 | "valid": true 30 | } 31 | ] 32 | }, 33 | { 34 | "description": 35 | "additionalProperties allows a schema which should validate", 36 | "schema": { 37 | "properties": {"foo": {}, "bar": {}}, 38 | "additionalProperties": {"type": "boolean"} 39 | }, 40 | "tests": [ 41 | { 42 | "description": "no additional properties is valid", 43 | "data": {"foo": 1}, 44 | "valid": true 45 | }, 46 | { 47 | "description": "an additional valid property is valid", 48 | "data": {"foo" : 1, "bar" : 2, "quux" : true}, 49 | "valid": true 50 | }, 51 | { 52 | "description": "an additional invalid property is invalid", 53 | "data": {"foo" : 1, "bar" : 2, "quux" : 12}, 54 | "valid": false 55 | } 56 | ] 57 | }, 58 | { 59 | "description": 60 | "additionalProperties can exist by itself", 61 | "schema": { 62 | "additionalProperties": {"type": "boolean"} 63 | }, 64 | "tests": [ 65 | { 66 | "description": "an additional valid property is valid", 67 | "data": {"foo" : true}, 68 | "valid": true 69 | }, 70 | { 71 | "description": "an additional invalid property is invalid", 72 | "data": {"foo" : 1}, 73 | "valid": false 74 | } 75 | ] 76 | }, 77 | { 78 | "description": "additionalProperties are allowed by default", 79 | "schema": {"properties": {"foo": {}, "bar": {}}}, 80 | "tests": [ 81 | { 82 | "description": "additional properties are allowed", 83 | "data": {"foo": 1, "bar": 2, "quux": true}, 84 | "valid": true 85 | } 86 | ] 87 | } 88 | ] 89 | -------------------------------------------------------------------------------- /test/json-schema-draft4/properties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "object properties validation", 4 | "schema": { 5 | "properties": { 6 | "foo": {"type": "integer"}, 7 | "bar": {"type": "string"} 8 | } 9 | }, 10 | "tests": [ 11 | { 12 | "description": "both properties present and valid is valid", 13 | "data": {"foo": 1, "bar": "baz"}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "one property invalid is invalid", 18 | "data": {"foo": 1, "bar": {}}, 19 | "valid": false 20 | }, 21 | { 22 | "description": "both properties invalid is invalid", 23 | "data": {"foo": [], "bar": {}}, 24 | "valid": false 25 | }, 26 | { 27 | "description": "doesn't invalidate other properties", 28 | "data": {"quux": []}, 29 | "valid": true 30 | }, 31 | { 32 | "description": "ignores non-objects", 33 | "data": [], 34 | "valid": true 35 | } 36 | ] 37 | }, 38 | { 39 | "description": 40 | "properties, patternProperties, additionalProperties interaction", 41 | "schema": { 42 | "properties": { 43 | "foo": {"type": "array", "maxItems": 3}, 44 | "bar": {"type": "array"} 45 | }, 46 | "patternProperties": {"f.o": {"minItems": 2}}, 47 | "additionalProperties": {"type": "integer"} 48 | }, 49 | "tests": [ 50 | { 51 | "description": "property validates property", 52 | "data": {"foo": [1, 2]}, 53 | "valid": true 54 | }, 55 | { 56 | "description": "property invalidates property", 57 | "data": {"foo": [1, 2, 3, 4]}, 58 | "valid": false 59 | }, 60 | { 61 | "description": "patternProperty invalidates property", 62 | "data": {"foo": []}, 63 | "valid": false 64 | }, 65 | { 66 | "description": "patternProperty validates nonproperty", 67 | "data": {"fxo": [1, 2]}, 68 | "valid": true 69 | }, 70 | { 71 | "description": "patternProperty invalidates nonproperty", 72 | "data": {"fxo": []}, 73 | "valid": false 74 | }, 75 | { 76 | "description": "additionalProperty ignores property", 77 | "data": {"bar": []}, 78 | "valid": true 79 | }, 80 | { 81 | "description": "additionalProperty validates others", 82 | "data": {"quux": 3}, 83 | "valid": true 84 | }, 85 | { 86 | "description": "additionalProperty invalidates others", 87 | "data": {"quux": "foo"}, 88 | "valid": false 89 | } 90 | ] 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /test/json-schema-draft4/allOf.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "allOf", 4 | "schema": { 5 | "allOf": [ 6 | { 7 | "properties": { 8 | "bar": {"type": "integer"} 9 | }, 10 | "required": ["bar"] 11 | }, 12 | { 13 | "properties": { 14 | "foo": {"type": "string"} 15 | }, 16 | "required": ["foo"] 17 | } 18 | ] 19 | }, 20 | "tests": [ 21 | { 22 | "description": "allOf", 23 | "data": {"foo": "baz", "bar": 2}, 24 | "valid": true 25 | }, 26 | { 27 | "description": "mismatch second", 28 | "data": {"foo": "baz"}, 29 | "valid": false 30 | }, 31 | { 32 | "description": "mismatch first", 33 | "data": {"bar": 2}, 34 | "valid": false 35 | }, 36 | { 37 | "description": "wrong type", 38 | "data": {"foo": "baz", "bar": "quux"}, 39 | "valid": false 40 | } 41 | ] 42 | }, 43 | { 44 | "description": "allOf with base schema", 45 | "schema": { 46 | "properties": {"bar": {"type": "integer"}}, 47 | "required": ["bar"], 48 | "allOf" : [ 49 | { 50 | "properties": { 51 | "foo": {"type": "string"} 52 | }, 53 | "required": ["foo"] 54 | }, 55 | { 56 | "properties": { 57 | "baz": {"type": "null"} 58 | }, 59 | "required": ["baz"] 60 | } 61 | ] 62 | }, 63 | "tests": [ 64 | { 65 | "description": "valid", 66 | "data": {"foo": "quux", "bar": 2, "baz": null}, 67 | "valid": true 68 | }, 69 | { 70 | "description": "mismatch base schema", 71 | "data": {"foo": "quux", "baz": null}, 72 | "valid": false 73 | }, 74 | { 75 | "description": "mismatch first allOf", 76 | "data": {"bar": 2, "baz": null}, 77 | "valid": false 78 | }, 79 | { 80 | "description": "mismatch second allOf", 81 | "data": {"foo": "quux", "bar": 2}, 82 | "valid": false 83 | }, 84 | { 85 | "description": "mismatch both", 86 | "data": {"bar": 2}, 87 | "valid": false 88 | } 89 | ] 90 | }, 91 | { 92 | "description": "allOf simple types", 93 | "schema": { 94 | "allOf": [ 95 | {"maximum": 30}, 96 | {"minimum": 20} 97 | ] 98 | }, 99 | "tests": [ 100 | { 101 | "description": "valid", 102 | "data": 25, 103 | "valid": true 104 | }, 105 | { 106 | "description": "mismatch one", 107 | "data": 35, 108 | "valid": false 109 | } 110 | ] 111 | } 112 | ] 113 | -------------------------------------------------------------------------------- /test/json-schema-draft4/bignum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "integer", 4 | "schema": {"type": "integer"}, 5 | "tests": [ 6 | { 7 | "description": "a bignum is an integer", 8 | "data": 12345678910111213141516171819202122232425262728293031, 9 | "valid": true 10 | } 11 | ] 12 | }, 13 | { 14 | "description": "number", 15 | "schema": {"type": "number"}, 16 | "tests": [ 17 | { 18 | "description": "a bignum is a number", 19 | "data": 98249283749234923498293171823948729348710298301928331, 20 | "valid": true 21 | } 22 | ] 23 | }, 24 | { 25 | "description": "integer", 26 | "schema": {"type": "integer"}, 27 | "tests": [ 28 | { 29 | "description": "a negative bignum is an integer", 30 | "data": -12345678910111213141516171819202122232425262728293031, 31 | "valid": true 32 | } 33 | ] 34 | }, 35 | { 36 | "description": "number", 37 | "schema": {"type": "number"}, 38 | "tests": [ 39 | { 40 | "description": "a negative bignum is a number", 41 | "data": -98249283749234923498293171823948729348710298301928331, 42 | "valid": true 43 | } 44 | ] 45 | }, 46 | { 47 | "description": "string", 48 | "schema": {"type": "string"}, 49 | "tests": [ 50 | { 51 | "description": "a bignum is not a string", 52 | "data": 98249283749234923498293171823948729348710298301928331, 53 | "valid": false 54 | } 55 | ] 56 | }, 57 | { 58 | "description": "integer comparison", 59 | "schema": {"maximum": 18446744073709551615}, 60 | "tests": [ 61 | { 62 | "description": "comparison works for high numbers", 63 | "data": 18446744073709551600, 64 | "valid": true 65 | } 66 | ] 67 | }, 68 | { 69 | "description": "float comparison with high precision", 70 | "schema": { 71 | "maximum": 972783798187987123879878123.18878137, 72 | "exclusiveMaximum": true 73 | }, 74 | "tests": [ 75 | { 76 | "description": "comparison works for high numbers", 77 | "data": 972783798187987123879878123.188781371, 78 | "valid": false 79 | } 80 | ] 81 | }, 82 | { 83 | "description": "integer comparison", 84 | "schema": {"minimum": -18446744073709551615}, 85 | "tests": [ 86 | { 87 | "description": "comparison works for very negative numbers", 88 | "data": -18446744073709551600, 89 | "valid": true 90 | } 91 | ] 92 | }, 93 | { 94 | "description": "float comparison with high precision on negative numbers", 95 | "schema": { 96 | "minimum": -972783798187987123879878123.18878137, 97 | "exclusiveMinimum": true 98 | }, 99 | "tests": [ 100 | { 101 | "description": "comparison works for very negative numbers", 102 | "data": -972783798187987123879878123.188781371, 103 | "valid": false 104 | } 105 | ] 106 | } 107 | ] 108 | -------------------------------------------------------------------------------- /test/json-schema-draft4/dependencies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "dependencies", 4 | "schema": { 5 | "dependencies": {"bar": ["foo"]} 6 | }, 7 | "tests": [ 8 | { 9 | "description": "neither", 10 | "data": {}, 11 | "valid": true 12 | }, 13 | { 14 | "description": "nondependant", 15 | "data": {"foo": 1}, 16 | "valid": true 17 | }, 18 | { 19 | "description": "with dependency", 20 | "data": {"foo": 1, "bar": 2}, 21 | "valid": true 22 | }, 23 | { 24 | "description": "missing dependency", 25 | "data": {"bar": 2}, 26 | "valid": false 27 | }, 28 | { 29 | "description": "ignores non-objects", 30 | "data": "foo", 31 | "valid": true 32 | } 33 | ] 34 | }, 35 | { 36 | "description": "multiple dependencies", 37 | "schema": { 38 | "dependencies": {"quux": ["foo", "bar"]} 39 | }, 40 | "tests": [ 41 | { 42 | "description": "neither", 43 | "data": {}, 44 | "valid": true 45 | }, 46 | { 47 | "description": "nondependants", 48 | "data": {"foo": 1, "bar": 2}, 49 | "valid": true 50 | }, 51 | { 52 | "description": "with dependencies", 53 | "data": {"foo": 1, "bar": 2, "quux": 3}, 54 | "valid": true 55 | }, 56 | { 57 | "description": "missing dependency", 58 | "data": {"foo": 1, "quux": 2}, 59 | "valid": false 60 | }, 61 | { 62 | "description": "missing other dependency", 63 | "data": {"bar": 1, "quux": 2}, 64 | "valid": false 65 | }, 66 | { 67 | "description": "missing both dependencies", 68 | "data": {"quux": 1}, 69 | "valid": false 70 | } 71 | ] 72 | }, 73 | { 74 | "description": "multiple dependencies subschema", 75 | "schema": { 76 | "dependencies": { 77 | "bar": { 78 | "properties": { 79 | "foo": {"type": "integer"}, 80 | "bar": {"type": "integer"} 81 | } 82 | } 83 | } 84 | }, 85 | "tests": [ 86 | { 87 | "description": "valid", 88 | "data": {"foo": 1, "bar": 2}, 89 | "valid": true 90 | }, 91 | { 92 | "description": "no dependency", 93 | "data": {"foo": "quux"}, 94 | "valid": true 95 | }, 96 | { 97 | "description": "wrong type", 98 | "data": {"foo": "quux", "bar": 2}, 99 | "valid": false 100 | }, 101 | { 102 | "description": "wrong type other", 103 | "data": {"foo": 2, "bar": "quux"}, 104 | "valid": false 105 | }, 106 | { 107 | "description": "wrong type both", 108 | "data": {"foo": "quux", "bar": "quux"}, 109 | "valid": false 110 | } 111 | ] 112 | } 113 | ] 114 | -------------------------------------------------------------------------------- /test/json-schema-draft4/patternProperties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": 4 | "patternProperties validates properties matching a regex", 5 | "schema": { 6 | "patternProperties": { 7 | "f.*o": {"type": "integer"} 8 | } 9 | }, 10 | "tests": [ 11 | { 12 | "description": "a single valid match is valid", 13 | "data": {"foo": 1}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "multiple valid matches is valid", 18 | "data": {"foo": 1, "foooooo" : 2}, 19 | "valid": true 20 | }, 21 | { 22 | "description": "a single invalid match is invalid", 23 | "data": {"foo": "bar", "fooooo": 2}, 24 | "valid": false 25 | }, 26 | { 27 | "description": "multiple invalid matches is invalid", 28 | "data": {"foo": "bar", "foooooo" : "baz"}, 29 | "valid": false 30 | }, 31 | { 32 | "description": "ignores non-objects", 33 | "data": 12, 34 | "valid": true 35 | } 36 | ] 37 | }, 38 | { 39 | "description": "multiple simultaneous patternProperties are validated", 40 | "schema": { 41 | "patternProperties": { 42 | "a*": {"type": "integer"}, 43 | "aaa*": {"maximum": 20} 44 | } 45 | }, 46 | "tests": [ 47 | { 48 | "description": "a single valid match is valid", 49 | "data": {"a": 21}, 50 | "valid": true 51 | }, 52 | { 53 | "description": "a simultaneous match is valid", 54 | "data": {"aaaa": 18}, 55 | "valid": true 56 | }, 57 | { 58 | "description": "multiple matches is valid", 59 | "data": {"a": 21, "aaaa": 18}, 60 | "valid": true 61 | }, 62 | { 63 | "description": "an invalid due to one is invalid", 64 | "data": {"a": "bar"}, 65 | "valid": false 66 | }, 67 | { 68 | "description": "an invalid due to the other is invalid", 69 | "data": {"aaaa": 31}, 70 | "valid": false 71 | }, 72 | { 73 | "description": "an invalid due to both is invalid", 74 | "data": {"aaa": "foo", "aaaa": 31}, 75 | "valid": false 76 | } 77 | ] 78 | }, 79 | { 80 | "description": "regexes are not anchored by default and are case sensitive", 81 | "schema": { 82 | "patternProperties": { 83 | "[0-9]{2,}": { "type": "boolean" }, 84 | "X_": { "type": "string" } 85 | } 86 | }, 87 | "tests": [ 88 | { 89 | "description": "non recognized members are ignored", 90 | "data": { "answer 1": "42" }, 91 | "valid": true 92 | }, 93 | { 94 | "description": "recognized members are accounted for", 95 | "data": { "a31b": null }, 96 | "valid": false 97 | }, 98 | { 99 | "description": "regexes are case sensitive", 100 | "data": { "a_x_3": 3 }, 101 | "valid": true 102 | }, 103 | { 104 | "description": "regexes are case sensitive, 2", 105 | "data": { "a_X_3": 3 }, 106 | "valid": false 107 | } 108 | ] 109 | } 110 | ] 111 | -------------------------------------------------------------------------------- /test/json-schema-draft4/ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "root pointer ref", 4 | "schema": { 5 | "properties": { 6 | "foo": {"$ref": "#"} 7 | }, 8 | "additionalProperties": false 9 | }, 10 | "tests": [ 11 | { 12 | "description": "match", 13 | "data": {"foo": false}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "recursive match", 18 | "data": {"foo": {"foo": false}}, 19 | "valid": true 20 | }, 21 | { 22 | "description": "mismatch", 23 | "data": {"bar": false}, 24 | "valid": false 25 | }, 26 | { 27 | "description": "recursive mismatch", 28 | "data": {"foo": {"bar": false}}, 29 | "valid": false 30 | } 31 | ] 32 | }, 33 | { 34 | "description": "relative pointer ref to object", 35 | "schema": { 36 | "properties": { 37 | "foo": {"type": "integer"}, 38 | "bar": {"$ref": "#/properties/foo"} 39 | } 40 | }, 41 | "tests": [ 42 | { 43 | "description": "match", 44 | "data": {"bar": 3}, 45 | "valid": true 46 | }, 47 | { 48 | "description": "mismatch", 49 | "data": {"bar": true}, 50 | "valid": false 51 | } 52 | ] 53 | }, 54 | { 55 | "description": "relative pointer ref to array", 56 | "schema": { 57 | "items": [ 58 | {"type": "integer"}, 59 | {"$ref": "#/items/0"} 60 | ] 61 | }, 62 | "tests": [ 63 | { 64 | "description": "match array", 65 | "data": [1, 2], 66 | "valid": true 67 | }, 68 | { 69 | "description": "mismatch array", 70 | "data": [1, "foo"], 71 | "valid": false 72 | } 73 | ] 74 | }, 75 | { 76 | "description": "escaped pointer ref", 77 | "schema": { 78 | "tilda~field": {"type": "integer"}, 79 | "slash/field": {"type": "integer"}, 80 | "percent%field": {"type": "integer"}, 81 | "properties": { 82 | "tilda": {"$ref": "#/tilda~0field"}, 83 | "slash": {"$ref": "#/slash~1field"}, 84 | "percent": {"$ref": "#/percent%25field"} 85 | } 86 | }, 87 | "tests": [ 88 | { 89 | "description": "slash", 90 | "data": {"slash": "aoeu"}, 91 | "valid": false 92 | }, 93 | { 94 | "description": "tilda", 95 | "data": {"tilda": "aoeu"}, 96 | "valid": false 97 | }, 98 | { 99 | "description": "percent", 100 | "data": {"percent": "aoeu"}, 101 | "valid": false 102 | } 103 | ] 104 | }, 105 | { 106 | "description": "nested refs", 107 | "schema": { 108 | "definitions": { 109 | "a": {"type": "integer"}, 110 | "b": {"$ref": "#/definitions/a"}, 111 | "c": {"$ref": "#/definitions/b"} 112 | }, 113 | "$ref": "#/definitions/c" 114 | }, 115 | "tests": [ 116 | { 117 | "description": "nested ref valid", 118 | "data": 5, 119 | "valid": true 120 | }, 121 | { 122 | "description": "nested ref invalid", 123 | "data": "a", 124 | "valid": false 125 | } 126 | ] 127 | } 128 | ] 129 | -------------------------------------------------------------------------------- /test/fixtures/cosmic.js: -------------------------------------------------------------------------------- 1 | exports.valid = { 2 | fullName : "John Doe", 3 | age : 47, 4 | state : "Massachusetts", 5 | city : "Boston", 6 | zip : 16417, 7 | married : false, 8 | dozen : 12, 9 | dozenOrBakersDozen : 13, 10 | favoriteEvenNumber : 14, 11 | topThreeFavoriteColors : [ "red", "blue", "green" ], 12 | favoriteSingleDigitWholeNumbers : [ 7 ], 13 | favoriteFiveLetterWord : "coder", 14 | emailAddresses : 15 | [ 16 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@letters-in-local.org", 17 | "01234567890@numbers-in-local.net", 18 | "&'*+-./=?^_{}~@other-valid-characters-in-local.net", 19 | "mixed-1234-in-{+^}-local@sld.net", 20 | "a@single-character-in-local.org", 21 | "\"quoted\"@sld.com", 22 | "\"\\e\\s\\c\\a\\p\\e\\d\"@sld.com", 23 | "\"quoted-at-sign@sld.org\"@sld.com", 24 | "\"escaped\\\"quote\"@sld.com", 25 | "\"back\\slash\"@sld.com", 26 | "one-character-third-level@a.example.com", 27 | "single-character-in-sld@x.org", 28 | "local@dash-in-sld.com", 29 | "letters-in-sld@123.com", 30 | "one-letter-sld@x.org", 31 | "uncommon-tld@sld.museum", 32 | "uncommon-tld@sld.travel", 33 | "uncommon-tld@sld.mobi", 34 | "country-code-tld@sld.uk", 35 | "country-code-tld@sld.rw", 36 | "local@sld.newTLD", 37 | "the-total-length@of-an-entire-address.cannot-be-longer-than-two-hundred-and-fifty-four-characters.and-this-address-is-254-characters-exactly.so-it-should-be-valid.and-im-going-to-add-some-more-words-here.to-increase-the-lenght-blah-blah-blah-blah-bla.org", 38 | "the-character-limit@for-each-part.of-the-domain.is-sixty-three-characters.this-is-exactly-sixty-three-characters-so-it-is-valid-blah-blah.com", 39 | "local@sub.domains.com" 40 | ], 41 | ipAddresses : [ "127.0.0.1", "24.48.64.2", "192.168.1.1", "209.68.44.3", "2.2.2.2" ] 42 | } 43 | 44 | exports.invalid = { 45 | fullName : null, 46 | age : -1, 47 | state : 47, 48 | city : false, 49 | zip : [null], 50 | married : "yes", 51 | dozen : 50, 52 | dozenOrBakersDozen : "over 9000", 53 | favoriteEvenNumber : 15, 54 | topThreeFavoriteColors : [ "red", 5 ], 55 | favoriteSingleDigitWholeNumbers : [ 78, 2, 999 ], 56 | favoriteFiveLetterWord : "codernaut", 57 | emailAddresses : [], 58 | ipAddresses : [ "999.0.099.1", "294.48.64.2346", false, "2221409.64214128.42414.235233", "124124.12412412" ] 59 | } 60 | 61 | exports.schema = { // from cosmic thingy 62 | name : "test", 63 | type : "object", 64 | additionalProperties : false, 65 | required : ["fullName", "age", "zip", "married", "dozen", "dozenOrBakersDozen", "favoriteEvenNumber", "topThreeFavoriteColors", "favoriteSingleDigitWholeNumbers", "favoriteFiveLetterWord", "emailAddresses", "ipAddresses"], 66 | properties : 67 | { 68 | fullName : { type : "string" }, 69 | age : { type : "integer", minimum : 0 }, 70 | optionalItem : { type : "string" }, 71 | state : { type : "string" }, 72 | city : { type : "string" }, 73 | zip : { type : "integer", minimum : 0, maximum : 99999 }, 74 | married : { type : "boolean" }, 75 | dozen : { type : "integer", minimum : 12, maximum : 12 }, 76 | dozenOrBakersDozen : { type : "integer", minimum : 12, maximum : 13 }, 77 | favoriteEvenNumber : { type : "integer", multipleOf : 2 }, 78 | topThreeFavoriteColors : { type : "array", minItems : 3, maxItems : 3, uniqueItems : true, items : { type : "string" }}, 79 | favoriteSingleDigitWholeNumbers : { type : "array", minItems : 1, maxItems : 10, uniqueItems : true, items : { type : "integer", minimum : 0, maximum : 9 }}, 80 | favoriteFiveLetterWord : { type : "string", minLength : 5, maxLength : 5 }, 81 | emailAddresses : { type : "array", minItems : 1, uniqueItems : true, items : { type : "string", format : "email" }}, 82 | ipAddresses : { type : "array", uniqueItems : true, items : { type : "string", format : "ipv4" }}, 83 | } 84 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # is-my-json-valid 2 | 3 | A [JSONSchema](http://json-schema.org/) validator that uses code generation 4 | to be extremely fast 5 | 6 | ``` 7 | npm install is-my-json-valid 8 | ``` 9 | 10 | It passes the entire JSONSchema v4 test suite except for `remoteRefs` and `maxLength`/`minLength` when using unicode surrogate pairs. 11 | 12 | [![build status](http://img.shields.io/travis/mafintosh/is-my-json-valid.svg?style=flat)](http://travis-ci.org/mafintosh/is-my-json-valid) 13 | 14 | ## Usage 15 | 16 | Simply pass a schema to compile it 17 | 18 | ``` js 19 | var validator = require('is-my-json-valid') 20 | 21 | var validate = validator({ 22 | required: true, 23 | type: 'object', 24 | properties: { 25 | hello: { 26 | required: true, 27 | type: 'string' 28 | } 29 | } 30 | }) 31 | 32 | console.log('should be valid', validate({hello: 'world'})) 33 | console.log('should not be valid', validate({})) 34 | 35 | // get the last list of errors by checking validate.errors 36 | // the following will print [{field: 'data.hello', message: 'is required'}] 37 | console.log(validate.errors) 38 | ``` 39 | 40 | You can also pass the schema as a string 41 | 42 | ``` js 43 | var validate = validator('{"type": ... }') 44 | ``` 45 | 46 | Optionally you can use the require submodule to load a schema from `__dirname` 47 | 48 | ``` js 49 | var validator = require('is-my-json-valid/require') 50 | var validate = validator('my-schema.json') 51 | ``` 52 | 53 | ## Custom formats 54 | 55 | is-my-json-valid supports the formats specified in JSON schema v4 (such as date-time). 56 | If you want to add your own custom formats pass them as the formats options to the validator 57 | 58 | ``` js 59 | var validate = validator({ 60 | type: 'string', 61 | required: true, 62 | format: 'only-a' 63 | }, { 64 | formats: { 65 | 'only-a': /^a+$/ 66 | } 67 | }) 68 | 69 | console.log(validate('aa')) // true 70 | console.log(validate('ab')) // false 71 | ``` 72 | 73 | ## External schemas 74 | 75 | You can pass in external schemas that you reference using the `$ref` attribute as the `schemas` option 76 | 77 | ``` js 78 | var ext = { 79 | required: true, 80 | type: 'string' 81 | } 82 | 83 | var schema = { 84 | $ref: '#ext' // references another schema called ext 85 | } 86 | 87 | // pass the external schemas as an option 88 | var validate = validator(schema, {schemas: {ext: ext}}) 89 | 90 | validate('hello') // returns true 91 | validate(42) // return false 92 | ``` 93 | 94 | ## Filtering away additional properties 95 | 96 | is-my-json-valid supports filtering away properties not in the schema 97 | 98 | ``` js 99 | var filter = validator.filter({ 100 | required: true, 101 | type: 'object', 102 | properties: { 103 | hello: {type: 'string', required: true} 104 | }, 105 | additionalProperties: false 106 | }) 107 | 108 | var doc = {hello: 'world', notInSchema: true} 109 | console.log(filter(doc)) // {hello: 'world'} 110 | ``` 111 | 112 | ## Verbose mode outputs the value on errors 113 | 114 | is-my-json-valid outputs the value causing an error when verbose is set to true 115 | 116 | ``` js 117 | var validate = validator({ 118 | required: true, 119 | type: 'object', 120 | properties: { 121 | hello: { 122 | required: true, 123 | type: 'string' 124 | } 125 | } 126 | }, { 127 | verbose: true 128 | }) 129 | 130 | validate({hello: 100}); 131 | console.log(validate.errors) // {field: 'data.hello', message: 'is the wrong type', value: 100, type: 'string'} 132 | ``` 133 | 134 | ## Greedy mode tries to validate as much as possible 135 | 136 | By default is-my-json-valid bails on first validation error but when greedy is 137 | set to true it tries to validate as much as possible: 138 | 139 | ``` js 140 | var validate = validator({ 141 | type: 'object', 142 | properties: { 143 | x: { 144 | type: 'number' 145 | } 146 | }, 147 | required: ['x', 'y'] 148 | }, { 149 | greedy: true 150 | }); 151 | 152 | validate({x: 'string'}); 153 | console.log(validate.errors) // [{field: 'data.y', message: 'is required'}, 154 | // {field: 'data.x', message: 'is the wrong type'}] 155 | ``` 156 | 157 | ## Error messages 158 | 159 | Here is a list of possible `message` values for errors: 160 | 161 | * `is required` 162 | * `is the wrong type` 163 | * `has additional items` 164 | * `must be FORMAT format` (FORMAT is the `format` property from the schema) 165 | * `must be unique` 166 | * `must be an enum value` 167 | * `dependencies not set` 168 | * `has additional properties` 169 | * `referenced schema does not match` 170 | * `negative schema matches` 171 | * `pattern mismatch` 172 | * `no schemas match` 173 | * `no (or more than one) schemas match` 174 | * `has a remainder` 175 | * `has more properties than allowed` 176 | * `has less properties than allowed` 177 | * `has more items than allowed` 178 | * `has less items than allowed` 179 | * `has longer length than allowed` 180 | * `has less length than allowed` 181 | * `is less than minimum` 182 | * `is more than maximum` 183 | 184 | ## Performance 185 | 186 | is-my-json-valid uses code generation to turn your JSON schema into basic javascript code that is easily optimizeable by v8. 187 | 188 | At the time of writing, is-my-json-valid is the __fastest validator__ when running 189 | 190 | * [json-schema-benchmark](https://github.com/Muscula/json-schema-benchmark) 191 | * [cosmicreals.com benchmark](http://cosmicrealms.com/blog/2014/08/29/benchmark-of-node-dot-js-json-validation-modules-part-3/) 192 | * [jsck benchmark](https://github.com/pandastrike/jsck/issues/72#issuecomment-70992684) 193 | * [themis benchmark](https://cdn.rawgit.com/playlyfe/themis/master/benchmark/results.html) 194 | * [z-schema benchmark](https://rawgit.com/zaggino/z-schema/master/benchmark/results.html) 195 | 196 | If you know any other relevant benchmarks open a PR and I'll add them. 197 | 198 | ## License 199 | 200 | MIT 201 | -------------------------------------------------------------------------------- /test/json-schema-draft4/format.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "validation of date-time strings", 4 | "schema": {"format": "date-time"}, 5 | "tests": [ 6 | { 7 | "description": "a valid date-time string", 8 | "data": "1963-06-19T08:30:06.283185Z", 9 | "valid": true 10 | }, 11 | { 12 | "description": "an invalid date-time string", 13 | "data": "06/19/1963 08:30:06 PST", 14 | "valid": false 15 | }, 16 | { 17 | "description": "only RFC3339 not all of ISO 8601 are valid", 18 | "data": "2013-350T01:01:01", 19 | "valid": false 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "validation of URIs", 25 | "schema": {"format": "uri"}, 26 | "tests": [ 27 | { 28 | "description": "a valid URI", 29 | "data": "http://foo.bar/?baz=qux#quux", 30 | "valid": true 31 | }, 32 | { 33 | "description": "an invalid URI", 34 | "data": "\\\\WINDOWS\\fileshare", 35 | "valid": false 36 | }, 37 | { 38 | "description": "an invalid URI though valid URI reference", 39 | "data": "abc", 40 | "valid": false 41 | } 42 | ] 43 | }, 44 | { 45 | "description": "validation of URI templates", 46 | "schema": {"format": "uri-template"}, 47 | "tests": [ 48 | { 49 | "description": "a valid URI template", 50 | "data": "{{protocol}}://foo.bar/?baz=qux#quux", 51 | "valid": true 52 | }, 53 | { 54 | "description": "a valid URI", 55 | "data": "http://foo.bar/?baz=qux#quux", 56 | "valid": true 57 | }, 58 | { 59 | "description": "an invalid URI template", 60 | "data": "\\\\WINDOWS\\fileshare", 61 | "valid": false 62 | }, 63 | { 64 | "description": "an invalid URI though valid URI reference", 65 | "data": "abc", 66 | "valid": false 67 | } 68 | ] 69 | }, 70 | { 71 | "description": "validation of e-mail addresses", 72 | "schema": {"format": "email"}, 73 | "tests": [ 74 | { 75 | "description": "a valid e-mail address", 76 | "data": "joe.bloggs@example.com", 77 | "valid": true 78 | }, 79 | { 80 | "description": "an invalid e-mail address", 81 | "data": "2962", 82 | "valid": false 83 | } 84 | ] 85 | }, 86 | { 87 | "description": "validation of IP addresses", 88 | "schema": {"format": "ipv4"}, 89 | "tests": [ 90 | { 91 | "description": "a valid IP address", 92 | "data": "192.168.0.1", 93 | "valid": true 94 | }, 95 | { 96 | "description": "an IP address with too many components", 97 | "data": "127.0.0.0.1", 98 | "valid": false 99 | }, 100 | { 101 | "description": "an IP address with out-of-range values", 102 | "data": "256.256.256.256", 103 | "valid": false 104 | }, 105 | { 106 | "description": "an IP address without 4 components", 107 | "data": "127.0", 108 | "valid": false 109 | }, 110 | { 111 | "description": "an IP address as an integer", 112 | "data": "0x7f000001", 113 | "valid": false 114 | } 115 | ] 116 | }, 117 | { 118 | "description": "validation of IPv6 addresses", 119 | "schema": {"format": "ipv6"}, 120 | "tests": [ 121 | { 122 | "description": "a valid IPv6 address", 123 | "data": "::1", 124 | "valid": true 125 | }, 126 | { 127 | "description": "an IPv6 address with out-of-range values", 128 | "data": "12345::", 129 | "valid": false 130 | }, 131 | { 132 | "description": "an IPv6 address with too many components", 133 | "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", 134 | "valid": false 135 | }, 136 | { 137 | "description": "an IPv6 address containing illegal characters", 138 | "data": "::laptop", 139 | "valid": false 140 | } 141 | ] 142 | }, 143 | { 144 | "description": "validation of host names", 145 | "schema": {"format": "hostname"}, 146 | "tests": [ 147 | { 148 | "description": "a valid host name", 149 | "data": "www.example.com", 150 | "valid": true 151 | }, 152 | { 153 | "description": "a host name starting with an illegal character", 154 | "data": "-a-host-name-that-starts-with--", 155 | "valid": false 156 | }, 157 | { 158 | "description": "a host name containing illegal characters", 159 | "data": "not_a_valid_host_name", 160 | "valid": false 161 | }, 162 | { 163 | "description": "a host name with a component too long", 164 | "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", 165 | "valid": false 166 | } 167 | ] 168 | } 169 | ] 170 | -------------------------------------------------------------------------------- /test/json-schema-draft4/type.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "integer type matches integers", 4 | "schema": {"type": "integer"}, 5 | "tests": [ 6 | { 7 | "description": "an integer is an integer", 8 | "data": 1, 9 | "valid": true 10 | }, 11 | { 12 | "description": "a float is not an integer", 13 | "data": 1.1, 14 | "valid": false 15 | }, 16 | { 17 | "description": "a string is not an integer", 18 | "data": "foo", 19 | "valid": false 20 | }, 21 | { 22 | "description": "an object is not an integer", 23 | "data": {}, 24 | "valid": false 25 | }, 26 | { 27 | "description": "an array is not an integer", 28 | "data": [], 29 | "valid": false 30 | }, 31 | { 32 | "description": "a boolean is not an integer", 33 | "data": true, 34 | "valid": false 35 | }, 36 | { 37 | "description": "null is not an integer", 38 | "data": null, 39 | "valid": false 40 | } 41 | ] 42 | }, 43 | { 44 | "description": "number type matches numbers", 45 | "schema": {"type": "number"}, 46 | "tests": [ 47 | { 48 | "description": "an integer is a number", 49 | "data": 1, 50 | "valid": true 51 | }, 52 | { 53 | "description": "a float is a number", 54 | "data": 1.1, 55 | "valid": true 56 | }, 57 | { 58 | "description": "a string is not a number", 59 | "data": "foo", 60 | "valid": false 61 | }, 62 | { 63 | "description": "an object is not a number", 64 | "data": {}, 65 | "valid": false 66 | }, 67 | { 68 | "description": "an array is not a number", 69 | "data": [], 70 | "valid": false 71 | }, 72 | { 73 | "description": "a boolean is not a number", 74 | "data": true, 75 | "valid": false 76 | }, 77 | { 78 | "description": "null is not a number", 79 | "data": null, 80 | "valid": false 81 | } 82 | ] 83 | }, 84 | { 85 | "description": "string type matches strings", 86 | "schema": {"type": "string"}, 87 | "tests": [ 88 | { 89 | "description": "1 is not a string", 90 | "data": 1, 91 | "valid": false 92 | }, 93 | { 94 | "description": "a float is not a string", 95 | "data": 1.1, 96 | "valid": false 97 | }, 98 | { 99 | "description": "a string is a string", 100 | "data": "foo", 101 | "valid": true 102 | }, 103 | { 104 | "description": "an object is not a string", 105 | "data": {}, 106 | "valid": false 107 | }, 108 | { 109 | "description": "an array is not a string", 110 | "data": [], 111 | "valid": false 112 | }, 113 | { 114 | "description": "a boolean is not a string", 115 | "data": true, 116 | "valid": false 117 | }, 118 | { 119 | "description": "null is not a string", 120 | "data": null, 121 | "valid": false 122 | } 123 | ] 124 | }, 125 | { 126 | "description": "object type matches objects", 127 | "schema": {"type": "object"}, 128 | "tests": [ 129 | { 130 | "description": "an integer is not an object", 131 | "data": 1, 132 | "valid": false 133 | }, 134 | { 135 | "description": "a float is not an object", 136 | "data": 1.1, 137 | "valid": false 138 | }, 139 | { 140 | "description": "a string is not an object", 141 | "data": "foo", 142 | "valid": false 143 | }, 144 | { 145 | "description": "an object is an object", 146 | "data": {}, 147 | "valid": true 148 | }, 149 | { 150 | "description": "an array is not an object", 151 | "data": [], 152 | "valid": false 153 | }, 154 | { 155 | "description": "a boolean is not an object", 156 | "data": true, 157 | "valid": false 158 | }, 159 | { 160 | "description": "null is not an object", 161 | "data": null, 162 | "valid": false 163 | } 164 | ] 165 | }, 166 | { 167 | "description": "array type matches arrays", 168 | "schema": {"type": "array"}, 169 | "tests": [ 170 | { 171 | "description": "an integer is not an array", 172 | "data": 1, 173 | "valid": false 174 | }, 175 | { 176 | "description": "a float is not an array", 177 | "data": 1.1, 178 | "valid": false 179 | }, 180 | { 181 | "description": "a string is not an array", 182 | "data": "foo", 183 | "valid": false 184 | }, 185 | { 186 | "description": "an object is not an array", 187 | "data": {}, 188 | "valid": false 189 | }, 190 | { 191 | "description": "an array is not an array", 192 | "data": [], 193 | "valid": true 194 | }, 195 | { 196 | "description": "a boolean is not an array", 197 | "data": true, 198 | "valid": false 199 | }, 200 | { 201 | "description": "null is not an array", 202 | "data": null, 203 | "valid": false 204 | } 205 | ] 206 | }, 207 | { 208 | "description": "boolean type matches booleans", 209 | "schema": {"type": "boolean"}, 210 | "tests": [ 211 | { 212 | "description": "an integer is not a boolean", 213 | "data": 1, 214 | "valid": false 215 | }, 216 | { 217 | "description": "a float is not a boolean", 218 | "data": 1.1, 219 | "valid": false 220 | }, 221 | { 222 | "description": "a string is not a boolean", 223 | "data": "foo", 224 | "valid": false 225 | }, 226 | { 227 | "description": "an object is not a boolean", 228 | "data": {}, 229 | "valid": false 230 | }, 231 | { 232 | "description": "an array is not a boolean", 233 | "data": [], 234 | "valid": false 235 | }, 236 | { 237 | "description": "a boolean is not a boolean", 238 | "data": true, 239 | "valid": true 240 | }, 241 | { 242 | "description": "null is not a boolean", 243 | "data": null, 244 | "valid": false 245 | } 246 | ] 247 | }, 248 | { 249 | "description": "null type matches only the null object", 250 | "schema": {"type": "null"}, 251 | "tests": [ 252 | { 253 | "description": "an integer is not null", 254 | "data": 1, 255 | "valid": false 256 | }, 257 | { 258 | "description": "a float is not null", 259 | "data": 1.1, 260 | "valid": false 261 | }, 262 | { 263 | "description": "a string is not null", 264 | "data": "foo", 265 | "valid": false 266 | }, 267 | { 268 | "description": "an object is not null", 269 | "data": {}, 270 | "valid": false 271 | }, 272 | { 273 | "description": "an array is not null", 274 | "data": [], 275 | "valid": false 276 | }, 277 | { 278 | "description": "a boolean is not null", 279 | "data": true, 280 | "valid": false 281 | }, 282 | { 283 | "description": "null is null", 284 | "data": null, 285 | "valid": true 286 | } 287 | ] 288 | }, 289 | { 290 | "description": "multiple types can be specified in an array", 291 | "schema": {"type": ["integer", "string"]}, 292 | "tests": [ 293 | { 294 | "description": "an integer is valid", 295 | "data": 1, 296 | "valid": true 297 | }, 298 | { 299 | "description": "a string is valid", 300 | "data": "foo", 301 | "valid": true 302 | }, 303 | { 304 | "description": "a float is invalid", 305 | "data": 1.1, 306 | "valid": false 307 | }, 308 | { 309 | "description": "an object is invalid", 310 | "data": {}, 311 | "valid": false 312 | }, 313 | { 314 | "description": "an array is invalid", 315 | "data": [], 316 | "valid": false 317 | }, 318 | { 319 | "description": "a boolean is invalid", 320 | "data": true, 321 | "valid": false 322 | }, 323 | { 324 | "description": "null is invalid", 325 | "data": null, 326 | "valid": false 327 | } 328 | ] 329 | } 330 | ] 331 | -------------------------------------------------------------------------------- /test/misc.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var cosmic = require('./fixtures/cosmic') 3 | var validator = require('../') 4 | var validatorRequire = require('../require') 5 | 6 | tape('simple', function(t) { 7 | var schema = { 8 | required: true, 9 | type: 'object', 10 | properties: { 11 | hello: {type:'string', required:true} 12 | } 13 | } 14 | 15 | var validate = validator(schema) 16 | 17 | t.ok(validate({hello: 'world'}), 'should be valid') 18 | t.notOk(validate(), 'should be invalid') 19 | t.notOk(validate({}), 'should be invalid') 20 | t.end() 21 | }) 22 | 23 | tape('data is undefined', function (t) { 24 | var validate = validator({type: 'string'}) 25 | 26 | t.notOk(validate(null)) 27 | t.notOk(validate(undefined)) 28 | t.end() 29 | }) 30 | 31 | tape('advanced', function(t) { 32 | var validate = validator(cosmic.schema) 33 | 34 | t.ok(validate(cosmic.valid), 'should be valid') 35 | t.notOk(validate(cosmic.invalid), 'should be invalid') 36 | t.end() 37 | }) 38 | 39 | tape('greedy/false', function(t) { 40 | var validate = validator({ 41 | type: 'object', 42 | properties: { 43 | x: { 44 | type: 'number' 45 | } 46 | }, 47 | required: ['x', 'y'] 48 | }); 49 | t.notOk(validate({}), 'should be invalid') 50 | t.strictEqual(validate.errors.length, 2); 51 | t.strictEqual(validate.errors[0].field, 'data.x') 52 | t.strictEqual(validate.errors[0].message, 'is required') 53 | t.strictEqual(validate.errors[1].field, 'data.y') 54 | t.strictEqual(validate.errors[1].message, 'is required') 55 | t.notOk(validate({x: 'string'}), 'should be invalid') 56 | t.strictEqual(validate.errors.length, 1); 57 | t.strictEqual(validate.errors[0].field, 'data.y') 58 | t.strictEqual(validate.errors[0].message, 'is required') 59 | t.notOk(validate({x: 'string', y: 'value'}), 'should be invalid') 60 | t.strictEqual(validate.errors.length, 1); 61 | t.strictEqual(validate.errors[0].field, 'data.x') 62 | t.strictEqual(validate.errors[0].message, 'is the wrong type') 63 | t.end(); 64 | }); 65 | 66 | tape('greedy/true', function(t) { 67 | var validate = validator({ 68 | type: 'object', 69 | properties: { 70 | x: { 71 | type: 'number' 72 | } 73 | }, 74 | required: ['x', 'y'] 75 | }, { 76 | greedy: true 77 | }); 78 | t.notOk(validate({}), 'should be invalid') 79 | t.strictEqual(validate.errors.length, 2); 80 | t.strictEqual(validate.errors[0].field, 'data.x') 81 | t.strictEqual(validate.errors[0].message, 'is required') 82 | t.strictEqual(validate.errors[1].field, 'data.y') 83 | t.strictEqual(validate.errors[1].message, 'is required') 84 | t.notOk(validate({x: 'string'}), 'should be invalid') 85 | t.strictEqual(validate.errors.length, 2); 86 | t.strictEqual(validate.errors[0].field, 'data.y') 87 | t.strictEqual(validate.errors[0].message, 'is required') 88 | t.strictEqual(validate.errors[1].field, 'data.x') 89 | t.strictEqual(validate.errors[1].message, 'is the wrong type') 90 | t.notOk(validate({x: 'string', y: 'value'}), 'should be invalid') 91 | t.strictEqual(validate.errors.length, 1); 92 | t.strictEqual(validate.errors[0].field, 'data.x') 93 | t.strictEqual(validate.errors[0].message, 'is the wrong type') 94 | t.ok(validate({x: 1, y: 'value'}), 'should be invalid') 95 | t.end(); 96 | }); 97 | 98 | tape('additional props', function(t) { 99 | var validate = validator({ 100 | type: 'object', 101 | additionalProperties: false 102 | }, { 103 | verbose: true 104 | }) 105 | 106 | t.ok(validate({})) 107 | t.notOk(validate({foo:'bar'})) 108 | t.ok(validate.errors[0].value === 'data.foo', 'should output the property not allowed in verbose mode') 109 | t.strictEqual(validate.errors[0].type, 'object', 'error object should contain the type') 110 | t.end() 111 | }) 112 | 113 | tape('array', function(t) { 114 | var validate = validator({ 115 | type: 'array', 116 | required: true, 117 | items: { 118 | type: 'string' 119 | } 120 | }) 121 | 122 | t.notOk(validate({}), 'wrong type') 123 | t.notOk(validate(), 'is required') 124 | t.ok(validate(['test'])) 125 | t.end() 126 | }) 127 | 128 | tape('nested array', function(t) { 129 | var validate = validator({ 130 | type: 'object', 131 | properties: { 132 | list: { 133 | type: 'array', 134 | required: true, 135 | items: { 136 | type: 'string' 137 | } 138 | } 139 | } 140 | }) 141 | 142 | t.notOk(validate({}), 'is required') 143 | t.ok(validate({list:['test']})) 144 | t.notOk(validate({list:[1]})) 145 | t.end() 146 | }) 147 | 148 | tape('enum', function(t) { 149 | var validate = validator({ 150 | type: 'object', 151 | properties: { 152 | foo: { 153 | type: 'number', 154 | required: true, 155 | enum: [42] 156 | } 157 | } 158 | }) 159 | 160 | t.notOk(validate({}), 'is required') 161 | t.ok(validate({foo:42})) 162 | t.notOk(validate({foo:43})) 163 | t.end() 164 | }) 165 | 166 | tape('minimum/maximum', function(t) { 167 | var validate = validator({ 168 | type: 'object', 169 | properties: { 170 | foo: { 171 | type: 'number', 172 | minimum: 0, 173 | maximum: 0 174 | } 175 | } 176 | }) 177 | 178 | t.notOk(validate({foo:-42})) 179 | t.ok(validate({foo:0})) 180 | t.notOk(validate({foo:42})) 181 | t.end() 182 | }) 183 | 184 | tape('exclusiveMinimum/exclusiveMaximum', function(t) { 185 | var validate = validator({ 186 | type: 'object', 187 | properties: { 188 | foo: { 189 | type: 'number', 190 | minimum: 10, 191 | maximum: 20, 192 | exclusiveMinimum: true, 193 | exclusiveMaximum: true 194 | } 195 | } 196 | }) 197 | 198 | t.notOk(validate({foo:10})) 199 | t.ok(validate({foo:11})) 200 | t.notOk(validate({foo:20})) 201 | t.ok(validate({foo:19})) 202 | t.end() 203 | }) 204 | 205 | tape('minimum/maximum number type', function(t) { 206 | var validate = validator({ 207 | type: ['integer', 'null'], 208 | minimum: 1, 209 | maximum: 100 210 | }) 211 | 212 | t.notOk(validate(-1)) 213 | t.notOk(validate(0)) 214 | t.ok(validate(null)) 215 | t.ok(validate(1)) 216 | t.ok(validate(100)) 217 | t.notOk(validate(101)) 218 | t.end() 219 | }) 220 | 221 | tape('custom format', function(t) { 222 | var validate = validator({ 223 | type: 'object', 224 | properties: { 225 | foo: { 226 | type: 'string', 227 | format: 'as' 228 | } 229 | } 230 | }, {formats: {as:/^a+$/}}) 231 | 232 | t.notOk(validate({foo:''}), 'not as') 233 | t.notOk(validate({foo:'b'}), 'not as') 234 | t.notOk(validate({foo:'aaab'}), 'not as') 235 | t.ok(validate({foo:'a'}), 'as') 236 | t.ok(validate({foo:'aaaaaa'}), 'as') 237 | t.end() 238 | }) 239 | 240 | tape('custom format function', function(t) { 241 | var validate = validator({ 242 | type: 'object', 243 | properties: { 244 | foo: { 245 | type: 'string', 246 | format: 'as' 247 | } 248 | } 249 | }, {formats: {as:function(s) { return /^a+$/.test(s) } }}) 250 | 251 | t.notOk(validate({foo:''}), 'not as') 252 | t.notOk(validate({foo:'b'}), 'not as') 253 | t.notOk(validate({foo:'aaab'}), 'not as') 254 | t.ok(validate({foo:'a'}), 'as') 255 | t.ok(validate({foo:'aaaaaa'}), 'as') 256 | t.end() 257 | }) 258 | 259 | tape('do not mutate schema', function(t) { 260 | var sch = { 261 | items: [ 262 | {} 263 | ], 264 | additionalItems: { 265 | type: 'integer' 266 | } 267 | } 268 | 269 | var copy = JSON.parse(JSON.stringify(sch)) 270 | 271 | validator(sch) 272 | 273 | t.same(sch, copy, 'did not mutate') 274 | t.end() 275 | }) 276 | 277 | tape('#toJSON()', function(t) { 278 | var schema = { 279 | required: true, 280 | type: 'object', 281 | properties: { 282 | hello: {type:'string', required:true} 283 | } 284 | } 285 | 286 | var validate = validator(schema) 287 | 288 | t.deepEqual(validate.toJSON(), schema, 'should return original schema') 289 | t.end() 290 | }) 291 | 292 | tape('external schemas', function(t) { 293 | var ext = {type: 'string'} 294 | var schema = { 295 | required: true, 296 | $ref: '#ext' 297 | } 298 | 299 | var validate = validator(schema, {schemas: {ext:ext}}) 300 | 301 | t.ok(validate('hello string'), 'is a string') 302 | t.notOk(validate(42), 'not a string') 303 | t.end() 304 | }) 305 | 306 | tape('external schema URIs', function(t) { 307 | var ext = {type: 'string'} 308 | var schema = { 309 | required: true, 310 | $ref: 'http://example.com/schemas/schemaURIs' 311 | } 312 | 313 | var opts = {schemas:{}}; 314 | opts.schemas['http://example.com/schemas/schemaURIs'] = ext; 315 | var validate = validator(schema, opts) 316 | 317 | t.ok(validate('hello string'), 'is a string') 318 | t.notOk(validate(42), 'not a string') 319 | t.end() 320 | }) 321 | 322 | tape('top-level external schema', function(t) { 323 | var defs = { 324 | "string": { 325 | type: "string" 326 | }, 327 | "sex": { 328 | type: "string", 329 | enum: ["male", "female", "other"] 330 | } 331 | } 332 | var schema = { 333 | type: "object", 334 | properties: { 335 | "name": { $ref: "definitions.json#/string" }, 336 | "sex": { $ref: "definitions.json#/sex" } 337 | }, 338 | required: ["name", "sex"] 339 | } 340 | 341 | var validate = validator(schema, { 342 | schemas: { 343 | "definitions.json": defs 344 | } 345 | }) 346 | t.ok(validate({name:"alice", sex:"female"}), 'is an object') 347 | t.notOk(validate({name:"alice", sex: "bob"}), 'recognizes external schema') 348 | t.notOk(validate({name:2, sex: "female"}), 'recognizes external schema') 349 | t.end() 350 | }) 351 | 352 | tape('nested required array decl', function(t) { 353 | var schema = { 354 | properties: { 355 | x: { 356 | type: 'object', 357 | properties: { 358 | y: { 359 | type: 'object', 360 | properties: { 361 | z: { 362 | type: 'string' 363 | } 364 | }, 365 | required: ['z'] 366 | } 367 | } 368 | } 369 | }, 370 | required: ['x'] 371 | } 372 | 373 | var validate = validator(schema) 374 | 375 | t.ok(validate({x: {}}), 'should be valid') 376 | t.notOk(validate({}), 'should not be valid') 377 | t.strictEqual(validate.errors[0].field, 'data.x', 'should output the missing field') 378 | t.end() 379 | }) 380 | 381 | tape('verbose mode', function(t) { 382 | var schema = { 383 | required: true, 384 | type: 'object', 385 | properties: { 386 | hello: { 387 | required: true, 388 | type: 'string' 389 | } 390 | } 391 | }; 392 | 393 | var validate = validator(schema, {verbose: true}) 394 | 395 | t.ok(validate({hello: 'string'}), 'should be valid') 396 | t.notOk(validate({hello: 100}), 'should not be valid') 397 | t.strictEqual(validate.errors[0].value, 100, 'error object should contain the invalid value') 398 | t.strictEqual(validate.errors[0].type, 'string', 'error object should contain the type') 399 | t.end() 400 | }) 401 | 402 | tape('additional props in verbose mode', function(t) { 403 | var schema = { 404 | type: 'object', 405 | required: true, 406 | additionalProperties: false, 407 | properties: { 408 | foo: { 409 | type: 'string' 410 | }, 411 | 'hello world': { 412 | type: 'object', 413 | required: true, 414 | additionalProperties: false, 415 | properties: { 416 | foo: { 417 | type: 'string' 418 | } 419 | } 420 | } 421 | } 422 | }; 423 | 424 | var validate = validator(schema, {verbose: true}) 425 | 426 | validate({'hello world': {bar: 'string'}}); 427 | 428 | t.strictEqual(validate.errors[0].value, 'data["hello world"].bar', 'should output the path to the additional prop in the error') 429 | t.end() 430 | }) 431 | 432 | tape('Date.now() is an integer', function(t) { 433 | var schema = {type: 'integer'} 434 | var validate = validator(schema) 435 | 436 | t.ok(validate(Date.now()), 'is integer') 437 | t.end() 438 | }) 439 | 440 | tape('field shows item index in arrays', function(t) { 441 | var schema = { 442 | type: 'array', 443 | items: { 444 | type: 'array', 445 | items: { 446 | properties: { 447 | foo: { 448 | type: 'string', 449 | required: true 450 | } 451 | } 452 | } 453 | } 454 | } 455 | 456 | var validate = validator(schema) 457 | 458 | validate([ 459 | [ 460 | { foo: 'test' }, 461 | { foo: 'test' } 462 | ], 463 | [ 464 | { foo: 'test' }, 465 | { baz: 'test' } 466 | ] 467 | ]) 468 | 469 | t.strictEqual(validate.errors[0].field, 'data.1.1.foo', 'should output the field with specific index of failing item in the error') 470 | t.end() 471 | }) 472 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var genobj = require('generate-object-property') 2 | var genfun = require('generate-function') 3 | var jsonpointer = require('jsonpointer') 4 | var xtend = require('xtend') 5 | var formats = require('./formats') 6 | 7 | var get = function(obj, additionalSchemas, ptr) { 8 | 9 | var visit = function(sub) { 10 | if (sub && sub.id === ptr) return sub 11 | if (typeof sub !== 'object' || !sub) return null 12 | return Object.keys(sub).reduce(function(res, k) { 13 | return res || visit(sub[k]) 14 | }, null) 15 | } 16 | 17 | var res = visit(obj) 18 | if (res) return res 19 | 20 | ptr = ptr.replace(/^#/, '') 21 | ptr = ptr.replace(/\/$/, '') 22 | 23 | try { 24 | return jsonpointer.get(obj, decodeURI(ptr)) 25 | } catch (err) { 26 | var end = ptr.indexOf('#') 27 | var other 28 | // external reference 29 | if (end !== 0) { 30 | // fragment doesn't exist. 31 | if (end === -1) { 32 | other = additionalSchemas[ptr] 33 | } else { 34 | var ext = ptr.slice(0, end) 35 | other = additionalSchemas[ext] 36 | var fragment = ptr.slice(end).replace(/^#/, '') 37 | try { 38 | return jsonpointer.get(other, fragment) 39 | } catch (err) {} 40 | } 41 | } else { 42 | other = additionalSchemas[ptr] 43 | } 44 | return other || null 45 | } 46 | } 47 | 48 | var formatName = function(field) { 49 | field = JSON.stringify(field) 50 | var pattern = /\[([^\[\]"]+)\]/ 51 | while (pattern.test(field)) field = field.replace(pattern, '."+$1+"') 52 | return field 53 | } 54 | 55 | var types = {} 56 | 57 | types.any = function() { 58 | return 'true' 59 | } 60 | 61 | types.null = function(name) { 62 | return name+' === null' 63 | } 64 | 65 | types.boolean = function(name) { 66 | return 'typeof '+name+' === "boolean"' 67 | } 68 | 69 | types.array = function(name) { 70 | return 'Array.isArray('+name+')' 71 | } 72 | 73 | types.object = function(name) { 74 | return 'typeof '+name+' === "object" && '+name+' && !Array.isArray('+name+')' 75 | } 76 | 77 | types.number = function(name) { 78 | return 'typeof '+name+' === "number"' 79 | } 80 | 81 | types.integer = function(name) { 82 | return 'typeof '+name+' === "number" && (Math.floor('+name+') === '+name+' || '+name+' > 9007199254740992 || '+name+' < -9007199254740992)' 83 | } 84 | 85 | types.string = function(name) { 86 | return 'typeof '+name+' === "string"' 87 | } 88 | 89 | var unique = function(array) { 90 | var list = [] 91 | for (var i = 0; i < array.length; i++) { 92 | list.push(typeof array[i] === 'object' ? JSON.stringify(array[i]) : array[i]) 93 | } 94 | for (var i = 1; i < list.length; i++) { 95 | if (list.indexOf(list[i]) !== i) return false 96 | } 97 | return true 98 | } 99 | 100 | var isMultipleOf = function(name, multipleOf) { 101 | var res; 102 | var factor = ((multipleOf | 0) !== multipleOf) ? Math.pow(10, multipleOf.toString().split('.').pop().length) : 1 103 | if (factor > 1) { 104 | var factorName = ((name | 0) !== name) ? Math.pow(10, name.toString().split('.').pop().length) : 1 105 | if (factorName > factor) res = true 106 | else res = Math.round(factor * name) % (factor * multipleOf) 107 | } 108 | else res = name % multipleOf; 109 | return !res; 110 | } 111 | 112 | var compile = function(schema, cache, root, reporter, opts) { 113 | var fmts = opts ? xtend(formats, opts.formats) : formats 114 | var scope = {unique:unique, formats:fmts, isMultipleOf:isMultipleOf} 115 | var verbose = opts ? !!opts.verbose : false; 116 | var greedy = opts && opts.greedy !== undefined ? 117 | opts.greedy : false; 118 | 119 | var syms = {} 120 | var gensym = function(name) { 121 | return name+(syms[name] = (syms[name] || 0)+1) 122 | } 123 | 124 | var reversePatterns = {} 125 | var patterns = function(p) { 126 | if (reversePatterns[p]) return reversePatterns[p] 127 | var n = gensym('pattern') 128 | scope[n] = new RegExp(p) 129 | reversePatterns[p] = n 130 | return n 131 | } 132 | 133 | var vars = ['i','j','k','l','m','n','o','p','q','r','s','t','u','v','x','y','z'] 134 | var genloop = function() { 135 | var v = vars.shift() 136 | vars.push(v+v[0]) 137 | return v 138 | } 139 | 140 | var visit = function(name, node, reporter, filter) { 141 | var properties = node.properties 142 | var type = node.type 143 | var tuple = false 144 | 145 | if (Array.isArray(node.items)) { // tuple type 146 | properties = {} 147 | node.items.forEach(function(item, i) { 148 | properties[i] = item 149 | }) 150 | type = 'array' 151 | tuple = true 152 | } 153 | 154 | var indent = 0 155 | var error = function(msg, prop, value) { 156 | validate('errors++') 157 | if (reporter === true) { 158 | validate('if (validate.errors === null) validate.errors = []') 159 | if (verbose) { 160 | validate('validate.errors.push({field:%s,message:%s,value:%s,type:%s})', formatName(prop || name), JSON.stringify(msg), value || name, JSON.stringify(type)) 161 | } else { 162 | validate('validate.errors.push({field:%s,message:%s})', formatName(prop || name), JSON.stringify(msg)) 163 | } 164 | } 165 | } 166 | 167 | if (node.required === true) { 168 | indent++ 169 | validate('if (%s === undefined) {', name) 170 | error('is required') 171 | validate('} else {') 172 | } else { 173 | indent++ 174 | validate('if (%s !== undefined) {', name) 175 | } 176 | 177 | var valid = [].concat(type) 178 | .map(function(t) { 179 | if (t && !types.hasOwnProperty(t)) { 180 | throw new Error('Unknown type: ' + t) 181 | } 182 | 183 | return types[t || 'any'](name) 184 | }) 185 | .join(' || ') || 'true' 186 | 187 | if (valid !== 'true') { 188 | indent++ 189 | validate('if (!(%s)) {', valid) 190 | error('is the wrong type') 191 | validate('} else {') 192 | } 193 | 194 | if (tuple) { 195 | if (node.additionalItems === false) { 196 | validate('if (%s.length > %d) {', name, node.items.length) 197 | error('has additional items') 198 | validate('}') 199 | } else if (node.additionalItems) { 200 | var i = genloop() 201 | validate('for (var %s = %d; %s < %s.length; %s++) {', i, node.items.length, i, name, i) 202 | visit(name+'['+i+']', node.additionalItems, reporter, filter) 203 | validate('}') 204 | } 205 | } 206 | 207 | if (node.format && fmts[node.format]) { 208 | if (type !== 'string' && formats[node.format]) validate('if (%s) {', types.string(name)) 209 | var n = gensym('format') 210 | scope[n] = fmts[node.format] 211 | 212 | if (typeof scope[n] === 'function') validate('if (!%s(%s)) {', n, name) 213 | else validate('if (!%s.test(%s)) {', n, name) 214 | error('must be '+node.format+' format') 215 | validate('}') 216 | if (type !== 'string' && formats[node.format]) validate('}') 217 | } 218 | 219 | if (Array.isArray(node.required)) { 220 | var checkRequired = function (req) { 221 | var prop = genobj(name, req); 222 | validate('if (%s === undefined) {', prop) 223 | error('is required', prop) 224 | validate('missing++') 225 | validate('}') 226 | } 227 | validate('if ((%s)) {', type !== 'object' ? types.object(name) : 'true') 228 | validate('var missing = 0') 229 | node.required.map(checkRequired) 230 | validate('}'); 231 | if (!greedy) { 232 | validate('if (missing === 0) {') 233 | indent++ 234 | } 235 | } 236 | 237 | if (node.uniqueItems) { 238 | if (type !== 'array') validate('if (%s) {', types.array(name)) 239 | validate('if (!(unique(%s))) {', name) 240 | error('must be unique') 241 | validate('}') 242 | if (type !== 'array') validate('}') 243 | } 244 | 245 | if (node.enum) { 246 | var complex = node.enum.some(function(e) { 247 | return typeof e === 'object' 248 | }) 249 | 250 | var compare = complex ? 251 | function(e) { 252 | return 'JSON.stringify('+name+')'+' !== JSON.stringify('+JSON.stringify(e)+')' 253 | } : 254 | function(e) { 255 | return name+' !== '+JSON.stringify(e) 256 | } 257 | 258 | validate('if (%s) {', node.enum.map(compare).join(' && ') || 'false') 259 | error('must be an enum value') 260 | validate('}') 261 | } 262 | 263 | if (node.dependencies) { 264 | if (type !== 'object') validate('if (%s) {', types.object(name)) 265 | 266 | Object.keys(node.dependencies).forEach(function(key) { 267 | var deps = node.dependencies[key] 268 | if (typeof deps === 'string') deps = [deps] 269 | 270 | var exists = function(k) { 271 | return genobj(name, k) + ' !== undefined' 272 | } 273 | 274 | if (Array.isArray(deps)) { 275 | validate('if (%s !== undefined && !(%s)) {', genobj(name, key), deps.map(exists).join(' && ') || 'true') 276 | error('dependencies not set') 277 | validate('}') 278 | } 279 | if (typeof deps === 'object') { 280 | validate('if (%s !== undefined) {', genobj(name, key)) 281 | visit(name, deps, reporter, filter) 282 | validate('}') 283 | } 284 | }) 285 | 286 | if (type !== 'object') validate('}') 287 | } 288 | 289 | if (node.additionalProperties || node.additionalProperties === false) { 290 | if (type !== 'object') validate('if (%s) {', types.object(name)) 291 | 292 | var i = genloop() 293 | var keys = gensym('keys') 294 | 295 | var toCompare = function(p) { 296 | return keys+'['+i+'] !== '+JSON.stringify(p) 297 | } 298 | 299 | var toTest = function(p) { 300 | return '!'+patterns(p)+'.test('+keys+'['+i+'])' 301 | } 302 | 303 | var additionalProp = Object.keys(properties || {}).map(toCompare) 304 | .concat(Object.keys(node.patternProperties || {}).map(toTest)) 305 | .join(' && ') || 'true' 306 | 307 | validate('var %s = Object.keys(%s)', keys, name) 308 | ('for (var %s = 0; %s < %s.length; %s++) {', i, i, keys, i) 309 | ('if (%s) {', additionalProp) 310 | 311 | if (node.additionalProperties === false) { 312 | if (filter) validate('delete %s', name+'['+keys+'['+i+']]') 313 | error('has additional properties', null, JSON.stringify(name+'.') + ' + ' + keys + '['+i+']') 314 | } else { 315 | visit(name+'['+keys+'['+i+']]', node.additionalProperties, reporter, filter) 316 | } 317 | 318 | validate 319 | ('}') 320 | ('}') 321 | 322 | if (type !== 'object') validate('}') 323 | } 324 | 325 | if (node.$ref) { 326 | var sub = get(root, opts && opts.schemas || {}, node.$ref) 327 | if (sub) { 328 | var fn = cache[node.$ref] 329 | if (!fn) { 330 | cache[node.$ref] = function proxy(data) { 331 | return fn(data) 332 | } 333 | fn = compile(sub, cache, root, false, opts) 334 | } 335 | var n = gensym('ref') 336 | scope[n] = fn 337 | validate('if (!(%s(%s))) {', n, name) 338 | error('referenced schema does not match') 339 | validate('}') 340 | } 341 | } 342 | 343 | if (node.not) { 344 | var prev = gensym('prev') 345 | validate('var %s = errors', prev) 346 | visit(name, node.not, false, filter) 347 | validate('if (%s === errors) {', prev) 348 | error('negative schema matches') 349 | validate('} else {') 350 | ('errors = %s', prev) 351 | ('}') 352 | } 353 | 354 | if (node.items && !tuple) { 355 | if (type !== 'array') validate('if (%s) {', types.array(name)) 356 | 357 | var i = genloop() 358 | validate('for (var %s = 0; %s < %s.length; %s++) {', i, i, name, i) 359 | visit(name+'['+i+']', node.items, reporter, filter) 360 | validate('}') 361 | 362 | if (type !== 'array') validate('}') 363 | } 364 | 365 | if (node.patternProperties) { 366 | if (type !== 'object') validate('if (%s) {', types.object(name)) 367 | var keys = gensym('keys') 368 | var i = genloop() 369 | validate 370 | ('var %s = Object.keys(%s)', keys, name) 371 | ('for (var %s = 0; %s < %s.length; %s++) {', i, i, keys, i) 372 | 373 | Object.keys(node.patternProperties).forEach(function(key) { 374 | var p = patterns(key) 375 | validate('if (%s.test(%s)) {', p, keys+'['+i+']') 376 | visit(name+'['+keys+'['+i+']]', node.patternProperties[key], reporter, filter) 377 | validate('}') 378 | }) 379 | 380 | validate('}') 381 | if (type !== 'object') validate('}') 382 | } 383 | 384 | if (node.pattern) { 385 | var p = patterns(node.pattern) 386 | if (type !== 'string') validate('if (%s) {', types.string(name)) 387 | validate('if (!(%s.test(%s))) {', p, name) 388 | error('pattern mismatch') 389 | validate('}') 390 | if (type !== 'string') validate('}') 391 | } 392 | 393 | if (node.allOf) { 394 | node.allOf.forEach(function(sch) { 395 | visit(name, sch, reporter, filter) 396 | }) 397 | } 398 | 399 | if (node.anyOf && node.anyOf.length) { 400 | var prev = gensym('prev') 401 | 402 | node.anyOf.forEach(function(sch, i) { 403 | if (i === 0) { 404 | validate('var %s = errors', prev) 405 | } else { 406 | validate('if (errors !== %s) {', prev) 407 | ('errors = %s', prev) 408 | } 409 | visit(name, sch, false, false) 410 | }) 411 | node.anyOf.forEach(function(sch, i) { 412 | if (i) validate('}') 413 | }) 414 | validate('if (%s !== errors) {', prev) 415 | error('no schemas match') 416 | validate('}') 417 | } 418 | 419 | if (node.oneOf && node.oneOf.length) { 420 | var prev = gensym('prev') 421 | var passes = gensym('passes') 422 | 423 | validate 424 | ('var %s = errors', prev) 425 | ('var %s = 0', passes) 426 | 427 | node.oneOf.forEach(function(sch, i) { 428 | visit(name, sch, false, false) 429 | validate('if (%s === errors) {', prev) 430 | ('%s++', passes) 431 | ('} else {') 432 | ('errors = %s', prev) 433 | ('}') 434 | }) 435 | 436 | validate('if (%s !== 1) {', passes) 437 | error('no (or more than one) schemas match') 438 | validate('}') 439 | } 440 | 441 | if (node.multipleOf !== undefined) { 442 | if (type !== 'number' && type !== 'integer') validate('if (%s) {', types.number(name)) 443 | 444 | validate('if (!isMultipleOf(%s, %d)) {', name, node.multipleOf) 445 | 446 | error('has a remainder') 447 | validate('}') 448 | 449 | if (type !== 'number' && type !== 'integer') validate('}') 450 | } 451 | 452 | if (node.maxProperties !== undefined) { 453 | if (type !== 'object') validate('if (%s) {', types.object(name)) 454 | 455 | validate('if (Object.keys(%s).length > %d) {', name, node.maxProperties) 456 | error('has more properties than allowed') 457 | validate('}') 458 | 459 | if (type !== 'object') validate('}') 460 | } 461 | 462 | if (node.minProperties !== undefined) { 463 | if (type !== 'object') validate('if (%s) {', types.object(name)) 464 | 465 | validate('if (Object.keys(%s).length < %d) {', name, node.minProperties) 466 | error('has less properties than allowed') 467 | validate('}') 468 | 469 | if (type !== 'object') validate('}') 470 | } 471 | 472 | if (node.maxItems !== undefined) { 473 | if (type !== 'array') validate('if (%s) {', types.array(name)) 474 | 475 | validate('if (%s.length > %d) {', name, node.maxItems) 476 | error('has more items than allowed') 477 | validate('}') 478 | 479 | if (type !== 'array') validate('}') 480 | } 481 | 482 | if (node.minItems !== undefined) { 483 | if (type !== 'array') validate('if (%s) {', types.array(name)) 484 | 485 | validate('if (%s.length < %d) {', name, node.minItems) 486 | error('has less items than allowed') 487 | validate('}') 488 | 489 | if (type !== 'array') validate('}') 490 | } 491 | 492 | if (node.maxLength !== undefined) { 493 | if (type !== 'string') validate('if (%s) {', types.string(name)) 494 | 495 | validate('if (%s.length > %d) {', name, node.maxLength) 496 | error('has longer length than allowed') 497 | validate('}') 498 | 499 | if (type !== 'string') validate('}') 500 | } 501 | 502 | if (node.minLength !== undefined) { 503 | if (type !== 'string') validate('if (%s) {', types.string(name)) 504 | 505 | validate('if (%s.length < %d) {', name, node.minLength) 506 | error('has less length than allowed') 507 | validate('}') 508 | 509 | if (type !== 'string') validate('}') 510 | } 511 | 512 | if (node.minimum !== undefined) { 513 | if (type !== 'number' && type !== 'integer') validate('if (%s) {', types.number(name)) 514 | 515 | validate('if (%s %s %d) {', name, node.exclusiveMinimum ? '<=' : '<', node.minimum) 516 | error('is less than minimum') 517 | validate('}') 518 | 519 | if (type !== 'number' && type !== 'integer') validate('}') 520 | } 521 | 522 | if (node.maximum !== undefined) { 523 | if (type !== 'number' && type !== 'integer') validate('if (%s) {', types.number(name)) 524 | 525 | validate('if (%s %s %d) {', name, node.exclusiveMaximum ? '>=' : '>', node.maximum) 526 | error('is more than maximum') 527 | validate('}') 528 | 529 | if (type !== 'number' && type !== 'integer') validate('}') 530 | } 531 | 532 | if (properties) { 533 | Object.keys(properties).forEach(function(p) { 534 | if (Array.isArray(type) && type.indexOf('null') !== -1) validate('if (%s !== null) {', name) 535 | 536 | visit(genobj(name, p), properties[p], reporter, filter) 537 | 538 | if (Array.isArray(type) && type.indexOf('null') !== -1) validate('}') 539 | }) 540 | } 541 | 542 | while (indent--) validate('}') 543 | } 544 | 545 | var validate = genfun 546 | ('function validate(data) {') 547 | // Since undefined is not a valid JSON value, we coerce to null and other checks will catch this 548 | ('if (data === undefined) data = null') 549 | ('validate.errors = null') 550 | ('var errors = 0') 551 | 552 | visit('data', schema, reporter, opts && opts.filter) 553 | 554 | validate 555 | ('return errors === 0') 556 | ('}') 557 | 558 | validate = validate.toFunction(scope) 559 | validate.errors = null 560 | 561 | if (Object.defineProperty) { 562 | Object.defineProperty(validate, 'error', { 563 | get: function() { 564 | if (!validate.errors) return '' 565 | return validate.errors.map(function(err) { 566 | return err.field + ' ' + err.message; 567 | }).join('\n') 568 | } 569 | }) 570 | } 571 | 572 | validate.toJSON = function() { 573 | return schema 574 | } 575 | 576 | return validate 577 | } 578 | 579 | module.exports = function(schema, opts) { 580 | if (typeof schema === 'string') schema = JSON.parse(schema) 581 | return compile(schema, {}, schema, true, opts) 582 | } 583 | 584 | module.exports.filter = function(schema, opts) { 585 | var validate = module.exports(schema, xtend(opts, {filter: true})) 586 | return function(sch) { 587 | validate(sch) 588 | return sch 589 | } 590 | } 591 | --------------------------------------------------------------------------------