Repository: pandastrike/jsck Branch: master Commit: 944aa33bcc30 Files: 107 Total size: 145.5 KB Directory structure: gitextract__sjmg1fx/ ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── benchmarks/ │ ├── benchmark.coffee │ ├── draft3/ │ │ ├── index.coffee │ │ ├── medium/ │ │ │ ├── index.coffee │ │ │ ├── schema.coffee │ │ │ └── valid_doc.coffee │ │ ├── trivial/ │ │ │ ├── index.coffee │ │ │ ├── schema.coffee │ │ │ └── valid_doc.coffee │ │ └── validators.coffee │ ├── draft4/ │ │ ├── complex/ │ │ │ ├── index.coffee │ │ │ ├── schema.coffee │ │ │ ├── test.coffee │ │ │ └── valid_doc.coffee │ │ ├── index.coffee │ │ ├── medium/ │ │ │ ├── index.coffee │ │ │ ├── schema.coffee │ │ │ └── valid_doc.coffee │ │ ├── trivial/ │ │ │ ├── index.coffee │ │ │ ├── schema.coffee │ │ │ └── valid_doc.coffee │ │ └── validators.coffee │ ├── index.coffee │ ├── results/ │ │ └── all.txt │ ├── runner.coffee │ └── statistics.js ├── doc/ │ ├── README.pfm.md │ ├── benchmarks.md │ ├── benchmarks.pfm.md │ ├── tests.md │ └── tests.pfm.md ├── examples/ │ └── draft4/ │ ├── advanced.coffee │ └── basic.coffee ├── package.json ├── schemas/ │ ├── draft-03/ │ │ └── schema.json │ └── draft-04/ │ └── schema.json ├── src/ │ ├── common/ │ │ ├── arrays.coffee │ │ ├── comparison.coffee │ │ ├── numeric.coffee │ │ ├── objects.coffee │ │ ├── strings.coffee │ │ └── type.coffee │ ├── draft3/ │ │ ├── logical.coffee │ │ ├── numeric.coffee │ │ ├── objects.coffee │ │ └── strings.coffee │ ├── draft3.coffee │ ├── draft4/ │ │ ├── logical.coffee │ │ ├── numeric.coffee │ │ ├── objects.coffee │ │ ├── strings.coffee │ │ └── type.coffee │ ├── draft4.coffee │ ├── index.coffee │ ├── uri.coffee │ ├── util.coffee │ └── validator.coffee ├── tasks/ │ ├── build/ │ │ ├── benchmarks.coffee │ │ ├── browser.coffee │ │ ├── docs.coffee │ │ ├── index.coffee │ │ └── javascript.coffee │ ├── helpers.coffee │ ├── update/ │ │ ├── index.coffee │ │ └── submodules.coffee │ └── watch/ │ ├── docs.coffee │ └── src.coffee └── test/ ├── .gitignore ├── draft3/ │ ├── builtins.coffee │ ├── index.coffee │ ├── official.coffee │ └── unit/ │ ├── errors.coffee │ ├── index.coffee │ ├── references.coffee │ └── uri_test.coffee ├── draft4/ │ ├── adhoc.coffee │ ├── builtins.coffee │ ├── index.coffee │ ├── invalid/ │ │ ├── additionalItems.coffee │ │ ├── additionalProperties.coffee │ │ ├── allOf.coffee │ │ ├── anyOf.coffee │ │ ├── dependencies.coffee │ │ ├── items.coffee │ │ ├── not.coffee │ │ ├── numbers.coffee │ │ ├── oneOf.coffee │ │ ├── patternProperties.coffee │ │ ├── properties.coffee │ │ ├── required.coffee │ │ ├── strings.coffee │ │ └── type.coffee │ ├── invalid.coffee │ ├── official.coffee │ ├── unit/ │ │ ├── errors.coffee │ │ ├── index.coffee │ │ ├── logical.coffee │ │ ├── references.coffee │ │ └── uri_test.coffee │ ├── valid/ │ │ ├── basic.coffee │ │ ├── definitions.coffee │ │ └── properties.coffee │ └── valid.coffee └── index.coffee ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ lib node_modules mine .ruby-gemset npm-debug.log *.tgz ================================================ FILE: .gitmodules ================================================ [submodule "test/JSON-Schema-Test-Suite"] path = test/JSON-Schema-Test-Suite url = git@github.com:json-schema/JSON-Schema-Test-Suite.git ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2013 Matthew King Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # JSON Schema Compiled checK JSCK is one of the fastest [JSON Schema](http://json-schema.org) validators for Node.js. It supports JSON Schema drafts [3][draft3_doc] and [4][draft4_doc], with a few caveats (see the [Coverage section](#coverage) below). ## Installation and Usage Install with NPM: npm install --save jsck Require: ```coffee JSCK = require "jsck" ``` JSCK can create validators from multiple schemas, but this requires that each schema be identified with a URI in a top-level "id" field. In many cases, only a single schema is needed, and there is no need to uniquely identify the schema. This is the easiest way to use JSCK, as it is a common pattern. ```coffee # Construct a validator for a schema lacking an "id" declaration jsck = new JSCK.draft4 type: "object" properties: user: type: "object" required: ["login"] properties: login: type: "string" pattern: "^[\\w\\d_]{3,32}$" email: type: "string" format: "email" console.log "valid document:", jsck.validate user: login: "matthew" email: "matthew@pandastrike.com" {errors} = jsck.validate user: login: "matthew" email: "pandastrike.com" console.log "invalid document:", errors ``` To use Draft 3 schemas: ```.coffee validator = new JSCK.draft3(schema) ``` See these [advanced usage examples](examples/draft4/advanced.coffee) for help working with multiple schemas. ## Why JSCK? JSCK is [faster](#benchmarks) than most other JavaScript/CoffeeScript libraries for validating JSON Schemas because it "compiles" the schemas. That is, JSCK generates the tree of functions needed to validate a particular schema when you construct a validator. The schema is thus traversed only during preparation, and most of the work of interpreting the schema is done at this time, rather than for every document submitted for validation. This minimizes the work required during validation, which leads to substantial performance improvements over non-compiling validators. ## Coverage ### Draft 4 JSCK passes all tests in the canonical [JSON Schema Test Suite][canonical], except for these items: * use of `maxLength` and `minLength` with Unicode surrogate pairs. * `refRemote` (this is an essential feature we do plan to support) * `ref` * remote ref, containing refs itself * `uniqueItems` * `optional/zeroTerminatedFloats` ### Draft 3 Currently passing the canonical [test suite][canonical] for draft3 except for these items: * `refRemote` * `ref` * remote ref, containing refs itself * `uniqueItems` * `optional/zeroTerminatedFloats` ### Managing resolution scope with the "id" attribute JSCK does not support the full range of scope manipulations suggested by JSON Schema drafts 3 and 4. Scope manipulation is a controversial topic, and with JSCK we have chosen to play it safe, supporting "id" declarations only in cases that will (probably) not lead to any ambiguity. Specifically, JSCK uses "id" declarations only in these cases: * at the top level of a schema, to provide a namespace for schemas not loaded from URIs. * non-JSON-pointer fragments (`"id": "#user"`), which serve merely as aliases for specific subschemas, and are thus convenient and unambiguous. For more information on the topic of the "id" attribute and scope manipulation, see this issue: https://github.com/json-schema/json-schema/issues/77. ## Contributing To contribute, hack on it, or run the tests: ```shell git clone git@github.com:pandastrike/jsck.git cd jsck coffee tasks/update npm install ``` ### Tests JSCK uses the official [JSON Schema Test Suite][canonical] as well as some custom tests. To run all tests for all versions: coffee test See [this document](doc/tests.md) for more information on working with JSCK tests. ## Benchmarks JSCK has fairly comprehensive benchmarks which show it to be one of the very fastest JSON Schema validators available for Node.js. Pull requests welcome, of course. Because performance varies (at very least) based on the complexity of the schema being validated, we run benchmarks against several different schemas, ranging from quite simple to moderately complex. For JSON Schema Draft4, we run benchmarks against JSCK, tv4, jayschema, z-schema, and other validators. On the [trivial schema](benchmarks/draft4/trivial/schema.coffee), our benchmarks produce this relative performance for these validators (lower is better): ```coffee ajv : 1 jsen : 2.9 is-my-json-valid : 4.4 Themis (minimal) : 5.2 Themis : 5.3 JSCK : 34.2 z-schema : 48.3 tv4 : 54.4 jayschema : 2507.4 ``` For the schema of [medium complexity](benchmarks/draft4/medium/schema.coffee), our benchmarks produce this relative performance for the tested validators (lower is better): ```coffee ajv : 1 is-my-json-valid : 2.8 jsen : 3.0 Themis (minimal) : 11.1 Themis : 11.6 JSCK : 22.0 tv4 : 43.4 z-schema : 46.0 jayschema : 2319.4 ``` For the schema of [higher complexity](benchmarks/draft4/complex/schema.coffee), our benchmarks produce this relative performance for the tested validators (lower is better): ```coffee ajv : 1 is-my-json-valid : 1.23 jsen : 1.31 Themis (minimal) : 1.7 Themis : 1.8 JSCK : 4.8 z-schema : 17.2 tv4 : 27.0 jayschema : 1215.1 ``` As the complexity of the schema increases, the performance benefits of the compilation model become more evident. See [this document](doc/benchmarks.md) for detailed results and information on running and creating benchmarks. [draft3_doc]:http://tools.ietf.org/html/draft-zyp-json-schema-03 [draft3_impl]:https://github.com/json-schema/json-schema/tree/master/draft-03 [draft4_doc]:http://tools.ietf.org/html/draft-zyp-json-schema-04 [canonical]:https://github.com/json-schema/JSON-Schema-Test-Suite ================================================ FILE: benchmarks/benchmark.coffee ================================================ microtime = require "microtime" require "./statistics" module.exports = class Benchmark @compare: (benchmarks, options) -> out = {} for benchmark in benchmarks out[benchmark.name] = benchmark.run(options) out constructor: (@name, callback) -> callback(@) setup: (@_setup) -> measure: (@_measure) -> run: ({samples, warmup}) -> console.error " #{@name}" results = [] subject = @_setup() if warmup process.stderr.write " Warming up: " for i in [1..warmup] @_measure(subject) process.stderr.write "." process.stderr.write("\n") process.stderr.write " Iterations: " for i in [1..samples] t0 = microtime.now() @_measure(subject) t1 = microtime.now() results.push (t1 - t0) / 1000 process.stderr.write "." console.error("\n") results ================================================ FILE: benchmarks/draft3/index.coffee ================================================ console.log "## Benchmarks for Draft 3" console.log require "./trivial/" require "./medium/" ================================================ FILE: benchmarks/draft3/medium/index.coffee ================================================ validators = require "../validators" {benchmark} = require("../../runner")(validators) benchmark { name: "Configuration" repeats: 128 schema: require "./schema" document: require "./valid_doc" } ================================================ FILE: benchmarks/draft3/medium/schema.coffee ================================================ module.exports = description: "A moderately complex schema with some nesting and value constraints" type: "object" additionalProperties: false properties: api_server: description: "Settings for the HTTP API server" type: "object" additionalProperties: false required: true properties: url: type: "string" format: "uri" required: true host: type: "string" required: true port: type: "integer" minimum: 1000 required: true transport: description: "Settings for the Redis tranport" additionalProperties: false required: true properties: server: type: "string" required: true options: type: "object" queues: properties: blocking_timeout: type: "integer" minimum: 0 storage: description: "Settings for the PostgreSQL storage" required: true properties: server: type: "string" required: true database: type: "string" required: true user: type: "string" required: true options: type: "object" chain: description: "Settings for the Chain.com client" required: true properties: api_key_id: type: "string" required: true api_key_secret: type: "string" required: true ================================================ FILE: benchmarks/draft3/medium/valid_doc.coffee ================================================ module.exports = api_server: url: "http://example.com:8998" host: "example.com" port: 8998 transport: server: "127.0.0.1:6381" queues: blocking_timeout: 0 storage: server: "127.0.0.1:5432" database: "thingy-test" user: "thingy-test" password: "password" chain: api_key_id: "cafebabe" api_key_secret: "babecafe" ================================================ FILE: benchmarks/draft3/trivial/index.coffee ================================================ validators = require "../validators" {benchmark} = require("../../runner")(validators) benchmark { name: "Event" repeats: 512 schema: require "./schema" document: require "./valid_doc" } ================================================ FILE: benchmarks/draft3/trivial/schema.coffee ================================================ module.exports = description: "A simple schema, exercising very few attributes" type: "object" additionalProperties: false properties: origin: required: true type: "string" name: type: "string" required: true tags: type: "array" items: {type: "string"} timestamp: type: "integer" data: { type: "object" } ================================================ FILE: benchmarks/draft3/trivial/valid_doc.coffee ================================================ module.exports = origin: "monkey" name: "shines" tags: ["in", "the", "closet"] timestamp: Date.now() data: uno: 1 dos: 2 ================================================ FILE: benchmarks/draft3/validators.coffee ================================================ module.exports = "JSCK": setup: (schema) -> JSCK = require "../../src/draft3" new JSCK(schema) validate: ({validator, schema, document}) -> validator.validate(document) error: (result) -> if result.valid == true false else result.errors "amanda": setup: (schema) -> amanda = require "amanda" validate: ({validator, schema, document}) -> result = null validator.validate document, schema, (error) -> result = error result error: (result) -> result || false "JSV": setup: (schema) -> JSV = require("JSV").JSV jsv = JSV.createEnvironment("json-schema-draft-03") jsv.createSchema(schema) validate: ({validator, schema, document}) -> validator.validate(document) error: (result) -> if result.errors.length == 0 false else result.errors "json-gate": setup: (schema) -> jsonGateCreateSchema = require("json-gate").createSchema jsonGateCreateSchema(schema) validate: ({validator, schema, document}) -> try validator.validate(document) catch e e error: (result) -> result || false ================================================ FILE: benchmarks/draft4/complex/index.coffee ================================================ validators = require "../validators" {benchmark} = require("../../runner")(validators) benchmark { name: "Transaction" repeats: 64 schema: require "./schema" document: require "./valid_doc" } ================================================ FILE: benchmarks/draft4/complex/schema.coffee ================================================ module.exports = type: "array" items: {$ref: "#transaction"} minItems: 1 definitions: base58: id: "#base58" # https://en.bitcoin.it/wiki/Base58Check_encoding type: "string" pattern: "^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$" hex: id: "#hex" type: "string" pattern: "^[0123456789A-Fa-f]+$" tx_id: id: "#tx_id" allOf: [ {$ref: "#hex"} { minLength: 64 maxLength: 64 } ] address: id: "#address" allOf: [ {$ref: "#base58"} { minLength: 34 maxLength: 34 } ] signature: id: "#signature" allOf: [ {$ref: "#hex"} { minLength: 128 maxLength: 128 } ] transaction: id: "#transaction" additionalProperties: false required: ["metadata", "hash", "inputs", "outputs"] properties: metadata: type: "object" required: ["amount", "fee"] properties: amount: type: "integer" fee: type: "integer" multipleOf: 10000 status: type: "string" enum: ["unsigned", "unconfirmed", "confirmed", "invalid"] confirmations: type: "integer" minimum: 0 block_time: type: "integer" version: {type: "integer"} lock_time: {type: "integer"} hash: {$ref: "#tx_id"} inputs: type: "array" items: {$ref: "#input"} minItems: 1 outputs: type: "array" items: {$ref: "#output"} minItems: 1 input: id: "#input" type: "object" additionalProperties: false required: ["index", "output", "script_sig"] properties: index: type: "integer" minimum: 0 output: {$ref: "#output"} sig_hash: {$ref: "#hex"} script_sig: {$ref: "#hex"} signatures: type: "object" description: "A dictionary of signatures. Keys represent keypair names" minProperties: 1 maxProperties: 3 additionalProperties: {$ref: "#signature"} output: id: "#output" type: "object" additionalProperties: false required: ["hash", "index", "value", "script"] properties: hash: {$ref: "#tx_id"} index: type: "integer" minimum: 0 value: type: "integer" script: type: "object" properties: type: type: "string" enum: ["standard", "p2sh"] asm: type: "string" address: {$ref: "#address"} metadata: type: "object" dependencies: # if a wallet path is given, the metadata must also contain the # HDW public seed values. Use case would be for verifying that # a change output is going to a legit address. wallet_path: ["public_seeds"] properties: wallet_path: type: "string" public_seeds: type: "object" minProperties: 1 maxProperties: 3 additionalProperties: anyOf: [ {$ref: "#base58"} {$ref: "#hex"} ] ================================================ FILE: benchmarks/draft4/complex/test.coffee ================================================ JSCK = require "../../../src/draft4" schema = require "./schema" valid_doc = require "./valid_doc" schema = type: "array" items: {$ref: "#smurf"} minItems: 1 definitions: smurf: id: "#smurf" type: "object" valid_doc = [ {} ] jsck = new JSCK(schema) report = jsck.validate(valid_doc) console.log "JSCK" console.log JSON.stringify(report, null, 2) ZSchema = require("z-schema") validator = new ZSchema() console.log "ZSchema" console.log validator.validate(valid_doc, schema) console.log validator.getLastErrors() console.log "JSONSchema" JSONSchema = require('jsonschema').Validator validator = new JSONSchema() {errors} = validator.validate(valid_doc, schema) console.log errors ================================================ FILE: benchmarks/draft4/complex/valid_doc.coffee ================================================ $ = module.exports = [] $.push metadata: amount: 38043749285 fee: 2 * 10000 status: "confirmed" confirmations: 73 block_time: 1415993584376 version: 1 lock_time: 0 hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" inputs: [ index: 0 script_sig: "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01" signatures: primary: "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a7" cosigner: "a2ad5ebf16dadf9d357ef2867cb9b1de682b336db000b6e0012200ebda7c8802f7c5ea2afd97439840a191c756be6528521b214487d5fc79796eb00122064037" output: hash: "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d" index: 1 value: 38043749285 script: type: "standard" asm: "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" ] outputs: [ { hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" index: 0 value: 38042249285 script: type: "standard" asm: "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" address: "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1" metadata: type: "change" wallet_path: "m/44/0/1/356" public_seeds: primary: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" cosigner: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" } { hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" index: 1 value: 1500000 script: type: "standard" asm: "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" address: "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] $.push metadata: amount: 38043749285 fee: 2 * 10000 status: "unconfirmed" confirmations: 73 block_time: 1415993584376 version: 1 lock_time: 0 hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" inputs: [ index: 0 script_sig: "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01" output: hash: "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d" index: 1 value: 38043749285 script: type: "standard" asm: "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" ] outputs: [ { hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" index: 0 value: 38042249285 script: type: "standard" asm: "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" address: "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1" metadata: type: "change" } { hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" index: 1 value: 1500000 script: type: "standard" asm: "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" address: "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] $.push metadata: amount: 38043749285 fee: 2 * 10000 status: "unconfirmed" confirmations: 73 block_time: 1415993584376 version: 1 lock_time: 0 hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" inputs: [ index: 0 script_sig: "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01" output: hash: "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d" index: 1 value: 38043749285 script: type: "standard" asm: "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" ] outputs: [ { hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" index: 0 value: 38042249285 script: type: "standard" asm: "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" address: "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1" metadata: type: "change" } { hash: "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471" index: 1 value: 1500000 script: type: "standard" asm: "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" address: "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] ================================================ FILE: benchmarks/draft4/index.coffee ================================================ console.log "## Benchmarks for Draft 4" console.log require "./trivial/" require "./medium/" require "./complex/" ================================================ FILE: benchmarks/draft4/medium/index.coffee ================================================ validators = require "../validators" {benchmark} = require("../../runner")(validators) benchmark { name: "Configuration" repeats: 256 schema: require "./schema" document: require "./valid_doc" } ================================================ FILE: benchmarks/draft4/medium/schema.coffee ================================================ module.exports = description: "A moderately complex schema with some nesting and value constraints" type: "object" additionalProperties: false required: ["api_server", "transport", "storage", "chain"] properties: api_server: description: "Settings for the HTTP API server" type: "object" additionalProperties: false required: ["url", "host", "port"] properties: url: type: "string" format: "uri" host: type: "string" port: type: "integer" minimum: 1000 transport: description: "Settings for the Redis tranport" additionalProperties: false required: ["server"] properties: server: type: "string" options: type: "object" queues: properties: blocking_timeout: type: "integer" minimum: 0 storage: description: "Settings for the PostgreSQL storage" required: ["server", "database", "user"] properties: server: type: "string" database: type: "string" user: type: "string" options: type: "object" chain: description: "Settings for the Chain.com client" required: ["api_key_id", "api_key_secret"] properties: api_key_id: type: "string" api_key_secret: type: "string" ================================================ FILE: benchmarks/draft4/medium/valid_doc.coffee ================================================ module.exports = api_server: url: "http://example.com:8998" host: "example.com" port: 8998 transport: server: "127.0.0.1:6381" queues: blocking_timeout: 0 storage: server: "127.0.0.1:5432" database: "thingy-test" user: "thingy-test" password: "password" chain: api_key_id: "cafebabe" api_key_secret: "babecafe" ================================================ FILE: benchmarks/draft4/trivial/index.coffee ================================================ validators = require "../validators" {benchmark} = require("../../runner")(validators) benchmark { name: "Event - Valid document" repeats: 1024 schema: require "./schema" document: require "./valid_doc" } ================================================ FILE: benchmarks/draft4/trivial/schema.coffee ================================================ module.exports = description: "A simple schema, exercising very few attributes" type: "object" additionalProperties: false required: ["origin", "name", "tags", "timestamp", "data"] properties: origin: type: "string" name: type: "string" tags: type: "array" items: {type: "string"} timestamp: type: "integer" data: { type: "object" } ================================================ FILE: benchmarks/draft4/trivial/valid_doc.coffee ================================================ module.exports = origin: "monkey" name: "shines" tags: ["in", "the", "closet"] timestamp: Date.now() data: uno: 1 dos: 2 ================================================ FILE: benchmarks/draft4/validators.coffee ================================================ module.exports = "JSCK": setup: (schema) -> JSCK = require "../../src/draft4" new JSCK(schema) validate: ({validator, schema, document}) -> validator.validate(document) error: (result) -> if result.valid == true false else result.errors "ajv": setup: (schema) -> ajv = require("ajv")() ajv.compile(schema) validate: ({validator, schema, document}) -> validator(document) error: (result) -> if result == true false else validator.errors "tv4": setup: (schema) -> require("tv4").tv4 validate: ({validator, schema, document}) -> validator.validateResult(document, schema) error: (result) -> if result.valid == true false else result.error "Themis (minimal)": setup: (schema) -> Themis = require('themis') Themis.validator(schema, { enable_defaults: false, algorithm: 'none', errors: { messages: false, validator_value: false, schema: false } }) validate: ({validator, schema, document}) -> validator(document, '0') error: (result) -> if result.valid == true false else result.errors "Themis": setup: (schema) -> Themis = require('themis') Themis.validator(schema) validate: ({validator, schema, document}) -> validator(document, '0') error: (result) -> if result.valid == true false else result.errors "jayschema": setup: (schema) -> JaySchema = require("jayschema") new JaySchema() validate: ({validator, schema, document}) -> validator.validate(document, schema) error: (result) -> if result.length == 0 false else result "is-my-json-valid": setup: (schema) -> require("is-my-json-valid")(schema) validate: ({validator, schema, document}) -> validator(document) error: (result) -> if result == true false else validator.errors # Putting z-schema last because it appears to be affecting other libs' # performance if run first. "z-schema": setup: (schema) -> z = require("z-schema") validator = new z() url = "http://json-schema.org/draft-04/schema" # don't actually download the draft, because GitHub Pages might be down. actualDraft = require("fs").readFileSync("./test/json-schema/draft-04/schema", "utf8") validator.setRemoteReference(url, JSON.parse(actualDraft)) validator validate: ({validator, schema, document}) -> valid = validator.validate(document, schema) if valid == false validator.getLastErrors() error: (result) -> result || false "jsen": setup: (schema) -> require("jsen")(schema) validate: ({validator, schema, document}) -> validator(document) error: (result) -> if result == true false else validator.errors ================================================ FILE: benchmarks/index.coffee ================================================ require "./draft3/" require "./draft4/" ================================================ FILE: benchmarks/results/all.txt ================================================ ## Benchmarks for Draft 3 Schema: 'Event'. A simple schema, exercising very few attributes Sample size: 64 Validations per sample: 512 JSCK Warming up: ................................ Iterations: ................................................................ amanda Warming up: ................................ Iterations: ................................................................ JSV Warming up: ................................ Iterations: ................................................................ json-gate Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 193.025 max: 202.692 min: 175.945 amanda: validations/millisecond median: 4.258 max: 4.608 min: 3.778 JSV: validations/millisecond median: 2.685 max: 2.748 min: 2.43 json-gate: validations/millisecond median: 82.514 max: 86.036 min: 48.234 Relative speeds: JSCK : 1.000 json-gate : 2.339 amanda : 45.337 JSV : 71.896 Schema: 'Configuration'. A moderately complex schema with some nesting and value constraints Sample size: 64 Validations per sample: 128 JSCK Warming up: ................................ Iterations: ................................................................ amanda Warming up: ................................ Iterations: ................................................................ JSV Warming up: ................................ Iterations: ................................................................ json-gate Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 108.936 max: 109.966 min: 85.964 amanda: validations/millisecond median: 3.788 max: 5.651 min: 2.371 JSV: validations/millisecond median: 1.344 max: 1.359 min: 1.255 json-gate: validations/millisecond median: 44.176 max: 45.845 min: 41.803 Relative speeds: JSCK : 1.000 json-gate : 2.466 amanda : 28.760 JSV : 81.025 ## Benchmarks for Draft 4 Schema: 'Event - Valid document'. A simple schema, exercising very few attributes Sample size: 64 Validations per sample: 1024 JSCK Warming up: ................................ Iterations: ................................................................ tv4 Warming up: ................................ Iterations: ................................................................ z-schema Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 186.81 max: 191.617 min: 138.716 tv4: validations/millisecond median: 54.377 max: 55.907 min: 46.942 z-schema: validations/millisecond median: 93.657 max: 93.979 min: 89.44 Relative speeds: JSCK : 1.000 z-schema : 1.995 tv4 : 3.435 Schema: 'Configuration'. A moderately complex schema with some nesting and value constraints Sample size: 64 Validations per sample: 256 JSCK Warming up: ................................ Iterations: ................................................................ tv4 Warming up: ................................ Iterations: ................................................................ z-schema Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 118.573 max: 119.07 min: 105.22 tv4: validations/millisecond median: 19.608 max: 20.389 min: 16.505 z-schema: validations/millisecond median: 40.606 max: 40.934 min: 34.934 Relative speeds: JSCK : 1.000 z-schema : 2.920 tv4 : 6.047 Schema: 'Transaction'. Sample size: 64 Validations per sample: 64 JSCK Warming up: ................................ Iterations: ................................................................ tv4 Warming up: ................................ Iterations: ................................................................ z-schema Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 12.814 max: 14.873 min: 6.984 tv4: validations/millisecond median: 1.946 max: 1.965 min: 1.832 z-schema: validations/millisecond median: 3.768 max: 3.836 min: 3.297 Relative speeds: JSCK : 1.000 z-schema : 3.401 tv4 : 6.583 ================================================ FILE: benchmarks/runner.coffee ================================================ # stdlib util = require "util" # our simple benchmarking library Benchmark = require "./benchmark.coffee" samples = 64 module.exports = (validators) -> benchmark: ({name, schema, document, repeats}) -> libraries = [] for library, {setup, validate, error} of validators ## preflight to check for incorrect validation errors ## Currently disabled because of issues with z-schema. #validator = setup(schema) #e = error(validate({validator, schema, document})) #if e #console.log "Aborting because #{library} declared the document invalid:" #console.log e #process.exit(1) do (setup, validate) -> libraries.push new Benchmark library, (bm) -> bm.setup -> setup(schema) bm.measure (validator) -> for i in [1..repeats] validate({validator, schema, document}) console.log """ Schema: '#{name}'. #{schema.description || ''} Sample size: #{samples} Validations per sample: #{repeats} """ results = Benchmark.compare libraries, {samples, warmup: 32} console.log() x = for name, result of results {median, max, min} = result.summarize() console.log " #{name}: validations/millisecond" console.log util.format " median: %d max: %d min: %d ", (repeats * 1 / median).toFixed(3), (repeats * 1 / min).toFixed(3), (repeats * 1 / max).toFixed(3) console.log() [name, median] sorted = x.sort (a, b) -> a[1] > b[1] fastest = sorted[0] ftime = fastest[1] console.log "Relative speeds:" for [name, time] in x console.log name, ":", (time / ftime).toFixed(3) console.log() ================================================ FILE: benchmarks/statistics.js ================================================ /**************************************************************************************************** JavaScript Math and Statistics Library @file pseudoMathStats.js @version 1.0 @author Paul Ellis @url http://code.google.com/p/pseudosavant @copyright Copyright 2010, Paul Ellis @license BSD ****************************************************************************************************/ /**************************************************************************************************** Calculates the Standard Deviation of an array ****************************************************************************************************/ Array.prototype.stdDev = function () { // Calculate Mean var mean = this.mean(); var length = this.length; // Calculate Variance for (var i = 0, sumOfSquares = 0; i < length; i++) { sumOfSquares += Math.pow(this[i] - mean, 2); } var stdDev = Math.sqrt(sumOfSquares / length); return stdDev; } /**************************************************************************************************** Calculates the Variance of an array ****************************************************************************************************/ Array.prototype.variance = function () { // Calculate Mean var mean = this.mean(); var length = this.length; // Calculate Variance for (var i = 0, sumOfSquares = 0; i < length; i++) { sumOfSquares += Math.pow(this[i] - mean, 2); } var variance = sumOfSquares / length; return variance; } /**************************************************************************************************** Sums all values in an array ****************************************************************************************************/ Array.prototype.sum = function () { for (var i = 0, length = this.length, sum = 0; i < length; sum += this[i++]); return sum; } /**************************************************************************************************** Calculates the arithmetic mean of an array ****************************************************************************************************/ Array.prototype.mean = function () { for (var i = 0, length = this.length, sum = 0; i < length; sum += this[i++]); var mean = sum / length; return mean; } /**************************************************************************************************** Returns the highest numeric value of an array ****************************************************************************************************/ Array.prototype.max = function () { return this.reduceRight(function(i,j){if(ji){return i}else{return j} }); } /**************************************************************************************************** Calculates the median of an array ****************************************************************************************************/ Array.prototype.median = function () { var length = this.length; if (length % 2 == 1) // Odd { var middle = Math.floor(length / 2); var median = this.sortNumber()[middle]; } else // Even { var middle = length / 2; var sorted = this.sortNumber(); var median = (sorted[middle-1] + sorted[middle]) / 2; } return median; } /**************************************************************************************************** Returns the array sorted ascendingly, or decendingly if sortNumber(true). ****************************************************************************************************/ Array.prototype.sortNumber = function (invert) { if (invert == true) // Decending { return this.slice().sort(function (a, b) { return a - b }).reverse(); // Using reverse() is faster than b - a. } else // Ascending, default { return this.slice().sort(function (a, b) { return a - b }); } } /**************************************************************************************************** Returns an object containing the most common stats for the array. ****************************************************************************************************/ Array.prototype.summarize = function summarize (places) { if (!this.length) { return { error: "no values" }; } return { max: fixedDecimal(this.max(), places), median: fixedDecimal(this.median(), places), min: fixedDecimal(this.min(), places), mean: fixedDecimal(this.mean(), places), stdDev: fixedDecimal(this.stdDev(), places), sample_size: this.length, }; }; var fixedDecimal = function fixedDecimal (num, numOfDec) { var pow10s = Math.pow( 10, numOfDec || 0 ); return ( numOfDec ) ? Math.round( pow10s * num ) / pow10s : num; }; /**************************************************************************************************** Lower tail quantile for standard normal distribution function. Written by Alankar Misra (alankar@digitalsutras.com) Algorithm by Peter John Acklam (pjacklam@online.no, http://home.online.no/~pjacklam) ****************************************************************************************************/ function normsinv(p) { // Coefficients in rational approximations var a = new Array(-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02, 1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00); var b = new Array(-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02, 6.680131188771972e+01, -1.328068155288572e+01); var c = new Array(-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00, -2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00); var d = new Array(7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00, 3.754408661907416e+00); // Define break-points. var plow = 0.02425; var phigh = 1 - plow; // Rational approximation for lower region: if (p < plow) { var q = Math.sqrt(-2 * Math.log(p)); return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1); } // Rational approximation for upper region: if (phigh < p) { var q = Math.sqrt(-2 * Math.log(1 - p)); return -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1); } // Rational approximation for central region: var q = p - 0.5; var r = q * q; return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1); } ================================================ FILE: doc/README.pfm.md ================================================ # JSON Schema Compiled checK JSCK is one of the fastest [JSON Schema](http://json-schema.org) validators for Node.js. It supports JSON Schema drafts [3][draft3_doc] and [4][draft4_doc], with a few caveats (see the [Coverage section](#coverage) below). ## Installation and Usage Install with NPM: npm install --save jsck Require: ```coffee JSCK = require "jsck" ``` JSCK can create validators from multiple schemas, but this requires that each schema be identified with a URI in a top-level "id" field. In many cases, only a single schema is needed, and there is no need to uniquely identify the schema. This is the easiest way to use JSCK, as it is a common pattern. ```examples/draft4/basic.coffee#L3-29``` To use Draft 3 schemas: ```.coffee validator = new JSCK.draft3(schema) ``` See these [advanced usage examples](examples/draft4/advanced.coffee) for help working with multiple schemas. ## Why JSCK? JSCK is [faster](#benchmarks) than most other JavaScript/CoffeeScript libraries for validating JSON Schemas because it "compiles" the schemas. That is, JSCK generates the tree of functions needed to validate a particular schema when you construct a validator. The schema is thus traversed only during preparation, and most of the work of interpreting the schema is done at this time, rather than for every document submitted for validation. This minimizes the work required during validation, which leads to substantial performance improvements over non-compiling validators. ## Coverage ### Draft 4 JSCK passes all tests in the canonical [JSON Schema Test Suite][canonical], except for these items: * use of `maxLength` and `minLength` with Unicode surrogate pairs. * `refRemote` (this is an essential feature we do plan to support) * `ref` * remote ref, containing refs itself * `uniqueItems` * `optional/zeroTerminatedFloats` ### Draft 3 Currently passing the canonical [test suite][canonical] for draft3 except for these items: * `refRemote` * `ref` * remote ref, containing refs itself * `uniqueItems` * `optional/zeroTerminatedFloats` ### Managing resolution scope with the "id" attribute JSCK does not support the full range of scope manipulations suggested by JSON Schema drafts 3 and 4. Scope manipulation is a controversial topic, and with JSCK we have chosen to play it safe, supporting "id" declarations only in cases that will (probably) not lead to any ambiguity. Specifically, JSCK uses "id" declarations only in these cases: * at the top level of a schema, to provide a namespace for schemas not loaded from URIs. * non-JSON-pointer fragments (`"id": "#user"`), which serve merely as aliases for specific subschemas, and are thus convenient and unambiguous. For more information on the topic of the "id" attribute and scope manipulation, see this issue: https://github.com/json-schema/json-schema/issues/77. ## Contributing To contribute, hack on it, or run the tests: ```shell git clone git@github.com:pandastrike/jsck.git cd jsck coffee tasks/update npm install ``` ### Tests JSCK uses the official [JSON Schema Test Suite][canonical] as well as some custom tests. To run all tests for all versions: coffee test See [this document](doc/tests.md) for more information on working with JSCK tests. ## Benchmarks JSCK has fairly comprehensive benchmarks which show it to be one of the very fastest JSON Schema validators available for Node.js. Pull requests welcome, of course. Because performance varies (at very least) based on the complexity of the schema being validated, we run benchmarks against several different schemas, ranging from quite simple to moderately complex. For JSON Schema Draft4, we run benchmarks against JSCK, tv4, jayschema, z-schema, and other validators. On the [trivial schema](benchmarks/draft4/trivial/schema.coffee), our benchmarks produce this relative performance for these validators (lower is better): ```coffee ajv : 1 jsen : 2.9 is-my-json-valid : 4.4 Themis (minimal) : 5.2 Themis : 5.3 JSCK : 34.2 z-schema : 48.3 tv4 : 54.4 jayschema : 2507.4 ``` For the schema of [medium complexity](benchmarks/draft4/medium/schema.coffee), our benchmarks produce this relative performance for the tested validators (lower is better): ```coffee ajv : 1 is-my-json-valid : 2.8 jsen : 3.0 Themis (minimal) : 11.1 Themis : 11.6 JSCK : 22.0 tv4 : 43.4 z-schema : 46.0 jayschema : 2319.4 ``` For the schema of [higher complexity](benchmarks/draft4/complex/schema.coffee), our benchmarks produce this relative performance for the tested validators (lower is better): ```coffee ajv : 1 is-my-json-valid : 1.23 jsen : 1.31 Themis (minimal) : 1.7 Themis : 1.8 JSCK : 4.8 z-schema : 17.2 tv4 : 27.0 jayschema : 1215.1 ``` As the complexity of the schema increases, the performance benefits of the compilation model become more evident. See [this document](doc/benchmarks.md) for detailed results and information on running and creating benchmarks. [draft3_doc]:http://tools.ietf.org/html/draft-zyp-json-schema-03 [draft3_impl]:https://github.com/json-schema/json-schema/tree/master/draft-03 [draft4_doc]:http://tools.ietf.org/html/draft-zyp-json-schema-04 [canonical]:https://github.com/json-schema/JSON-Schema-Test-Suite ================================================ FILE: doc/benchmarks.md ================================================ # Benchmarks JSCK has fairly comprehensive benchmarks which show it to be one of the fastest JSON Schema validators available for Node.js. To run the benchmarks immediately after you `git clone` the repo: ```shell git clone git@github.com:pandastrike/jsck.git && \ git submodule update --init && \ npm install && \ coffee benchmarks/ ``` Benchmarking harness and content lives in `benchmarks`, with separate directories for each draft. For each draft there are a number of different benchmarks (contained in subdirectories) that can be run in whole or in part. This is the basic directory structure: benchmarks/ draft3/ draft4/ complex/ medium/ trivial/ index.coffee validators.coffee ## Adding Additional Validators The validators are defined in `validators.coffee`. Here's an example from the Draft 4 benchmarks: ```coffee setup: (schema) -> ajv = require("ajv")() ajv.compile(schema) validate: ({validator, schema, document}) -> validator(document) error: (result) -> if result == true false else validator.errors ``` The `setup` function returns a validator object usable by the `validate` function. The `validate` function performs the actual validation work that is measured in benchmarking. The `error` function is used only in the preflight stage of benchmarking to determine whether the result of the `validate` function contains any errors. This is useful in making sure that all the validators are actually operating correctly and thus being compared fairly. ## Adding New Benchmarks Each individual benchmark consists of a directory containing a schema, a valid document, and an `index.coffee` file which runs the benchmark. ```coffee validators = require "../validators" {benchmark} = require("../../runner")(validators) benchmark { name: "Event - Valid document" repeats: 1024 schema: require "./schema" document: require "./valid_doc" } ``` The "repeats" parameter determines how many times the document will be validated during a single measurement sample. ## Running Benchmarks You can run very specific benchmarks, like the medium-complexity benchmarks for draft 3 only, like so: `coffee benchmarks/draft3/medium` You can run all benchmarks for a specific JSON Schema draft: `coffee benchmarks/draft4` Or, to run all benchmarks: `coffee benchmarks/` You should then see something like this: ```txt ## Benchmarks for Draft 3 Schema: 'Event'. A simple schema, exercising very few attributes Sample size: 64 Validations per sample: 512 JSCK Warming up: ................................ Iterations: ................................................................ amanda Warming up: ................................ Iterations: ................................................................ JSV Warming up: ................................ Iterations: ................................................................ json-gate Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 193.025 max: 202.692 min: 175.945 amanda: validations/millisecond median: 4.258 max: 4.608 min: 3.778 JSV: validations/millisecond median: 2.685 max: 2.748 min: 2.43 json-gate: validations/millisecond median: 82.514 max: 86.036 min: 48.234 Relative speeds: JSCK : 1.000 json-gate : 2.339 amanda : 45.337 JSV : 71.896 Schema: 'Configuration'. A moderately complex schema with some nesting and value constraints Sample size: 64 Validations per sample: 128 JSCK Warming up: ................................ Iterations: ................................................................ amanda Warming up: ................................ Iterations: ................................................................ JSV Warming up: ................................ Iterations: ................................................................ json-gate Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 108.936 max: 109.966 min: 85.964 amanda: validations/millisecond median: 3.788 max: 5.651 min: 2.371 JSV: validations/millisecond median: 1.344 max: 1.359 min: 1.255 json-gate: validations/millisecond median: 44.176 max: 45.845 min: 41.803 Relative speeds: JSCK : 1.000 json-gate : 2.466 amanda : 28.760 JSV : 81.025 ## Benchmarks for Draft 4 Schema: 'Event - Valid document'. A simple schema, exercising very few attributes Sample size: 64 Validations per sample: 1024 JSCK Warming up: ................................ Iterations: ................................................................ tv4 Warming up: ................................ Iterations: ................................................................ z-schema Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 186.81 max: 191.617 min: 138.716 tv4: validations/millisecond median: 54.377 max: 55.907 min: 46.942 z-schema: validations/millisecond median: 93.657 max: 93.979 min: 89.44 Relative speeds: JSCK : 1.000 z-schema : 1.995 tv4 : 3.435 Schema: 'Configuration'. A moderately complex schema with some nesting and value constraints Sample size: 64 Validations per sample: 256 JSCK Warming up: ................................ Iterations: ................................................................ tv4 Warming up: ................................ Iterations: ................................................................ z-schema Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 118.573 max: 119.07 min: 105.22 tv4: validations/millisecond median: 19.608 max: 20.389 min: 16.505 z-schema: validations/millisecond median: 40.606 max: 40.934 min: 34.934 Relative speeds: JSCK : 1.000 z-schema : 2.920 tv4 : 6.047 Schema: 'Transaction'. Sample size: 64 Validations per sample: 64 JSCK Warming up: ................................ Iterations: ................................................................ tv4 Warming up: ................................ Iterations: ................................................................ z-schema Warming up: ................................ Iterations: ................................................................ JSCK: validations/millisecond median: 12.814 max: 14.873 min: 6.984 tv4: validations/millisecond median: 1.946 max: 1.965 min: 1.832 z-schema: validations/millisecond median: 3.768 max: 3.836 min: 3.297 Relative speeds: JSCK : 1.000 z-schema : 3.401 tv4 : 6.583 ``` ================================================ FILE: doc/benchmarks.pfm.md ================================================ # Benchmarks JSCK has fairly comprehensive benchmarks which show it to be one of the fastest JSON Schema validators available for Node.js. To run the benchmarks immediately after you `git clone` the repo: ```shell git clone git@github.com:pandastrike/jsck.git && \ git submodule update --init && \ npm install && \ coffee benchmarks/ ``` Benchmarking harness and content lives in `benchmarks`, with separate directories for each draft. For each draft there are a number of different benchmarks (contained in subdirectories) that can be run in whole or in part. This is the basic directory structure: benchmarks/ draft3/ draft4/ complex/ medium/ trivial/ index.coffee validators.coffee ## Adding Additional Validators The validators are defined in `validators.coffee`. Here's an example from the Draft 4 benchmarks: ```benchmarks/draft4/validators.coffee#L16-25``` The `setup` function returns a validator object usable by the `validate` function. The `validate` function performs the actual validation work that is measured in benchmarking. The `error` function is used only in the preflight stage of benchmarking to determine whether the result of the `validate` function contains any errors. This is useful in making sure that all the validators are actually operating correctly and thus being compared fairly. ## Adding New Benchmarks Each individual benchmark consists of a directory containing a schema, a valid document, and an `index.coffee` file which runs the benchmark. ```benchmarks/draft4/trivial/index.coffee``` The "repeats" parameter determines how many times the document will be validated during a single measurement sample. ## Running Benchmarks You can run very specific benchmarks, like the medium-complexity benchmarks for draft 3 only, like so: `coffee benchmarks/draft3/medium` You can run all benchmarks for a specific JSON Schema draft: `coffee benchmarks/draft4` Or, to run all benchmarks: `coffee benchmarks/` You should then see something like this: ```benchmarks/results/all.txt``` ================================================ FILE: doc/tests.md ================================================ # Testing JSCK JSCK is developed against several sets of tests: * the official [JSON Schema Test Suite][canonical] * tests for schemas that should be considered invalid * tests for schemas that should be considered valid * unit tests particular to JSCK To run all tests for all versions of JSON Schema: coffee test ## The official test suite The good folks behind JSON Schema have provided a language-agnostic test suite, composed of JSON documents that define schemas and input objects that should be judged as valid or invalid for each schema. The README for the test suite provides a [good overview of the structure](https://github.com/json-schema/JSON-Schema-Test-Suite#structure-of-a-test) of the JSON test files. JSCK includes the official test suite as a git submodule at `test/JSON-Schema-Test-Suite`. Running `coffee tasks/update` will initialize and/or update the submodule. As a concrete example, here is the first case for the "items" attribute in Draft 4: ```json { "description": "a schema given for items", "schema": { "items": {"type": "integer"} }, "tests": [ { "description": "valid items", "data": [ 1, 2, 3 ], "valid": true }, { "description": "wrong type of items", "data": [1, "x"], "valid": false }, { "description": "ignores non-arrays", "data": {"foo" : "bar"}, "valid": true } ] }, ``` Breaking this down * the case described is for when the value of "items" is a schema * the schema under consideration does not declare a type for the input data * if the input data is an array, all of its items must be integers There are three tests for this case: * valid input data * invalid input data * irrelevant input data ### Running the official tests To run the official tests for a specific draft: coffee test/draft4 It can be difficult to troubleshoot bugs when running hundreds of tests. To run only the tests for a specific attribute (in this case, "patternProperties"): coffee test/draft4 patternProperties And to run only a certain test for that attribute, append the (zero-based) index of the test: coffee test/draft4 patternProperties 3 ### Ignoring specific tests The test harness provided by JSCK allows you to ignore specific tests from the official suite. Here is how we define the ignores for Draft 4: ```coffee #cmd = "node_modules/.bin/nserver -p 5725 -d test/JSON-Schema-Test-Suite/remotes" #proc = shell.exec cmd, (code, output) -> # #Testify = require "testify" #Testify.once "done", -> #console.log "Shutting down the 'remotes' test server" #proc.kill("SIGTERM") [_node, _script, attribute, test_number] = process.argv suite { attribute test_number version: "draft4" validate: (schema, document) -> v = new draft4(schema) v.validate(document) ignores: # Doubtful value for the majority of use cases. # https://github.com/pandastrike/jsck/issues/42 minLength: [ "one supplementary Unicode code point is not long enough" ``` ## Invalid Schemas A tool for working with JSON Schemas must be able to reject invalid schemas. The official test suite (at this time) only provides tests data for validating documents against correctly formed schemas, so JSCK introduces a similar test format for checking the invalidity of schema definitions. These tests are defined as CoffeeScript files in `test/draft{3,4}/invalid/`. Though we use CoffeeScript for easy maintenance, the actual content could be represented as JSON, or YAML, or any other data format. Here's an example of an invalidity test for the "type" attribute: ```coffee { description: "string values must be one of the primitive types" schemas: [ type: "bogus" ] } ``` All of the objects in the `schemas` array are invalid schemas that should cause JSCK (or any other validation library) to reject the schema. One of the benefits of using CoffeeScript (or JavaScript, or any other language) to generate the test content is the ability to use helper functions to create multiple test schemas: ```coffee { description: "value MUST be either a string or an array" schemas: for value in json_types.except("string", "array") {type: value} } ``` In the above example, `schemas` is defined using a list comprehension and a helper function that generates an invalid schema for all of the primitive JSON Schema types except those explicitly named. To run only the invalidity tests: coffee test/draft4/invalid.coffee ## Valid Schemas It is also valuable to have tests for the acceptance of valid schemas that buggy or overzealous libraries might reject. JSCK's schema validity tests are defined as CoffeeScript files in `test/draft{3,4}/valid/`. As an example, the names of JSON Schema attributes ("properties", "type", "required", etc.) are not reserved terms. It is legal to use these strings as the names of object properties. During one round of refactoring, JSCK began rejecting schemas that used JSON Schema attribute names as properties. To prevent this, a validity test was added: ```coffee required: type: "object" properties: required: type: "boolean" id: type: "object" properties: id: ``` To run only the validity tests: coffee test/draft4/valid.coffee ## Unit tests JSCK also uses a few unit tests to assist in developing and troubleshooting some of the internal functions, such as scope resolution and URI construction. To run only the unit tests for a particular draft version: coffee test/draft4/unit [canonical]:https://github.com/json-schema/JSON-Schema-Test-Suite ================================================ FILE: doc/tests.pfm.md ================================================ # Testing JSCK JSCK is developed against several sets of tests: * the official [JSON Schema Test Suite][canonical] * tests for schemas that should be considered invalid * tests for schemas that should be considered valid * unit tests particular to JSCK To run all tests for all versions of JSON Schema: coffee test ## The official test suite The good folks behind JSON Schema have provided a language-agnostic test suite, composed of JSON documents that define schemas and input objects that should be judged as valid or invalid for each schema. The README for the test suite provides a [good overview of the structure](https://github.com/json-schema/JSON-Schema-Test-Suite#structure-of-a-test) of the JSON test files. JSCK includes the official test suite as a git submodule at `test/JSON-Schema-Test-Suite`. Running `coffee tasks/update` will initialize and/or update the submodule. As a concrete example, here is the first case for the "items" attribute in Draft 4: ```test/JSON-Schema-Test-Suite/tests/draft4/items.json#L2-24``` Breaking this down * the case described is for when the value of "items" is a schema * the schema under consideration does not declare a type for the input data * if the input data is an array, all of its items must be integers There are three tests for this case: * valid input data * invalid input data * irrelevant input data ### Running the official tests To run the official tests for a specific draft: coffee test/draft4 It can be difficult to troubleshoot bugs when running hundreds of tests. To run only the tests for a specific attribute (in this case, "patternProperties"): coffee test/draft4 patternProperties And to run only a certain test for that attribute, append the (zero-based) index of the test: coffee test/draft4 patternProperties 3 ### Ignoring specific tests The test harness provided by JSCK allows you to ignore specific tests from the official suite. Here is how we define the ignores for Draft 4: ```test/draft4/official.coffee#L7-32``` ## Invalid Schemas A tool for working with JSON Schemas must be able to reject invalid schemas. The official test suite (at this time) only provides tests data for validating documents against correctly formed schemas, so JSCK introduces a similar test format for checking the invalidity of schema definitions. These tests are defined as CoffeeScript files in `test/draft{3,4}/invalid/`. Though we use CoffeeScript for easy maintenance, the actual content could be represented as JSON, or YAML, or any other data format. Here's an example of an invalidity test for the "type" attribute: ```test/draft4/invalid/type.coffee#L24-29``` All of the objects in the `schemas` array are invalid schemas that should cause JSCK (or any other validation library) to reject the schema. One of the benefits of using CoffeeScript (or JavaScript, or any other language) to generate the test content is the ability to use helper functions to create multiple test schemas: ```test/draft4/invalid/type.coffee#L5-9``` In the above example, `schemas` is defined using a list comprehension and a helper function that generates an invalid schema for all of the primitive JSON Schema types except those explicitly named. To run only the invalidity tests: coffee test/draft4/invalid.coffee ## Valid Schemas It is also valuable to have tests for the acceptance of valid schemas that buggy or overzealous libraries might reject. JSCK's schema validity tests are defined as CoffeeScript files in `test/draft{3,4}/valid/`. As an example, the names of JSON Schema attributes ("properties", "type", "required", etc.) are not reserved terms. It is legal to use these strings as the names of object properties. During one round of refactoring, JSCK began rejecting schemas that used JSON Schema attribute names as properties. To prevent this, a validity test was added: ```test/draft4/valid/properties.coffee#L5-15``` To run only the validity tests: coffee test/draft4/valid.coffee ## Unit tests JSCK also uses a few unit tests to assist in developing and troubleshooting some of the internal functions, such as scope resolution and URI construction. To run only the unit tests for a particular draft version: coffee test/draft4/unit [canonical]:https://github.com/json-schema/JSON-Schema-Test-Suite ================================================ FILE: examples/draft4/advanced.coffee ================================================ JSCK = require("../../src/index") # using a schema that declares a URI with "id" jsck = new JSCK.draft4 $schema: "http://json-schema.org/draft-04/schema#" id: "urn:jsck.examples.advanced#" definitions: user: type: "object" required: ["login"] properties: login: type: "string" pattern: "^[\\w\\d_]{3,32}$" email: type: "string" validator = jsck.validator(uri: "urn:jsck.examples.advanced#") {valid} = validator.validate login: "automatthew" email: "automatthew@mailinator.com" console.log "Schema with id:", valid # validating against a subschema using a JSON Pointer validator = jsck.validator "urn:jsck.examples.advanced#/definitions/user" {valid} = validator.validate login: "automatthew" email: "automatthew@mailinator.com" console.log "Schema identified by JSON Pointer:", valid # Adding multiple schemas # # You can instantiate JSCK with multiple schemas or add them later # # Instantiation: # validator = new JSCK(schema1, schema2, schema3) jsck.add id: "urn:jsck.examples.user_list#" type: "array" items: {$ref: "urn:jsck.examples.advanced#/definitions/user"} validator = jsck.validator "urn:jsck.examples.user_list#" {valid} = validator.validate [ { login: "dyoder" } { login: "automatthew" } ] console.log "Multiple schemas:", valid ================================================ FILE: examples/draft4/basic.coffee ================================================ JSCK = require("../../src/index") # Construct a validator for a schema lacking an "id" declaration jsck = new JSCK.draft4 type: "object" properties: user: type: "object" required: ["login"] properties: login: type: "string" pattern: "^[\\w\\d_]{3,32}$" email: type: "string" format: "email" console.log "valid document:", jsck.validate user: login: "matthew" email: "matthew@pandastrike.com" {errors} = jsck.validate user: login: "matthew" email: "pandastrike.com" console.log "invalid document:", errors ================================================ FILE: package.json ================================================ { "name": "jsck", "version": "0.3.2", "description": "JSON Schema Compiled checK", "scripts": { "test": "coffee test", "prepublish": "coffee tasks/build" }, "main": "lib/index.js", "files": [ "lib/", "schemas/", "README.md" ], "dependencies": {}, "devDependencies": { "ajv": "latest", "amanda": "latest", "ajv": "^0.6.1", "amanda": "~0.5.1", "coffee-script": "~1.7", "commonjs-everywhere": "^0.9.7", "glob": "~3.2.6", "is-my-json-valid": "latest", "jayschema": "latest", "jsen": "latest", "json-gate": "latest", "json-schema-tests": "^0.1.1", "request": "~2.48.0", "shelljs": "^0.3.0", "simple-http-server": "~0.1.8", "testify": "~0.2.11", "themis": "latest", "tv4": "latest", "z-schema": "latest" }, "repository": "git@github.com:pandastrike/jsck.git", "author": "Matthew King ", "license": "MIT" } ================================================ FILE: schemas/draft-03/schema.json ================================================ { "$schema" : "http://json-schema.org/draft-03/schema#", "id" : "http://json-schema.org/draft-03/schema#", "type" : "object", "properties" : { "type" : { "type" : ["string", "array"], "items" : { "type" : ["string", {"$ref" : "#"}] }, "uniqueItems" : true, "default" : "any" }, "properties" : { "type" : "object", "additionalProperties" : {"$ref" : "#"}, "default" : {} }, "patternProperties" : { "type" : "object", "additionalProperties" : {"$ref" : "#"}, "default" : {} }, "additionalProperties" : { "type" : [{"$ref" : "#"}, "boolean"], "default" : {} }, "items" : { "type" : [{"$ref" : "#"}, "array"], "items" : {"$ref" : "#"}, "default" : {} }, "additionalItems" : { "type" : [{"$ref" : "#"}, "boolean"], "default" : {} }, "required" : { "type" : "boolean", "default" : false }, "dependencies" : { "type" : "object", "additionalProperties" : { "type" : ["string", "array", {"$ref" : "#"}], "items" : { "type" : "string" } }, "default" : {} }, "minimum" : { "type" : "number" }, "maximum" : { "type" : "number" }, "exclusiveMinimum" : { "type" : "boolean", "default" : false }, "exclusiveMaximum" : { "type" : "boolean", "default" : false }, "minItems" : { "type" : "integer", "minimum" : 0, "default" : 0 }, "maxItems" : { "type" : "integer", "minimum" : 0 }, "uniqueItems" : { "type" : "boolean", "default" : false }, "pattern" : { "type" : "string", "format" : "regex" }, "minLength" : { "type" : "integer", "minimum" : 0, "default" : 0 }, "maxLength" : { "type" : "integer" }, "enum" : { "type" : "array", "minItems" : 1, "uniqueItems" : true }, "default" : { "type" : "any" }, "title" : { "type" : "string" }, "description" : { "type" : "string" }, "format" : { "type" : "string" }, "divisibleBy" : { "type" : "number", "minimum" : 0, "exclusiveMinimum" : true, "default" : 1 }, "disallow" : { "type" : ["string", "array"], "items" : { "type" : ["string", {"$ref" : "#"}] }, "uniqueItems" : true }, "extends" : { "type" : [{"$ref" : "#"}, "array"], "items" : {"$ref" : "#"}, "default" : {} }, "id" : { "type" : "string", "format" : "uri" }, "$ref" : { "type" : "string", "format" : "uri" }, "$schema" : { "type" : "string", "format" : "uri" } }, "dependencies" : { "exclusiveMinimum" : "minimum", "exclusiveMaximum" : "maximum" }, "default" : {} } ================================================ FILE: schemas/draft-04/schema.json ================================================ { "id": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#", "description": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "positiveInteger": { "type": "integer", "minimum": 0 }, "positiveIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "type": "object", "properties": { "id": { "type": "string", "format": "uri" }, "$schema": { "type": "string", "format": "uri" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": {}, "multipleOf": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "boolean", "default": false }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": false }, "maxLength": { "$ref": "#/definitions/positiveInteger" }, "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": {} }, "maxItems": { "$ref": "#/definitions/positiveInteger" }, "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "maxProperties": { "$ref": "#/definitions/positiveInteger" }, "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "dependencies": { "exclusiveMaximum": [ "maximum" ], "exclusiveMinimum": [ "minimum" ] }, "default": {} } ================================================ FILE: src/common/arrays.coffee ================================================ module.exports = # handlers maxItems: (value, context) -> (data, runtime) => if @test_type "array", data if data.length > value runtime.error context, data, description: "Array must have fewer than #{value} items." minItems: (value, context) -> (data, runtime) => if @test_type "array", data if data.length < value runtime.error context, data, description: "Array must have more than #{value} items." items: (definition, context) -> if @test_type "array", definition test = @_tuple_items definition, context else if @test_type "object", definition test = @compile(context, definition) # TODO check for array data? (data, runtime) => for item, i in data test item, runtime.child(i) null else throw new Error "The 'items' attribute must be an object or an array" _additionalItems: (definition, context) -> if @test_type "object", definition test = @compile(context, definition) else if definition == false test = (data, runtime) -> runtime.error context, data, description: "Array is supposed to be a tuple, but has too many items." else if definition == true # valid else throw new Error "The 'additionalItems' attribute must be an object or false" (data, runtime) => for item, i in data test item, runtime.child(i) null _tuple_items: (definition, context) -> {additionalItems} = context.modifiers if additionalItems? add_item_test = @_additionalItems additionalItems, context.sibling "additionalItems" else add_item_test = null tests = [] for schema, i in definition unless @test_type "object", schema throw new Error "The 'items' attribute must be an object or an array" tests.push @compile context.child(i), schema (data, runtime) => if @test_type "array", data for test, i in tests test data[i], runtime.child(i) if (data.length > tests.length) && add_item_test add_item_test data.slice(tests.length), runtime uniqueItems: (definition, context) -> null ================================================ FILE: src/common/comparison.coffee ================================================ module.exports = # handlers enum: (definition, context) -> # TODO: add more cases to the draft3 test suite for enum.js, # as they're not doing full coverage if @test_type "array", definition (data, runtime) => for value in definition return if @equal(data, value) values = definition.join ', ' runtime.error context, data, description: "Value must be one of #{values}." else throw new Error "Value of 'enum' MUST be an Array" # helpers equal: (got, want) -> if want instanceof Array @array_equal(got, want) else if @is_object(want) @object_equal(got, want) else got == want array_equal: (got, want) -> return false unless (got instanceof Array) return true if want.length == 0 return false unless got.length == want.length for item, i in want return false if !@equal(got[i], item) true object_equal: (got, want) -> return false unless @is_object(got) return false unless Object.keys(got).length == Object.keys(want).length for key, value of want return false if !@equal(got[key], value) true ================================================ FILE: src/common/numeric.coffee ================================================ module.exports = minimum: (value, context) -> {modifiers: {exclusiveMinimum}} = context if exclusiveMinimum (data, runtime) => if @test_type "number", data if !(data > value) runtime.error context, data, description: "Value must be > #{value}." else (data, runtime) => if @test_type "number", data if !(data >= value) runtime.error context, data, description: "Value must be >= #{value}." maximum: (value, context) -> {modifiers: {exclusiveMaximum}} = context if exclusiveMaximum (data, runtime) => if @test_type "number", data if !(data < value) runtime.error context, data, description: "Value must be < #{value}." else (data, runtime) => if @test_type "number", data if !(data <= value) runtime.error context, data, description: "Value must be <= #{value}." ================================================ FILE: src/common/objects.coffee ================================================ module.exports = # handlers patternProperties: (definition, context) -> {additionalProperties} = context.modifiers if additionalProperties # The additionalProperties compiler runs the patternProperties # validation. This is necessary because properties are not considered # additional if they match a pattern. return null if !@test_type "object", definition throw new Error "The 'patternProperties' attribute must be an object" if Object.keys(definition).length == 0 throw new Error "The 'patternProperties' object should not be empty" tests = {} for pattern, schema of definition unless @test_type "object", schema throw new Error "Values of 'patternProperties' must be an objects" tests[pattern] = regex: new RegExp(pattern) test: @compile context.child(pattern), schema (data, runtime) => for property, value of data for pattern, object of tests if object.regex.test(property) object.test value, runtime.child(property) null additionalProperties: (definition, context) -> # TODO: refactor this method for clarity. It's likely that this will # also improve performance. {properties, patternProperties} = context.modifiers if @test_type "object", definition add_prop_test = @compile(context, definition) else if definition == false add_prop_test = (data, runtime) => runtime.error context, data, description: "Unspecified properties are not allowed on this object." else if definition == undefined add_prop_test = null else throw new Error "The 'additionalProperties' attribute must be an object or false" patterns = {} for pattern, schema of patternProperties patterns[pattern] = regex: new RegExp(pattern) test: @compile(context.sibling("patternProperties").child(pattern), schema) (data, runtime) => if @test_type "object", data for property, value of data explicit = false patterned = false if properties?[property] explicit = true if patterns for pattern, object of patterns if object.regex.test(property) patterned = true object.test value, runtime.child(property) if !explicit && !patterned && add_prop_test add_prop_test value, runtime.child(property) null ================================================ FILE: src/common/strings.coffee ================================================ module.exports = pattern: (pattern, context) -> unless @test_type "string", pattern throw new Error "Value of 'pattern' must be a string" regex = new RegExp(pattern) (data, runtime) => if @test_type "string", data if !regex.test(data) runtime.error context, data, description: "String did not match regex pattern." minLength: (value, context) -> unless @test_type "integer", value throw new Error "Value of 'minLength' must be an integer" (data, runtime) => if @test_type "string", data if !(data.length >= value) runtime.error context, data, description: "String must be longer than #{value} bytes." maxLength: (value, context) -> unless @test_type "integer", value throw new Error "Value of 'maxLength' must be an integer" (data, runtime) => if @test_type "string", data if !(data.length <= value) runtime.error context, data, description: "String must be smaller than #{value} bytes." ================================================ FILE: src/common/type.coffee ================================================ module.exports = # handlers type: (definition, context) -> if @test_type "array", definition tests = [] for type in definition do (type) => if @test_type "object", type test = @compile context, type tests.push (data, runtime) => temp = new runtime.constructor pointer: "" errors: [] test data, temp temp.errors.length == 0 else tests.push (data, runtime) => @test_type type, data (data, runtime) => valid = false for test in tests if test(data, runtime) valid = true if valid == false runtime.error context, data else if @test_type "object", definition @compile(context, definition) else (data, runtime) => if !@test_type definition, data runtime.error context, data # helpers is_object: (data) -> data? && (typeof data) == "object" && !(data instanceof Array) && !(data instanceof Date) is_primitive: (name) -> name in ["integer", "number", "string", "object", "array", "boolean", "null"] get_type: (data) -> if typeof(data) == "number" && data % 1 == 0 return "integer" if typeof(data) == "number" return "number" if typeof(data) == "string" return "string" if @is_object(data) return "object" if data instanceof Array return "array" if typeof data == "boolean" return "boolean" if data == null return "null" test_type: (type_name, data) -> switch type_name when "integer" typeof(data) == "number" && data % 1 == 0 when "number" typeof(data) == "number" when "string" typeof(data) == "string" when "object" @is_object(data) when "array" data instanceof Array when "boolean" typeof data == "boolean" when "null" data == null when "any" true else throw new Error "Bad type: '#{type_name}'" ================================================ FILE: src/draft3/logical.coffee ================================================ URI = require "../uri" module.exports = extends: (schemas, context) -> unless @test_type "array", schemas schemas = [schemas] for schema, i in schemas if (ref = schema.$ref)? uri = URI.resolve(context.scope, ref) parent = @find(uri) if !parent throw new Error "No schema found for $ref '#{ref}'" else schemas[i] = parent tests = [] for schema, i in schemas new_context = context.child(i) tests.push @compile(new_context, schema) (data, runtime) => for test in tests test data, runtime disallow: (definition, context) -> if @test_type "array", definition tests = [] for type, i in definition do (i) => if @test_type "object", type inverse = @compile context, type tests.push (data, runtime) => temp = new runtime.constructor pointer: "" errors: [] inverse data, temp if temp.errors.length == 0 runtime.error context, data else tests.push @disallow type, context (data, runtime) => for test in tests test data, runtime else (data, runtime) => if @test_type definition, data runtime.error context, data ================================================ FILE: src/draft3/numeric.coffee ================================================ module.exports = divisibleBy: (value, context) -> (data, runtime) => if @test_type "number", data if !((data / value) % 1 == 0) runtime.error context, data ================================================ FILE: src/draft3/objects.coffee ================================================ module.exports = properties: (definition, context) -> if !@test_type "object", definition throw new Error "The 'properties' attribute must be an object" tests = {} required = [] for property, schema of definition new_context = context.child(property) test = @compile(new_context, schema) tests[property] = test if schema.required == true required.push property (data, runtime) => if @test_type "object", data for property, value of data if (test = tests[property])? test value, runtime.child(property) for key in required if data[key] == undefined runtime.error context.child(key).child("required") true dependencies: (definition, context) -> unless @test_type "object", definition throw new Error "Value of 'dependencies' must be an object" else tests = [] for property, dependency of definition if @test_type "string", dependency tests.push (data, runtime) => if data[property]? && !data[dependency]? runtime.child(property).error context else if @test_type "array", dependency tests.push (data, runtime) => if data[property]? for item in dependency if !data[item]? runtime.child(property).error context null else if @test_type "object", dependency fn = @compile context, dependency tests.push (data, runtime) => if data[property] fn data, runtime else true else throw new Error "Invalid dependency" (data, runtime) => if @test_type "object", data for test in tests test data, runtime null ================================================ FILE: src/draft3/strings.coffee ================================================ module.exports = format: (format_name, context) -> if format_name == "regex" (data, runtime) => if @test_type "string", data try new RegExp(data) catch error runtime.error context, data else if regex = format_regexes[format_name] do (regex) => (data, runtime) => if @test_type "string", data if !regex.test(data) runtime.error context, data else throw new Error "Invalid format_name for 'format'" # regexes below were derived from # https://github.com/tdegrunt/jsonschema # #Copyright (C) 2012-2013 Tom de Grunt #Permission is hereby granted, free of charge, to any person obtaining a copy of #this software and associated documentation files (the "Software"), to deal in #the Software without restriction, including without limitation the rights to #use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies #of the Software, and to permit persons to whom the Software is furnished to do #so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in all #copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. format_regexes = "date-time": /^(\d{4}-(?:0[0-9]|1[0-2])-[0-9]{2}(T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.\d+)?(Z|(\-|\+)[0-9]{2}:[0-9]{2})?)?)$/ date: /^(\d{4}-(?:0[0-9]|1[0-2])-[0-9]{2})$/ time: /^\d{2}:\d{2}:\d{2}$/ email: /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/ "ip-address": /^(?:(?: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]?)$/ 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*$/ uri: /^[a-zA-Z][a-zA-Z0-9+-.]*:[^\s]*$/ color: /^(((#[0-9A-Fa-f]{3,6}))|(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)|(rgb\(\s*([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*,\s*([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*,\s*([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/ "host-name": /^(([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])){0,3}\.?)$/ alpha: /^[a-zA-Z]+$/ alphanumeric: /^[a-zA-Z0-9]+$/ "utc-millisec": (input) -> (typeof input is "string") and parseFloat(input) is parseInt(input, 10) and not isNaN(input) style: /\s*(.+?):\s*([^;]+);?/g phone: /^\+(?:[0-9] ?){6,14}[0-9]$/ ================================================ FILE: src/draft3.coffee ================================================ validator = require("./validator") module.exports = validator schema_uri: "http://json-schema.org/draft-03/schema#" mixins: [ require "./draft3/logical" require "./draft3/numeric" require "./draft3/objects" require "./draft3/strings" ] ================================================ FILE: src/draft4/logical.coffee ================================================ module.exports = anyOf: (definition, context) -> unless @test_type "array", definition throw new Error "The 'anyOf' attribute must be an array" if definition.length == 0 throw new Error "The 'anyOf' array may not be empty" tests = [] for schema, i in definition unless @test_type "object", schema throw new Error "The 'anyOf' array values must be objects" new_context = context.child(i) tests.push @compile(new_context, schema) (data, runtime) => most_items_tested = 0 best_errors = [] answer = tests.some (test) => temp = new runtime.constructor pointer: "" error_pointer: runtime.pointer errors: [] test(data, temp) if temp.items_tested && temp.items_tested > most_items_tested best_errors = temp.errors most_items_tested = temp.items_tested temp.errors.length == 0 unless answer if @options.closestMatch Array::push.apply runtime.errors, best_errors else runtime.error context, data # Note from author of draft4 on how allOf works w/r/t additionalProperties # http://stackoverflow.com/questions/22689900/json-schema-allof-with-additionalproperties/23001194#23001194 # also https://github.com/fge/json-schema-validator/issues/88 allOf: (definition, context) -> unless @test_type "array", definition throw new Error "The 'allOf' attribute must be an array" if definition.length == 0 throw new Error "The 'allOf' array may not be empty" # TODO: check for proper error reporting. Do we need to create new # runtimes, contexts, etc.? tests = [] for schema, i in definition unless @test_type "object", schema throw new Error "The 'allOf' array values must be objects" new_context = context.child(i) tests.push @compile(new_context, schema) (data, runtime) => for test in tests test data, runtime null oneOf: (definition, context) -> unless @test_type "array", definition throw new Error "The 'oneOf' attribute must be an array" if definition.length == 0 throw new Error "The 'oneOf' array may not be empty" tests = [] for schema, i in definition unless @test_type "object", schema throw new Error "The 'oneOf' array values must be objects" new_context = context.child(i) tests.push @compile(new_context, schema) # TODO optimize? (data, runtime) => valids = 0 most_items_tested = 0 best_errors = [] for test in tests temp = new runtime.constructor pointer: "" error_pointer: runtime.pointer errors: [] test(data, temp) if temp.errors.length == 0 valids++ else if temp.items_tested && temp.items_tested > most_items_tested best_errors = temp.errors most_items_tested = temp.items_tested if valids == 0 && @options.closestMatch Array::push.apply runtime.errors, best_errors else if valids != 1 runtime.error context, data not: (definition, context) -> unless @test_type "object", definition throw new Error "The 'not' attribute must be an object" inverse = @compile context, definition (data, runtime) => temp = new runtime.constructor pointer: "" errors: [] inverse data, temp if temp.errors.length == 0 runtime.error context, data ================================================ FILE: src/draft4/numeric.coffee ================================================ module.exports = multipleOf: (value, context) -> unless @test_type "number", value throw new Error "The 'multipleOf' attribute must be a number" (data, runtime) => if @test_type "number", data if !((data / value) % 1 == 0) runtime.error context, data, description: "Value `#{data}` is not a multiple of #{value}" ================================================ FILE: src/draft4/objects.coffee ================================================ module.exports = # handlers required: (definition, context) -> unless @test_type "array", definition throw new Error "The 'required' attribute must be an array" if definition.length == 0 throw new Error "The 'required' array must have at least one element" for property, i in definition unless @test_type "string", property throw new Error "The 'required' array may only contain strings" (data, runtime) => if @test_type "object", data for property, i in definition if data[property] == undefined c = context.child(i) c.definition = property runtime.error c, undefined, description: "Required property '#{property}' is missing" null properties: (definition, context) -> unless @test_type "object", definition throw new Error "The 'properties' attribute must be an object" tests = {} for property, schema of definition unless @test_type "object", schema throw new Error "The 'properties' attribute must be an object" new_context = context.child(property) test = @compile(new_context, schema) tests[property] = test (data, runtime) => if @test_type "object", data for property, value of data if (test = tests[property])? test value, runtime.child(property) null minProperties: (definition, context) -> (data, runtime) => if @test_type "object", data if Object.keys(data).length < definition runtime.error context, data, description: "Object must have at least #{definition} properties." maxProperties: (definition, context) -> (data, runtime) => if @test_type "object", data if Object.keys(data).length > definition runtime.error context, data, description: "Object cannot have more than #{definition} properties." dependencies: (definition, context) -> unless @test_type "object", definition throw new Error "Value of 'dependencies' must be an object" else tests = [] for property, dependency of definition if @test_type "array", dependency if dependency.length == 0 throw new Error "Arrays in 'dependencies' may not be empty" for name in dependency unless @test_type "string", name throw new Error "Vales of 'dependencies' arrays must be strings" tests.push (data, runtime) => if data[property]? for item in dependency if !data[item]? runtime.child(property).error context null else if @test_type "object", dependency fn = @compile context, dependency tests.push (data, runtime) => if data[property] fn data, runtime else true else throw new Error "Invalid dependency" (data, runtime) => if @test_type "object", data for test in tests test data, runtime null ================================================ FILE: src/draft4/strings.coffee ================================================ module.exports = format: (format_name, context) -> if format_name == "regex" (data, runtime) => if @test_type "string", data try new RegExp(data) catch error runtime.error context, data, description: "String is not in the #{format_name} format." else if regex = format_regexes[format_name] do (regex) => (data, runtime) => if @test_type "string", data if !regex.test(data) runtime.error context, data, description: "String is not in the #{format_name} format." else throw new Error "Invalid format_name for 'format'" # regexes below were derived from # https://github.com/tdegrunt/jsonschema # #Copyright (C) 2012-2013 Tom de Grunt #Permission is hereby granted, free of charge, to any person obtaining a copy of #this software and associated documentation files (the "Software"), to deal in #the Software without restriction, including without limitation the rights to #use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies #of the Software, and to permit persons to whom the Software is furnished to do #so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in all #copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. format_regexes = "date-time": /^(\d{4}-(?:0[0-9]|1[0-2])-[0-9]{2}(T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.\d+)?(Z|(\-|\+)[0-9]{2}:[0-9]{2})?)?)$/ date: /^(\d{4}-(?:0[0-9]|1[0-2])-[0-9]{2})$/ time: /^\d{2}:\d{2}:\d{2}$/ email: /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/ "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]?)$/ 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*$/ uri: /^[a-zA-Z][a-zA-Z0-9+-.]*:[^\s]*$/ color: /^(((#[0-9A-Fa-f]{3,6}))|(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)|(rgb\(\s*([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*,\s*([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*,\s*([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/ "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])){0,3}\.?)$/ alpha: /^[a-zA-Z]+$/ alphanumeric: /^[a-zA-Z0-9]+$/ "utc-millisec": (input) -> (typeof input is "string") and parseFloat(input) is parseInt(input, 10) and not isNaN(input) style: /\s*(.+?):\s*([^;]+);?/g phone: /^\+(?:[0-9] ?){6,14}[0-9]$/ ================================================ FILE: src/draft4/type.coffee ================================================ module.exports = # handlers type: (definition, context) -> if @test_type "array", definition if definition.length == 0 throw new Error "Invalid 'type': arrays may not be empty" tests = [] for type in definition unless @is_primitive(type) throw new Error "Invalid 'type': #{type} is not a primitive type" do (type) => tests.push (data, runtime) => @test_type type, data (data, runtime) => valid = false for test in tests if test(data, runtime) valid = true if valid == false types = definition.join ', ' runtime.error context, data, description: "Expected type to be one of [#{types}] but found `#{@get_type(data)}`" else if @test_type "string", definition unless @is_primitive(definition) throw new Error "Invalid 'type': #{definition} is not a primitive type" (data, runtime) => if !@test_type definition, data runtime.error context, data, description: "Expected type to be `#{definition}` but found `#{@get_type(data)}`" else throw new Error "The value of 'type' must be a string or an array" ================================================ FILE: src/draft4.coffee ================================================ validator = require("./validator") module.exports = validator schema_uri: "http://json-schema.org/draft-04/schema#" mixins: [ require "./draft4/type" require "./draft4/logical" require "./draft4/numeric" require "./draft4/objects" require "./draft4/strings" ] ================================================ FILE: src/index.coffee ================================================ module.exports = draft3: require "./draft3" draft4: require "./draft4" ================================================ FILE: src/uri.coffee ================================================ is_absolute = (string) -> /^[\w\d+.-]+:/.test(string) is_url = (string) -> /^[\w\d+.-]+:\/\//.test(string) resolve = (scope, uri) -> if is_absolute(uri) uri else scope = scope.replace /#$/, "" if uri.indexOf("#") == 0 [main, _frag] = scope.split("#") main + uri else if is_url(scope) || scope.indexOf("/") != -1 scope.replace /\/[^/]+$/, "/#{uri}" else "#{scope}/#{uri}" module.exports = {is_absolute, is_url, resolve} ================================================ FILE: src/util.coffee ================================================ module.exports = escape: (string) -> string .replace(/~0/g, "~") .replace(/~1/g, "/") .replace(/%25/g, "%") # Used during document validation. Maintains a list of errors and the # JSON pointer for the section of the document. Runtime: class Runtime constructor: ({@errors, @pointer, @error_pointer, @tested_item}) -> @items_tested = 0 @tested_item ?= => @items_tested++ @error_pointer ?= @pointer child: (token) -> new @constructor errors: @errors pointer: "#{@pointer}/#{token.toString()}" error_pointer: "#{@error_pointer}/#{token.toString()}" tested_item: @tested_item path: -> @pointer.slice(2).replace(/\//g, ".") error: (context, value, options={}) -> e = schema: pointer: context.pointer attribute: context._attribute definition: context.definition document: pointer: @error_pointer path: @path() value: value if options.description? e.description = options.description @errors.push e # Maintains the URI scope and JSON pointer during traversal # of a schema. Context: class Context constructor: ({@pointer, @scope, @_attribute}) -> attribute: (name) -> new Context pointer: "#{@pointer}/#{name.toString()}" scope: @scope _attribute: name child: (token) -> new Context pointer: "#{@pointer}/#{token.toString()}" scope: @scope _attribute: @_attribute sibling: (token) -> pointer = @pointer.replace(/\/.*$/, "/#{token.toString()}") new Context pointer: pointer scope: @scope ================================================ FILE: src/validator.coffee ================================================ URI = require "./uri" {escape, Runtime, Context} = require "./util" # Schemas should always be JSON stringifiable, so this is a simple # method for obtaining a deep clone of one. This function only gets # used at schema-compilation time, so there are no performance # implications unless you are constantly compiling new schemas. clone = (value) -> JSON.parse(JSON.stringify(value)) DEFINITIONS = "http://json-schema.org/draft-03/schema#": require "../schemas/draft-03/schema.json" "http://json-schema.org/draft-04/schema#": require "../schemas/draft-04/schema.json" module.exports = ({schema_uri, mixins}) -> SCHEMA_URI = schema_uri class Validator @modifiers: patternProperties: [ "additionalProperties" ] additionalProperties: [ "properties" "patternProperties" ] items: [ "additionalItems" ] minimum: [ "exclusiveMinimum" ] maximum: [ "exclusiveMaximum" ] common_modules = "type": require "./common/type" "numeric": require "./common/numeric" "comparison": require "./common/comparison" "arrays": require "./common/arrays" "objects": require "./common/objects" "strings": require "./common/strings" common = for name of common_modules mixin = common_modules[name] for name, method of mixin Validator.prototype[name] = method for mixin in mixins for name, method of mixin Validator.prototype[name] = method constructor: (schemas...) -> @uris = {} @media_types = {} @unresolved = {} @options = {} @add(DEFINITIONS[SCHEMA_URI]) for schema in schemas if schema["$schema"]? && schema["$schema"] != SCHEMA_URI throw "This validator doesn't support this JSON schema." @add(schema) set_options: (args) -> @options = args return this add: (schema) -> # Clone the schema to prevent any user changes from affecting JSCK. schema = clone(schema) if schema.id # Make sure the schema id always ends with "#" schema.id = schema.id.replace /#?$/, "#" # The context keeps track of where we are in the schema while # we traverse it for compilation. context = new Context pointer: schema.id || "#" scope: schema.id || "#" @compile_references context, schema @compile(context, schema) validate: (data) -> @validator("#").validate(data) validator: (arg) -> if (schema = @find arg)? validate: (data) => errors = [] runtime = new Runtime {errors, pointer: "#"} schema._test(data, runtime) if errors.length > 0 for error in errors [base..., attribute] = error.schema.pointer.split("/") pointer = base.join("/") error.schema.definition ?= @resolve_uri(pointer)?[attribute] if error.document.value is undefined delete error.document.value valid = runtime.errors.length == 0 {valid, errors} toJSON: (args...) -> schema else throw new Error "No schema found for '#{JSON.stringify(arg)}'" # Find a registered schema. # # Takes either a URI string or an options object. # Valid options: # * uri # * mediaType find: (arg) -> if @test_type "string", arg uri = escape(arg) #if /required/.test uri #for u, def of @uris when /required/.test u #console.error u @uris[uri] else if (uri = arg.uri)? uri = escape(uri) @uris[uri] else if (media_type = arg.mediaType)? @media_types[media_type] else null resolve_uri: (uri, scope) -> if (schema = @find(uri))? if schema.$ref @resolve_uri URI.resolve(scope, schema.$ref) else schema register: (uri, schema) -> @uris[uri] = schema # TODO: enforce uniqueness of types if (media_type = schema.mediaType)? if media_type != "application/json" @media_types[media_type] = schema compile_references: (context, schema) -> # Make an initial pass over the schema looking for $ref fields, # recording their targets for use in actual compilation. @schema_references(context, schema) # We try a second time to resolve $ref values, because a schema may have # been defined after we initially tried to resolve a $ref. for ref, {scope, uri} of @unresolved if (found_schema = @resolve_uri(uri, scope))? delete @unresolved[ref] @register ref, found_schema if Object.keys(@unresolved).length > 0 pointers = (uri for key, {uri} of @unresolved) throw new Error "Unresolvable $ref values: #{JSON.stringify pointers}" schema_references: (context, schema) -> if !@test_type "object", schema throw new Error "Schema must be an object - #{context.pointer}" {scope, pointer} = context @register pointer, schema # This is one of the two cases where we pay attention to an "id" # attribute. The other is top-level id declaration, serving to identify # the entire schema. # # Here, we treat bare fragment identifiers (e.g. "#user") as aliases. if schema.id && schema.id.indexOf("#") == 0 uri = URI.resolve scope, schema.id schema.id = uri @register uri, schema for attribute, definition of schema if "$ref" == attribute @resolve_reference(context, schema, definition) else new_context = context.child(attribute) if "properties" == attribute @properties_references new_context, definition else if "items" == attribute @items_references new_context, definition else if "definitions" == attribute @definitions_references new_context, definition else if @test_type "object", definition @schema_references new_context, definition else if attribute in ["allOf", "anyOf", "not"] for s, i in definition @schema_references new_context.child(i), s resolve_reference: (context, schema, definition) -> {scope, pointer} = context # turn relative refs into absolute URIs uri = URI.resolve(scope, definition) # When the URI of a $ref is a substring of the present context's URI, # we're in a recursive reference situation. # Ignore recursive references during this stage. if pointer.indexOf(uri + "/") != 0 if (found_schema = @resolve_uri(uri, scope))? delete schema.$ref for k, v of found_schema schema[k] = v @schema_references context, schema else # Store the unresolvable reference so we can try to resolve # it again after having traversed the all schemas. @unresolved[pointer] = {scope, uri} properties_references: (context, properties) -> if !@test_type "object", properties throw new Error "Properties must be an object - #{context.pointer}" for property, schema of properties @schema_references context.child(property), schema items_references: (context, definition) -> if @test_type "array", definition for def, i in definition @schema_references context.child(i), def else @schema_references context, definition definitions_references: (context, object) -> if !@test_type "object", object throw new Error "Value of 'definitions' must be an object - #{context.pointer}" for name, schema of object @schema_references context.child(name), schema compile: (context, schema) -> {scope, pointer} = context tests = [] # When the schema contains the $ref attribute, locate the referenced # schema and use in place of the present schema. if (uri = schema.$ref)? if @uris[uri] return (args...) => @uris[uri]._test(args...) uri = URI.resolve(scope, uri) if pointer.indexOf(uri) == 0 # When the URI of a $ref is a substring of the present context's URI, # we're in a recursive reference situation. return @recursive_test(schema, context) schema = @find(uri) if !schema throw new Error "No schema found for $ref '#{uri}'" for key, definition of schema when key != "_test" # Create a child context to track our progress into a new attribute. new_context = context.attribute(key) if @[key]? test = @compile_attribute(new_context, key, schema, definition) tests.push(test) if test else # If the key doesn't correspond to a known attribute name, treat # the object as a container of definitions. @compile_definitions(new_context, definition) test_function = (data, runtime) -> return null if typeof(data) == "undefined" runtime.tested_item() for test in tests test(data, runtime) null # Record the test function for use by such things as @recursive_test. @find(pointer)?._test = test_function # Also record the function for schemas with "alias" ids. if schema.id uri = URI.resolve scope, schema.id @find(uri)?._test = test_function return test_function compile_attribute: (context, attribute, schema, definition) -> # Some validation attributes can be modified by other attributes # at the same level. E.g. minimum is modified by exclusiveMinimum. # Here we check the schema for such auxiliary attributes and stow # them in the context, so the primary attribute handler can act # on them. context.modifiers = {} if (modifiers = Validator.modifiers[attribute])? for key in modifiers context.modifiers[key] = schema[key] # Call the attribute's handler. # The return value will be a function that validates a document. # In rare cases, the attribute handler does not return a test # function, because some related attribute performs the test. if @[attribute]? if (test = @[attribute](definition, context))? return test compile_definitions: (context, object) -> if @is_schema(object) @compile(context, object) else if @test_type "object", object for name, definition of object @compile_definitions context.child(name), definition is_schema: (object) -> object.type? || object.$ref? || object.allOf? || object.anyOf? || object.not? recursive_test: (schema, {scope, pointer}) -> uri = URI.resolve(scope, schema.$ref) if (schema = @find uri)? (data, runtime) -> schema._test(data, runtime) else throw new Error "No schema found for $ref '#{uri}'" ================================================ FILE: tasks/build/benchmarks.coffee ================================================ fs = require "fs" shell = require "shelljs" result = shell.exec "coffee benchmarks" fs.writeFileSync "benchmarks/results/all.txt", result.output ================================================ FILE: tasks/build/browser.coffee ================================================ {exec} = require "../helpers" require "./javascript" base = "node_modules/.bin/cjsify lib/index.js --no-node --export JSCK" # https://www.npmjs.com/package/commonjs-everywhere exec "#{base} -o jsck.js" exec "#{base} -m -o jsck.min.js" ================================================ FILE: tasks/build/docs.coffee ================================================ {build_docs} = require "../helpers" build_docs() ================================================ FILE: tasks/build/index.coffee ================================================ require "./javascript" require "./docs" ================================================ FILE: tasks/build/javascript.coffee ================================================ {exec} = require "../helpers" exec "mkdir -p lib" exec "coffee --compile --bare --output lib/ src/" ================================================ FILE: tasks/helpers.coffee ================================================ shell = require "shelljs" exec = (cmd) -> console.log cmd shell.exec cmd docs = "doc/README.pfm.md": "README.md" "doc/tests.pfm.md": "doc/tests.md" "doc/benchmarks.pfm.md": "doc/benchmarks.md" module.exports = exec: exec docs: docs build_docs: (name) -> # pfm is a rubygem for converting Panda Flavored Markdown into # GitHub Flavored Markdown. # gem install pfm if name destination = docs[name] exec "pfm #{name} -o #{destination}" else for source, destination of docs exec "pfm #{source} -o #{destination}" ================================================ FILE: tasks/update/index.coffee ================================================ require "./submodules" ================================================ FILE: tasks/update/submodules.coffee ================================================ {exec} = require "../helpers" exec "git submodule init" exec "git submodule update" ================================================ FILE: tasks/watch/docs.coffee ================================================ fs = require "fs" {docs, build_docs} = require "../helpers" for source, _dest of docs do (source) -> fs.watchFile source, (curr, prev) -> build_docs(source) ================================================ FILE: tasks/watch/src.coffee ================================================ fs = require "fs" {exec} = require "../helpers" exec "coffee --compile --watch --bare --output lib/ src/", -> ================================================ FILE: test/.gitignore ================================================ suite suite.tgz ================================================ FILE: test/draft3/builtins.coffee ================================================ assert = require "assert" Testify = require "testify" JSCK = require("../../src/index").draft3 Testify.test "Built in schemas", (context) -> context.test "$ref to draft 3 works", -> jsck = new JSCK type: "object" properties: foo: $ref: "http://json-schema.org/draft-03/schema#" report = jsck.validate foo: properties: {} assert.equal report.valid, true report = jsck.validate foo: properties: "smurf" assert.equal report.valid, false ================================================ FILE: test/draft3/index.coffee ================================================ require "./unit" require "./official" require "./builtins" # TODO: implement these tests for draft3 #require "./invalid" #require "./valid" ================================================ FILE: test/draft3/official.coffee ================================================ {draft3} = require "../../src/index" suite = require "json-schema-tests" [_node, _script, attribute, test_number] = process.argv suite { attribute test_number version: "draft3" validate: (schema, document) -> v = new draft3(schema) v.validate(document) ignores: # Doubtful value for the majority of use cases. # https://github.com/pandastrike/jsck/issues/42 minLength: [ "one supplementary Unicode code point is not long enough" ] maxLength: [ "two supplementary Unicode code points is long enough" ] # Not supported because of the potential performance implications # https://github.com/pandastrike/jsck/issues/2 uniqueItems: true # Impossible to test when using output of JSON.parse # https://github.com/pandastrike/jsck/issues/6 "optional/zeroTerminatedFloats": true # The following items require fetching of remote schemas. # Support for remote references is planned for the next version of JSCK refRemote: true ref: [ "remote ref, containing refs itself" ] definitions: true } ================================================ FILE: test/draft3/unit/errors.coffee ================================================ assert = require "assert" Testify = require "testify" JSCK = require("../../src/index").draft3 jsck = new JSCK type: "object" additionalProperties: type: "object" additionalProperties: false properties: resource: required: true type: "string" url: type: "string" report = jsck.validate smurf: {url: 4} console.log() console.log report.errors[0].schema.pointer console.log() ================================================ FILE: test/draft3/unit/index.coffee ================================================ #require "./errors" require "./references" require "./uri_test" ================================================ FILE: test/draft3/unit/references.coffee ================================================ assert = require "assert" Testify = require "testify" JSCK = require("../../../src/index").draft3 Testify.test "JSCK draft 3 dereferencing", (context) -> context.test "Finding by uri", (context) -> jsck = new JSCK $schema: "http://json-schema.org/draft-03/schema#" id: "urn:jsck.test" type: "object" properties: user_list: id: "#user_list" type: "array" items: {$ref: "#/properties/user"} user_dict: {$ref: "#/properties/user"} user: id: "#user" type: "object" properties: name: required: true type: "string" email: type: "string" context.test "JSON Pointer", -> result = jsck.validator("urn:jsck.test#/properties/user").validate {name: "automatthew"} assert.equal result.valid, true context.test "id fragment", -> result = jsck.validator("urn:jsck.test#user").validate {name: "automatthew"} assert.equal result.valid, true context.test "schema without 'id'", (context) -> test_schema = definitions: schema1: id: "#foo" type: "string" format: "uri" jsck = new JSCK(test_schema) context.test "JSON pointers", (context) -> context.test "Pointer relative to empty URI", -> schema = jsck.find "#/foo" assert.deepEqual schema, test_schema.schema1 return # FIXME: find out whether these tests fail because of app problems # or test problems. context.test "find", (context) -> test_schema = id: "http://x.y.z/rootschema.json#" schema1: id: "#foo" schema2: id: "otherschema.json" type: "string" nested: id: "#bar" alsonested: id: "t/inner.json#a" schema3: id: "some://where.else/completely#" schema4: $ref: "#foo" jsck = new JSCK(test_schema) context.test "JSON pointers", (context) -> context.test "Absolute URI", -> schema = jsck.find "http://x.y.z/rootschema.json#/schema1" assert.deepEqual schema, test_schema.schema1 schema = jsck.find "http://x.y.z/rootschema.json#/schema2/nested" assert.deepEqual schema, test_schema.schema2.nested context.test "Setting scope with 'id'", (context) -> context.test "works for fragment", -> schema = jsck.find "http://x.y.z/rootschema.json#foo" assert.deepEqual schema, test_schema.schema1 context.test "ignores path change", -> schema = jsck.find "http://x.y.z/otherschema.json#bar" assert.deepEqual schema, undefined context.test "ignores nested path change", -> schema = jsck.find "http://x.y.z/t/inner.json#a" assert.deepEqual schema, undefined context.test "Inline reference resolution", -> schema = jsck.find "http://x.y.z/rootschema.json#/schema4" assert.deepEqual schema, test_schema.schema1 ================================================ FILE: test/draft3/unit/uri_test.coffee ================================================ assert = require "assert" Testify = require "testify" URI = require "../../../src/uri" Testify.test "URI helper methods", (context) -> context.test "is_absolute", (context) -> context.test "URNs", -> assert.equal URI.is_absolute("urn:foo:bar:baz"), true assert.equal URI.is_absolute("urn"), false context.test "URLs", -> assert.equal URI.is_absolute("http://example.com:1452/smurf"), true assert.equal URI.is_absolute("example.com"), false context.test "is_url", -> assert.equal URI.is_url("https://monkeyshines.org/x?y=z"), true assert.equal URI.is_url("urn:foo:bar/baz,bat"), false context.test "resolve", (context) -> context.test "URL scope", (context) -> scope = "http://pandastrike.com/patchboard.json#" context.test "absolute reference", -> assert.equal URI.resolve(scope, "http://example.com/schema.json#"), "http://example.com/schema.json#" context.test "fragment", -> assert.equal URI.resolve(scope, "#service"), "http://pandastrike.com/patchboard.json#service" context.test "root reference", -> assert.equal URI.resolve(scope, "#"), "http://pandastrike.com/patchboard.json#" context.test "fragment reference", -> assert.equal URI.resolve(scope, "#/properties/resource"), "http://pandastrike.com/patchboard.json#/properties/resource" context.test "hierarchical references", -> assert.equal URI.resolve(scope, "other.json"), "http://pandastrike.com/other.json" scope = "http://pandastrike.com/schemas/patchboard.json#" assert.equal URI.resolve(scope, "other.json"), "http://pandastrike.com/schemas/other.json" context.test "scope reference, root reference", -> s = "http://pandastrike.com/patchboard.json#/properties/foo" assert.equal URI.resolve(s, "#"), "http://pandastrike.com/patchboard.json#" context.test "URI other than URL scope", (context) -> scope = "urn:jsck.anon#" context.test "fragment", -> assert.equal URI.resolve(scope, "#service"), "urn:jsck.anon#service" context.test "hierarchical references", -> assert.equal URI.resolve(scope, "other.json"), "urn:jsck.anon/other.json" ================================================ FILE: test/draft4/adhoc.coffee ================================================ assert = require "assert" Testify = require "testify" JSCK = require("../../src/index").draft4 Testify.test "Ad hoc tests", (context) -> context.test "undefinedness", (context) -> jsck = new JSCK $schema: "http://json-schema.org/draft-04/schema#" type: "object" properties: foo: type: [ "number" ] context.test "valid", -> result = jsck.validate foo: undefined assert.equal result.valid, true result = jsck.validate foo: 42 assert.equal result.valid, true result = jsck.validate Object.create({foo: 42}) assert.equal result.valid, true context.test "invalid", -> result = jsck.validate foo: null assert.equal result.valid, false context.test "issue #94 from SciencePiggy", -> jsck = new JSCK type: "object" required: [ "fullName" ] properties: fullName: type: "string" settings: $ref: "#/definitions/userSettings" definitions: userSettings: type: "object" required: [ "language" ] properties: language: type: "string" result = jsck.validate fullName: "Homer Simpson" settings: null assert.equal result.valid, false ================================================ FILE: test/draft4/builtins.coffee ================================================ assert = require "assert" Testify = require "testify" JSCK = require("../../src/index").draft4 Testify.test "Built in schemas", (context) -> context.test "$ref to draft 4 works", -> jsck = new JSCK type: "object" properties: foo: $ref: "http://json-schema.org/draft-04/schema#" report = jsck.validate foo: properties: {} assert.equal report.valid, true report = jsck.validate foo: properties: "smurf" assert.equal report.valid, false ================================================ FILE: test/draft4/index.coffee ================================================ require "./unit" require "./official" require "./invalid" require "./valid" require "./builtins" ================================================ FILE: test/draft4/invalid/additionalItems.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be either a boolean or an object" schemas: for value in json_types.except("boolean", "object") # additionalItems only takes effect when the value of 'items' is an array items: [ {} ] additionalItems: value } ] ================================================ FILE: test/draft4/invalid/additionalProperties.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be a boolean or an object" schemas: for value in json_types.except("boolean", "object") additionalProperties: value } ] ================================================ FILE: test/draft4/invalid/allOf.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be an array" schemas: for value in json_types.except("array") allOf: value } { description: "array MUST have at least one element" schemas: [ allOf: [] ] } { description: "elements of the array MUST be objects" schemas: for value in json_types.except("object") allOf: [ value ] } ] ================================================ FILE: test/draft4/invalid/anyOf.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be an array" schemas: for value in json_types.except("array") anyOf: value } { description: "array MUST have at least one element" schemas: [ anyOf: [] ] } { description: "elements of the array MUST be objects" schemas: for value in json_types.except("object") anyOf: [ value ] } ] ================================================ FILE: test/draft4/invalid/dependencies.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be an object" schemas: for value in json_types.except("object") dependencies: value } { description: "each value MUST be either an object or an array" schemas: for value in json_types.except("object", "array") dependencies: foo: value } { description: "array values must have at least one element" schemas: [ dependencies: foo: [] ] } { description: "array values must contain only strings" schemas: [ dependencies: foo: [33] ] } ] ================================================ FILE: test/draft4/invalid/items.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be either an object or an array" schemas: for value in json_types.except("object", "array") {items: value} } { description: "arrays may only contain objects" schemas: for value in json_types.except("object") {items: [value]} } ] ================================================ FILE: test/draft4/invalid/not.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be an object" schemas: for value in json_types.except("object") not: value } ] ================================================ FILE: test/draft4/invalid/numbers.coffee ================================================ module.exports = ({json_types}) -> [ { description: "'multipleOf' value MUST be a number" schemas: for value in json_types.except("number", "integer") multipleOf: value } ] ================================================ FILE: test/draft4/invalid/oneOf.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be an array" schemas: for value in json_types.except("array") oneOf: value } { description: "array MUST have at least one element" schemas: [ oneOf: [] ] } { description: "elements of the array MUST be objects" schemas: for value in json_types.except("object") oneOf: [ value ] } ] ================================================ FILE: test/draft4/invalid/patternProperties.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be an object" schemas: for value in json_types.except("object") patternProperties: value } { description: "object should not be empty" schemas: for value in json_types.except("object") patternProperties: {} } { description: "names should be valid regexes" schemas: [ { patternProperties: "[": {} } ] } { description: "object values MUST themselves be objects" schemas: for value in json_types.except("object") patternProperties: foo: value } ] ================================================ FILE: test/draft4/invalid/properties.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be an object" schemas: for value in json_types.except("object") properties: value } { description: "property values must themselves be objects" schemas: for value in json_types.except("object") properties: big: value } ] ================================================ FILE: test/draft4/invalid/required.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be an array" schemas: for value in json_types.except("array") required: value } { description: "the array must have at least one value" schemas: [ required: [] ] } { description: "the array values must be strings" schemas: for value in json_types.except("string") required: [value] } ] ================================================ FILE: test/draft4/invalid/strings.coffee ================================================ module.exports = ({json_types}) -> [ { description: "'pattern' value must be a string" schemas: for value in json_types.except("string") pattern: value } { description: "'maxLength' value must be an integer" schemas: for value in json_types.except("integer") maxLength: value } { description: "'minLength' value must be an integer" schemas: for value in json_types.except("integer") minLength: value } ] ================================================ FILE: test/draft4/invalid/type.coffee ================================================ module.exports = ({json_types}) -> [ { description: "value MUST be either a string or an array" schemas: for value in json_types.except("string", "array") {type: value} } { description: "arrays may not be empty" schemas: [ {type: []} ] } { description: "arrays may only contain strings" schemas: for value in json_types.except("string") {type: value} } { description: "string values must be one of the primitive types" schemas: [ type: "bogus" ] } ] ================================================ FILE: test/draft4/invalid.coffee ================================================ glob = require "glob" assert = require "assert" Testify = require "testify" {draft4} = require("../../src/index") [_, _, single_attr] = process.argv helpers = json_types: values: integer: 2 number: 2.1 string: "foo" object: {} array: [ ] boolean: false null: null except: (names...) -> for name, value of @values when !(name in names) value Testify.test "Rejecting invalid schemas", (context) -> files = glob.sync("#{__dirname}/invalid/*.coffee").sort() l = "#{__dirname}/invalid/".length for file in files # chomp .coffee attribute_name = file.slice(l, -7) if single_attr && attribute_name != single_attr continue context.test attribute_name, (context) -> tests = require(file) # Dependency injection, where needed if tests.constructor == Function tests = tests(helpers) for test in tests context.test test.description, -> for schema in test.schemas try new draft4(schema) context.fail "#{test.description} - #{JSON.stringify schema}" catch e if test.debug console.log "\n", attribute_name, "-", test.description console.log schema console.log e.stack ================================================ FILE: test/draft4/official.coffee ================================================ {draft4} = require "../../src/index" suite = require "json-schema-tests" #shell = require "shelljs" #cmd = "node_modules/.bin/nserver -p 5725 -d test/JSON-Schema-Test-Suite/remotes" #proc = shell.exec cmd, (code, output) -> # #Testify = require "testify" #Testify.once "done", -> #console.log "Shutting down the 'remotes' test server" #proc.kill("SIGTERM") [_node, _script, attribute, test_number] = process.argv suite { attribute test_number version: "draft4" validate: (schema, document) -> v = new draft4(schema) v.validate(document) ignores: # Doubtful value for the majority of use cases. # https://github.com/pandastrike/jsck/issues/42 minLength: [ "one supplementary Unicode code point is not long enough" ] maxLength: [ "two supplementary Unicode code points is long enough" ] # Not supported because of the potential performance implications # https://github.com/pandastrike/jsck/issues/2 uniqueItems: true # Impossible to test when using output of JSON.parse # https://github.com/pandastrike/jsck/issues/6 "optional/zeroTerminatedFloats": true # The following items require fetching of remote schemas. # Support for remote references is planned for the next version of JSCK refRemote: true ref: [ "remote ref, containing refs itself" ] definitions: true } ================================================ FILE: test/draft4/unit/errors.coffee ================================================ assert = require "assert" Testify = require "testify" JSCK = require("../../src/index").draft3 jsck = new JSCK type: "object" additionalProperties: type: "object" additionalProperties: false properties: resource: required: true type: "string" url: type: "string" report = jsck.validate smurf: {url: 4} console.log() console.log report.errors[0].schema.pointer console.log() ================================================ FILE: test/draft4/unit/index.coffee ================================================ #require "./errors" require "./references" require "./uri_test" ================================================ FILE: test/draft4/unit/logical.coffee ================================================ Testify = require "testify" assert = require "assert" JSCK = require("../../../src") jsck = new JSCK.draft4 definitions: assetType: type: "string" enum: [ "vote" "image" "audio" "video" ] campaign: base: type: "object" properties: name: type: "string" metadata: type: "object" assetTypes: type: "array" items: {$ref: "#/definitions/assetType"} active: type: "boolean" startTime: type: "string" format: "date-time" endTime: type: "string" format: "date-time" create: allOf: [ {$ref: "#/definitions/campaign/base"} { required: ["name", "assetTypes"] } ] validator = jsck.validator("#/definitions/campaign/create") Testify.test "using logical keywords", (context) -> context.test "allOf", -> {errors} = validator.validate name: "foobar" assetTypes: ["vote"] startTime: "monkey" assert.equal errors.length, 0, "Unexpected errors:\n#{JSON.stringify(errors, null, 2)}" ================================================ FILE: test/draft4/unit/references.coffee ================================================ assert = require "assert" Testify = require "testify" JSCK = require("../../../src/index").draft4 Testify.test "JSCK draft 4 dereferencing", (context) -> context.test "Finding by uri", (context) -> jsck = new JSCK $schema: "http://json-schema.org/draft-04/schema#" id: "urn:jsck.test" type: "object" properties: user_list: id: "#user_list" type: "array" items: {$ref: "#/properties/user"} user_dict: {$ref: "#/properties/user"} user: id: "#user" type: "object" required: ["name"] properties: name: type: "string" email: type: "string" context.test "JSON Pointer", -> result = jsck.validator("urn:jsck.test#/properties/user").validate {name: "automatthew"} assert.equal result.valid, true context.test "id fragment", -> result = jsck.validator("urn:jsck.test#user").validate {name: "automatthew"} assert.equal result.valid, true context.test "schema without 'id'", (context) -> test_schema = definitions: schema1: id: "#foo" type: "string" format: "uri" jsck = new JSCK(test_schema) context.test "JSON pointers", (context) -> context.test "Pointer relative to empty URI", -> schema = jsck.find "#/foo" assert.deepEqual schema, test_schema.schema1 return # FIXME: find out whether these tests fail because of app problems # or test problems. context.test "find", (context) -> test_schema = id: "http://x.y.z/rootschema.json#" schema1: id: "#foo" schema2: id: "otherschema.json" type: "string" nested: id: "#bar" alsonested: id: "t/inner.json#a" schema3: id: "some://where.else/completely#" schema4: $ref: "#foo" jsck = new JSCK(test_schema) context.test "JSON pointers", (context) -> context.test "Absolute URI", -> schema = jsck.find "http://x.y.z/rootschema.json#/schema1" assert.deepEqual schema, test_schema.schema1 schema = jsck.find "http://x.y.z/rootschema.json#/schema2/nested" assert.deepEqual schema, test_schema.schema2.nested context.test "Setting scope with 'id'", (context) -> context.test "works for fragment", -> schema = jsck.find "http://x.y.z/rootschema.json#foo" assert.deepEqual schema, test_schema.schema1 context.test "ignores path change", -> schema = jsck.find "http://x.y.z/otherschema.json#bar" assert.deepEqual schema, undefined context.test "ignores nested path change", -> schema = jsck.find "http://x.y.z/t/inner.json#a" assert.deepEqual schema, undefined context.test "Inline reference resolution", -> schema = jsck.find "http://x.y.z/rootschema.json#/schema4" assert.deepEqual schema, test_schema.schema1 ================================================ FILE: test/draft4/unit/uri_test.coffee ================================================ assert = require "assert" Testify = require "testify" URI = require "../../../src/uri" Testify.test "URI helper methods", (context) -> context.test "is_absolute", (context) -> context.test "URNs", -> assert.equal URI.is_absolute("urn:foo:bar:baz"), true assert.equal URI.is_absolute("urn"), false context.test "URLs", -> assert.equal URI.is_absolute("http://example.com:1452/smurf"), true assert.equal URI.is_absolute("example.com"), false context.test "is_url", -> assert.equal URI.is_url("https://monkeyshines.org/x?y=z"), true assert.equal URI.is_url("urn:foo:bar/baz,bat"), false context.test "resolve", (context) -> context.test "URL scope", (context) -> scope = "http://pandastrike.com/patchboard.json#" context.test "absolute reference", -> assert.equal URI.resolve(scope, "http://example.com/schema.json#"), "http://example.com/schema.json#" context.test "fragment", -> assert.equal URI.resolve(scope, "#service"), "http://pandastrike.com/patchboard.json#service" context.test "root reference", -> assert.equal URI.resolve(scope, "#"), "http://pandastrike.com/patchboard.json#" context.test "fragment reference", -> assert.equal URI.resolve(scope, "#/properties/resource"), "http://pandastrike.com/patchboard.json#/properties/resource" context.test "hierarchical references", -> assert.equal URI.resolve(scope, "other.json"), "http://pandastrike.com/other.json" scope = "http://pandastrike.com/schemas/patchboard.json#" assert.equal URI.resolve(scope, "other.json"), "http://pandastrike.com/schemas/other.json" context.test "scope reference, root reference", -> s = "http://pandastrike.com/patchboard.json#/properties/foo" assert.equal URI.resolve(s, "#"), "http://pandastrike.com/patchboard.json#" context.test "URI other than URL scope", (context) -> scope = "urn:jsck.anon#" context.test "fragment", -> assert.equal URI.resolve(scope, "#service"), "urn:jsck.anon#service" context.test "hierarchical references", -> assert.equal URI.resolve(scope, "other.json"), "urn:jsck.anon/other.json" ================================================ FILE: test/draft4/valid/basic.coffee ================================================ module.exports = ({json_types}) -> "may contain properties which are not schema keywords": schemas: "string value": monkey: "shines" "integer value": monkey: 84 "schema value": monkey: type: "array" ================================================ FILE: test/draft4/valid/definitions.coffee ================================================ module.exports = ({json_types}) -> "known attribute name as key of 'definitions' dictionary": schemas: required: definitions: required: type: "object" id: definitions: id: type: "object" items: definitions: items: type: "object" "nested definitions": schemas: foo: definitions: bar: {type: "string"} definitions: baz: {type: "string"} ================================================ FILE: test/draft4/valid/properties.coffee ================================================ module.exports = ({json_types}) -> "known attribute name": schemas: required: type: "object" properties: required: type: "boolean" id: type: "object" properties: id: type: "string" properties: type: "object" properties: properties: {type: "array"} ================================================ FILE: test/draft4/valid.coffee ================================================ glob = require "glob" assert = require "assert" Testify = require "testify" {draft4} = require("../../src/index") helpers = json_types: values: integer: 2 number: 2.1 string: "foo" object: {} array: [ ] boolean: false null: null except: (names...) -> for name, value of @values when !(name in names) value Testify.test "Accepting valid schemas", (context) -> files = glob.sync("#{__dirname}/valid/*.coffee").sort() l = "#{__dirname}/valid/".length for file in files # chomp .coffee attribute_name = file.slice(l, -7) context.test attribute_name, (context) -> tests = require(file) # Dependency injection, where needed if tests.constructor == Function tests = tests(helpers) for name, test of tests context.test name, (context) -> for k, schema of test.schemas context.test k, -> try new draft4(schema) catch e console.log e.stack context.fail(e) ================================================ FILE: test/index.coffee ================================================ require "./draft3/unit" require "./draft3" require "./draft4/unit" require "./draft4"