diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 8230d66..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "rules": { - "block-scoped-var": 2, - "callback-return": 2, - "complexity": [2, 13], - "curly": [2, "multi-or-nest", "consistent"], - "dot-location": [2, "property"], - "dot-notation": 2, - "indent": [ 2, 2, {"SwitchCase": 1} ], - "linebreak-style": [ 2, "unix" ], - "new-cap": 2, - "no-console": [ 2, { "allow": ["warn", "error"] } ], - "no-else-return": 2, - "no-eq-null": 2, - "no-fallthrough": 2, - "no-invalid-this": 2, - "no-return-assign": 2, - "no-shadow": 1, - "no-trailing-spaces": 2, - "no-use-before-define": [2, "nofunc"], - "quotes": [ 2, "single", "avoid-escape" ], - "semi": [ 2, "always" ], - "strict": [2, "global"], - "valid-jsdoc": [ 2, { "requireReturn": false } ] - }, - "env": { - "node": true, - "browser": true - }, - "extends": "eslint:recommended" -} diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..db636f5 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,28 @@ +extends: eslint:recommended +env: + node: true + browser: true +rules: + block-scoped-var: 2 + callback-return: 2 + complexity: [2, 13] + curly: [2, multi-or-nest, consistent] + dot-location: [2, property] + dot-notation: 2 + indent: [2, 2, SwitchCase: 1] + linebreak-style: [2, unix] + new-cap: 2 + no-console: [2, allow: [warn, error]] + no-else-return: 2 + no-eq-null: 2 + no-fallthrough: 2 + no-invalid-this: 2 + no-return-assign: 2 + no-shadow: 1 + no-trailing-spaces: 2 + no-use-before-define: [2, nofunc] + quotes: [2, single, avoid-escape] + semi: [2, always] + strict: [2, global] + valid-jsdoc: [2, requireReturn: false] + no-control-regex: 0 diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 3c0f578..0000000 --- a/.jshintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "laxcomma": true, - "laxbreak": true, - "curly": false, - "-W058": true, - "eqeqeq": false, - "node": true -} diff --git a/CUSTOM.md b/CUSTOM.md index 97c37f4..8fb74ec 100644 --- a/CUSTOM.md +++ b/CUSTOM.md @@ -39,7 +39,7 @@ This way to define keywords is useful for: __Please note__: In cases when validation flow is different depending on the schema and you have to use `if`s, this way to define keywords will have worse performance than compiled keyword returning different validation functions depending on the schema. -Example. `constant` keyword from version 5 proposals (that is equivalent to `enum` keyword with one item): +Example. `constant` keyword (a synonym for draft6 keyword `const`, it is equivalent to `enum` keyword with one item): ```javascript ajv.addKeyword('constant', { validate: function (schema, data) { @@ -59,7 +59,7 @@ console.log(validate({foo: 'bar'})); // true console.log(validate({foo: 'baz'})); // false ``` -`constant` keyword is already available in Ajv with option `v5: true`. +`const` keyword is already available in Ajv. __Please note:__ If the keyword does not define custom errors (see [Reporting errors in custom keywords](#reporting-errors-in-custom-keywords)) pass `errors: false` in its definition; it will make generated code more efficient. diff --git a/KEYWORDS.md b/KEYWORDS.md index 3ea4e71..6e14ac7 100644 --- a/KEYWORDS.md +++ b/KEYWORDS.md @@ -10,19 +10,19 @@ The keywords and their values define what rules the data should satisfy to be va - [type](#type) - [Keywords for numbers](#keywords-for-numbers) - - [maximum / minimum and exclusiveMaximum / exclusiveMinimum](#maximum--minimum-and-exclusivemaximum--exclusiveminimum) + - [maximum / minimum and exclusiveMaximum / exclusiveMinimum](#maximum--minimum-and-exclusivemaximum--exclusiveminimum) (CHANGED in draft 6) - [multipleOf](#multipleof) - [Keywords for strings](#keywords-for-strings) - [maxLength/minLength](#maxlength--minlength) - [pattern](#pattern) - [format](#format) - - [formatMaximum / formatMinimum and formatExclusiveMaximum / formatExclusiveMinimum](#formatmaximum--formatminimum-and-exclusiveformatmaximum--exclusiveformatminimum-v5-proposal) (v5) + - [formatMaximum / formatMinimum and formatExclusiveMaximum / formatExclusiveMinimum](#formatmaximum--formatminimum-and-formatexclusivemaximum--formatexclusiveminimum-proposed) (proposed) - [Keywords for arrays](#keywords-for-arrays) - [maxItems/minItems](#maxitems--minitems) - [uniqueItems](#uniqueitems) - [items](#items) - [additionalItems](#additionalitems) - - [contains](#contains-v5-proposal) (v5) + - [contains](#contains) (NEW in draft 6) - [Keywords for objects](#keywords-for-objects) - [maxProperties/minProperties](#maxproperties--minproperties) - [required](#required) @@ -30,16 +30,18 @@ The keywords and their values define what rules the data should satisfy to be va - [patternProperties](#patternproperties) - [additionalProperties](#additionalproperties) - [dependencies](#dependencies) - - [patternGroups](#patterngroups-v5-proposal) (v5) - - [patternRequired](#patternrequired-v5-proposal) (v5) + - [propertyNames](#propertynames) (NEW in draft 6) + - [patternGroups](#patterngroups-deprecated) (deprecated) + - [patternRequired](#patternrequired-proposed) (proposed) - [Keywords for all types](#keywords-for-all-types) - [enum](#enum) - - [constant](#constant-v5-proposal) (v5) + - [const](#const) (NEW in draft 6) +- [Compound keywords](#compound-keywords) - [not](#not) - [oneOf](#oneof) - [anyOf](#anyof) - [allOf](#allof) - - [switch](#switch-v5-proposal) (v5) + - [switch](#switch-proposed) (proposed) @@ -86,7 +88,11 @@ Most other keywords apply only to a particular type of data. If the data is of d The value of keyword `maximum` (`minimum`) should be a number. This value is the maximum (minimum) allowed value for the data to be valid. -The value of keyword `exclusiveMaximum` (`exclusiveMinimum`) should be a boolean value. These keyword cannot be used without `maximum` (`minimum`). If this keyword value is equal to `true`, the data should not be equal to the value in `maximum` (`minimum`) keyword to be valid. +Draft 4: The value of keyword `exclusiveMaximum` (`exclusiveMinimum`) should be a boolean value. These keyword cannot be used without `maximum` (`minimum`). If this keyword value is equal to `true`, the data should not be equal to the value in `maximum` (`minimum`) keyword to be valid. + +Draft 6: The value of keyword `exclusiveMaximum` (`exclusiveMinimum`) should be a number. This value is the exclusive maximum (minimum) allowed value for the data to be valid (the data equal to this keyword value is invalid). + +Ajv supports both draft 4 and draft 6 syntaxes. __Examples__ @@ -105,7 +111,9 @@ __Examples__ _invalid_: `4`, `4.5` -3. _schema_: `{ "minimum": 5, "exclusiveMinimum": true }` +3. _schema_: + draft 4: `{ "minimum": 5, "exclusiveMinimum": true }` + draft 6: `{ "exclusiveMinimum": 5 }` _valid_: `6`, `7`, any non-number (`"abc"`, `[]`, `{}`, `null`, `true`) @@ -193,7 +201,9 @@ _invalid_: `"abc"` -### `formatMaximum` / `formatMinimum` and `formatExclusiveMaximum` / `formatExclusiveMinimum` (v5 proposal) +### `formatMaximum` / `formatMinimum` and `formatExclusiveMaximum` / `formatExclusiveMinimum` (proposed) + +Defined in [ajv-keywords](https://github.com/epoberezkin/ajv-keywords) package. The value of keyword `formatMaximum` (`formatMinimum`) should be a string. This value is the maximum (minimum) allowed value for the data to be valid as determined by `format` keyword. @@ -214,7 +224,7 @@ _schema_: } ``` -_valid_: `2015-12-31`, `"2016-02-05"`, any non-string +_valid_: `"2015-12-31"`, `"2016-02-05"`, any non-string _invalid_: `"2016-02-06"`, `"2016-02-07"`, `"abc"` @@ -353,7 +363,7 @@ __Examples__ _invalid_: `["abc"]`, `[1, 2, 3]` -### `contains` (v5 proposal) +### `contains` The value of the keyword is a JSON-schema. The array is valid if it contains at least one item that is valid according to this schema. @@ -366,7 +376,7 @@ _valid_: `[1]`, `[1, "foo"]`, any array with at least one integer, any non-array _invalid_: `[]`, `["foo", "bar"]`, any array without integers -The same can be expressed using only draft 4 keywords but it is quite verbose. The schema from the example above is equivalent to: +The schema from the example above is equivalent to: ```json { @@ -535,6 +545,7 @@ __Examples__ _invalid_: `{"bar": 2}`, `{"baz": 3}`, `{"foo": 1, "bar": 2}`, etc. + ### `dependencies` The value of the keyword is a map with keys equal to data object properties. Each value in the map should be either an array of unique property names ("property dependency") or a JSON schema ("schema dependency"). @@ -579,7 +590,32 @@ __Examples__ -### `patternGroups` (v5 proposal) +### `propertyNames` + +The value of this keyword is a JSON schema. + +For data object to be valid each property name in this object should be valid according to this schema. + + +__Example__ + +_schema_: + +```json +{ + "propertyNames": { "format": "email" } +} +``` + +_valid_: `{"foo@bar.com": "any", "bar@bar.com": "any"}`, any non-object + +_invalid_: `{"foo": "any value"}` + + + +### `patternGroups` (deprecated) + +This keyword is only provided for backward compatibility, it will be removed in the next major version. To use it, pass option `patternGroups: true`. The value of this keyword should be a map where keys should be regular expressions and the values should be objects with the following properties: @@ -612,7 +648,9 @@ _invalid_: `{}`, `{ "foo": "bar" }`, `{ "1": "2" }` -### `patternRequired` (v5 proposal) +### `patternRequired` (proposed) + +Defined in [ajv-keywords](https://github.com/epoberezkin/ajv-keywords) package. The value of this keyword should be an array of strings, each string being a regular expression. For data object to be valid each regular expression in this array should match at least one property name in the data object. @@ -651,20 +689,20 @@ _invalid_: `1`, `"bar"`, `{"foo": "baz"}`, `[1, 2, 3, 4]`, any value not in the -### `constant` (v5 proposal) +### `const` The value of this keyword can be anything. The data is valid if it is deeply equal to the value of the keyword. __Example__ -_schema_: `{ "constant": "foo" }` +_schema_: `{ "const": "foo" }` _valid_: `"foo"` _invalid_: any other value -The same can be achieved with `enum` keyword using the array with one item. But `constant` keyword is more than just a syntax sugar for `enum`. In combination with the [$data reference](https://github.com/epoberezkin/ajv#data-reference) it allows to define equality relations between different parts of the data. This cannot be achieved with `enum` keyword even with `$data` reference because `$data` cannot be used in place of one item - it can only be used in place of the whole array in `enum` keyword. +The same can be achieved with `enum` keyword using the array with one item. But `const` keyword is more than just a syntax sugar for `enum`. In combination with the [$data reference](https://github.com/epoberezkin/ajv#data-reference) it allows to define equality relations between different parts of the data. This cannot be achieved with `enum` keyword even with `$data` reference because `$data` cannot be used in place of one item - it can only be used in place of the whole array in `enum` keyword. __Example__ @@ -675,7 +713,7 @@ _schema_: { "properties": { "foo": { "type": "number" }, - "bar": { "constant": { "$data": "1/foo" } } + "bar": { "const": { "$data": "1/foo" } } } } ``` @@ -686,6 +724,8 @@ _invalid_: `{ "foo": 1 }`, `{ "bar": 1 }`, `{ "foo": 1, "bar": 2 }` +## Compound keywords + ### `not` The value of the keyword should be a JSON schema. The data is valid if it is invalid according to this schema. @@ -786,7 +826,9 @@ _invalid_: `1.5`, `2.5`, `4`, `4.5`, `5`, `5.5`, any non-number -### `switch` (v5 proposal) +### `switch` (proposed) + +Defined in [ajv-keywords](https://github.com/epoberezkin/ajv-keywords) package. The value of the keyword is the array of if/then clauses. Each clause is the object with the following properties: diff --git a/README.md b/README.md index 9d4fcbb..d0529af 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Ajv: Another JSON Schema Validator -The fastest JSON Schema validator for node.js and browser. Supports [v5 proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals). +The fastest JSON Schema validator for node.js and browser with draft 6 support. [![Build Status](https://travis-ci.org/epoberezkin/ajv.svg?branch=master)](https://travis-ci.org/epoberezkin/ajv) @@ -32,7 +32,7 @@ Also see [docs](https://github.com/epoberezkin/ajv/tree/5.0.4-beta.3) for 5.0.4. - [$data reference](#data-reference) - NEW: [$merge and $patch keywords](#merge-and-patch-keywords) - [Defining custom keywords](#defining-custom-keywords) - - [Asynchronous schema compilation](#asynchronous-compilation) + - [Asynchronous schema compilation](#asynchronous-schema-compilation) - [Asynchronous validation](#asynchronous-validation) - Modifying data during validation - [Filtering data](#filtering-data) @@ -66,7 +66,7 @@ Performace of different validators by [json-schema-benchmark](https://github.com ## Features -- Ajv implements full [JSON Schema draft 4](http://json-schema.org/) standard: +- Ajv implements full JSON Schema [draft 6](http://json-schema.org/) and draft 4 standards: - all validation keywords (see [JSON-Schema validation keywords](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md)) - full support of remote refs (remote schemas have to be added with `addSchema` or compiled to be available) - support of circular references between schemas @@ -74,7 +74,7 @@ Performace of different validators by [json-schema-benchmark](https://github.com - [formats](#formats) defined by JSON Schema draft 4 standard and custom formats (can be turned off) - [validates schemas against meta-schema](#api-validateschema) - supports [browsers](#using-in-browser) and nodejs 0.10-6.x -- [asynchronous loading](#asynchronous-compilation) of referenced schemas during compilation +- [asynchronous loading](#asynchronous-schema-compilation) of referenced schemas during compilation - "All errors" validation mode with [option allErrors](#options) - [error messages with parameters](#validation-errors) describing error reasons to allow creating custom error messages - i18n error messages support with [ajv-i18n](https://github.com/epoberezkin/ajv-i18n) package @@ -82,9 +82,10 @@ Performace of different validators by [json-schema-benchmark](https://github.com - [assigning defaults](#assigning-defaults) to missing properties and items - [coercing data](#coercing-data-types) to the types specified in `type` keywords - [custom keywords](#defining-custom-keywords) -- keywords `switch`, `constant`, `contains`, `patternGroups`, `patternRequired`, `formatMaximum` / `formatMinimum` and `formatExclusiveMaximum` / `formatExclusiveMinimum` from [JSON-schema v5 proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals) with [option v5](#options) -- [v5 meta-schema](https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#) for schemas using v5 keywords -- [v5 $data reference](#data-reference) to use values from the validated data as values for the schema keywords +- draft-6 keywords `const`, `contains` and `propertyNames` +- draft-6 boolean schemas (`true`/`false` as a schema to always pass/fail). +- keywords `switch`, `patternRequired`, `formatMaximum` / `formatMinimum` and `formatExclusiveMaximum` / `formatExclusiveMinimum` from [JSON-schema extension proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals) with [ajv-keywords](https://github.com/epoberezkin/ajv-keywords) package +- [$data reference](#data-reference) to use values from the validated data as values for the schema keywords - [asynchronous validation](#asynchronous-validation) of custom formats and keywords Currently Ajv is the only validator that passes all the tests from [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) (according to [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark), apart from the test that requires that `1.0` is not an integer that is impossible to satisfy in JavaScript). @@ -139,7 +140,7 @@ if (!valid) console.log(ajv.errorsText()); See [API](#api) and [Options](#options) for more details. -Ajv compiles schemas to functions and caches them in all cases (using schema stringified with [json-stable-stringify](https://github.com/substack/json-stable-stringify) as a key), so that the next time the same schema is used (not necessarily the same object instance) it won't be compiled again. +Ajv compiles schemas to functions and caches them in all cases (using schema serialized with [json-stable-stringify](https://github.com/substack/json-stable-stringify) or a custom function as a key), so that the next time the same schema is used (not necessarily the same object instance) it won't be compiled again. The best performance is achieved when using compiled functions returned by `compile` or `getSchema` methods (there is no additional function call). @@ -174,6 +175,7 @@ CLI is available as a separate npm package [ajv-cli](https://github.com/jessedc/ - compiling JSON-schemas to test their validity - BETA: generating standalone module exporting a validation function to be used without Ajv (using [ajv-pack](https://github.com/epoberezkin/ajv-pack)) +- migrate schemas to draft-06 (using [json-schema-migrate](https://github.com/epoberezkin/json-schema-migrate)) - validating data file(s) against JSON-schema - testing expected validity of data against JSON-schema - referenced schemas @@ -190,18 +192,16 @@ Ajv supports all validation keywords from draft 4 of JSON-schema standard: - [type](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#type) - [for numbers](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#keywords-for-numbers) - maximum, minimum, exclusiveMaximum, exclusiveMinimum, multipleOf - [for strings](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#keywords-for-strings) - maxLength, minLength, pattern, format -- [for arrays](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#keywords-for-arrays) - maxItems, minItems, uniqueItems, items, additionalItems -- [for objects](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#keywords-for-objects) - maxProperties, minproperties, required, properties, patternProperties, additionalProperties, dependencies -- [compound keywords](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#keywords-for-all-types) - enum, not, oneOf, anyOf, allOf +- [for arrays](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#keywords-for-arrays) - maxItems, minItems, uniqueItems, items, additionalItems, [contains](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#contains) +- [for objects](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#keywords-for-objects) - maxProperties, minproperties, required, properties, patternProperties, additionalProperties, dependencies, [propertyNames](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#propertynames) +- [for all types](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#keywords-for-all-types) - enum, [const](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#const) +- [compound keywords](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#compound-keywords) - not, oneOf, anyOf, allOf -With option `v5: true` Ajv also supports all validation keywords and [$data reference](#data-reference) from [v5 proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals) for JSON-schema standard: +With [ajv-keywords](https://github.com/epoberezkin/ajv-keywords) package Ajv also supports validation keywords from [JSON-Schema extension proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals) for JSON-schema standard: -- [switch](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#switch-v5-proposal) - conditional validation with a sequence of if/then clauses -- [contains](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#contains-v5-proposal) - check that array contains a valid item -- [constant](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#constant-v5-proposal) - check that data is equal to some value -- [patternGroups](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#patterngroups-v5-proposal) - a more powerful alternative to patternProperties -- [patternRequired](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#patternrequired-v5-proposal) - like `required` but with patterns that some property should match. -- [formatMaximum, formatMinimum, formatExclusiveMaximum, formatExclusiveMinimum](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#formatmaximum--formatminimum-and-exclusiveformatmaximum--exclusiveformatminimum-v5-proposal) - setting limits for date, time, etc. +- [switch](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#switch-proposed) - conditional validation with a sequence of if/then clauses +- [patternRequired](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#patternrequired-proposed) - like `required` but with patterns that some property should match. +- [formatMaximum, formatMinimum, formatExclusiveMaximum, formatExclusiveMinimum](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#formatmaximum--formatminimum-and-exclusiveformatmaximum--exclusiveformatminimum-proposed) - setting limits for date, time, etc. See [JSON-Schema validation keywords](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md) for more details. @@ -214,6 +214,7 @@ The following formats are supported for string validation with "format" keyword: - _time_: time with optional time-zone. - _date-time_: date-time from the same source (time-zone is mandatory). `date`, `time` and `date-time` validate ranges in `full` mode and only regexp in `fast` mode (see [options](#options)). - _uri_: full uri with optional protocol. +- _url_: [URL record](https://url.spec.whatwg.org/#concept-url). - _email_: email address. - _hostname_: host name acording to [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5). - _ipv4_: IP address v4. @@ -227,16 +228,16 @@ There are two modes of format validation: `fast` and `full`. This mode affects f You can add additional formats and replace any of the formats above using [addFormat](#api-addformat) method. -The option `unknownFormats` allows to change the behaviour in case an unknown format is encountered - Ajv can either ignore them (default now) or fail schema compilation (will be the default in 5.0.0). +The option `unknownFormats` allows to change the behaviour in case an unknown format is encountered - Ajv can either fail schema compilation (default) or ignore it (default in versions before 5.0.0). You also can whitelist specific format(s) to be ignored. See [Options](#options) for details. You can find patterns used for format validation and the sources that were used in [formats.js](https://github.com/epoberezkin/ajv/blob/master/lib/compile/formats.js). ## $data reference -With `v5` option you can use values from the validated data as the values for the schema keywords. See [v5 proposal](https://github.com/json-schema/json-schema/wiki/$data-(v5-proposal)) for more information about how it works. +With `$data` option you can use values from the validated data as the values for the schema keywords. See [proposal](https://github.com/json-schema/json-schema/wiki/$data-(v5-proposal)) for more information about how it works. -`$data` reference is supported in the keywords: constant, enum, format, maximum/minimum, exclusiveMaximum / exclusiveMinimum, maxLength / minLength, maxItems / minItems, maxProperties / minProperties, formatMaximum / formatMinimum, formatExclusiveMaximum / formatExclusiveMinimum, multipleOf, pattern, required, uniqueItems. +`$data` reference is supported in the keywords: const, enum, format, maximum/minimum, exclusiveMaximum / exclusiveMinimum, maxLength / minLength, maxItems / minItems, maxProperties / minProperties, formatMaximum / formatMinimum, formatExclusiveMaximum / formatExclusiveMinimum, multipleOf, pattern, required, uniqueItems. The value of "$data" should be a [JSON-pointer](https://tools.ietf.org/html/rfc6901) to the data (the root is always the top level data object, even if the $data reference is inside a referenced subschema) or a [relative JSON-pointer](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00) (it is relative to the current point in data; if the $data reference is inside a referenced subschema it cannot point to the data outside of the root level for this subschema). @@ -277,12 +278,12 @@ var validData = { } ``` -`$data` reference is resolved safely - it won't throw even if some property is undefined. If `$data` resolves to `undefined` the validation succeeds (with the exclusion of `constant` keyword). If `$data` resolves to incorrect type (e.g. not "number" for maximum keyword) the validation fails. +`$data` reference is resolved safely - it won't throw even if some property is undefined. If `$data` resolves to `undefined` the validation succeeds (with the exclusion of `const` keyword). If `$data` resolves to incorrect type (e.g. not "number" for maximum keyword) the validation fails. ## $merge and $patch keywords -With v5 option and the package [ajv-merge-patch](https://github.com/epoberezkin/ajv-merge-patch) you can use the keywords `$merge` and `$patch` that allow extending JSON-schemas with patches using formats [JSON Merge Patch (RFC 7396)](https://tools.ietf.org/html/rfc7396) and [JSON Patch (RFC 6902)](https://tools.ietf.org/html/rfc6902). +With the package [ajv-merge-patch](https://github.com/epoberezkin/ajv-merge-patch) you can use the keywords `$merge` and `$patch` that allow extending JSON-schemas with patches using formats [JSON Merge Patch (RFC 7396)](https://tools.ietf.org/html/rfc7396) and [JSON Patch (RFC 6902)](https://tools.ietf.org/html/rfc6902). To add keywords `$merge` and `$patch` to Ajv instance use this code: @@ -391,27 +392,26 @@ Several custom keywords (typeof, instanceof, range and propertyNames) are define See [Defining custom keywords](https://github.com/epoberezkin/ajv/blob/master/CUSTOM.md) for more details. -## Asynchronous compilation +## Asynchronous schema compilation -During asynchronous compilation remote references are loaded using supplied function. See `compileAsync` method and `loadSchema` [option](#options). +During asynchronous compilation remote references are loaded using supplied function. See `compileAsync` [method](#api-compileAsync) and `loadSchema` [option](#options). Example: ```javascript var ajv = new Ajv({ loadSchema: loadSchema }); -ajv.compileAsync(schema, function (err, validate) { - if (err) return; - var valid = validate(data); +ajv.compileAsync(schema).then(function (validate) { + var valid = validate(data); + // ... }); -function loadSchema(uri, callback) { - request.json(uri, function(err, res, body) { - if (err || res.statusCode >= 400) - callback(err || new Error('Loading error: ' + res.statusCode)); - else - callback(null, body); - }); +function loadSchema(uri) { + return request.json(uri).then(function (res) { + if (res.statusCode >= 400) + throw new Error('Loading error: ' + res.statusCode); + return res.body; + }); } ``` @@ -428,29 +428,39 @@ If your schema uses asynchronous formats/keywords or refers to some schema that __Please note__: all asynchronous subschemas that are referenced from the current or other schemas should have `"$async": true` keyword as well, otherwise the schema compilation will fail. -Validation function for an asynchronous custom format/keyword should return a promise that resolves to `true` or `false` (or rejects with `new Ajv.ValidationError(errors)` if you want to return custom errors from the keyword function). Ajv compiles asynchronous schemas to either [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) (default) that can be optionally transpiled with [regenerator](https://github.com/facebook/regenerator) or to [es7 async function](http://tc39.github.io/ecmascript-asyncawait/) that can be transpiled with [nodent](https://github.com/MatAtBread/nodent) or with regenerator as well. You can also supply any other transpiler as a function. See [Options](#options). +Validation function for an asynchronous custom format/keyword should return a promise that resolves with `true` or `false` (or rejects with `new Ajv.ValidationError(errors)` if you want to return custom errors from the keyword function). Ajv compiles asynchronous schemas to either [es7 async functions](http://tc39.github.io/ecmascript-asyncawait/) that can optionally be transpiled with [nodent](https://github.com/MatAtBread/nodent) or with [regenerator](https://github.com/facebook/regenerator) or to [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) that can be optionally transpiled with regenerator as well. You can also supply any other transpiler as a function. See [Options](#options). The compiled validation function has `$async: true` property (if the schema is asynchronous), so you can differentiate these functions if you are using both syncronous and asynchronous schemas. If you are using generators, the compiled validation function can be either wrapped with [co](https://github.com/tj/co) (default) or returned as generator function, that can be used directly, e.g. in [koa](http://koajs.com/) 1.0. `co` is a small library, it is included in Ajv (both as npm dependency and in the browser bundle). -Generator functions are currently supported in Chrome, Firefox and node.js (0.11+); if you are using Ajv in other browsers or in older versions of node.js you should use one of available transpiling options. All provided async modes use global Promise class. If your platform does not have Promise you should use a polyfill that defines it. +Async functions are currently supported in Chrome 55, Firefox 52, Node 7 (with --harmony-async-await) and MS Edge 13 (with flag). -Validation result will be a promise that resolves to `true` or rejects with an exception `Ajv.ValidationError` that has the array of validation errors in `errors` property. +Generator functions are currently supported in Chrome, Firefox and node.js. + +If you are using Ajv in other browsers or in older versions of node.js you should use one of available transpiling options. All provided async modes use global Promise class. If your platform does not have Promise you should use a polyfill that defines it. + +Validation result will be a promise that resolves with validated data or rejects with an exception `Ajv.ValidationError` that has the array of validation errors in `errors` property. Example: ```javascript /** - * without "async" and "transpile" options (or with option {async: true}) + * Default mode is non-transpiled generator function wrapped with `co`. + * Using package ajv-async (https://github.com/epoberezkin/ajv-async) + * you can auto-detect the best async mode. + * In this case, without "async" and "transpile" options + * (or with option {async: true}) * Ajv will choose the first supported/installed option in this order: - * 1. native generator function wrapped with co - * 2. es7 async functions transpiled with nodent - * 3. es7 async functions transpiled with regenerator + * 1. native async function + * 2. native generator function wrapped with co + * 3. es7 async functions transpiled with nodent + * 4. es7 async functions transpiled with regenerator */ -var ajv = new Ajv; +var setupAsync = require('ajv-async'); +var ajv = setupAsync(new Ajv); ajv.addKeyword('idExists', { async: true, @@ -485,9 +495,8 @@ var schema = { var validate = ajv.compile(schema); validate({ userId: 1, postId: 19 })) -.then(function (valid) { - // "valid" is always true here - console.log('Data is valid'); +.then(function (data) { + console.log('Data is valid', data); // { userId: 1, postId: 19 } }) .catch(function (err) { if (!(err instanceof Ajv.ValidationError)) throw err; @@ -507,7 +516,9 @@ Ajv npm package includes minified browser bundles of regenerator and nodent in d #### Using nodent ```javascript +var setupAsync = require('ajv-async'); var ajv = new Ajv({ /* async: 'es7', */ transpile: 'nodent' }); +setupAsync(ajv); var validate = ajv.compile(schema); // transpiled es7 async function validate(data).then(successFunc).catch(errorFunc); ``` @@ -518,7 +529,9 @@ validate(data).then(successFunc).catch(errorFunc); #### Using regenerator ```javascript +var setupAsync = require('ajv-async'); var ajv = new Ajv({ /* async: 'es7', */ transpile: 'regenerator' }); +setupAsync(ajv); var validate = ajv.compile(schema); // transpiled es7 async function validate(data).then(successFunc).catch(errorFunc); ``` @@ -529,7 +542,7 @@ validate(data).then(successFunc).catch(errorFunc); #### Using other transpilers ```javascript -var ajv = new Ajv({ async: 'es7', transpile: transpileFunc }); +var ajv = new Ajv({ async: 'es7', processCode: transpileFunc }); var validate = ajv.compile(schema); // transpiled es7 async function validate(data).then(successFunc).catch(errorFunc); ``` @@ -541,12 +554,13 @@ See [Options](#options). |mode|transpile
speed*|run-time
speed*|bundle
size| |---|:-:|:-:|:-:| +|es7 async
(native)|-|0.75|-| |generators
(native)|-|1.0|-| -|es7.nodent|1.35|1.1|183Kb| -|es7.regenerator|1.0|2.7|322Kb| -|regenerator|1.0|3.2|322Kb| +|es7.nodent|1.35|1.1|215Kb| +|es7.regenerator|1.0|2.7|1109Kb| +|regenerator|1.0|3.2|1109Kb| -\* Relative performance in node v.4, smaller is better. +\* Relative performance in node v.7, smaller is better. [nodent](https://github.com/MatAtBread/nodent) has several advantages: @@ -555,8 +569,6 @@ See [Options](#options). - much better performace than native generators in other browsers - works in IE 9 (regenerator does not) -[regenerator](https://github.com/facebook/regenerator) is a more widely adopted alternative. - ## Filtering data @@ -731,7 +743,7 @@ console.log(data2); // { foo: { bar: 2 } } - not in `properties` or `items` subschemas - in schemas inside `anyOf`, `oneOf` and `not` (see [#42](https://github.com/epoberezkin/ajv/issues/42)) -- in `if` subschema of v5 `switch` keyword +- in `if` subschema of `switch` keyword - in schemas generated by custom macro keywords @@ -807,17 +819,19 @@ Validating function returns boolean and has properties `errors` with the errors Unless the option `validateSchema` is false, the schema will be validated against meta-schema and if schema is invalid the error will be thrown. See [options](#options). -##### .compileAsync(Object schema, Function callback) +##### .compileAsync(Object schema [, Boolean meta] [, Function callback]) -> Promise -Asyncronous version of `compile` method that loads missing remote schemas using asynchronous function in `options.loadSchema`. Callback will always be called with 2 parameters: error (or null) and validating function. Error will be not null in the following cases: +Asyncronous version of `compile` method that loads missing remote schemas using asynchronous function in `options.loadSchema`. This function returns a Promise that resolves to a validation function. An optional callback passed to `compileAsync` will be called with 2 parameters: error (or null) and validating function. The returned promise will reject (and callback if passed will be called with an error) when: -- missing schema can't be loaded (`loadSchema` calls callback with error). +- missing schema can't be loaded (`loadSchema` returns the Promise that rejects). - the schema containing missing reference is loaded, but the reference cannot be resolved. - schema (or some referenced schema) is invalid. -The function compiles schema and loads the first missing schema multiple times, until all missing schemas are loaded. +The function compiles schema and loads the first missing schema (or meta-schema), until all missing schemas are loaded. -See example in [Asynchronous compilation](#asynchronous-compilation). +You can asynchronously compile meta-schema by passing `true` as the second parameter. + +See example in [Asynchronous compilation](#asynchronous-schema-compilation). ##### .validate(Object schema|String key|String ref, data) -> Boolean @@ -853,9 +867,7 @@ By default the schema is validated against meta-schema before it is added, and i Adds meta schema(s) that can be used to validate other schemas. That function should be used instead of `addSchema` because there may be instance options that would compile a meta schema incorrectly (at the moment it is `removeAdditional` option). -There is no need to explicitly add draft 4 meta schema (http://json-schema.org/draft-04/schema and http://json-schema.org/schema) - it is added by default, unless option `meta` is set to `false`. You only need to use it if you have a changed meta-schema that you want to use to validate your schemas. See `validateSchema`. - -With option `v5: true` [meta-schema that includes v5 keywords](https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json) also added. +There is no need to explicitly add draft 6 meta schema (http://json-schema.org/draft-06/schema and http://json-schema.org/schema) - it is added by default, unless option `meta` is set to `false`. You only need to use it if you have a changed meta-schema that you want to use to validate your schemas. See `validateSchema`. ##### .validateSchema(Object schema) -> Boolean @@ -864,7 +876,7 @@ Validates schema. This method should be used to validate schemas rather than `va By default this method is called automatically when the schema is added, so you rarely need to use it directly. -If schema doesn't have `$schema` property it is validated against draft 4 meta-schema (option `meta` should not be false) or against [v5 meta-schema](https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#) if option `v5` is true. +If schema doesn't have `$schema` property it is validated against draft 6 meta-schema (option `meta` should not be false). If schema has `$schema` property then the schema with this id (that should be previously added) is used to validate passed schema. @@ -891,7 +903,7 @@ If no parameter is passed all schemas but meta-schemas will be removed and the c ##### .addFormat(String name, String|RegExp|Function|Object format) -Add custom format to validate strings. It can also be used to replace pre-defined formats for Ajv instance. +Add custom format to validate strings or numbers. It can also be used to replace pre-defined formats for Ajv instance. Strings are converted to RegExp. @@ -900,8 +912,9 @@ Function should return validation result as `true` or `false`. If object is passed it should have properties `validate`, `compare` and `async`: - _validate_: a string, RegExp or a function as described above. -- _compare_: an optional comparison function that accepts two strings and compares them according to the format meaning. This function is used with keywords `formatMaximum`/`formatMinimum` (from [v5 proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals) - `v5` option should be used). It should return `1` if the first value is bigger than the second value, `-1` if it is smaller and `0` if it is equal. +- _compare_: an optional comparison function that accepts two strings and compares them according to the format meaning. This function is used with keywords `formatMaximum`/`formatMinimum` (defined in [ajv-keywords](https://github.com/epoberezkin/ajv-keywords) package). It should return `1` if the first value is bigger than the second value, `-1` if it is smaller and `0` if it is equal. - _async_: an optional `true` value if `validate` is an asynchronous function; in this case it should return a promise that resolves with a value `true` or `false`. +- _type_: an optional type of data that the format applies to. It can be `"string"` (default) or `"number"` (see https://github.com/epoberezkin/ajv/issues/291#issuecomment-259923858). If the type of data is different, the validation will pass. Custom formats can be also added via `formats` option. @@ -970,7 +983,7 @@ Defaults: ```javascript { // validation and reporting options: - v5: false, + $data: false, allErrors: false, verbose: false, jsonPointers: false, @@ -978,19 +991,20 @@ Defaults: unicode: true, format: 'fast', formats: {}, - unknownFormats: 'ignore', + unknownFormats: true, schemas: {}, // referenced schema options: + schemaId: undefined // recommended '$id' missingRefs: true, - extendRefs: true, - loadSchema: undefined, // function(uri, cb) { /* ... */ cb(err, schema); }, + extendRefs: 'ignore', // recommended 'fail' + loadSchema: undefined, // function(uri: string): Promise {} // options to modify validated data: removeAdditional: false, useDefaults: false, coerceTypes: false, // asynchronous validation options: - async: undefined, - transpile: undefined, + async: 'co*', + transpile: undefined, // requires ajv-async package // advanced options: meta: true, validateSchema: true, @@ -1001,16 +1015,17 @@ Defaults: ownProperties: false, multipleOfPrecision: false, errorDataPath: 'object', - sourceCode: true, messages: true, - beautify: false, - cache: new Cache + sourceCode: false, + processCode: undefined, // function (str: string): string {} + cache: new Cache, + serialize: undefined } ``` ##### Validation and reporting options -- _v5_: add keywords `switch`, `constant`, `contains`, `patternGroups`, `patternRequired`, `formatMaximum` / `formatMinimum` and `formatExclusiveMaximum` / `formatExclusiveMinimum` from [JSON-schema v5 proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals). With this option added schemas without `$schema` property are validated against [v5 meta-schema](https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#). `false` by default. +- _$data_: support [$data references]([$data reference](#data-reference)). Draft 6 meta-schema that is added by default will be extended to allow them. If you want to use another meta-schema you need to use $dataMetaSchema method to add support for $data reference. See [API](#api). - _allErrors_: check all rules collecting all errors. Default is to return after the first error. - _verbose_: include the reference to the part of the schema (`schema` and `parentSchema`) and validated data in errors (false by default). - _jsonPointers_: set `dataPath` propery of errors using [JSON Pointers](https://tools.ietf.org/html/rfc6901) instead of JavaScript property access notation. @@ -1019,23 +1034,27 @@ Defaults: - _format_: formats validation mode ('fast' by default). Pass 'full' for more correct and slow validation or `false` not to validate formats at all. E.g., 25:00:00 and 2015/14/33 will be invalid time and date in 'full' mode but it will be valid in 'fast' mode. - _formats_: an object with custom formats. Keys and values will be passed to `addFormat` method. - _unknownFormats_: handling of unknown formats. Option values: - - `true` (will be default in 5.0.0) - if the unknown format is encountered the exception is thrown during schema compilation. If `format` keyword value is [v5 $data reference](#data-reference) and it is unknown the validation will fail. - - `[String]` - an array of unknown format names that will be ignored. This option can be used to allow usage of third party schemas with format(s) for which you don't have definitions, but still fail if some other unknown format is used. If `format` keyword value is [v5 $data reference](#data-reference) and it is not in this array the validation will fail. - - `"ignore"` (default now) - to log warning during schema compilation and always pass validation. This option is not recommended, as it allows to mistype format name. This behaviour is required by JSON-schema specification. + - `true` (default) - if an unknown format is encountered the exception is thrown during schema compilation. If `format` keyword value is [$data reference](#data-reference) and it is unknown the validation will fail. + - `[String]` - an array of unknown format names that will be ignored. This option can be used to allow usage of third party schemas with format(s) for which you don't have definitions, but still fail if another unknown format is used. If `format` keyword value is [$data reference](#data-reference) and it is not in this array the validation will fail. + - `"ignore"` - to log warning during schema compilation and always pass validation (the default behaviour in versions before 5.0.0). This option is not recommended, as it allows to mistype format name and it won't be validated without any error message. This behaviour is required by JSON-schema specification. - _schemas_: an array or object of schemas that will be added to the instance. If the order is important, pass array. In this case schemas must have IDs in them. Otherwise the object can be passed - `addSchema(value, key)` will be called for each schema in this object. ##### Referenced schema options +- _schemaId_: this option defines which keywords are used as schema URI. Option value: + - `"$id"` (recommended) - only use `$id` keyword as schema URI (as specified in JSON-Schema draft-06), ignore `id` keyword (if it is present a warning will be logged). + - `"id"` - only use `id` keyword as schema URI (as specified in JSON-Schema draft-04), ignore `$id` keyword (if it is present a warning will be logged). + - `undefined` (default) - use both `$id` and `id` keywords as schema URI. If both are present (in the same schema object) and different the exception will be thrown during schema compilation. - _missingRefs_: handling of missing referenced schemas. Option values: - `true` (default) - if the reference cannot be resolved during compilation the exception is thrown. The thrown error has properties `missingRef` (with hash fragment) and `missingSchema` (without it). Both properties are resolved relative to the current base id (usually schema id, unless it was substituted). - `"ignore"` - to log error during compilation and always pass validation. - `"fail"` - to log error and successfully compile schema but fail validation if this rule is checked. - _extendRefs_: validation of other keywords when `$ref` is present in the schema. Option values: - - `true` (default) - validate all keywords in the schemas with `$ref`. - - `"ignore"` - when `$ref` is used other keywords are ignored (as per [JSON Reference](https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03#section-3) standard). A warning will be logged during the schema compilation. - - `"fail"` - if other validation keywords are used together with `$ref` the exception will be throw when the schema is compiled. -- _loadSchema_: asynchronous function that will be used to load remote schemas when the method `compileAsync` is used and some reference is missing (option `missingRefs` should NOT be 'fail' or 'ignore'). This function should accept 2 parameters: remote schema uri and node-style callback. See example in [Asynchronous compilation](#asynchronous-compilation). + - `"ignore"` (default) - when `$ref` is used other keywords are ignored (as per [JSON Reference](https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03#section-3) standard). A warning will be logged during the schema compilation. + - `"fail"` (recommended) - if other validation keywords are used together with `$ref` the exception will be thrown when the schema is compiled. This option is recomended to make sure schema has no keywords that are ignored, which can be confusing. + - `true` - validate all keywords in the schemas with `$ref` (the default behaviour in versions before 5.0.0). +- _loadSchema_: asynchronous function that will be used to load remote schemas when `compileAsync` [method](#api-compileAsync) is used and some reference is missing (option `missingRefs` should NOT be 'fail' or 'ignore'). This function should accept remote schema uri as a parameter and return a Promise that resolves to a schema. See example in [Asynchronous compilation](#asynchronous-schema-compilation). ##### Options to modify validated data @@ -1058,37 +1077,49 @@ Defaults: ##### Asynchronous validation options - _async_: determines how Ajv compiles asynchronous schemas (see [Asynchronous validation](#asynchronous-validation)) to functions. Option values: - - `"*"` / `"co*"` - compile to generator function ("co*" - wrapped with `co.wrap`). If generators are not supported and you don't provide `transpile` option, the exception will be thrown when Ajv instance is created. - - `"es7"` - compile to es7 async function. Unless your platform supports them you need to provide `transpile` option. Currently only MS Edge 13 with flag supports es7 async functions according to [compatibility table](http://kangax.github.io/compat-table/es7/)). - - `true` - if transpile option is not passed Ajv will choose the first supported/installed async/transpile modes in this order: "co*" (native generator with co.wrap), "es7"/"nodent", "co*"/"regenerator" during the creation of the Ajv instance. If none of the options is available the exception will be thrown. - - `undefined`- Ajv will choose the first available async mode in the same way as with `true` option but when the first asynchronous schema is compiled. -- _transpile_: determines whether Ajv transpiles compiled asynchronous validation function. Option values: + - `"*"` / `"co*"` (default) - compile to generator function ("co*" - wrapped with `co.wrap`). If generators are not supported and you don't provide `processCode` option (or `transpile` option if you use [ajv-async](https://github.com/epoberezkin/ajv-async) package), the exception will be thrown when async schema is compiled. + - `"es7"` - compile to es7 async function. Unless your platform supports them you need to provide `processCode`/`transpile` option. According to [compatibility table](http://kangax.github.io/compat-table/es7/)) async functions are supported by: + - Firefox 52, + - Chrome 55, + - Node.js 7 (with `--harmony-async-await`), + - MS Edge 13 (with flag). + - `undefined`/`true` - auto-detect async mode. It requires [ajv-async](https://github.com/epoberezkin/ajv-async) package. If transpile option is not passed ajv-async will choose the first of supported/installed async/transpile modes in this order: + - "es7" (native async functions), + - "co*" (native generators with co.wrap), + - "es7"/"nodent", + - "co*"/"regenerator" during the creation of the Ajv instance. + + If none of the options is available the exception will be thrown. +- _transpile_: Requires [ajv-async](https://github.com/epoberezkin/ajv-async) package. It determines whether Ajv transpiles compiled asynchronous validation function. Option values: - `"nodent"` - transpile with [nodent](https://github.com/MatAtBread/nodent). If nodent is not installed, the exception will be thrown. nodent can only transpile es7 async functions; it will enforce this mode. - `"regenerator"` - transpile with [regenerator](https://github.com/facebook/regenerator). If regenerator is not installed, the exception will be thrown. - - a function - this function should accept the code of validation function as a string and return transpiled code. This option allows you to use any other transpiler you prefer. + - a function - this function should accept the code of validation function as a string and return transpiled code. This option allows you to use any other transpiler you prefer. If you are passing a function, you can simply pass it to `processCode` option without using ajv-async. ##### Advanced options -- _meta_: add [meta-schema](http://json-schema.org/documentation.html) so it can be used by other schemas (true by default). With option `v5: true` [v5 meta-schema](https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#) will be added as well. If an object is passed, it will be used as the default meta-schema for schemas that have no `$schema` keyword. This default meta-schema MUST have `$schema` keyword. +- _meta_: add [meta-schema](http://json-schema.org/documentation.html) so it can be used by other schemas (true by default). If an object is passed, it will be used as the default meta-schema for schemas that have no `$schema` keyword. This default meta-schema MUST have `$schema` keyword. - _validateSchema_: validate added/compiled schemas against meta-schema (true by default). `$schema` property in the schema can either be http://json-schema.org/schema or http://json-schema.org/draft-04/schema or absent (draft-4 meta-schema will be used) or can be a reference to the schema previously added with `addMetaSchema` method. Option values: - `true` (default) - if the validation fails, throw the exception. - `"log"` - if the validation fails, log error. - `false` - skip schema validation. -- _addUsedSchema_: by default methods `compile` and `validate` add schemas to the instance if they have `id` property that doesn't start with "#". If `id` is present and it is not unique the exception will be thrown. Set this option to `false` to skip adding schemas to the instance and the `id` uniqueness check when these methods are used. This option does not affect `addSchema` method. +- _addUsedSchema_: by default methods `compile` and `validate` add schemas to the instance if they have `$id` (or `id`) property that doesn't start with "#". If `$id` is present and it is not unique the exception will be thrown. Set this option to `false` to skip adding schemas to the instance and the `$id` uniqueness check when these methods are used. This option does not affect `addSchema` method. - _inlineRefs_: Affects compilation of referenced schemas. Option values: - `true` (default) - the referenced schemas that don't have refs in them are inlined, regardless of their size - that substantially improves performance at the cost of the bigger size of compiled schema functions. - `false` - to not inline referenced schemas (they will be compiled as separate functions). - integer number - to limit the maximum number of keywords of the schema that will be inlined. - _passContext_: pass validation context to custom keyword functions. If this option is `true` and you pass some context to the compiled validation function with `validate.call(context, data)`, the `context` will be available as `this` in your custom keywords. By default `this` is Ajv instance. - _loopRequired_: by default `required` keyword is compiled into a single expression (or a sequence of statements in `allErrors` mode). In case of a very large number of properties in this keyword it may result in a very big validation function. Pass integer to set the number of properties above which `required` keyword will be validated in a loop - smaller validation function size but also worse performance. -- _ownProperties_: by default ajv iterates over all enumerable object properties; when this option is `true` only own enumerable object properties (i.e. found directly on the object rather than on its prototype) are iterated. Contributed by @mbroadst. +- _ownProperties_: by default Ajv iterates over all enumerable object properties; when this option is `true` only own enumerable object properties (i.e. found directly on the object rather than on its prototype) are iterated. Contributed by @mbroadst. - _multipleOfPrecision_: by default `multipleOf` keyword is validated by comparing the result of division with parseInt() of that result. It works for dividers that are bigger than 1. For small dividers such as 0.01 the result of the division is usually not integer (even when it should be integer, see issue [#84](https://github.com/epoberezkin/ajv/issues/84)). If you need to use fractional dividers set this option to some positive integer N to have `multipleOf` validated using this formula: `Math.abs(Math.round(division) - division) < 1e-N` (it is slower but allows for float arithmetics deviations). - _errorDataPath_: set `dataPath` to point to 'object' (default) or to 'property' when validating keywords `required`, `additionalProperties` and `dependencies`. -- _sourceCode_: add `sourceCode` property to validating function (for debugging; this code can be different from the result of toString call). - _messages_: Include human-readable messages in errors. `true` by default. `false` can be passed when custom messages are used (e.g. with [ajv-i18n](https://github.com/epoberezkin/ajv-i18n)). -- _beautify_: format the generated function with [js-beautify](https://github.com/beautify-web/js-beautify) (the validating function is generated without line-breaks). `npm install js-beautify` to use this option. `true` or js-beautify options can be passed. +- _sourceCode_: add `sourceCode` property to validating function (for debugging; this code can be different from the result of toString call). +- _processCode_: an optional function to process generated code before it is passed to Function constructor. It can be used to either beautify (the validating function is generated without line-breaks) or to transpile code. Starting from version 5.0.0 this option replaced options: + - `beautify` that formatted the generated function using [js-beautify](https://github.com/beautify-web/js-beautify). If you want to beautify the generated code pass `require('js-beautify').js_beautify`. + - `transpile` that transpiled asynchronous validation function. You can still use `transpile` option with [ajv-async](https://github.com/epoberezkin/ajv-async) package. See [Asynchronous validation](#asynchronous-validation) for more information. - _cache_: an optional instance of cache to store compiled schemas using stable-stringified schema as a key. For example, set-associative cache [sacjs](https://github.com/epoberezkin/sacjs) can be used. If not passed then a simple hash is used which is good enough for the common use case (a limited number of statically defined schemas). Cache should have methods `put(key, value)`, `get(key)`, `del(key)` and `clear()`. +- _serialize_: an optional function to serialize schema to cache key. Pass `false` to use schema itself as a key (e.g., if WeakMap used as a cache). By default [json-stable-stringify](https://github.com/substack/json-stable-stringify) is used. ## Validation errors @@ -1109,6 +1140,8 @@ Each error is an object with the following properties: - _parentSchema_: the schema containing the keyword (added with `verbose` option) - _data_: the data validated by the keyword (added with `verbose` option). +__Please note__: `propertyNames` keyword schema validation errors have an additional property `propertyName`, `dataPath` points to the object. After schema validation for each property name, if it is invalid an additional error is added with the property `keyword` equal to `"propertyNames"`. + ### Error parameters @@ -1117,10 +1150,6 @@ Properties of `params` object in errors depend on the keyword that failed valida - `maxItems`, `minItems`, `maxLength`, `minLength`, `maxProperties`, `minProperties` - property `limit` (number, the schema of the keyword). - `additionalItems` - property `limit` (the maximum number of allowed items in case when `items` keyword is an array of schemas and `additionalItems` is false). - `additionalProperties` - property `additionalProperty` (the property not used in `properties` and `patternProperties` keywords). -- `patternGroups` (with v5 option) - properties: - - `pattern` - - `reason` ("minimum"/"maximum"), - - `limit` (max/min allowed number of properties matching number) - `dependencies` - properties: - `property` (dependent property), - `missingProperty` (required missing dependency - only the first one is reported currently) @@ -1134,7 +1163,8 @@ Properties of `params` object in errors depend on the keyword that failed valida - `multipleOf` - property `multipleOf` (the schema of the keyword) - `pattern` - property `pattern` (the schema of the keyword) - `required` - property `missingProperty` (required property that is missing). -- `patternRequired` (with v5 option) - property `missingPattern` (required pattern that did not match any property). +- `propertyNames` - property `propertyName` (an invalid property name). +- `patternRequired` (in ajv-keywords) - property `missingPattern` (required pattern that did not match any property). - `type` - property `type` (required type(s), a string, can be a comma-separated list) - `uniqueItems` - properties `i` and `j` (indices of duplicate items). - `enum` - property `allowedValues` pointing to the array of values (the schema of the keyword). @@ -1146,7 +1176,7 @@ Properties of `params` object in errors depend on the keyword that failed valida - [ajv-cli](https://github.com/epoberezkin/ajv-cli) - command line interface for Ajv - [ajv-i18n](https://github.com/epoberezkin/ajv-i18n) - internationalised error messages -- [ajv-merge-patch](https://github.com/epoberezkin/ajv-merge-patch) - keywords $merge and $patch from v5 proposals. +- [ajv-merge-patch](https://github.com/epoberezkin/ajv-merge-patch) - keywords $merge and $patch. - [ajv-keywords](https://github.com/epoberezkin/ajv-keywords) - several custom keywords that can be used with Ajv (typeof, instanceof, range, propertyNames) diff --git a/lib/$data.js b/lib/$data.js new file mode 100644 index 0000000..60cfc2d --- /dev/null +++ b/lib/$data.js @@ -0,0 +1,49 @@ +'use strict'; + +var KEYWORDS = [ + 'multipleOf', + 'maximum', + 'exclusiveMaximum', + 'minimum', + 'exclusiveMinimum', + 'maxLength', + 'minLength', + 'pattern', + 'additionalItems', + 'maxItems', + 'minItems', + 'uniqueItems', + 'maxProperties', + 'minProperties', + 'required', + 'additionalProperties', + 'enum', + 'format', + 'const' +]; + +module.exports = function (metaSchema, keywordsJsonPointers) { + for (var i=0; i; /** * Create validating function for passed schema. - * @param {Object} schema schema object + * @param {Object|Boolean} schema schema object * @return {Function} validating function */ - compile(schema: Object): ValidateFunction; + compile(schema: Object | boolean): ValidateFunction; /** * Creates validating function for passed schema with asynchronous loading of missing schemas. * `loadSchema` option should be a function that accepts schema uri and node-style callback. * @this Ajv - * @param {Object} schema schema object - * @param {Function} callback node-style callback, it is always called with 2 parameters: error (or null) and validating function. + * @param {Object|Boolean} schema schema object + * @param {Boolean} meta optional true to compile meta-schema; this parameter can be skipped + * @param {Function} callback optional node-style callback, it is always called with 2 parameters: error (or null) and validating function. + * @return {Thenable} validating function */ - compileAsync(schema: Object, callback: (err: Error, validate: ValidateFunction) => any): void; + compileAsync(schema: Object | boolean, meta?: Boolean, callback?: (err: Error, validate: ValidateFunction) => any): Thenable; /** * Adds schema to the instance. * @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored. @@ -42,10 +44,10 @@ declare namespace ajv { addMetaSchema(schema: Object, key?: string): void; /** * Validate schema - * @param {Object} schema schema to validate + * @param {Object|Boolean} schema schema to validate * @return {Boolean} true if schema is valid */ - validateSchema(schema: Object): boolean; + validateSchema(schema: Object | boolean): boolean; /** * Get compiled schema from the instance by `key` or `ref`. * @param {String} keyRef `key` that was passed to `addSchema` or full schema reference (`schema.id` or resolved id). @@ -57,9 +59,9 @@ declare namespace ajv { * If no parameter is passed all schemas but meta-schemas are removed. * If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed. * Even if schema is referenced by other schemas it still can be removed as other schemas have local references. - * @param {String|Object|RegExp} schemaKeyRef key, ref, pattern to match key/ref or schema object + * @param {String|Object|RegExp|Boolean} schemaKeyRef key, ref, pattern to match key/ref or schema object */ - removeSchema(schemaKeyRef?: Object | string | RegExp): void; + removeSchema(schemaKeyRef?: Object | string | RegExp | boolean): void; /** * Add custom format * @param {String} name format name @@ -107,13 +109,13 @@ declare namespace ajv { parentData?: Object | Array, parentDataProperty?: string | number, rootData?: Object | Array - ): boolean | Thenable; + ): boolean | Thenable; errors?: Array; - schema?: Object; + schema?: Object | boolean; } interface Options { - v5?: boolean; + $data?: boolean; allErrors?: boolean; verbose?: boolean; jsonPointers?: boolean; @@ -121,28 +123,29 @@ declare namespace ajv { unicode?: boolean; format?: string; formats?: Object; - unknownFormats?: boolean | string | Array; + unknownFormats?: true | string[] | 'ignore'; schemas?: Array | Object; - ownProperties?: boolean; - missingRefs?: boolean | string; - extendRefs?: boolean | string; - loadSchema?: (uri: string, cb: (err: Error, schema: Object) => any) => any; - removeAdditional?: boolean | string; - useDefaults?: boolean | string; - coerceTypes?: boolean | string; + schemaId?: '$id' | 'id'; + missingRefs?: true | 'ignore' | 'fail'; + extendRefs?: true | 'ignore' | 'fail'; + loadSchema?: (uri: string, cb?: (err: Error, schema: Object) => void) => Thenable; + removeAdditional?: boolean | 'all' | 'failing'; + useDefaults?: boolean | 'shared'; + coerceTypes?: boolean | 'array'; async?: boolean | string; transpile?: string | ((code: string) => string); meta?: boolean | Object; - validateSchema?: boolean | string; + validateSchema?: boolean | 'log'; addUsedSchema?: boolean; inlineRefs?: boolean | number; passContext?: boolean; loopRequired?: number; - multipleOfPrecision?: number; - errorDataPath?: string; + ownProperties?: boolean; + multipleOfPrecision?: boolean | number; + errorDataPath?: string, messages?: boolean; sourceCode?: boolean; - beautify?: boolean | Object; + processCode?: (code: string) => string; cache?: Object; } @@ -157,27 +160,30 @@ declare namespace ajv { interface KeywordDefinition { type?: string | Array; async?: boolean; + $data?: boolean; errors?: boolean | string; + metaSchema?: Object; // schema: false makes validate not to expect schema (ValidateFunction) schema?: boolean; modifying?: boolean; valid?: boolean; // one and only one of the following properties should be present - validate?: ValidateFunction | SchemaValidateFunction; - compile?: (schema: Object, parentSchema: Object) => ValidateFunction; - macro?: (schema: Object, parentSchema: Object) => Object; - inline?: (it: Object, keyword: string, schema: Object, parentSchema: Object) => string; + validate?: SchemaValidateFunction | ValidateFunction; + compile?: (schema: any, parentSchema: Object) => ValidateFunction; + macro?: (schema: any, parentSchema: Object) => Object | boolean; + inline?: (it: Object, keyword: string, schema: any, parentSchema: Object) => string; } interface SchemaValidateFunction { ( - schema: Object, + schema: any, data: any, parentSchema?: Object, dataPath?: string, parentData?: Object | Array, - parentDataProperty?: string | number - ): boolean | Thenable; + parentDataProperty?: string | number, + rootData?: Object | Array + ): boolean | Thenable; errors?: Array; } @@ -191,10 +197,12 @@ declare namespace ajv { dataPath: string; schemaPath: string; params: ErrorParameters; + // Added to validation errors of propertyNames keyword schema + propertyName?: string; // Excluded if messages set to false. message?: string; // These are added with the `verbose` option. - schema?: Object; + schema?: any; parentSchema?: Object; data?: any; } @@ -204,7 +212,7 @@ declare namespace ajv { MultipleOfParams | PatternParams | RequiredParams | TypeParams | UniqueItemsParams | CustomParams | PatternGroupsParams | PatternRequiredParams | - SwitchParams | NoParams | EnumParams; + PropertyNamesParams | SwitchParams | NoParams | EnumParams; interface RefParams { ref: string; @@ -270,6 +278,10 @@ declare namespace ajv { missingPattern: string; } + interface PropertyNamesParams { + propertyName: string; + } + interface SwitchParams { caseIndex: number; } diff --git a/lib/ajv.js b/lib/ajv.js index 0502c1f..1409559 100644 --- a/lib/ajv.js +++ b/lib/ajv.js @@ -7,28 +7,41 @@ var compileSchema = require('./compile') , stableStringify = require('json-stable-stringify') , formats = require('./compile/formats') , rules = require('./compile/rules') - , v5 = require('./v5') + , $dataMetaSchema = require('./$data') + , patternGroups = require('./patternGroups') , util = require('./compile/util') - , async = require('./async') , co = require('co'); module.exports = Ajv; -Ajv.prototype.compileAsync = async.compile; +Ajv.prototype.validate = validate; +Ajv.prototype.compile = compile; +Ajv.prototype.addSchema = addSchema; +Ajv.prototype.addMetaSchema = addMetaSchema; +Ajv.prototype.validateSchema = validateSchema; +Ajv.prototype.getSchema = getSchema; +Ajv.prototype.removeSchema = removeSchema; +Ajv.prototype.addFormat = addFormat; +Ajv.prototype.errorsText = errorsText; +Ajv.prototype._addSchema = _addSchema; +Ajv.prototype._compile = _compile; + +Ajv.prototype.compileAsync = require('./compile/async'); var customKeyword = require('./keyword'); Ajv.prototype.addKeyword = customKeyword.add; Ajv.prototype.getKeyword = customKeyword.get; Ajv.prototype.removeKeyword = customKeyword.remove; -Ajv.ValidationError = require('./compile/validation_error'); -var META_SCHEMA_ID = 'http://json-schema.org/draft-04/schema'; -var SCHEMA_URI_FORMAT = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/\/)?[^\s]*$/i; -function SCHEMA_URI_FORMAT_FUNC(str) { - return SCHEMA_URI_FORMAT.test(str); -} +var errorClasses = require('./compile/error_classes'); +Ajv.ValidationError = errorClasses.Validation; +Ajv.MissingRefError = errorClasses.MissingRef; +Ajv.$dataMetaSchema = $dataMetaSchema; + +var META_SCHEMA_ID = 'http://json-schema.org/draft-06/schema'; var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes' ]; +var META_SUPPORT_DATA = ['/properties']; /** * Creates validator instance. @@ -38,383 +51,427 @@ var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes' ]; */ function Ajv(opts) { if (!(this instanceof Ajv)) return new Ajv(opts); - var self = this; - opts = this._opts = util.copy(opts) || {}; this._schemas = {}; this._refs = {}; this._fragments = {}; this._formats = formats(opts.format); + var schemaUriFormat = this._schemaUriFormat = this._formats['uri-reference']; + this._schemaUriFormatFunc = function (str) { return schemaUriFormat.test(str); }; + this._cache = opts.cache || new Cache; this._loadingSchemas = {}; this._compilations = []; this.RULES = rules(); - - // this is done on purpose, so that methods are bound to the instance - // (without using bind) so that they can be used without the instance - this.validate = validate; - this.compile = compile; - this.addSchema = addSchema; - this.addMetaSchema = addMetaSchema; - this.validateSchema = validateSchema; - this.getSchema = getSchema; - this.removeSchema = removeSchema; - this.addFormat = addFormat; - this.errorsText = errorsText; - - this._addSchema = _addSchema; - this._compile = _compile; + this._getId = chooseGetId(opts); opts.loopRequired = opts.loopRequired || Infinity; - if (opts.async || opts.transpile) async.setup(opts); - if (opts.beautify === true) opts.beautify = { indent_size: 2 }; if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true; - this._metaOpts = getMetaSchemaOptions(); + if (opts.serialize === undefined) opts.serialize = stableStringify; + this._metaOpts = getMetaSchemaOptions(this); - if (opts.formats) addInitialFormats(); - addDraft4MetaSchema(); - if (opts.v5) v5.enable(this); - if (typeof opts.meta == 'object') addMetaSchema(opts.meta); - addInitialSchemas(); + if (opts.formats) addInitialFormats(this); + addDraft6MetaSchema(this); + if (typeof opts.meta == 'object') this.addMetaSchema(opts.meta); + addInitialSchemas(this); + if (opts.patternGroups) patternGroups(this); +} - /** - * Validate data using schema - * Schema will be compiled and cached (using serialized JSON as key. [json-stable-stringify](https://github.com/substack/json-stable-stringify) is used to serialize. - * @param {String|Object} schemaKeyRef key, ref or schema object - * @param {Any} data to be validated - * @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`). - */ - function validate(schemaKeyRef, data) { - var v; - if (typeof schemaKeyRef == 'string') { - v = getSchema(schemaKeyRef); - if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"'); - } else { - var schemaObj = _addSchema(schemaKeyRef); - v = schemaObj.validate || _compile(schemaObj); - } - var valid = v(data); - if (v.$async === true) - return self._opts.async == '*' ? co(valid) : valid; - self.errors = v.errors; - return valid; +/** + * Validate data using schema + * Schema will be compiled and cached (using serialized JSON as key. [json-stable-stringify](https://github.com/substack/json-stable-stringify) is used to serialize. + * @this Ajv + * @param {String|Object} schemaKeyRef key, ref or schema object + * @param {Any} data to be validated + * @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`). + */ +function validate(schemaKeyRef, data) { + var v; + if (typeof schemaKeyRef == 'string') { + v = this.getSchema(schemaKeyRef); + if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"'); + } else { + var schemaObj = this._addSchema(schemaKeyRef); + v = schemaObj.validate || this._compile(schemaObj); } + var valid = v(data); + if (v.$async === true) + return this._opts.async == '*' ? co(valid) : valid; + this.errors = v.errors; + return valid; +} - /** - * Create validating function for passed schema. - * @param {Object} schema schema object - * @param {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords. - * @return {Function} validating function - */ - function compile(schema, _meta) { - var schemaObj = _addSchema(schema, undefined, _meta); - return schemaObj.validate || _compile(schemaObj); + +/** + * Create validating function for passed schema. + * @this Ajv + * @param {Object} schema schema object + * @param {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords. + * @return {Function} validating function + */ +function compile(schema, _meta) { + var schemaObj = this._addSchema(schema, undefined, _meta); + return schemaObj.validate || this._compile(schemaObj); +} + + +/** + * Adds schema to the instance. + * @this Ajv + * @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored. + * @param {String} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`. + * @param {Boolean} _skipValidation true to skip schema validation. Used internally, option validateSchema should be used instead. + * @param {Boolean} _meta true if schema is a meta-schema. Used internally, addMetaSchema should be used instead. + */ +function addSchema(schema, key, _skipValidation, _meta) { + if (Array.isArray(schema)){ + for (var i=0; i} errors optional array of validation errors, if not passed errors from the instance are used. - * @param {Object} options optional options with properties `separator` and `dataVar`. - * @return {String} human readable string with all errors descriptions - */ - function errorsText(errors, options) { - errors = errors || self.errors; - if (!errors) return 'No errors'; - options = options || {}; - var separator = options.separator === undefined ? ', ' : options.separator; - var dataVar = options.dataVar === undefined ? 'data' : options.dataVar; - - var text = ''; - for (var i=0; i} errors optional array of validation errors, if not passed errors from the instance are used. + * @param {Object} options optional options with properties `separator` and `dataVar`. + * @return {String} human readable string with all errors descriptions + */ +function errorsText(errors, options) { + errors = errors || this.errors; + if (!errors) return 'No errors'; + options = options || {}; + var separator = options.separator === undefined ? ', ' : options.separator; + var dataVar = options.dataVar === undefined ? 'data' : options.dataVar; + + var text = ''; + for (var i=0; i%\\^`{|}]|%[0-9a-f]{2})|\{[+#.\/;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\:[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\:[1-9][0-9]{0,3}|\*)?)*\})*$/i; +// For the source: https://gist.github.com/dperini/729294 +// For test cases: https://mathiasbynens.be/demo/url-regex +// @todo Delete current URL in favour of the commented out URL rule when this issue is fixed https://github.com/eslint/eslint/issues/7983. +// var URL = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)(?:\.(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu; +var URL = /^(?:(?:http[s\u017F]?|ftp):\/\/)(?:(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+(?::(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)?@)?(?:(?!10(?:\.[0-9]{1,3}){3})(?!127(?:\.[0-9]{1,3}){3})(?!169\.254(?:\.[0-9]{1,3}){2})(?!192\.168(?:\.[0-9]{1,3}){2})(?!172\.(?:1[6-9]|2[0-9]|3[01])(?:\.[0-9]{1,3}){2})(?:[1-9][0-9]?|1[0-9][0-9]|2[01][0-9]|22[0-3])(?:\.(?:1?[0-9]{1,2}|2[0-4][0-9]|25[0-5])){2}(?:\.(?:[1-9][0-9]?|1[0-9][0-9]|2[0-4][0-9]|25[0-4]))|(?:(?:(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+\-?)*(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)(?:\.(?:(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+\-?)*(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)*(?:\.(?:(?:[KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){2,})))(?::[0-9]{2,5})?(?:\/(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)?$/i; var UUID = /^(?:urn\:uuid\:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; var JSON_POINTER = /^(?:\/(?:[^~\/]|~0|~1)*)*$|^\#(?:\/(?:[a-z0-9_\-\.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i; var RELATIVE_JSON_POINTER = /^(?:0|[1-9][0-9]*)(?:\#|(?:\/(?:[^~\/]|~0|~1)*)*)$/; @@ -16,14 +24,7 @@ module.exports = formats; function formats(mode) { mode = mode == 'full' ? 'full' : 'fast'; - var formatDefs = util.copy(formats[mode]); - for (var fName in formats.compare) { - formatDefs[fName] = { - validate: formatDefs[fName], - compare: formats.compare[fName] - }; - } - return formatDefs; + return util.copy(formats[mode]); } @@ -34,7 +35,10 @@ formats.fast = { time: /^[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:z|[+-]\d\d:\d\d)?$/i, 'date-time': /^\d\d\d\d-[0-1]\d-[0-3]\d[t\s][0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:z|[+-]\d\d:\d\d)$/i, // uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js - uri: /^(?:[a-z][a-z0-9+-.]*)?(?:\:|\/)\/?[^\s]*$/i, + uri: /^(?:[a-z][a-z0-9+-.]*)(?:\:|\/)\/?[^\s]*$/i, + 'uri-reference': /^(?:(?:[a-z][a-z0-9+-.]*:)?\/\/)?[^\s]*$/i, + 'uri-template': URITEMPLATE, + url: URL, // email (sources from jsen validator): // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363 // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'willful violation') @@ -60,6 +64,9 @@ formats.full = { time: time, 'date-time': date_time, uri: uri, + 'uri-reference': URIREF, + 'uri-template': URITEMPLATE, + url: URL, email: /^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, hostname: hostname, ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/, @@ -71,13 +78,6 @@ formats.full = { }; -formats.compare = { - date: compareDate, - time: compareTime, - 'date-time': compareDateTime -}; - - function date(str) { // full-date from http://tools.ietf.org/html/rfc3339#section-5.6 var matches = str.match(DATE); @@ -123,7 +123,9 @@ function uri(str) { } +var Z_ANCHOR = /[^\\]\\Z/; function regex(str) { + if (Z_ANCHOR.test(str)) return false; try { new RegExp(str); return true; @@ -131,34 +133,3 @@ function regex(str) { return false; } } - - -function compareDate(d1, d2) { - if (!(d1 && d2)) return; - if (d1 > d2) return 1; - if (d1 < d2) return -1; - if (d1 === d2) return 0; -} - - -function compareTime(t1, t2) { - if (!(t1 && t2)) return; - t1 = t1.match(TIME); - t2 = t2.match(TIME); - if (!(t1 && t2)) return; - t1 = t1[1] + t1[2] + t1[3] + (t1[4]||''); - t2 = t2[1] + t2[2] + t2[3] + (t2[4]||''); - if (t1 > t2) return 1; - if (t1 < t2) return -1; - if (t1 === t2) return 0; -} - - -function compareDateTime(dt1, dt2) { - if (!(dt1 && dt2)) return; - dt1 = dt1.split(DATE_TIME_SEPARATOR); - dt2 = dt2.split(DATE_TIME_SEPARATOR); - var res = compareDate(dt1[0], dt2[0]); - if (res === undefined) return; - return res || compareTime(dt1[1], dt2[1]); -} diff --git a/lib/compile/index.js b/lib/compile/index.js index c9c6730..1f04fd1 100644 --- a/lib/compile/index.js +++ b/lib/compile/index.js @@ -2,18 +2,8 @@ var resolve = require('./resolve') , util = require('./util') - , stableStringify = require('json-stable-stringify') - , async = require('../async'); - -var beautify; - -function loadBeautify(){ - if (beautify === undefined) { - var name = 'js-beautify'; - try { beautify = require(name).js_beautify; } - catch(e) { beautify = false; } - } -} + , errorClasses = require('./error_classes') + , stableStringify = require('json-stable-stringify'); var validateGenerator = require('../dotjs/validate'); @@ -26,7 +16,7 @@ var ucs2length = util.ucs2length; var equal = require('./equal'); // this error is thrown by async schemas to return validation errors via exception -var ValidationError = require('./validation_error'); +var ValidationError = errorClasses.Validation; module.exports = compile; @@ -51,8 +41,7 @@ function compile(schema, root, localRefs, baseId) { , patternsHash = {} , defaults = [] , defaultsHash = {} - , customRules = [] - , keepSourceCode = opts.sourceCode !== false; + , customRules = []; root = root || { schema: schema, refVal: refVal, refs: refs }; @@ -74,7 +63,7 @@ function compile(schema, root, localRefs, baseId) { cv.refVal = v.refVal; cv.root = v.root; cv.$async = v.$async; - if (keepSourceCode) cv.sourceCode = v.sourceCode; + if (opts.sourceCode) cv.source = v.source; } return v; } finally { @@ -94,7 +83,6 @@ function compile(schema, root, localRefs, baseId) { return compile.call(self, _schema, _root, localRefs, baseId); var $async = _schema.$async === true; - if ($async && !opts.transpile) async.setup(opts); var sourceCode = validateGenerator({ isTop: true, @@ -105,6 +93,7 @@ function compile(schema, root, localRefs, baseId) { schemaPath: '', errSchemaPath: '#', errorPath: '""', + MissingRefError: errorClasses.MissingRef, RULES: RULES, validate: validateGenerator, util: util, @@ -122,20 +111,10 @@ function compile(schema, root, localRefs, baseId) { + vars(defaults, defaultCode) + vars(customRules, customRuleCode) + sourceCode; - if (opts.beautify) { - loadBeautify(); - /* istanbul ignore else */ - if (beautify) sourceCode = beautify(sourceCode, opts.beautify); - else console.error('"npm install js-beautify" to use beautify option'); - } - // console.log('\n\n\n *** \n', sourceCode); - var validate, validateCode - , transpile = opts._transpileFunc; + if (opts.processCode) sourceCode = opts.processCode(sourceCode); + // console.log('\n\n\n *** \n', JSON.stringify(sourceCode)); + var validate; try { - validateCode = $async && transpile - ? transpile(sourceCode) - : sourceCode; - var makeValidate = new Function( 'self', 'RULES', @@ -148,7 +127,7 @@ function compile(schema, root, localRefs, baseId) { 'equal', 'ucs2length', 'ValidationError', - validateCode + sourceCode ); validate = makeValidate( @@ -167,7 +146,7 @@ function compile(schema, root, localRefs, baseId) { refVal[0] = validate; } catch(e) { - console.error('Error compiling schema, function code:', validateCode); + console.error('Error compiling schema, function code:', sourceCode); throw e; } @@ -177,9 +156,9 @@ function compile(schema, root, localRefs, baseId) { validate.refVal = refVal; validate.root = isRoot ? validate : _root; if ($async) validate.$async = true; - if (keepSourceCode) validate.sourceCode = sourceCode; if (opts.sourceCode === true) { validate.source = { + code: sourceCode, patterns: patterns, defaults: defaults }; @@ -208,7 +187,7 @@ function compile(schema, root, localRefs, baseId) { refCode = addLocalRef(ref); var v = resolve.call(self, localCompile, root, ref); - if (!v) { + if (v === undefined) { var localSchema = localRefs && localRefs[ref]; if (localSchema) { v = resolve.inlineRef(localSchema, opts.inlineRefs) @@ -217,7 +196,7 @@ function compile(schema, root, localRefs, baseId) { } } - if (v) { + if (v !== undefined) { replaceLocalRef(ref, v); return resolvedRef(v, refCode); } @@ -236,7 +215,7 @@ function compile(schema, root, localRefs, baseId) { } function resolvedRef(refVal, code) { - return typeof refVal == 'object' + return typeof refVal == 'object' || typeof refVal == 'boolean' ? { code: code, schema: refVal, inline: true } : { code: code, $async: refVal && refVal.$async }; } @@ -294,8 +273,12 @@ function compile(schema, root, localRefs, baseId) { validate = inline.call(self, it, rule.keyword, schema, parentSchema); } else { validate = rule.definition.validate; + if (!validate) return; } + if (validate === undefined) + throw new Error('custom keyword "' + rule.keyword + '"failed to compile'); + var index = customRules.length; customRules[index] = validate; @@ -372,7 +355,7 @@ function defaultCode(i) { function refValCode(i, refVal) { - return refVal[i] ? 'var refVal' + i + ' = refVal[' + i + '];' : ''; + return refVal[i] === undefined ? '' : 'var refVal' + i + ' = refVal[' + i + '];'; } diff --git a/lib/compile/resolve.js b/lib/compile/resolve.js index db2b91f..84be5b5 100644 --- a/lib/compile/resolve.js +++ b/lib/compile/resolve.js @@ -47,7 +47,7 @@ function resolve(compile, root, ref) { if (schema instanceof SchemaObject) { v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId); - } else if (schema) { + } else if (schema !== undefined) { v = inlineRef(schema, this._opts.inlineRefs) ? schema : compile.call(this, schema, root, undefined, baseId); @@ -68,7 +68,7 @@ function resolveSchema(root, ref) { /* jshint validthis: true */ var p = url.parse(ref, false, true) , refPath = _getFullPath(p) - , baseId = getFullPath(root.schema.id); + , baseId = getFullPath(this._getId(root.schema)); if (refPath !== baseId) { var id = normalizeId(refPath); var refVal = this._refs[id]; @@ -89,7 +89,7 @@ function resolveSchema(root, ref) { } } if (!root.schema) return; - baseId = getFullPath(root.schema.id); + baseId = getFullPath(this._getId(root.schema)); } return getJsonPointer.call(this, p, baseId, root.schema, root); } @@ -103,7 +103,8 @@ function resolveRecursive(root, ref, parsedRef) { var schema = res.schema; var baseId = res.baseId; root = res.root; - if (schema.id) baseId = resolveUrl(baseId, schema.id); + var id = this._getId(schema); + if (id) baseId = resolveUrl(baseId, id); return getJsonPointer.call(this, parsedRef, baseId, schema, root); } } @@ -122,20 +123,24 @@ function getJsonPointer(parsedRef, baseId, schema, root) { if (part) { part = util.unescapeFragment(part); schema = schema[part]; - if (!schema) break; - if (schema.id && !PREVENT_SCOPE_CHANGE[part]) baseId = resolveUrl(baseId, schema.id); - if (schema.$ref) { - var $ref = resolveUrl(baseId, schema.$ref); - var res = resolveSchema.call(this, root, $ref); - if (res) { - schema = res.schema; - root = res.root; - baseId = res.baseId; + if (schema === undefined) break; + var id; + if (!PREVENT_SCOPE_CHANGE[part]) { + id = this._getId(schema); + if (id) baseId = resolveUrl(baseId, id); + if (schema.$ref) { + var $ref = resolveUrl(baseId, schema.$ref); + var res = resolveSchema.call(this, root, $ref); + if (res) { + schema = res.schema; + root = res.root; + baseId = res.baseId; + } } } } } - if (schema && schema != root.schema) + if (schema !== undefined && schema !== root.schema) return { schema: schema, root: root, baseId: baseId }; } @@ -227,7 +232,7 @@ function resolveUrl(baseId, id) { function resolveIds(schema) { /* eslint no-shadow: 0 */ /* jshint validthis: true */ - var id = normalizeId(schema.id); + var id = normalizeId(this._getId(schema)); var localRefs = {}; _resolveIds.call(this, schema, getFullPath(id, false), id); return localRefs; @@ -239,11 +244,9 @@ function resolveIds(schema) { for (var i=0; i' , $notOp = $isMax ? '>' : '<'; }} @@ -16,33 +16,69 @@ {{ var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr) , $exclusive = 'exclusive' + $lvl + , $exclType = 'exclType' + $lvl + , $exclIsNumber = 'exclIsNumber' + $lvl , $opExpr = 'op' + $lvl , $opStr = '\' + ' + $opExpr + ' + \''; }} var schemaExcl{{=$lvl}} = {{=$schemaValueExcl}}; {{ $schemaValueExcl = 'schemaExcl' + $lvl; }} - var exclusive{{=$lvl}}; - if (typeof {{=$schemaValueExcl}} != 'boolean' && typeof {{=$schemaValueExcl}} != 'undefined') { + var {{=$exclusive}}; + var {{=$exclType}} = typeof {{=$schemaValueExcl}}; + if ({{=$exclType}} != 'boolean' && {{=$exclType}} != 'undefined' && {{=$exclType}} != 'number') { {{ var $errorKeyword = $exclusiveKeyword; }} {{# def.error:'_exclusiveLimit' }} - } else if({{# def.$dataNotType:'number' }} - ((exclusive{{=$lvl}} = {{=$schemaValueExcl}} === true) - ? {{=$data}} {{=$notOp}}= {{=$schemaValue}} - : {{=$data}} {{=$notOp}} {{=$schemaValue}}) + } else if ({{# def.$dataNotType:'number' }} + {{=$exclType}} == 'number' + ? ( + ({{=$exclusive}} = {{=$schemaValue}} === undefined || {{=$schemaValueExcl}} {{=$op}}= {{=$schemaValue}}) + ? {{=$data}} {{=$notOp}}= {{=$schemaValueExcl}} + : {{=$data}} {{=$notOp}} {{=$schemaValue}} + ) + : ( + ({{=$exclusive}} = {{=$schemaValueExcl}} === true) + ? {{=$data}} {{=$notOp}}= {{=$schemaValue}} + : {{=$data}} {{=$notOp}} {{=$schemaValue}} + ) || {{=$data}} !== {{=$data}}) { - var op{{=$lvl}} = exclusive{{=$lvl}} ? '{{=$op}}' : '{{=$op}}='; + var op{{=$lvl}} = {{=$exclusive}} ? '{{=$op}}' : '{{=$op}}='; {{??}} {{ - var $exclusive = $schemaExcl === true + var $exclIsNumber = typeof $schemaExcl == 'number' , $opStr = $op; /*used in error*/ - if (!$exclusive) $opStr += '='; - var $opExpr = '\'' + $opStr + '\''; /*used in error*/ }} - if ({{# def.$dataNotType:'number' }} - {{=$data}} {{=$notOp}}{{?$exclusive}}={{?}} {{=$schemaValue}} - || {{=$data}} !== {{=$data}}) { + {{? $exclIsNumber && $isData }} + {{ var $opExpr = '\'' + $opStr + '\''; /*used in error*/ }} + if ({{# def.$dataNotType:'number' }} + ( {{=$schemaValue}} === undefined + || {{=$schemaExcl}} {{=$op}}= {{=$schemaValue}} + ? {{=$data}} {{=$notOp}}= {{=$schemaExcl}} + : {{=$data}} {{=$notOp}} {{=$schemaValue}} ) + || {{=$data}} !== {{=$data}}) { + {{??}} + {{ + if ($exclIsNumber && $schema === undefined) { + $schemaValue = $schemaExcl; + $notOp += '='; + } else { + if ($exclIsNumber) + $schemaValue = Math[$isMax ? 'min' : 'max']($schemaExcl, $schema); + + if ($schemaExcl === ($exclIsNumber ? $schemaValue : true)) + $notOp += '='; + else + $opStr += '='; + } + + var $opExpr = '\'' + $opStr + '\''; /*used in error*/ + }} + + if ({{# def.$dataNotType:'number' }} + {{=$data}} {{=$notOp}} {{=$schemaValue}} + || {{=$data}} !== {{=$data}}) { + {{?}} {{?}} {{ var $errorKeyword = $keyword; }} {{# def.error:'_limit' }} diff --git a/lib/dot/anyOf.jst b/lib/dot/anyOf.jst index 93c3cd8..086cf2b 100644 --- a/lib/dot/anyOf.jst +++ b/lib/dot/anyOf.jst @@ -35,7 +35,7 @@ {{= $closingBraces }} if (!{{=$valid}}) { - {{# def.addError:'anyOf' }} + {{# def.extraError:'anyOf' }} } else { {{# def.resetErrors }} {{? it.opts.allErrors }} } {{?}} diff --git a/lib/dot/v5/constant.jst b/lib/dot/const.jst similarity index 77% rename from lib/dot/v5/constant.jst rename to lib/dot/const.jst index 67969c1..2aa2298 100644 --- a/lib/dot/v5/constant.jst +++ b/lib/dot/const.jst @@ -7,4 +7,5 @@ var schema{{=$lvl}} = validate.schema{{=$schemaPath}}; {{?}} var {{=$valid}} = equal({{=$data}}, schema{{=$lvl}}); -{{# def.checkError:'constant' }} +{{# def.checkError:'const' }} +{{? $breakOnError }} else { {{?}} diff --git a/lib/dot/contains.jst b/lib/dot/contains.jst new file mode 100644 index 0000000..12751c5 --- /dev/null +++ b/lib/dot/contains.jst @@ -0,0 +1,55 @@ +{{# def.definitions }} +{{# def.errors }} +{{# def.setupKeyword }} +{{# def.setupNextLevel }} + + +{{ + var $idx = 'i' + $lvl + , $dataNxt = $it.dataLevel = it.dataLevel + 1 + , $nextData = 'data' + $dataNxt + , $currentBaseId = it.baseId + , $nonEmptySchema = {{# def.nonEmptySchema:$schema }}; +}} + +var {{=$errs}} = errors; +var {{=$valid}}; + +{{? $nonEmptySchema }} + {{# def.setCompositeRule }} + + {{ + $it.schema = $schema; + $it.schemaPath = $schemaPath; + $it.errSchemaPath = $errSchemaPath; + }} + + for (var {{=$idx}} = 0; {{=$idx}} < {{=$data}}.length; {{=$idx}}++) { + {{ + $it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true); + var $passData = $data + '[' + $idx + ']'; + $it.dataPathArr[$dataNxt] = $idx; + }} + + {{# def.generateSubschemaCode }} + {{# def.optimizeValidate }} + + if ({{=$nextValid}}) break; + } + + {{# def.resetCompositeRule }} + {{= $closingBraces }} + + if (!{{=$nextValid}}) { +{{??}} + if ({{=$data}}.length == 0) { +{{?}} + + {{# def.error:'contains' }} + } else { + {{? $nonEmptySchema }} + {{# def.resetErrors }} + {{?}} + {{? it.opts.allErrors }} } {{?}} + +{{# def.cleanUp }} diff --git a/lib/dot/custom.jst b/lib/dot/custom.jst index e91c50e..402028e 100644 --- a/lib/dot/custom.jst +++ b/lib/dot/custom.jst @@ -6,7 +6,8 @@ {{ var $rule = this , $definition = 'definition' + $lvl - , $rDef = $rule.definition; + , $rDef = $rule.definition + , $closingBraces = ''; var $validate = $rDef.validate; var $compile, $inline, $macro, $ruleValidate, $validateCode; }} @@ -21,6 +22,7 @@ {{??}} {{ $ruleValidate = it.useCustomRule($rule, $schema, it.schema, it); + if (!$ruleValidate) return; $schemaValue = 'validate.schema' + $schemaPath; $validateCode = $ruleValidate.code; $compile = $rDef.compile; @@ -76,9 +78,16 @@ var {{=$valid}}; #}} -{{? $validateSchema }} - {{=$valid}} = {{=$definition}}.validateSchema({{=$schemaValue}}); - if ({{=$valid}}) { +{{? $isData && $rDef.$data }} + {{ $closingBraces += '}'; }} + if ({{=$schemaValue}} === undefined) { + {{=$valid}} = true; + } else { + {{? $validateSchema }} + {{ $closingBraces += '}'; }} + {{=$valid}} = {{=$definition}}.validateSchema({{=$schemaValue}}); + if ({{=$valid}}) { + {{?}} {{?}} {{? $inline }} @@ -123,12 +132,10 @@ var {{=$valid}}; {{?}} {{? $rDef.modifying }} - {{=$data}} = {{=$parentData}}[{{=$parentDataProperty}}]; + if ({{=$parentData}}) {{=$data}} = {{=$parentData}}[{{=$parentDataProperty}}]; {{?}} -{{? $validateSchema }} - } -{{?}} +{{= $closingBraces }} {{## def.notValidationResult: {{? $rDef.valid === undefined }} diff --git a/lib/dot/definitions.def b/lib/dot/definitions.def index a442346..cdbe140 100644 --- a/lib/dot/definitions.def +++ b/lib/dot/definitions.def @@ -113,12 +113,12 @@ {{## def.cleanUp: {{ out = it.util.cleanUpCode(out); }} #}} -{{## def.cleanUpVarErrors: {{ out = it.util.cleanUpVarErrors(out, $async); }} #}} +{{## def.finalCleanUp: {{ out = it.util.finalCleanUpCode(out, $async); }} #}} {{## def.$data: {{ - var $isData = it.opts.v5 && $schema && $schema.$data + var $isData = it.opts.$data && $schema && $schema.$data , $schemaValue; }} {{? $isData }} @@ -175,8 +175,25 @@ #}} -{{## def.checkOwnProperty: +{{## def.iterateProperties: {{? $ownProperties }} - if (!Object.prototype.hasOwnProperty.call({{=$data}}, {{=$key}})) continue; + {{=$dataProperties}} = {{=$dataProperties}} || Object.keys({{=$data}}); + for (var {{=$idx}}=0; {{=$idx}}<{{=$dataProperties}}.length; {{=$idx}}++) { + var {{=$key}} = {{=$dataProperties}}[{{=$idx}}]; + {{??}} + for (var {{=$key}} in {{=$data}}) { {{?}} #}} + + +{{## def.noPropertyInData: + {{=$useData}} === undefined + {{? $ownProperties }} + || !{{# def.isOwnProperty }} + {{?}} +#}} + + +{{## def.isOwnProperty: + Object.prototype.hasOwnProperty.call({{=$data}}, '{{=it.util.escapeQuotes($propertyKey)}}') +#}} diff --git a/lib/dot/dependencies.jst b/lib/dot/dependencies.jst index 1198a45..c41f334 100644 --- a/lib/dot/dependencies.jst +++ b/lib/dot/dependencies.jst @@ -5,9 +5,18 @@ {{# def.setupNextLevel }} +{{## def.propertyInData: + {{=$data}}{{= it.util.getProperty($property) }} !== undefined + {{? $ownProperties }} + && Object.prototype.hasOwnProperty.call({{=$data}}, '{{=it.util.escapeQuotes($property)}}') + {{?}} +#}} + + {{ var $schemaDeps = {} - , $propertyDeps = {}; + , $propertyDeps = {} + , $ownProperties = it.opts.ownProperties; for ($property in $schema) { var $sch = $schema[$property]; @@ -23,17 +32,19 @@ var {{=$errs}} = errors; var missing{{=$lvl}}; {{ for (var $property in $propertyDeps) { }} {{ $deps = $propertyDeps[$property]; }} - if ({{=$data}}{{= it.util.getProperty($property) }} !== undefined - {{? $breakOnError }} - && ({{# def.checkMissingProperty:$deps }})) { - {{# def.errorMissingProperty:'dependencies' }} - {{??}} - ) { - {{~ $deps:$reqProperty }} - {{# def.allErrorsMissingProperty:'dependencies' }} - {{~}} - {{?}} - } {{# def.elseIfValid }} + {{? $deps.length }} + if ({{# def.propertyInData }} + {{? $breakOnError }} + && ({{# def.checkMissingProperty:$deps }})) { + {{# def.errorMissingProperty:'dependencies' }} + {{??}} + ) { + {{~ $deps:$propertyKey }} + {{# def.allErrorsMissingProperty:'dependencies' }} + {{~}} + {{?}} + } {{# def.elseIfValid }} + {{?}} {{ } }} {{ @@ -47,7 +58,7 @@ var missing{{=$lvl}}; {{? {{# def.nonEmptySchema:$sch }} }} {{=$nextValid}} = true; - if ({{=$data}}{{= it.util.getProperty($property) }} !== undefined) { + if ({{# def.propertyInData }}) { {{ $it.schema = $sch; $it.schemaPath = $schemaPath + it.util.getProperty($property); diff --git a/lib/dot/errors.def b/lib/dot/errors.def index 3e04721..76f0fb0 100644 --- a/lib/dot/errors.def +++ b/lib/dot/errors.def @@ -91,10 +91,13 @@ {{## def.concatSchemaEQ:{{?$isData}}' + {{=$schemaValue}} + '{{??}}{{=it.util.escapeQuotes($schema)}}{{?}}#}} {{## def._errorMessages = { + 'false schema': "'boolean schema is false'", $ref: "'can\\\'t resolve reference {{=it.util.escapeQuotes($schema)}}'", additionalItems: "'should NOT have more than {{=$schema.length}} items'", additionalProperties: "'should NOT have additional properties'", anyOf: "'should match some schema in anyOf'", + const: "'should be equal to constant'", + contains: "'should contain a valid item'", dependencies: "'should have {{? $deps.length == 1 }}property {{= it.util.escapeQuotes($deps[0]) }}{{??}}properties {{= it.util.escapeQuotes($deps.join(\", \")) }}{{?}} when property {{= it.util.escapeQuotes($property) }} is present'", 'enum': "'should be equal to one of the allowed values'", format: "'should match format \"{{#def.concatSchemaEQ}}\"'", @@ -107,14 +110,14 @@ not: "'should NOT be valid'", oneOf: "'should match exactly one schema in oneOf'", pattern: "'should match pattern \"{{#def.concatSchemaEQ}}\"'", + patternGroups: "'should NOT have {{=$moreOrLess}} than {{=$limit}} properties matching pattern \"{{=it.util.escapeQuotes($pgProperty)}}\"'", + propertyNames: "'property name \\'{{=$invalidName}}\\' is invalid'", required: "'{{? it.opts._errorDataPathProperty }}is a required property{{??}}should have required property \\'{{=$missingProperty}}\\'{{?}}'", type: "'should be {{? $typeIsArray }}{{= $typeSchema.join(\",\") }}{{??}}{{=$typeSchema}}{{?}}'", uniqueItems: "'should NOT have duplicate items (items ## ' + j + ' and ' + i + ' are identical)'", custom: "'should pass \"{{=$rule.keyword}}\" keyword validation'", - patternGroups: "'should NOT have {{=$moreOrLess}} than {{=$limit}} properties matching pattern \"{{=it.util.escapeQuotes($pgProperty)}}\"'", patternRequired: "'should have property matching pattern \\'{{=$missingPattern}}\\''", switch: "'should pass \"switch\" keyword validation'", - constant: "'should be equal to constant'", _formatLimit: "'should be {{=$opStr}} \"{{#def.concatSchemaEQ}}\"'", _formatExclusiveLimit: "'{{=$exclusiveKeyword}} should be boolean'" } #}} @@ -124,10 +127,13 @@ {{## def.schemaRefOrQS: {{?$isData}}validate.schema{{=$schemaPath}}{{??}}{{=it.util.toQuotedString($schema)}}{{?}} #}} {{## def._errorSchemas = { + 'false schema': "false", $ref: "{{=it.util.toQuotedString($schema)}}", additionalItems: "false", additionalProperties: "false", anyOf: "validate.schema{{=$schemaPath}}", + const: "validate.schema{{=$schemaPath}}", + contains: "validate.schema{{=$schemaPath}}", dependencies: "validate.schema{{=$schemaPath}}", 'enum': "validate.schema{{=$schemaPath}}", format: "{{#def.schemaRefOrQS}}", @@ -140,14 +146,14 @@ not: "validate.schema{{=$schemaPath}}", oneOf: "validate.schema{{=$schemaPath}}", pattern: "{{#def.schemaRefOrQS}}", + patternGroups: "validate.schema{{=$schemaPath}}", + propertyNames: "validate.schema{{=$schemaPath}}", required: "validate.schema{{=$schemaPath}}", type: "validate.schema{{=$schemaPath}}", uniqueItems: "{{#def.schemaRefOrVal}}", custom: "validate.schema{{=$schemaPath}}", - patternGroups: "validate.schema{{=$schemaPath}}", patternRequired: "validate.schema{{=$schemaPath}}", switch: "validate.schema{{=$schemaPath}}", - constant: "validate.schema{{=$schemaPath}}", _formatLimit: "{{#def.schemaRefOrQS}}", _formatExclusiveLimit: "validate.schema{{=$schemaPath}}" } #}} @@ -156,10 +162,13 @@ {{## def.schemaValueQS: {{?$isData}}{{=$schemaValue}}{{??}}{{=it.util.toQuotedString($schema)}}{{?}} #}} {{## def._errorParams = { + 'false schema': "{}", $ref: "{ ref: '{{=it.util.escapeQuotes($schema)}}' }", additionalItems: "{ limit: {{=$schema.length}} }", additionalProperties: "{ additionalProperty: '{{=$additionalProperty}}' }", anyOf: "{}", + const: "{}", + contains: "{}", dependencies: "{ property: '{{= it.util.escapeQuotes($property) }}', missingProperty: '{{=$missingProperty}}', depsCount: {{=$deps.length}}, deps: '{{= it.util.escapeQuotes($deps.length==1 ? $deps[0] : $deps.join(\", \")) }}' }", 'enum': "{ allowedValues: schema{{=$lvl}} }", format: "{ format: {{#def.schemaValueQS}} }", @@ -172,14 +181,14 @@ not: "{}", oneOf: "{}", pattern: "{ pattern: {{#def.schemaValueQS}} }", + patternGroups: "{ reason: '{{=$reason}}', limit: {{=$limit}}, pattern: '{{=it.util.escapeQuotes($pgProperty)}}' }", + propertyNames: "{ propertyName: '{{=$invalidName}}' }", required: "{ missingProperty: '{{=$missingProperty}}' }", type: "{ type: '{{? $typeIsArray }}{{= $typeSchema.join(\",\") }}{{??}}{{=$typeSchema}}{{?}}' }", uniqueItems: "{ i: i, j: j }", custom: "{ keyword: '{{=$rule.keyword}}' }", - patternGroups: "{ reason: '{{=$reason}}', limit: {{=$limit}}, pattern: '{{=it.util.escapeQuotes($pgProperty)}}' }", patternRequired: "{ missingPattern: '{{=$missingPattern}}' }", switch: "{ caseIndex: {{=$caseIndex}} }", - constant: "{}", _formatLimit: "{ comparison: {{=$opExpr}}, limit: {{#def.schemaValueQS}}, exclusive: {{=$exclusive}} }", _formatExclusiveLimit: "{}" } #}} diff --git a/lib/dot/format.jst b/lib/dot/format.jst index 961fe4f..074d16c 100644 --- a/lib/dot/format.jst +++ b/lib/dot/format.jst @@ -15,13 +15,14 @@ {{## def.$dataCheckFormat: {{# def.$dataNotType:'string' }} - ({{? $unknownFormats === true || $allowUnknown }} + ({{? $unknownFormats != 'ignore' }} ({{=$schemaValue}} && !{{=$format}} {{? $allowUnknown }} && self._opts.unknownFormats.indexOf({{=$schemaValue}}) == -1 {{?}}) || {{?}} - ({{=$format}} && !(typeof {{=$format}} == 'function' + ({{=$format}} && {{=$formatType}} == '{{=$ruleType}}' + && !(typeof {{=$format}} == 'function' ? {{? it.async}} (async{{=$lvl}} ? {{=it.yieldAwait}} {{=$format}}({{=$data}}) : {{=$format}}({{=$data}})) {{??}} @@ -49,12 +50,17 @@ }} {{? $isData }} - {{ var $format = 'format' + $lvl; }} + {{ + var $format = 'format' + $lvl + , $isObject = 'isObject' + $lvl + , $formatType = 'formatType' + $lvl; + }} var {{=$format}} = formats[{{=$schemaValue}}]; - var isObject{{=$lvl}} = typeof {{=$format}} == 'object' - && !({{=$format}} instanceof RegExp) - && {{=$format}}.validate; - if (isObject{{=$lvl}}) { + var {{=$isObject}} = typeof {{=$format}} == 'object' + && !({{=$format}} instanceof RegExp) + && {{=$format}}.validate; + var {{=$formatType}} = {{=$isObject}} && {{=$format}}.type || 'string'; + if ({{=$isObject}}) { {{? it.async}} var async{{=$lvl}} = {{=$format}}.async; {{?}} @@ -64,28 +70,28 @@ {{??}} {{ var $format = it.formats[$schema]; }} {{? !$format }} - {{? $unknownFormats === true || ($allowUnknown && $unknownFormats.indexOf($schema) == -1) }} - {{ throw new Error('unknown format "' + $schema + '" is used in schema at path "' + it.errSchemaPath + '"'); }} - {{??}} - {{ - if (!$allowUnknown) { - console.warn('unknown format "' + $schema + '" ignored in schema at path "' + it.errSchemaPath + '"'); - if ($unknownFormats !== 'ignore') - console.warn('In the next major version it will throw exception. See option unknownFormats for more information'); - } - }} + {{? $unknownFormats == 'ignore' }} + {{ console.warn('unknown format "' + $schema + '" ignored in schema at path "' + it.errSchemaPath + '"'); }} {{# def.skipFormat }} + {{?? $allowUnknown && $unknownFormats.indexOf($schema) >= 0 }} + {{# def.skipFormat }} + {{??}} + {{ throw new Error('unknown format "' + $schema + '" is used in schema at path "' + it.errSchemaPath + '"'); }} {{?}} {{?}} {{ var $isObject = typeof $format == 'object' && !($format instanceof RegExp) && $format.validate; + var $formatType = $isObject && $format.type || 'string'; if ($isObject) { var $async = $format.async === true; $format = $format.validate; } }} + {{? $formatType != $ruleType }} + {{# def.skipFormat }} + {{?}} {{? $async }} {{ if (!it.async) throw new Error('async format in sync schema'); diff --git a/lib/dot/items.jst b/lib/dot/items.jst index a8b8aa7..8c0f5ac 100644 --- a/lib/dot/items.jst +++ b/lib/dot/items.jst @@ -90,7 +90,6 @@ var {{=$valid}}; $it.errSchemaPath = $errSchemaPath; }} {{# def.validateItems: 0 }} - {{# def.ifResultValid }} {{?}} {{? $breakOnError }} diff --git a/lib/dot/missing.def b/lib/dot/missing.def index 23ad04c..a73b9f9 100644 --- a/lib/dot/missing.def +++ b/lib/dot/missing.def @@ -1,8 +1,11 @@ {{## def.checkMissingProperty:_properties: - {{~ _properties:_$property:$i }} + {{~ _properties:$propertyKey:$i }} {{?$i}} || {{?}} - {{ var $prop = it.util.getProperty(_$property); }} - ( {{=$data}}{{=$prop}} === undefined && (missing{{=$lvl}} = {{= it.util.toQuotedString(it.opts.jsonPointers ? _$property : $prop) }}) ) + {{ + var $prop = it.util.getProperty($propertyKey) + , $useData = $data + $prop; + }} + ( ({{# def.noPropertyInData }}) && (missing{{=$lvl}} = {{= it.util.toQuotedString(it.opts.jsonPointers ? $propertyKey : $prop) }}) ) {{~}} #}} @@ -20,15 +23,17 @@ {{# def.error:_error }} #}} + {{## def.allErrorsMissingProperty:_error: {{ - var $prop = it.util.getProperty($reqProperty) - , $missingProperty = it.util.escapeQuotes($reqProperty); + var $prop = it.util.getProperty($propertyKey) + , $missingProperty = it.util.escapeQuotes($propertyKey) + , $useData = $data + $prop; if (it.opts._errorDataPathProperty) { - it.errorPath = it.util.getPath($currentErrorPath, $reqProperty, it.opts.jsonPointers); + it.errorPath = it.util.getPath($currentErrorPath, $propertyKey, it.opts.jsonPointers); } }} - if ({{=$data}}{{=$prop}} === undefined) { + if ({{# def.noPropertyInData }}) { {{# def.addError:_error }} } #}} diff --git a/lib/dot/oneOf.jst b/lib/dot/oneOf.jst index b7f7bff..59a4355 100644 --- a/lib/dot/oneOf.jst +++ b/lib/dot/oneOf.jst @@ -38,7 +38,7 @@ var {{=$valid}} = false; {{= $closingBraces }} if (!{{=$valid}}) { - {{# def.error:'oneOf' }} + {{# def.extraError:'oneOf' }} } else { {{# def.resetErrors }} {{? it.opts.allErrors }} } {{?}} diff --git a/lib/dot/properties.jst b/lib/dot/properties.jst index 3a4b966..8d56324 100644 --- a/lib/dot/properties.jst +++ b/lib/dot/properties.jst @@ -23,8 +23,10 @@ {{ var $key = 'key' + $lvl + , $idx = 'idx' + $lvl , $dataNxt = $it.dataLevel = it.dataLevel + 1 - , $nextData = 'data' + $dataNxt; + , $nextData = 'data' + $dataNxt + , $dataProperties = 'dataProperties' + $lvl; var $schemaKeys = Object.keys($schema || {}) , $pProperties = it.schema.patternProperties || {} @@ -43,7 +45,7 @@ if ($required && !(it.opts.v5 && $required.$data) && $required.length < it.opts.loopRequired) var $requiredHash = it.util.toHash($required); - if (it.opts.v5) { + if (it.opts.patternGroups) { var $pgProperties = it.schema.patternGroups || {} , $pgPropertyKeys = Object.keys($pgProperties); } @@ -52,10 +54,12 @@ var {{=$errs}} = errors; var {{=$nextValid}} = true; +{{? $ownProperties }} + var {{=$dataProperties}} = undefined; +{{?}} {{? $checkAdditional }} - for (var {{=$key}} in {{=$data}}) { - {{# def.checkOwnProperty }} + {{# def.iterateProperties }} {{? $someProperties }} var isAdditional{{=$lvl}} = !(false {{? $schemaKeys.length }} @@ -72,7 +76,7 @@ var {{=$nextValid}} = true; || {{= it.usePattern($pProperty) }}.test({{=$key}}) {{~}} {{?}} - {{? it.opts.v5 && $pgPropertyKeys && $pgPropertyKeys.length }} + {{? it.opts.patternGroups && $pgPropertyKeys.length }} {{~ $pgPropertyKeys:$pgProperty:$i }} || {{= it.usePattern($pgProperty) }}.test({{=$key}}) {{~}} @@ -170,7 +174,7 @@ var {{=$nextValid}} = true; {{= $code }} {{??}} {{? $requiredHash && $requiredHash[$propertyKey] }} - if ({{=$useData}} === undefined) { + if ({{# def.noPropertyInData }}) { {{=$nextValid}} = false; {{ var $currentErrorPath = it.errorPath @@ -187,11 +191,15 @@ var {{=$nextValid}} = true; } else { {{??}} {{? $breakOnError }} - if ({{=$useData}} === undefined) { + if ({{# def.noPropertyInData }}) { {{=$nextValid}} = true; } else { {{??}} - if ({{=$useData}} !== undefined) { + if ({{=$useData}} !== undefined + {{? $ownProperties }} + && {{# def.isOwnProperty }} + {{?}} + ) { {{?}} {{?}} @@ -204,40 +212,41 @@ var {{=$nextValid}} = true; {{~}} {{?}} -{{~ $pPropertyKeys:$pProperty }} - {{ var $sch = $pProperties[$pProperty]; }} +{{? $pPropertyKeys.length }} + {{~ $pPropertyKeys:$pProperty }} + {{ var $sch = $pProperties[$pProperty]; }} - {{? {{# def.nonEmptySchema:$sch}} }} - {{ - $it.schema = $sch; - $it.schemaPath = it.schemaPath + '.patternProperties' + it.util.getProperty($pProperty); - $it.errSchemaPath = it.errSchemaPath + '/patternProperties/' - + it.util.escapeFragment($pProperty); - }} + {{? {{# def.nonEmptySchema:$sch}} }} + {{ + $it.schema = $sch; + $it.schemaPath = it.schemaPath + '.patternProperties' + it.util.getProperty($pProperty); + $it.errSchemaPath = it.errSchemaPath + '/patternProperties/' + + it.util.escapeFragment($pProperty); + }} - for (var {{=$key}} in {{=$data}}) { - {{# def.checkOwnProperty }} - if ({{= it.usePattern($pProperty) }}.test({{=$key}})) { - {{ - $it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); - var $passData = $data + '[' + $key + ']'; - $it.dataPathArr[$dataNxt] = $key; - }} + {{# def.iterateProperties }} + if ({{= it.usePattern($pProperty) }}.test({{=$key}})) { + {{ + $it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); + var $passData = $data + '[' + $key + ']'; + $it.dataPathArr[$dataNxt] = $key; + }} - {{# def.generateSubschemaCode }} - {{# def.optimizeValidate }} + {{# def.generateSubschemaCode }} + {{# def.optimizeValidate }} - {{? $breakOnError }} if (!{{=$nextValid}}) break; {{?}} + {{? $breakOnError }} if (!{{=$nextValid}}) break; {{?}} + } + {{? $breakOnError }} else {{=$nextValid}} = true; {{?}} } - {{? $breakOnError }} else {{=$nextValid}} = true; {{?}} - } - {{# def.ifResultValid }} - {{?}} {{ /* def.nonEmptySchema */ }} -{{~}} + {{# def.ifResultValid }} + {{?}} {{ /* def.nonEmptySchema */ }} + {{~}} +{{?}} -{{? it.opts.v5 }} +{{? it.opts.patternGroups && $pgPropertyKeys.length }} {{~ $pgPropertyKeys:$pgProperty }} {{ var $pgSchema = $pgProperties[$pgProperty] @@ -255,8 +264,7 @@ var {{=$nextValid}} = true; var pgPropCount{{=$lvl}} = 0; - for (var {{=$key}} in {{=$data}}) { - {{# def.checkOwnProperty }} + {{# def.iterateProperties }} if ({{= it.usePattern($pgProperty) }}.test({{=$key}})) { pgPropCount{{=$lvl}}++; diff --git a/lib/dot/propertyNames.jst b/lib/dot/propertyNames.jst new file mode 100644 index 0000000..51caffc --- /dev/null +++ b/lib/dot/propertyNames.jst @@ -0,0 +1,54 @@ +{{# def.definitions }} +{{# def.errors }} +{{# def.setupKeyword }} +{{# def.setupNextLevel }} + +{{? {{# def.nonEmptySchema:$schema }} }} + {{ + $it.schema = $schema; + $it.schemaPath = $schemaPath; + $it.errSchemaPath = $errSchemaPath; + }} + + {{ + var $key = 'key' + $lvl + , $idx = 'idx' + $lvl + , $i = 'i' + $lvl + , $invalidName = '\' + ' + $key + ' + \'' + , $dataNxt = $it.dataLevel = it.dataLevel + 1 + , $nextData = 'data' + $dataNxt + , $dataProperties = 'dataProperties' + $lvl + , $ownProperties = it.opts.ownProperties + , $currentBaseId = it.baseId; + }} + + var {{=$errs}} = errors; + + {{? $ownProperties }} + var {{=$dataProperties}} = undefined; + {{?}} + {{# def.iterateProperties }} + var startErrs{{=$lvl}} = errors; + + {{ var $passData = $key; }} + {{# def.setCompositeRule }} + {{# def.generateSubschemaCode }} + {{# def.optimizeValidate }} + {{# def.resetCompositeRule }} + + if (!{{=$nextValid}}) { + for (var {{=$i}}=startErrs{{=$lvl}}; {{=$i}}= it.opts.loopRequired; + , $loopRequired = $isData || $required.length >= it.opts.loopRequired + , $ownProperties = it.opts.ownProperties; }} {{? $breakOnError }} @@ -53,7 +59,10 @@ {{?$isData}}{{# def.check$dataIsArray }}{{?}} for (var {{=$i}} = 0; {{=$i}} < {{=$vSchema}}.length; {{=$i}}++) { - {{=$valid}} = {{=$data}}[{{=$vSchema}}[{{=$i}}]] !== undefined; + {{=$valid}} = {{=$data}}[{{=$vSchema}}[{{=$i}}]] !== undefined + {{? $ownProperties }} + && {{# def.isRequiredOwnProperty }} + {{?}}; if (!{{=$valid}}) break; } @@ -76,14 +85,17 @@ {{?}} for (var {{=$i}} = 0; {{=$i}} < {{=$vSchema}}.length; {{=$i}}++) { - if ({{=$data}}[{{=$vSchema}}[{{=$i}}]] === undefined) { + if ({{=$data}}[{{=$vSchema}}[{{=$i}}]] === undefined + {{? $ownProperties }} + || !{{# def.isRequiredOwnProperty }} + {{?}}) { {{# def.addError:'required' }} } } {{? $isData }} } {{?}} {{??}} - {{~ $required:$reqProperty }} + {{~ $required:$propertyKey }} {{# def.allErrorsMissingProperty:'required' }} {{~}} {{?}} diff --git a/lib/dot/v5/_formatLimit.jst b/lib/dot/v5/_formatLimit.jst deleted file mode 100644 index af16b88..0000000 --- a/lib/dot/v5/_formatLimit.jst +++ /dev/null @@ -1,116 +0,0 @@ -{{# def.definitions }} -{{# def.errors }} -{{# def.setupKeyword }} - -var {{=$valid}} = undefined; - -{{## def.skipFormatLimit: - {{=$valid}} = true; - {{ return out; }} -#}} - -{{## def.compareFormat: - {{? $isData }} - if ({{=$schemaValue}} === undefined) {{=$valid}} = true; - else if (typeof {{=$schemaValue}} != 'string') {{=$valid}} = false; - else { - {{ $closingBraces += '}'; }} - {{?}} - - {{? $isDataFormat }} - if (!{{=$compare}}) {{=$valid}} = true; - else { - {{ $closingBraces += '}'; }} - {{?}} - - var {{=$result}} = {{=$compare}}({{=$data}}, {{# def.schemaValueQS }}); - - if ({{=$result}} === undefined) {{=$valid}} = false; -#}} - - -{{? it.opts.format === false }}{{# def.skipFormatLimit }}{{?}} - -{{ - var $schemaFormat = it.schema.format - , $isDataFormat = it.opts.v5 && $schemaFormat.$data - , $closingBraces = ''; -}} - -{{? $isDataFormat }} - {{ - var $schemaValueFormat = it.util.getData($schemaFormat.$data, $dataLvl, it.dataPathArr) - , $format = 'format' + $lvl - , $compare = 'compare' + $lvl; - }} - - var {{=$format}} = formats[{{=$schemaValueFormat}}] - , {{=$compare}} = {{=$format}} && {{=$format}}.compare; -{{??}} - {{ var $format = it.formats[$schemaFormat]; }} - {{? !($format && $format.compare) }} - {{# def.skipFormatLimit }} - {{?}} - {{ var $compare = 'formats' + it.util.getProperty($schemaFormat) + '.compare'; }} -{{?}} - -{{ - var $isMax = $keyword == 'formatMaximum' - , $exclusiveKeyword = 'formatExclusive' + ($isMax ? 'Maximum' : 'Minimum') - , $schemaExcl = it.schema[$exclusiveKeyword] - , $isDataExcl = it.opts.v5 && $schemaExcl && $schemaExcl.$data - , $op = $isMax ? '<' : '>' - , $result = 'result' + $lvl; -}} - -{{# def.$data }} - - -{{? $isDataExcl }} - {{ - var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr) - , $exclusive = 'exclusive' + $lvl - , $opExpr = 'op' + $lvl - , $opStr = '\' + ' + $opExpr + ' + \''; - }} - var schemaExcl{{=$lvl}} = {{=$schemaValueExcl}}; - {{ $schemaValueExcl = 'schemaExcl' + $lvl; }} - - if (typeof {{=$schemaValueExcl}} != 'boolean' && {{=$schemaValueExcl}} !== undefined) { - {{=$valid}} = false; - {{ var $errorKeyword = $exclusiveKeyword; }} - {{# def.error:'_formatExclusiveLimit' }} - } - - {{# def.elseIfValid }} - - {{# def.compareFormat }} - var {{=$exclusive}} = {{=$schemaValueExcl}} === true; - - if ({{=$valid}} === undefined) { - {{=$valid}} = {{=$exclusive}} - ? {{=$result}} {{=$op}} 0 - : {{=$result}} {{=$op}}= 0; - } - - if (!{{=$valid}}) var op{{=$lvl}} = {{=$exclusive}} ? '{{=$op}}' : '{{=$op}}='; -{{??}} - {{ - var $exclusive = $schemaExcl === true - , $opStr = $op; /*used in error*/ - if (!$exclusive) $opStr += '='; - var $opExpr = '\'' + $opStr + '\''; /*used in error*/ - }} - - {{# def.compareFormat }} - - if ({{=$valid}} === undefined) - {{=$valid}} = {{=$result}} {{=$op}}{{?!$exclusive}}={{?}} 0; -{{?}} - -{{= $closingBraces }} - -if (!{{=$valid}}) { - {{ var $errorKeyword = $keyword; }} - {{# def.error:'_formatLimit' }} -} diff --git a/lib/dot/v5/patternRequired.jst b/lib/dot/v5/patternRequired.jst deleted file mode 100644 index 9af2cdc..0000000 --- a/lib/dot/v5/patternRequired.jst +++ /dev/null @@ -1,28 +0,0 @@ -{{# def.definitions }} -{{# def.errors }} -{{# def.setupKeyword }} - -{{ - var $key = 'key' + $lvl - , $matched = 'patternMatched' + $lvl - , $closingBraces = '' - , $ownProperties = it.opts.ownProperties; -}} - -var {{=$valid}} = true; -{{~ $schema:$pProperty }} - var {{=$matched}} = false; - for (var {{=$key}} in {{=$data}}) { - {{# def.checkOwnProperty }} - {{=$matched}} = {{= it.usePattern($pProperty) }}.test({{=$key}}); - if ({{=$matched}}) break; - } - - {{ var $missingPattern = it.util.escapeQuotes($pProperty); }} - if (!{{=$matched}}) { - {{=$valid}} = false; - {{# def.addError:'patternRequired' }} - } {{# def.elseIfValid }} -{{~}} - -{{= $closingBraces }} diff --git a/lib/dot/v5/switch.jst b/lib/dot/v5/switch.jst deleted file mode 100644 index 389678e..0000000 --- a/lib/dot/v5/switch.jst +++ /dev/null @@ -1,73 +0,0 @@ -{{# def.definitions }} -{{# def.errors }} -{{# def.setupKeyword }} -{{# def.setupNextLevel }} - - -{{## def.validateIf: - {{# def.setCompositeRule }} - {{ $it.createErrors = false; }} - {{# def._validateSwitchRule:if }} - {{ $it.createErrors = true; }} - {{# def.resetCompositeRule }} - {{=$ifPassed}} = {{=$nextValid}}; -#}} - -{{## def.validateThen: - {{? typeof $sch.then == 'boolean' }} - {{? $sch.then === false }} - {{# def.error:'switch' }} - {{?}} - var {{=$nextValid}} = {{= $sch.then }}; - {{??}} - {{# def._validateSwitchRule:then }} - {{?}} -#}} - -{{## def._validateSwitchRule:_clause: - {{ - $it.schema = $sch._clause; - $it.schemaPath = $schemaPath + '[' + $caseIndex + ']._clause'; - $it.errSchemaPath = $errSchemaPath + '/' + $caseIndex + '/_clause'; - }} - {{# def.insertSubschemaCode }} -#}} - -{{## def.switchCase: - {{? $sch.if && {{# def.nonEmptySchema:$sch.if }} }} - var {{=$errs}} = errors; - {{# def.validateIf }} - if ({{=$ifPassed}}) { - {{# def.validateThen }} - } else { - {{# def.resetErrors }} - } - {{??}} - {{=$ifPassed}} = true; - {{# def.validateThen }} - {{?}} -#}} - - -{{ - var $ifPassed = 'ifPassed' + it.level - , $currentBaseId = $it.baseId - , $shouldContinue; -}} -var {{=$ifPassed}}; - -{{~ $schema:$sch:$caseIndex }} - {{? $caseIndex && !$shouldContinue }} - if (!{{=$ifPassed}}) { - {{ $closingBraces+= '}'; }} - {{?}} - - {{# def.switchCase }} - {{ $shouldContinue = $sch.continue }} -{{~}} - -{{= $closingBraces }} - -var {{=$valid}} = {{=$nextValid}}; - -{{# def.cleanUp }} diff --git a/lib/dot/validate.jst b/lib/dot/validate.jst index f216864..f20dbf7 100644 --- a/lib/dot/validate.jst +++ b/lib/dot/validate.jst @@ -14,48 +14,93 @@ * validateRef etc. are defined in the parent scope in index.js */ }} -{{ var $async = it.schema.$async === true; }} +{{ + var $async = it.schema.$async === true + , $refKeywords = it.util.schemaHasRulesExcept(it.schema, it.RULES.all, '$ref') + , $id = it.self._getId(it.schema); +}} -{{? it.isTop}} +{{? it.isTop }} + {{? $async }} + {{ + it.async = true; + var $es7 = it.opts.async == 'es7'; + it.yieldAwait = $es7 ? 'await' : 'yield'; + }} + {{?}} + + var validate = + {{? $async }} + {{? $es7 }} + (async function + {{??}} + {{? it.opts.async != '*'}}co.wrap{{?}}(function* + {{?}} + {{??}} + (function + {{?}} + (data, dataPath, parentData, parentDataProperty, rootData) { + 'use strict'; + {{? $id && (it.opts.sourceCode || it.opts.processCode) }} + {{= '/\*# sourceURL=' + $id + ' */' }} + {{?}} +{{?}} + +{{? typeof it.schema == 'boolean' || !($refKeywords || it.schema.$ref) }} + {{ var $keyword = 'false schema'; }} + {{# def.setupKeyword }} + {{? it.schema === false}} + {{? it.isTop}} + {{ $breakOnError = true; }} + {{??}} + var {{=$valid}} = false; + {{?}} + {{# def.error:'false schema' }} + {{??}} + {{? it.isTop}} + {{? $async }} + return data; + {{??}} + validate.errors = null; + return true; + {{?}} + {{??}} + var {{=$valid}} = true; + {{?}} + {{?}} + + {{? it.isTop}} + }); + return validate; + {{?}} + + {{ return out; }} +{{?}} + + +{{? it.isTop }} {{ var $top = it.isTop , $lvl = it.level = 0 , $dataLvl = it.dataLevel = 0 , $data = 'data'; - it.rootId = it.resolve.fullPath(it.root.schema.id); + it.rootId = it.resolve.fullPath(it.self._getId(it.root.schema)); it.baseId = it.baseId || it.rootId; - if ($async) { - it.async = true; - var $es7 = it.opts.async == 'es7'; - it.yieldAwait = $es7 ? 'await' : 'yield'; - } delete it.isTop; it.dataPathArr = [undefined]; }} - var validate = - {{? $async }} - {{? $es7 }} - (async function - {{??}} - {{? it.opts.async == 'co*'}}co.wrap{{?}}(function* - {{?}} - {{??}} - (function - {{?}} - (data, dataPath, parentData, parentDataProperty, rootData) { - 'use strict'; - var vErrors = null; {{ /* don't edit, used in replace */ }} - var errors = 0; {{ /* don't edit, used in replace */ }} - if (rootData === undefined) rootData = data; + var vErrors = null; {{ /* don't edit, used in replace */ }} + var errors = 0; {{ /* don't edit, used in replace */ }} + if (rootData === undefined) rootData = data; {{ /* don't edit, used in replace */ }} {{??}} {{ var $lvl = it.level , $dataLvl = it.dataLevel , $data = 'data' + ($dataLvl || ''); - if (it.schema.id) it.baseId = it.resolve.url(it.baseId, it.schema.id); + if ($id) it.baseId = it.resolve.url(it.baseId, $id); if ($async && !it.async) throw new Error('async schema in sync schema'); }} @@ -93,17 +138,14 @@ {{?}} {{?}} -{{ var $refKeywords; }} -{{? it.schema.$ref && ($refKeywords = it.util.schemaHasRulesExcept(it.schema, it.RULES.all, '$ref')) }} +{{? it.schema.$ref && $refKeywords }} {{? it.opts.extendRefs == 'fail' }} - {{ throw new Error('$ref: validation keywords used in schema at path "' + it.errSchemaPath + '"'); }} - {{?? it.opts.extendRefs == 'ignore' }} + {{ throw new Error('$ref: validation keywords used in schema at path "' + it.errSchemaPath + '" (see option extendRefs)'); }} + {{?? it.opts.extendRefs !== true }} {{ $refKeywords = false; - console.log('$ref: keywords ignored in schema at path "' + it.errSchemaPath + '"'); + console.warn('$ref: keywords ignored in schema at path "' + it.errSchemaPath + '"'); }} - {{?? it.opts.extendRefs !== true }} - {{ console.log('$ref: all keywords used in schema at path "' + it.errSchemaPath + '". It will change in the next major version, see issue #260. Use option { extendRefs: true } to keep current behaviour'); }} {{?}} {{?}} @@ -115,6 +157,9 @@ {{ $closingBraces2 += '}'; }} {{?}} {{??}} + {{? it.opts.v5 && it.schema.patternGroups }} + {{ console.warn('keyword "patternGroups" is deprecated and disabled. Use option patternGroups: true to enable.'); }} + {{?}} {{~ it.RULES:$rulesGroup }} {{? $shouldUseGroup($rulesGroup) }} {{? $rulesGroup.type }} @@ -129,9 +174,12 @@ {{?}} {{~ $rulesGroup.rules:$rule }} {{? $shouldUseRule($rule) }} - {{= $rule.code(it, $rule.keyword) }} - {{? $breakOnError }} - {{ $closingBraces1 += '}'; }} + {{ var $code = $rule.code(it, $rule.keyword, $rulesGroup.type); }} + {{? $code }} + {{= $code }} + {{? $breakOnError }} + {{ $closingBraces1 += '}'; }} + {{?}} {{?}} {{?}} {{~}} @@ -171,7 +219,7 @@ {{? $top }} {{? $async }} - if (errors === 0) return true; {{ /* don't edit, used in replace */ }} + if (errors === 0) return data; {{ /* don't edit, used in replace */ }} else throw new ValidationError(vErrors); {{ /* don't edit, used in replace */ }} {{??}} validate.errors = vErrors; {{ /* don't edit, used in replace */ }} @@ -186,25 +234,27 @@ {{# def.cleanUp }} -{{? $top && $breakOnError }} - {{# def.cleanUpVarErrors }} +{{? $top }} + {{# def.finalCleanUp }} {{?}} {{ function $shouldUseGroup($rulesGroup) { - for (var i=0; i < $rulesGroup.rules.length; i++) - if ($shouldUseRule($rulesGroup.rules[i])) + var rules = $rulesGroup.rules; + for (var i=0; i < rules.length; i++) + if ($shouldUseRule(rules[i])) return true; } function $shouldUseRule($rule) { return it.schema[$rule.keyword] !== undefined || - ( $rule.keyword == 'properties' && - ( it.schema.additionalProperties === false || - typeof it.schema.additionalProperties == 'object' - || ( it.schema.patternProperties && - Object.keys(it.schema.patternProperties).length ) - || ( it.opts.v5 && it.schema.patternGroups && - Object.keys(it.schema.patternGroups).length ))); + ($rule.implements && $ruleImlementsSomeKeyword($rule)); + } + + function $ruleImlementsSomeKeyword($rule) { + var impl = $rule.implements; + for (var i=0; i < impl.length; i++) + if (it.schema[impl[i]] !== undefined) + return true; } }} diff --git a/lib/keyword.js b/lib/keyword.js index 1c9cccf..c5fc87a 100644 --- a/lib/keyword.js +++ b/lib/keyword.js @@ -40,7 +40,7 @@ function addKeyword(keyword, definition) { _addRule(keyword, dataType, definition); } - var $data = definition.$data === true && this._opts.v5; + var $data = definition.$data === true && this._opts.$data; if ($data && !definition.validate) throw new Error('$data support: "validate" function is not defined'); @@ -50,7 +50,7 @@ function addKeyword(keyword, definition) { metaSchema = { anyOf: [ metaSchema, - { '$ref': 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#/definitions/$data' } + { '$ref': 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#' } ] }; } @@ -80,7 +80,8 @@ function addKeyword(keyword, definition) { keyword: keyword, definition: definition, custom: true, - code: customRuleCode + code: customRuleCode, + implements: definition.implements }; ruleGroup.rules.push(rule); RULES.custom[keyword] = rule; diff --git a/lib/patternGroups.js b/lib/patternGroups.js new file mode 100644 index 0000000..531a8d0 --- /dev/null +++ b/lib/patternGroups.js @@ -0,0 +1,36 @@ +'use strict'; + +var META_SCHEMA_ID = 'http://json-schema.org/draft-06/schema'; + +module.exports = function (ajv) { + var defaultMeta = ajv._opts.defaultMeta; + var metaSchemaRef = typeof defaultMeta == 'string' + ? { $ref: defaultMeta } + : ajv.getSchema(META_SCHEMA_ID) + ? { $ref: META_SCHEMA_ID } + : {}; + + ajv.addKeyword('patternGroups', { + // implemented in properties.jst + metaSchema: { + type: 'object', + additionalProperties: { + type: 'object', + required: [ 'schema' ], + properties: { + maximum: { + type: 'integer', + minimum: 0 + }, + minimum: { + type: 'integer', + minimum: 0 + }, + schema: metaSchemaRef + }, + additionalProperties: false + } + } + }); + ajv.RULES.all.properties.implements.push('patternGroups'); +}; diff --git a/lib/refs/$data.json b/lib/refs/$data.json new file mode 100644 index 0000000..4a2edec --- /dev/null +++ b/lib/refs/$data.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#", + "description": "Meta-schema for $data reference (JSON-schema extension proposal)", + "type": "object", + "required": [ "$data" ], + "properties": { + "$data": { + "type": "string", + "anyOf": [ + { "format": "relative-json-pointer" }, + { "format": "json-pointer" } + ] + } + }, + "additionalProperties": false +} diff --git a/lib/refs/json-schema-draft-06.json b/lib/refs/json-schema-draft-06.json new file mode 100644 index 0000000..621cc51 --- /dev/null +++ b/lib/refs/json-schema-draft-06.json @@ -0,0 +1,150 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "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" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": {}, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": {} +} diff --git a/lib/refs/json-schema-v5.json b/lib/refs/json-schema-v5.json index 5964042..cc679a4 100644 --- a/lib/refs/json-schema-v5.json +++ b/lib/refs/json-schema-v5.json @@ -1,7 +1,7 @@ { "id": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Core schema meta-schema (v5 proposals)", + "description": "Core schema meta-schema (v5 proposals - deprecated)", "definitions": { "schemaArray": { "type": "array", @@ -234,95 +234,17 @@ { "$ref": "#/definitions/$data" } ] }, - "formatMaximum": { - "anyOf": [ - { "type": "string" }, - { "$ref": "#/definitions/$data" } - ] - }, - "formatMinimum": { - "anyOf": [ - { "type": "string" }, - { "$ref": "#/definitions/$data" } - ] - }, - "formatExclusiveMaximum": { - "anyOf": [ - { - "type": "boolean", - "default": false - }, - { "$ref": "#/definitions/$data" } - ] - }, - "formatExclusiveMinimum": { - "anyOf": [ - { - "type": "boolean", - "default": false - }, - { "$ref": "#/definitions/$data" } - ] - }, "constant": { "anyOf": [ {}, { "$ref": "#/definitions/$data" } ] }, - "contains": { "$ref": "#" }, - "patternGroups": { - "type": "object", - "additionalProperties": { - "type": "object", - "required": [ "schema" ], - "properties": { - "maximum": { - "anyOf": [ - { "$ref": "#/definitions/positiveInteger" }, - { "$ref": "#/definitions/$data" } - ] - }, - "minimum": { - "anyOf": [ - { "$ref": "#/definitions/positiveIntegerDefault0" }, - { "$ref": "#/definitions/$data" } - ] - }, - "schema": { "$ref": "#" } - }, - "additionalProperties": false - }, - "default": {} - }, - "switch": { - "type": "array", - "items": { - "required": [ "then" ], - "properties": { - "if": { "$ref": "#" }, - "then": { - "anyOf": [ - { "type": "boolean" }, - { "$ref": "#" } - ] - }, - "continue": { "type": "boolean" } - }, - "additionalProperties": false, - "dependencies": { - "continue": [ "if" ] - } - } - } + "contains": { "$ref": "#" } }, "dependencies": { "exclusiveMaximum": [ "maximum" ], - "exclusiveMinimum": [ "minimum" ], - "formatMaximum": [ "format" ], - "formatMinimum": [ "format" ], - "formatExclusiveMaximum": [ "formatMaximum" ], - "formatExclusiveMinimum": [ "formatMinimum" ] + "exclusiveMinimum": [ "minimum" ] }, "default": {} } diff --git a/lib/v5.js b/lib/v5.js deleted file mode 100644 index 8f6e53f..0000000 --- a/lib/v5.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -var META_SCHEMA_ID = 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json'; - -module.exports = { - enable: enableV5, - META_SCHEMA_ID: META_SCHEMA_ID -}; - - -function enableV5(ajv) { - var inlineFunctions = { - 'switch': require('./dotjs/switch'), - 'constant': require('./dotjs/constant'), - '_formatLimit': require('./dotjs/_formatLimit'), - 'patternRequired': require('./dotjs/patternRequired') - }; - - if (ajv._opts.meta !== false) { - var metaSchema = require('./refs/json-schema-v5.json'); - ajv.addMetaSchema(metaSchema, META_SCHEMA_ID); - } - _addKeyword('constant'); - ajv.addKeyword('contains', { type: 'array', macro: containsMacro }); - - _addKeyword('formatMaximum', 'string', inlineFunctions._formatLimit); - _addKeyword('formatMinimum', 'string', inlineFunctions._formatLimit); - ajv.addKeyword('formatExclusiveMaximum'); - ajv.addKeyword('formatExclusiveMinimum'); - - ajv.addKeyword('patternGroups'); // implemented in properties.jst - _addKeyword('patternRequired', 'object'); - _addKeyword('switch'); - - - function _addKeyword(keyword, types, inlineFunc) { - var definition = { - inline: inlineFunc || inlineFunctions[keyword], - statements: true, - errors: 'full' - }; - if (types) definition.type = types; - ajv.addKeyword(keyword, definition); - } -} - - -function containsMacro(schema) { - return { - not: { items: { not: schema } } - }; -} diff --git a/package.json b/package.json index 37c9d4c..ea8f32b 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,8 @@ { "name": "ajv", - "version": "4.11.7", + "version": "5.0.4-beta.3", "description": "Another JSON Schema Validator", "main": "lib/ajv.js", - "webpack": "dist/ajv.bundle.js", "typings": "lib/ajv.d.ts", "files": [ "lib/", @@ -13,9 +12,8 @@ ".tonic_example.js" ], "scripts": { - "jshint": "jshint lib/*.js lib/**/*.js --exclude lib/dotjs/**/*", "eslint": "if-node-version \">=4\" eslint lib/*.js lib/compile/*.js spec scripts", - "test-spec": "mocha spec/*.spec.js -R spec", + "test-spec": "mocha spec/*.spec.js -R spec $(if-node-version 7 echo --harmony-async-await)", "test-fast": "AJV_FAST_TEST=true npm run test-spec", "test-debug": "mocha spec/*.spec.js --debug-brk -R spec", "test-cov": "nyc npm run test-spec", @@ -28,7 +26,7 @@ "build": "del-cli lib/dotjs/*.js && node scripts/compile-dots.js", "test-karma": "karma start --single-run --browsers PhantomJS", "test-browser": "del-cli .browser && npm run bundle-all && scripts/prepare-tests && npm run test-karma", - "test": "npm run jshint && npm run eslint && npm run test-ts && npm run build && npm run test-cov && if-node-version 4 npm run test-browser", + "test": "npm run eslint && npm run test-ts && npm run build && npm run test-cov && if-node-version 4 npm run test-browser", "prepublish": "npm run build && npm run bundle-all", "watch": "watch 'npm run build' ./lib/dot" }, @@ -68,6 +66,7 @@ "json-stable-stringify": "^1.0.1" }, "devDependencies": { + "ajv-async": "^0.1.0", "bluebird": "^3.1.5", "brfs": "^1.4.3", "browserify": "^14.1.0", @@ -80,8 +79,7 @@ "glob": "^7.0.0", "if-node-version": "^1.0.0", "js-beautify": "^1.5.6", - "jshint": "^2.8.0", - "json-schema-test": "^1.1.1", + "json-schema-test": "^1.3.0", "karma": "^1.0.0", "karma-chrome-launcher": "^2.0.0", "karma-mocha": "^1.1.1", @@ -95,7 +93,7 @@ "regenerator": "0.9.7", "require-globify": "^1.3.0", "typescript": "^2.0.3", - "uglify-js": "^2.6.1", + "uglify-js": "2.6.1", "watch": "^1.0.0" } } diff --git a/scripts/compile-dots.js b/scripts/compile-dots.js index e6a27dc..19db00a 100644 --- a/scripts/compile-dots.js +++ b/scripts/compile-dots.js @@ -42,7 +42,7 @@ files.forEach(function (f) { var code = doT.compile(template, defs); code = code.toString() .replace(OUT_EMPTY_STRING, '') - .replace(FUNCTION_NAME, 'function generate_' + keyword + '(it, $keyword) {') + .replace(FUNCTION_NAME, 'function generate_' + keyword + '(it, $keyword, $ruleType) {') .replace(ISTANBUL, '/* $1 */'); removeAlwaysFalsyInOr(); VARS.forEach(removeUnusedVar); diff --git a/spec/.eslintrc.json b/spec/.eslintrc.json deleted file mode 100644 index 1058a68..0000000 --- a/spec/.eslintrc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rules": { - "no-console": 0, - "no-empty": [ 2, { "allowEmptyCatch": true } ], - "quotes": 0, - "no-invalid-this": 0 - }, - "globals": { - "describe": false, - "it": false, - "before": false, - "beforeEach": false - } -} diff --git a/spec/.eslintrc.yml b/spec/.eslintrc.yml new file mode 100644 index 0000000..d2d4eda --- /dev/null +++ b/spec/.eslintrc.yml @@ -0,0 +1,10 @@ +rules: + no-console: 0 + no-empty: [2, allowEmptyCatch: true] + quotes: 0 + no-invalid-this: 0 +globals: + describe: false + it: false + before: false + beforeEach: false diff --git a/spec/JSON-Schema-Test-Suite b/spec/JSON-Schema-Test-Suite index f3d5aeb..8758156 160000 --- a/spec/JSON-Schema-Test-Suite +++ b/spec/JSON-Schema-Test-Suite @@ -1 +1 @@ -Subproject commit f3d5aeb5ffbe9d9a5a0ceb761dc47c7c4c2efa68 +Subproject commit 8758156cb3bae615e5e75abcab6e757883d10669 diff --git a/spec/ajv.spec.js b/spec/ajv.spec.js index 0cca2c9..d0fb3de 100644 --- a/spec/ajv.spec.js +++ b/spec/ajv.spec.js @@ -208,6 +208,15 @@ describe('Ajv', function () { it('should throw if schema is not an object', function() { should.throw(function() { ajv.addSchema('foo'); }); }); + + it('should throw if schema id is not a string', function() { + try { + ajv.addSchema({ id: 1, type: 'integer' }); + throw new Error('should have throw exception'); + } catch(e) { + e.message .should.equal('schema id must be string'); + } + }); }); @@ -392,11 +401,100 @@ describe('Ajv', function () { testFormat(); }); + it('should add format as object', function() { + ajv.addFormat('identifier', { + validate: function (str) { return /^[a-z_$][a-z0-9_$]*$/i.test(str); }, + }); + testFormat(); + }); + function testFormat() { var validate = ajv.compile({ format: 'identifier' }); validate('Abc1') .should.equal(true); validate('123') .should.equal(false); validate(123) .should.equal(true); } + + describe('formats for number', function() { + it('should validate only numbers', function() { + ajv.addFormat('positive', { + type: 'number', + validate: function(x) { + return x > 0; + } + }); + + var validate = ajv.compile({ + format: 'positive' + }); + validate(-2) .should.equal(false); + validate(0) .should.equal(false); + validate(2) .should.equal(true); + validate('abc') .should.equal(true); + }); + + it('should validate numbers with format via $data', function() { + ajv = new Ajv({$data: true}); + ajv.addFormat('positive', { + type: 'number', + validate: function(x) { + return x > 0; + } + }); + + var validate = ajv.compile({ + properties: { + data: { format: { $data: '1/frmt' } }, + frmt: { type: 'string' } + } + }); + validate({data: -2, frmt: 'positive'}) .should.equal(false); + validate({data: 0, frmt: 'positive'}) .should.equal(false); + validate({data: 2, frmt: 'positive'}) .should.equal(true); + validate({data: 'abc', frmt: 'positive'}) .should.equal(true); + }); + }); + }); + + + describe('validateSchema method', function() { + it('should validate schema against meta-schema', function() { + var valid = ajv.validateSchema({ + $schema: 'http://json-schema.org/draft-06/schema#', + type: 'number' + }); + + valid .should.equal(true); + should.equal(ajv.errors, null); + + valid = ajv.validateSchema({ + $schema: 'http://json-schema.org/draft-06/schema#', + type: 'wrong_type' + }); + + valid .should.equal(false); + ajv.errors.length .should.equal(3); + ajv.errors[0].keyword .should.equal('enum'); + ajv.errors[1].keyword .should.equal('type'); + ajv.errors[2].keyword .should.equal('anyOf'); + }); + + it('should throw exception if meta-schema is unknown', function() { + should.throw(function() { + ajv.validateSchema({ + $schema: 'http://example.com/unknown/schema#', + type: 'number' + }); + }); + }); + + it('should throw exception if $schema is not a string', function() { + should.throw(function() { + ajv.validateSchema({ + $schema: {}, + type: 'number' + }); + }); + }); }); }); diff --git a/spec/ajv_async_instances.js b/spec/ajv_async_instances.js index 5658d9e..85854f3 100644 --- a/spec/ajv_async_instances.js +++ b/spec/ajv_async_instances.js @@ -1,7 +1,8 @@ 'use strict'; var Ajv = require('./ajv') - , util = require('../lib/compile/util'); + , util = require('../lib/compile/util') + , setupAsync = require('ajv-async'); module.exports = getAjvInstances; @@ -98,5 +99,5 @@ function getAjvInstances(opts) { function getAjv(opts){ - try { return new Ajv(opts); } catch(e) {} + try { return setupAsync(new Ajv(opts)); } catch(e) {} } diff --git a/spec/ajv_options.js b/spec/ajv_options.js index c798656..e09e73b 100644 --- a/spec/ajv_options.js +++ b/spec/ajv_options.js @@ -14,6 +14,7 @@ var options = fullTest } : { allErrors: true }; -if (fullTest && !isBrowser) options.beautify = true; +if (fullTest && !isBrowser) + options.processCode = require('js-beautify').js_beautify; module.exports = options; diff --git a/spec/async.spec.js b/spec/async.spec.js index fcf7ee6..5363e32 100644 --- a/spec/async.spec.js +++ b/spec/async.spec.js @@ -1,4 +1,5 @@ 'use strict'; +/* global Promise */ var Ajv = require('./ajv') , should = require('./chai').should(); @@ -45,6 +46,20 @@ describe('compileAsync method', function() { "invalid": { "type": "number" } }, "required": "invalid" + }, + "http://example.com/foobar.json": { + "id": "http://example.com/foobar.json", + "$schema": "http://example.com/foobar_meta.json", + "myFooBar": "foo" + }, + "http://example.com/foobar_meta.json": { + "id": "http://example.com/foobar_meta.json", + "type": "object", + "properties": { + "myFooBar": { + "enum": ["foo", "bar"] + } + } } }; @@ -54,7 +69,23 @@ describe('compileAsync method', function() { }); - it('should compile schemas loading missing schemas with options.loadSchema function', function (done) { + it('should compile schemas loading missing schemas with options.loadSchema function', function() { + var schema = { + "id": "http://example.com/parent.json", + "properties": { + "a": { "$ref": "object.json" } + } + }; + return ajv.compileAsync(schema).then(function (validate) { + should.equal(loadCallCount, 2); + validate .should.be.a('function'); + validate({ a: { b: 2 } }) .should.equal(true); + validate({ a: { b: 1 } }) .should.equal(false); + }); + }); + + + it('should compile schemas loading missing schemas and return function via callback', function (done) { var schema = { "id": "http://example.com/parent.json", "properties": { @@ -72,33 +103,30 @@ describe('compileAsync method', function() { }); - it('should correctly load schemas when missing reference has JSON path', function (done) { + it('should correctly load schemas when missing reference has JSON path', function() { var schema = { "id": "http://example.com/parent.json", "properties": { "a": { "$ref": "object.json#/properties/b" } } }; - ajv.compileAsync(schema, function (err, validate) { + return ajv.compileAsync(schema).then(function (validate) { should.equal(loadCallCount, 2); - should.not.exist(err); validate .should.be.a('function'); validate({ a: 2 }) .should.equal(true); validate({ a: 1 }) .should.equal(false); - done(); }); }); - it('should correctly compile with remote schemas that have mutual references', function (done) { + it('should correctly compile with remote schemas that have mutual references', function() { var schema = { "id": "http://example.com/root.json", "properties": { "tree": { "$ref": "tree.json" } } }; - ajv.compileAsync(schema, function (err, validate) { - should.not.exist(err); + return ajv.compileAsync(schema).then(function (validate) { validate .should.be.a('function'); var validData = { tree: [ { name: 'a', subtree: [ { name: 'a.a' } ] }, @@ -109,32 +137,29 @@ describe('compileAsync method', function() { ] }; validate(validData) .should.equal(true); validate(invalidData) .should.equal(false); - done(); }); }); - it('should correctly compile with remote schemas that reference the compiled schema', function (done) { + it('should correctly compile with remote schemas that reference the compiled schema', function() { var schema = { "id": "http://example.com/parent.json", "properties": { "a": { "$ref": "recursive.json" } } }; - ajv.compileAsync(schema, function (err, validate) { + return ajv.compileAsync(schema).then(function (validate) { should.equal(loadCallCount, 1); - should.not.exist(err); validate .should.be.a('function'); var validData = { a: { b: { a: { b: {} } } } }; var invalidData = { a: { b: { a: {} } } }; validate(validData) .should.equal(true); validate(invalidData) .should.equal(false); - done(); }); }); - it('should resolve reference containing "properties" segment with the same property (issue #220)', function (done) { + it('should resolve reference containing "properties" segment with the same property (issue #220)', function() { var schema = { "id": "http://example.com/parent.json", "properties": { @@ -143,39 +168,64 @@ describe('compileAsync method', function() { } } }; - ajv.compileAsync(schema, function (err, validate) { - should.not.exist(err); + return ajv.compileAsync(schema).then(function (validate) { should.equal(loadCallCount, 2); validate .should.be.a('function'); validate({ a: 'foo' }) .should.equal(true); validate({ a: 42 }) .should.equal(false); - done(); }); }); - it('should return compiled schema on the next tick if there are no references (#51)', function (done) { + describe('loading metaschemas (#334)', function() { + it('should load metaschema if not available', function() { + return test(SCHEMAS['http://example.com/foobar.json'], 1); + }); + + it('should load metaschema of referenced schema if not available', function() { + return test({ "$ref": "http://example.com/foobar.json" }, 2); + }); + + function test(schema, expectedLoadCallCount) { + ajv.addKeyword('myFooBar', { + type: 'string', + validate: function (sch, data) { + return sch == data; + } + }); + + return ajv.compileAsync(schema).then(function (validate) { + should.equal(loadCallCount, expectedLoadCallCount); + validate .should.be.a('function'); + validate('foo') .should.equal(true); + validate('bar') .should.equal(false); + }); + } + }); + + + it('should return compiled schema on the next tick if there are no references (#51)', function() { var schema = { "id": "http://example.com/int2plus.json", "type": "integer", "minimum": 2 }; var beforeCallback1; - ajv.compileAsync(schema, function (err, validate) { + var p1 = ajv.compileAsync(schema).then(function (validate) { beforeCallback1 .should.equal(true); - spec(err, validate); + spec(validate); var beforeCallback2; - ajv.compileAsync(schema, function (_err, _validate) { + var p2 = ajv.compileAsync(schema).then(function (_validate) { beforeCallback2 .should.equal(true); - spec(_err, _validate); - done(); + spec(_validate); }); beforeCallback2 = true; + return p2; }); beforeCallback1 = true; + return p1; - function spec(err, validate) { - should.not.exist(err); + function spec(validate) { should.equal(loadCallCount, 0); validate .should.be.a('function'); var validData = 2; @@ -186,7 +236,7 @@ describe('compileAsync method', function() { }); - it('should queue calls so only one compileAsync executes at a time (#52)', function (done) { + it('should queue calls so only one compileAsync executes at a time (#52)', function() { var schema = { "id": "http://example.com/parent.json", "properties": { @@ -194,25 +244,17 @@ describe('compileAsync method', function() { } }; - var completedCount = 0; - ajv.compileAsync(schema, spec); - ajv.compileAsync(schema, spec); - ajv.compileAsync(schema, spec); + return Promise.all([ + ajv.compileAsync(schema).then(spec), + ajv.compileAsync(schema).then(spec), + ajv.compileAsync(schema).then(spec) + ]); - function spec(err, validate) { - should.not.exist(err); + function spec(validate) { + should.equal(loadCallCount, 2); validate .should.be.a('function'); validate({ a: { b: 2 } }) .should.equal(true); validate({ a: { b: 1 } }) .should.equal(false); - completed(); - } - - function completed() { - completedCount++; - if (completedCount == 3) { - should.equal(loadCallCount, 2); - done(); - } } }); @@ -243,11 +285,7 @@ describe('compileAsync method', function() { "type": "integer", "minimum": "invalid" }; - ajv.compileAsync(invalidSchema, function (err, validate) { - should.exist(err); - should.not.exist(validate); - done(); - }); + ajv.compileAsync(invalidSchema, shouldFail(done)); }); it('if loaded schema is invalid', function (done) { @@ -257,11 +295,7 @@ describe('compileAsync method', function() { "a": { "$ref": "invalid.json" } } }; - ajv.compileAsync(schema, function (err, validate) { - should.exist(err); - should.not.exist(validate); - done(); - }); + ajv.compileAsync(schema, shouldFail(done)); }); it('if required schema is loaded but the reference cannot be resolved', function (done) { @@ -271,11 +305,7 @@ describe('compileAsync method', function() { "a": { "$ref": "object.json#/definitions/not_found" } } }; - ajv.compileAsync(schema, function (err, validate) { - should.exist(err); - should.not.exist(validate); - done(); - }); + ajv.compileAsync(schema, shouldFail(done)); }); it('if loadSchema returned error', function (done) { @@ -286,38 +316,109 @@ describe('compileAsync method', function() { } }; ajv = new Ajv({ loadSchema: badLoadSchema }); - ajv.compileAsync(schema, function (err, validate) { - should.exist(err); - should.not.exist(validate); - done(); - }); + ajv.compileAsync(schema, shouldFail(done)); - function badLoadSchema(ref, callback) { - setTimeout(function() { callback(new Error('cant load')); }); + function badLoadSchema() { + return Promise.reject(new Error('cant load')); } }); it('if schema compilation throws some other exception', function (done) { ajv.addKeyword('badkeyword', { compile: badCompile }); var schema = { badkeyword: true }; - ajv.compileAsync(schema, function (err, validate) { - should.exist(err); - should.not.exist(validate); - done(); - }); + ajv.compileAsync(schema, shouldFail(done)); function badCompile(/* schema */) { throw new Error('cant compile keyword schema'); } }); + + function shouldFail(done) { + return function (err, validate) { + should.exist(err); + should.not.exist(validate); + done(); + }; + } }); - function loadSchema(uri, callback) { + describe('should return error via promise', function() { + it('if passed schema is invalid', function() { + var invalidSchema = { + "id": "http://example.com/int2plus.json", + "type": "integer", + "minimum": "invalid" + }; + return shouldReject(ajv.compileAsync(invalidSchema)); + }); + + it('if loaded schema is invalid', function() { + var schema = { + "id": "http://example.com/parent.json", + "properties": { + "a": { "$ref": "invalid.json" } + } + }; + return shouldReject(ajv.compileAsync(schema)); + }); + + it('if required schema is loaded but the reference cannot be resolved', function() { + var schema = { + "id": "http://example.com/parent.json", + "properties": { + "a": { "$ref": "object.json#/definitions/not_found" } + } + }; + return shouldReject(ajv.compileAsync(schema)); + }); + + it('if loadSchema returned error', function() { + var schema = { + "id": "http://example.com/parent.json", + "properties": { + "a": { "$ref": "object.json" } + } + }; + ajv = new Ajv({ loadSchema: badLoadSchema }); + return shouldReject(ajv.compileAsync(schema)); + + function badLoadSchema() { + return Promise.reject(new Error('cant load')); + } + }); + + it('if schema compilation throws some other exception', function() { + ajv.addKeyword('badkeyword', { compile: badCompile }); + var schema = { badkeyword: true }; + return shouldReject(ajv.compileAsync(schema)); + + function badCompile(/* schema */) { + throw new Error('cant compile keyword schema'); + } + }); + + function shouldReject(p) { + return p.then( + function(validate) { + should.not.exist(validate); + throw new Error('Promise has resolved; it should have rejected'); + }, + function(err) { + should.exist(err); + } + ); + } + }); + + + function loadSchema(uri) { loadCallCount++; - setTimeout(function() { - if (SCHEMAS[uri]) return callback(null, SCHEMAS[uri]); - callback(new Error('404')); - }, 10); + return new Promise(function (resolve, reject) { + setTimeout(function() { + if (SCHEMAS[uri]) resolve(SCHEMAS[uri]); + else reject(new Error('404')); + }, 10); + }); } }); diff --git a/spec/async/boolean.json b/spec/async/boolean.json new file mode 100644 index 0000000..59284c5 --- /dev/null +++ b/spec/async/boolean.json @@ -0,0 +1,126 @@ +[ + { + "description": "boolean schema = true in properties", + "schema": { + "$async": true, + "properties": { + "foo": true + } + }, + "tests": [ + { + "description": "any data is valid", + "data": { "foo": 1 }, + "valid": true + } + ] + }, + { + "description": "boolean schema = false in properties", + "schema": { + "$async": true, + "properties": { + "foo": false + } + }, + "tests": [ + { + "description": "any property is invalid", + "data": { "foo": 1 }, + "valid": false + }, + { + "description": "without property is valid", + "data": { "bar": 1 }, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "boolean schema = true in $ref", + "schema": { + "$async": true, + "$ref": "#/definitions/true", + "definitions": { + "true": true + } + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "boolean schema = false in $ref", + "schema": { + "$async": true, + "$ref": "#/definitions/false", + "definitions": { + "false": false + } + }, + "tests": [ + { + "description": "any data is invalid", + "data": 1, + "valid": false + } + ] + }, + { + "description": "boolean schema = true in properties with $ref", + "schema": { + "$async": true, + "properties": { + "foo": { "$ref": "#/definitions/foo" } + }, + "definitions": { + "foo": true + } + }, + "tests": [ + { + "description": "any data is valid", + "data": { "foo": 1 }, + "valid": true + } + ] + }, + { + "description": "boolean schema = false in properties with $ref", + "schema": { + "$async": true, + "properties": { + "foo": { "$ref": "#/definitions/foo" } + }, + "definitions": { + "foo": false + } + }, + "tests": [ + { + "description": "any property is invalid", + "data": { "foo": 1 }, + "valid": false + }, + { + "description": "without property is valid", + "data": { "bar": 1 }, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + } +] diff --git a/spec/async/keyword.json b/spec/async/keyword.json index d0ac3ec..34e10f3 100644 --- a/spec/async/keyword.json +++ b/spec/async/keyword.json @@ -137,7 +137,7 @@ "description": "custom keyword in async schema", "schema": { "$async": true, - "constant": 5 + "const": 5 }, "tests": [ { diff --git a/spec/async/switch.json b/spec/async/switch.json deleted file mode 100644 index 34d3262..0000000 --- a/spec/async/switch.json +++ /dev/null @@ -1,40 +0,0 @@ -[ - { - "description": "switch: async + sync conditions", - "schema": { - "$async": true, - "switch": [ - { - "if": { "idExists": { "table": "users" } }, - "then": { "minimum": 6 } - }, - { - "if": { "minimum": 21 }, - "then": { "idExists": { "table": "posts" } } - } - ] - }, - "tests": [ - { - "description": "valid - first condition", - "data": 8, - "valid": true - }, - { - "description": "valid - second condition", - "data": 28, - "valid": true - }, - { - "description": "invalid - first condition", - "data": 1, - "valid": false - }, - { - "description": "invalid - second condition", - "data": 22, - "valid": false - } - ] - } -] diff --git a/spec/async_schemas.spec.js b/spec/async_schemas.spec.js index 6527e50..cca8202 100644 --- a/spec/async_schemas.spec.js +++ b/spec/async_schemas.spec.js @@ -8,7 +8,7 @@ var jsonSchemaTest = require('json-schema-test') , after = require('./after_test'); -var instances = getAjvInstances({ v5: true }); +var instances = getAjvInstances({ $data: true }); instances.forEach(addAsyncFormatsAndKeywords); @@ -23,6 +23,7 @@ jsonSchemaTest(instances, { : './async/{**/,}*.json' }, async: true, + asyncValid: 'data', assert: require('./chai').assert, Promise: Promise, afterError: after.error, diff --git a/spec/async_validate.spec.js b/spec/async_validate.spec.js index bf550e1..fdfdf5d 100644 --- a/spec/async_validate.spec.js +++ b/spec/async_validate.spec.js @@ -4,7 +4,8 @@ var Ajv = require('./ajv') , Promise = require('./promise') , getAjvInstances = require('./ajv_async_instances') , should = require('./chai').should() - , co = require('co'); + , co = require('co') + , setupAsync = require('ajv-async'); describe('async schemas, formats and keywords', function() { @@ -38,7 +39,7 @@ describe('async schemas, formats and keywords', function() { var _co = useCo(_ajv); return Promise.all([ - shouldBeValid( _co(validate('abc')) ), + shouldBeValid( _co(validate('abc')), 'abc' ), shouldBeInvalid( _co(validate('abcd')) ), shouldBeInvalid( _co(validate(1)) ), ]); @@ -214,9 +215,10 @@ describe('async schemas, formats and keywords', function() { return repeat(function() { return Promise.all(instances.map(function (_ajv) { var validate = _ajv.compile(schema); var _co = useCo(_ajv); + var validData = { word: 'tomorrow' }; return Promise.all([ - shouldBeValid( _co(validate({ word: 'tomorrow' })) ), + shouldBeValid( _co(validate(validData)), validData ), shouldBeInvalid( _co(validate({ word: 'manana' })) ), shouldBeInvalid( _co(validate({ word: 1 })) ), shouldThrow( _co(validate({ word: 'today' })), 'unknown word' ) @@ -319,6 +321,11 @@ describe('async schemas, formats and keywords', function() { }; ajv.addSchema(schemaWord); + ajv.addFormat('english_word', { + async: true, + validate: checkWordOnServer + }); + shouldThrowFunc('async schema referenced by sync schema', function() { ajv.compile(schema); }); @@ -334,17 +341,18 @@ describe('async schemas, formats and keywords', function() { if (refSchema) try { _ajv.addSchema(refSchema); } catch(e) {} var validate = _ajv.compile(schema); var _co = useCo(_ajv); + var data; return Promise.all([ - shouldBeValid( _co(validate({ foo: 'tomorrow' })) ), + shouldBeValid( _co(validate(data = { foo: 'tomorrow' })), data ), shouldBeInvalid( _co(validate({ foo: 'manana' })) ), shouldBeInvalid( _co(validate({ foo: 1 })) ), shouldThrow( _co(validate({ foo: 'today' })), 'unknown word' ), - shouldBeValid( _co(validate({ foo: { foo: 'tomorrow' }})) ), + shouldBeValid( _co(validate(data = { foo: { foo: 'tomorrow' }})), data ), shouldBeInvalid( _co(validate({ foo: { foo: 'manana' }})) ), shouldBeInvalid( _co(validate({ foo: { foo: 1 }})) ), shouldThrow( _co(validate({ foo: { foo: 'today' }})), 'unknown word' ), - shouldBeValid( _co(validate({ foo: { foo: { foo: 'tomorrow' }}})) ), + shouldBeValid( _co(validate(data = { foo: { foo: { foo: 'tomorrow' }}})), data ), shouldBeInvalid( _co(validate({ foo: { foo: { foo: 'manana' }}})) ), shouldBeInvalid( _co(validate({ foo: { foo: { foo: 1 }}})) ), shouldThrow( _co(validate({ foo: { foo: { foo: 'today' }}})), 'unknown word' ) @@ -368,27 +376,27 @@ describe('async schemas, formats and keywords', function() { describe('async/transpile option', function() { it('should throw error with unknown async option', function() { shouldThrowFunc('bad async mode: es8', function() { - new Ajv({ async: 'es8' }); + setupAsync(new Ajv({ async: 'es8' })); }); }); it('should throw error with unknown transpile option', function() { shouldThrowFunc('bad transpiler: babel', function() { - new Ajv({ transpile: 'babel' }); + setupAsync(new Ajv({ transpile: 'babel' })); }); shouldThrowFunc('bad transpiler: [object Object]', function() { - new Ajv({ transpile: {} }); + setupAsync(new Ajv({ transpile: {} })); }); }); it('should set async option to es7 if tranpiler is nodent', function() { - var ajv1 = new Ajv({ transpile: 'nodent' }); + var ajv1 = setupAsync(new Ajv({ transpile: 'nodent' })); ajv1._opts.async .should.equal('es7'); - var ajv2 = new Ajv({ async: '*', transpile: 'nodent' }); + var ajv2 = setupAsync(new Ajv({ async: '*', transpile: 'nodent' })); ajv2._opts.async .should.equal('es7'); }); }); @@ -412,9 +420,9 @@ function shouldThrowFunc(message, func) { } -function shouldBeValid(p) { +function shouldBeValid(p, data) { return p.then(function (valid) { - valid .should.equal(true); + valid .should.equal(data); }); } diff --git a/spec/boolean.spec.js b/spec/boolean.spec.js new file mode 100644 index 0000000..c4c3b3e --- /dev/null +++ b/spec/boolean.spec.js @@ -0,0 +1,476 @@ +'use strict'; + +var Ajv = require('./ajv'); +require('./chai').should(); + + +describe('boolean schemas', function() { + var ajvs; + + before(function() { + ajvs = [ + new Ajv, + new Ajv({allErrors: true}), + new Ajv({inlineRefs: false}) + ]; + }); + + describe('top level schema', function() { + describe('schema = true', function() { + it('should validate any data as valid', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should validate any data as invalid', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var validate = ajv.compile(boolSchema); + testSchema(validate, valid); + }; + } + }); + + + describe('in properties / sub-properties', function() { + describe('schema = true', function() { + it('should be valid with any property value', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any property value', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + type: 'object', + properties: { + foo: boolSchema, + bar: { + type: 'object', + properties: { + baz: boolSchema + } + } + } + }; + + var validate = ajv.compile(schema); + validate({ foo: 1, bar: { baz: 1 }}) .should.equal(valid); + validate({ foo: '1', bar: { baz: '1' }}) .should.equal(valid); + validate({ foo: {}, bar: { baz: {} }}) .should.equal(valid); + validate({ foo: [], bar: { baz: [] }}) .should.equal(valid); + validate({ foo: true, bar: { baz: true }}) .should.equal(valid); + validate({ foo: false, bar: { baz: false }}) .should.equal(valid); + validate({ foo: null, bar: { baz: null }}) .should.equal(valid); + + validate({ bar: { quux: 1 } }) .should.equal(true); + }; + } + }); + + + describe('in items / sub-items', function() { + describe('schema = true', function() { + it('should be valid with any item value', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any item value', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + type: 'array', + items: boolSchema + }; + + var validate = ajv.compile(schema); + validate([ 1 ]) .should.equal(valid); + validate([ '1' ]) .should.equal(valid); + validate([ {} ]) .should.equal(valid); + validate([ [] ]) .should.equal(valid); + validate([ true ]) .should.equal(valid); + validate([ false ]) .should.equal(valid); + validate([ null ]) .should.equal(valid); + + validate([]) .should.equal(true); + + schema = { + type: 'array', + items: [ + true, + { + type: 'array', + items: [ + true, + boolSchema + ] + }, + boolSchema + ] + }; + + validate = ajv.compile(schema); + validate([ 1, [ 1, 1 ], 1 ]) .should.equal(valid); + validate([ '1', [ '1', '1' ], '1' ]) .should.equal(valid); + validate([ {}, [ {}, {} ], {} ]) .should.equal(valid); + validate([ [], [ [], [] ], [] ]) .should.equal(valid); + validate([ true, [ true, true ], true ]) .should.equal(valid); + validate([ false, [ false, false ], false ]) .should.equal(valid); + validate([ null, [ null, null ], null ]) .should.equal(valid); + + validate([ 1, [ 1 ] ]) .should.equal(true); + }; + } + }); + + + describe('in dependencies and sub-dependencies', function() { + describe('schema = true', function() { + it('should be valid with any property value', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any property value', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + type: 'object', + dependencies: { + foo: boolSchema, + bar: { + type: 'object', + dependencies: { + baz: boolSchema + } + } + } + }; + + var validate = ajv.compile(schema); + validate({ foo: 1, bar: 1, baz: 1 }) .should.equal(valid); + validate({ foo: '1', bar: '1', baz: '1' }) .should.equal(valid); + validate({ foo: {}, bar: {}, baz: {} }) .should.equal(valid); + validate({ foo: [], bar: [], baz: [] }) .should.equal(valid); + validate({ foo: true, bar: true, baz: true }) .should.equal(valid); + validate({ foo: false, bar: false, baz: false }) .should.equal(valid); + validate({ foo: null, bar: null, baz: null }) .should.equal(valid); + + validate({ bar: 1, quux: 1 }) .should.equal(true); + }; + } + }); + + + describe('in patternProperties', function () { + describe('schema = true', function() { + it('should be valid with any property matching pattern', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any property matching pattern', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + type: 'object', + patternProperties: { + '^f': boolSchema, + 'r$': { + type: 'object', + patternProperties: { + 'z$': boolSchema + } + } + } + }; + + var validate = ajv.compile(schema); + validate({ foo: 1, bar: { baz: 1 }}) .should.equal(valid); + validate({ foo: '1', bar: { baz: '1' }}) .should.equal(valid); + validate({ foo: {}, bar: { baz: {} }}) .should.equal(valid); + validate({ foo: [], bar: { baz: [] }}) .should.equal(valid); + validate({ foo: true, bar: { baz: true }}) .should.equal(valid); + validate({ foo: false, bar: { baz: false }}) .should.equal(valid); + validate({ foo: null, bar: { baz: null }}) .should.equal(valid); + + validate({ bar: { quux: 1 } }) .should.equal(true); + }; + } + }); + + + describe('in propertyNames', function() { + describe('schema = true', function() { + it('should be valid with any property', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any property', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + type: 'object', + propertyNames: boolSchema + }; + + var validate = ajv.compile(schema); + validate({ foo: 1 }) .should.equal(valid); + validate({ bar: 1 }) .should.equal(valid); + + validate({}) .should.equal(true); + }; + } + }); + + + describe('in contains', function() { + describe('schema = true', function() { + it('should be valid with any items', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any items', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + type: 'array', + contains: boolSchema + }; + + var validate = ajv.compile(schema); + validate([ 1 ]) .should.equal(valid); + validate([ 'foo' ]) .should.equal(valid); + validate([ {} ]) .should.equal(valid); + validate([ [] ]) .should.equal(valid); + validate([ true ]) .should.equal(valid); + validate([ false ]) .should.equal(valid); + validate([ null ]) .should.equal(valid); + + validate([]) .should.equal(false); + }; + } + }); + + + describe('in not', function() { + describe('schema = true', function() { + it('should be invalid with any data', function() { + ajvs.forEach(test(true, false)); + }); + }); + + describe('schema = false', function() { + it('should be valid with any data', function() { + ajvs.forEach(test(false, true)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + not: boolSchema + }; + + var validate = ajv.compile(schema); + testSchema(validate, valid); + }; + } + }); + + + describe('in allOf', function() { + describe('schema = true', function() { + it('should be valid with any data', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any data', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + allOf: [ + false, + boolSchema + ] + }; + + var validate = ajv.compile(schema); + testSchema(validate, false); + + schema = { + allOf: [ + true, + boolSchema + ] + }; + + validate = ajv.compile(schema); + testSchema(validate, valid); + }; + } + }); + + + describe('in anyOf', function() { + describe('schema = true', function() { + it('should be valid with any data', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any data', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + anyOf: [ + false, + boolSchema + ] + }; + + var validate = ajv.compile(schema); + testSchema(validate, valid); + + schema = { + anyOf: [ + true, + boolSchema + ] + }; + + validate = ajv.compile(schema); + testSchema(validate, true); + }; + } + }); + + + describe('in oneOf', function() { + describe('schema = true', function() { + it('should be valid with any data', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any data', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + oneOf: [ + false, + boolSchema + ] + }; + + var validate = ajv.compile(schema); + testSchema(validate, valid); + + schema = { + oneOf: [ + true, + boolSchema + ] + }; + + validate = ajv.compile(schema); + testSchema(validate, !valid); + }; + } + }); + + + describe('in $ref', function() { + describe('schema = true', function() { + it('should be valid with any data', function() { + ajvs.forEach(test(true, true)); + }); + }); + + describe('schema = false', function() { + it('should be invalid with any data', function() { + ajvs.forEach(test(false, false)); + }); + }); + + function test(boolSchema, valid) { + return function (ajv) { + var schema = { + $ref: '#/definitions/bool', + definitions: { + bool: boolSchema + } + }; + + var validate = ajv.compile(schema); + testSchema(validate, valid); + }; + } + }); + + + function testSchema(validate, valid) { + validate(1) .should.equal(valid); + validate('foo') .should.equal(valid); + validate({}) .should.equal(valid); + validate([]) .should.equal(valid); + validate(true) .should.equal(valid); + validate(false) .should.equal(valid); + validate(null) .should.equal(valid); + } +}); diff --git a/spec/custom.spec.js b/spec/custom.spec.js index 6272fc6..4ed6e13 100644 --- a/spec/custom.spec.js +++ b/spec/custom.spec.js @@ -377,10 +377,10 @@ describe('Custom keywords', function () { it('should correctly expand macros in macro expansions', function() { instances.forEach(function (_ajv) { _ajv.addKeyword('range', { type: 'number', macro: macroRange }); - _ajv.addKeyword('contains', { type: 'array', macro: macroContains }); + _ajv.addKeyword('myContains', { type: 'array', macro: macroContains }); var schema = { - "contains": { + "myContains": { "type": "number", "range": [4,7], "exclusiveRange": true @@ -429,12 +429,9 @@ describe('Custom keywords', function () { validateRangeSchema(schema, parentSchema); var exclusive = !!parentSchema.exclusiveRange; - return { - minimum: schema[0], - exclusiveMinimum: exclusive, - maximum: schema[1], - exclusiveMaximum: exclusive - }; + return exclusive + ? { exclusiveMinimum: schema[0], exclusiveMaximum: schema[1] } + : { minimum: schema[0], maximum: schema[1] }; } }); @@ -507,13 +504,13 @@ describe('Custom keywords', function () { }); - describe('$data reference support with custom keywords (v5 only)', function() { + describe('$data reference support with custom keywords (with $data option)', function() { beforeEach(function() { instances = getAjvInstances({ allErrors: true, verbose: true, inlineRefs: false - }, { v5: true }); + }, { $data: true }); ajv = instances[0]; }); @@ -726,14 +723,18 @@ describe('Custom keywords', function () { shouldBeValid(validate, { data: 3, evenValue: false }); shouldBeInvalid(validate, { data: 2, evenValue: "true" }); + + // valid if the value of x-even-$data keyword is undefined + shouldBeValid(validate, { data: 2 }); + shouldBeValid(validate, { data: 3 }); }); } function testConstantKeyword(definition, numErrors) { instances.forEach(function (_ajv) { - _ajv.addKeyword('constant', definition); + _ajv.addKeyword('myConstant', definition); - var schema = { "constant": "abc" }; + var schema = { "myConstant": "abc" }; var validate = _ajv.compile(schema); shouldBeValid(validate, 'abc'); diff --git a/spec/errors.spec.js b/spec/errors.spec.js index e6bddf9..7e83538 100644 --- a/spec/errors.spec.js +++ b/spec/errors.spec.js @@ -14,7 +14,7 @@ describe('Validation errors', function () { function createInstances(errorDataPath) { ajv = new Ajv({ errorDataPath: errorDataPath, loopRequired: 21 }); ajvJP = new Ajv({ errorDataPath: errorDataPath, jsonPointers: true, loopRequired: 21 }); - fullAjv = new Ajv({ errorDataPath: errorDataPath, allErrors: true, jsonPointers: true, loopRequired: 21 }); + fullAjv = new Ajv({ errorDataPath: errorDataPath, allErrors: true, verbose: true, jsonPointers: true, loopRequired: 21 }); } it('error should include dataPath', function() { @@ -281,7 +281,7 @@ describe('Validation errors', function () { it('should not validate required twice with $data ref', function() { - ajv = new Ajv({ v5: true, allErrors: true }); + ajv = new Ajv({ $data: true, allErrors: true }); var schema = { properties: { @@ -459,7 +459,7 @@ describe('Validation errors', function () { }); - it('should has correct schema path for additionalItems', function() { + it('should have correct schema path for additionalItems', function() { var schema = { type: 'array', items: [ { type: 'integer' }, { type: 'integer' } ], @@ -482,6 +482,89 @@ describe('Validation errors', function () { }); + describe('"propertyNames" errors', function() { + it('should add propertyName to errors', function() { + var schema = { + type: 'object', + propertyNames: { format: 'email' } + }; + + var data = { + 'bar.baz@email.example.com': {} + }; + + var invalidData = { + 'foo': {}, + 'bar': {}, + 'bar.baz@email.example.com': {} + }; + + test(ajv, 2); + test(ajvJP, 2); + test(fullAjv, 4); + + function test(_ajv, numErrors) { + var validate = _ajv.compile(schema); + shouldBeValid(validate, data); + shouldBeInvalid(validate, invalidData, numErrors); + shouldBeError(validate.errors[0], 'format', '#/propertyNames/format', '', 'should match format "email"'); + shouldBeError(validate.errors[1], 'propertyNames', '#/propertyNames', '', 'property name \'foo\' is invalid'); + if (numErrors == 4) { + shouldBeError(validate.errors[2], 'format', '#/propertyNames/format', '', 'should match format "email"'); + shouldBeError(validate.errors[3], 'propertyNames', '#/propertyNames', '', 'property name \'bar\' is invalid'); + } + } + }); + }); + + + describe('oneOf errors', function() { + it('should have errors from inner schemas', function() { + var schema = { + oneOf: [ + { type: 'number' }, + { type: 'integer' } + ] + }; + + test(ajv); + test(fullAjv); + + function test(_ajv) { + var validate = _ajv.compile(schema); + validate('foo') .should.equal(false); + validate.errors.length .should.equal(3); + validate(1) .should.equal(false); + validate.errors.length .should.equal(1); + validate(1.5) .should.equal(true); + } + }); + }); + + + describe('anyOf errors', function() { + it('should have errors from inner schemas', function() { + var schema = { + anyOf: [ + { type: 'number' }, + { type: 'integer' } + ] + }; + + test(ajv); + test(fullAjv); + + function test(_ajv) { + var validate = _ajv.compile(schema); + validate('foo') .should.equal(false); + validate.errors.length .should.equal(3); + validate(1) .should.equal(true); + validate(1.5) .should.equal(true); + } + }); + }); + + function testSchema1(schema, schemaPathPrefix) { _testSchema1(ajv, schema, schemaPathPrefix); _testSchema1(ajvJP, schema, schemaPathPrefix); diff --git a/spec/extras.spec.js b/spec/extras.spec.js new file mode 100644 index 0000000..dcf1713 --- /dev/null +++ b/spec/extras.spec.js @@ -0,0 +1,29 @@ +'use strict'; + +var jsonSchemaTest = require('json-schema-test') + , getAjvInstances = require('./ajv_instances') + , options = require('./ajv_options') + , suite = require('./browser_test_suite') + , after = require('./after_test'); + +var instances = getAjvInstances(options, { + $data: true, + patternGroups: true, + unknownFormats: ['allowedUnknown'] +}); + + +jsonSchemaTest(instances, { + description: 'Extra keywords schemas tests of ' + instances.length + ' ajv instances with different options', + suites: { + 'extras': typeof window == 'object' + ? suite(require('./extras/{**/,}*.json', {mode: 'list'})) + : './extras/{**/,}*.json' + }, + assert: require('./chai').assert, + afterError: after.error, + afterEach: after.each, + cwd: __dirname, + hideFolder: 'extras/', + timeout: 90000 +}); diff --git a/spec/v5/$data/absolute_ref.json b/spec/extras/$data/absolute_ref.json similarity index 97% rename from spec/v5/$data/absolute_ref.json rename to spec/extras/$data/absolute_ref.json index 3b48c85..38b3fe5 100644 --- a/spec/v5/$data/absolute_ref.json +++ b/spec/extras/$data/absolute_ref.json @@ -4,7 +4,7 @@ "schema": { "properties": { "sameAs": { - "constant": { + "const": { "$data": "/thisOne" } }, @@ -71,17 +71,17 @@ "sameArr": { "items": [ { - "constant": { + "const": { "$data": "/arr/0" } }, { - "constant": { + "const": { "$data": "/arr/1" } }, { - "constant": { + "const": { "$data": "/arr/2" } } @@ -143,7 +143,7 @@ "list": { "type": "array", "contains": { - "constant": { + "const": { "$data": "/name" } } @@ -386,7 +386,7 @@ "type": "object", "properties": { "foo": { - "constant": { + "const": { "$data": "/bar" } }, @@ -396,7 +396,7 @@ }, "properties": { "foo": { - "constant": { + "const": { "$data": "/bar" } }, diff --git a/spec/v5/$data/constant.json b/spec/extras/$data/const.json similarity index 91% rename from spec/v5/$data/constant.json rename to spec/extras/$data/const.json index 72f01a2..b6143c4 100644 --- a/spec/v5/$data/constant.json +++ b/spec/extras/$data/const.json @@ -3,7 +3,7 @@ "description": "property is equal to another property", "schema": { "properties": { - "sameAs": { "constant": { "$data": "1/thisOne" } }, + "sameAs": { "const": { "$data": "1/thisOne" } }, "thisOne": {} } }, @@ -46,7 +46,7 @@ "description": "property values are equal to property names", "schema": { "additionalProperties": { - "constant": { "$data": "0#" } + "const": { "$data": "0#" } } }, "tests": [ @@ -66,7 +66,7 @@ "description": "items are equal to their indeces", "schema": { "items": { - "constant": { "$data": "0#" } + "const": { "$data": "0#" } } }, "tests": [ @@ -92,9 +92,9 @@ }, "sameArr": { "items": [ - { "constant": { "$data": "2/arr/0" } }, - { "constant": { "$data": "2/arr/1" } }, - { "constant": { "$data": "2/arr/2" } } + { "const": { "$data": "2/arr/0" } }, + { "const": { "$data": "2/arr/1" } }, + { "const": { "$data": "2/arr/2" } } ], "additionalItems": false } @@ -122,7 +122,7 @@ { "description": "any data is equal to itself", "schema": { - "constant": { "$data": "0" } + "const": { "$data": "0" } }, "tests": [ { @@ -154,7 +154,7 @@ "name": { "type": "string" }, "list": { "type": "array", - "contains": { "constant": { "$data": "2/name" } } + "contains": { "const": { "$data": "2/name" } } } } }, diff --git a/spec/v5/$data/enum.json b/spec/extras/$data/enum.json similarity index 100% rename from spec/v5/$data/enum.json rename to spec/extras/$data/enum.json diff --git a/spec/extras/$data/exclusiveMaximum.json b/spec/extras/$data/exclusiveMaximum.json new file mode 100644 index 0000000..a33e191 --- /dev/null +++ b/spec/extras/$data/exclusiveMaximum.json @@ -0,0 +1,325 @@ +[ + { + "description": "one property is exclusiveMaximum for another", + "schema": { + "properties": { + "larger": {}, + "smaller": { + "exclusiveMaximum": { "$data": "1/larger" } + } + } + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": { + "larger": 3, + "smaller": 2 + }, + "valid": true + }, + { + "description": "equal to the exclusiveMaximum is invalid", + "data": { + "larger": 3, + "smaller": 3 + }, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": { + "larger": 3, + "smaller": 4 + }, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": { + "larger": 3, + "smaller": "4" + }, + "valid": true + }, + { + "description": "fails if value of exclusiveMaximum is not number", + "data": { + "larger": "3", + "smaller": 2 + }, + "valid": false + }, + { + "description": "valid if value of exclusiveMaximum is undefined", + "data": { + "smaller": 2 + }, + "valid": true + } + ] + }, + + { + "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum > maximum", + "schema": { + "properties": { + "larger": {}, + "smaller": { + "exclusiveMaximum": 3.5, + "maximum": { "$data": "1/larger" } + } + } + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": { + "larger": 3, + "smaller": 2 + }, + "valid": true + }, + { + "description": "equal to the maximum is valid", + "data": { + "larger": 3, + "smaller": 3 + }, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": { + "larger": 3, + "smaller": 3.2 + }, + "valid": false + } + ] + }, + { + "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum = maximum", + "schema": { + "properties": { + "larger": {}, + "smaller": { + "exclusiveMaximum": 3, + "maximum": { "$data": "1/larger" } + } + } + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": { + "larger": 3, + "smaller": 2 + }, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": { + "larger": 3, + "smaller": 3 + }, + "valid": false + }, + { + "description": "above the maximum is invalid", + "data": { + "larger": 3, + "smaller": 4 + }, + "valid": false + } + ] + }, + { + "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum < maximum", + "schema": { + "properties": { + "larger": {}, + "smaller": { + "exclusiveMaximum": 2.5, + "maximum": { "$data": "1/larger" } + } + } + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": { + "larger": 3, + "smaller": 2 + }, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": { + "larger": 3, + "smaller": 2.5 + }, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": { + "larger": 3, + "smaller": 2.8 + }, + "valid": false + } + ] + }, + + { + "description": "exclusiveMaximum and maximum as $data, exclusiveMaximum > maximum", + "schema": { + "properties": { + "larger": {}, + "largerExclusive": {}, + "smaller": { + "exclusiveMaximum": { "$data": "1/largerExclusive" }, + "maximum": { "$data": "1/larger" } + } + } + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": { + "larger": 3, + "largerExclusive": 3.5, + "smaller": 2 + }, + "valid": true + }, + { + "description": "equal to the maximum is valid", + "data": { + "larger": 3, + "largerExclusive": 3.5, + "smaller": 3 + }, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": { + "larger": 3, + "largerExclusive": 3.5, + "smaller": 3.2 + }, + "valid": false + } + ] + }, + { + "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum = maximum", + "schema": { + "properties": { + "larger": {}, + "largerExclusive": {}, + "smaller": { + "exclusiveMaximum": { "$data": "1/largerExclusive" }, + "maximum": { "$data": "1/larger" } + } + } + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": { + "larger": 3, + "largerExclusive": 3, + "smaller": 2 + }, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": { + "larger": 3, + "largerExclusive": 3, + "smaller": 3 + }, + "valid": false + }, + { + "description": "above the maximum is invalid", + "data": { + "larger": 3, + "largerExclusive": 3, + "smaller": 4 + }, + "valid": false + } + ] + }, + { + "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum < maximum", + "schema": { + "properties": { + "larger": {}, + "largerExclusive": {}, + "smaller": { + "exclusiveMaximum": { "$data": "1/largerExclusive" }, + "maximum": { "$data": "1/larger" } + } + } + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": { + "larger": 3, + "largerExclusive": 2.5, + "smaller": 2 + }, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": { + "larger": 3, + "largerExclusive": 2.5, + "smaller": 2.5 + }, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": { + "larger": 3, + "largerExclusive": 2.5, + "smaller": 2.8 + }, + "valid": false + } + ] + }, + + { + "description": "items in array are < than their indeces", + "schema": { + "items": { + "exclusiveMaximum": { "$data": "0#" } + } + }, + "tests": [ + { + "description": "valid array", + "data": ["", 0, 1, 2, 3, 4], + "valid": true + }, + { + "description": "invalid array (1=1)", + "data": ["", 1], + "valid": false + } + ] + } +] diff --git a/spec/extras/$data/exclusiveMinimum.json b/spec/extras/$data/exclusiveMinimum.json new file mode 100644 index 0000000..abb5ade --- /dev/null +++ b/spec/extras/$data/exclusiveMinimum.json @@ -0,0 +1,318 @@ +[ + { + "description": "one property is exclusiveMinimum for another", + "schema": { + "properties": { + "smaller": {}, + "larger": { + "exclusiveMinimum": { "$data": "1/smaller" } + } + } + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": { + "smaller": 3, + "larger": 4 + }, + "valid": true + }, + { + "description": "equal to the exclusiveMinimum is invalid", + "data": { + "smaller": 3, + "larger": 3 + }, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": { + "smaller": 3, + "larger": 2 + }, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": { + "smaller": 3, + "larger": "2" + }, + "valid": true + }, + { + "description": "fails if value of exclusiveMinimum is not number", + "data": { + "smaller": "3", + "larger": 4 + }, + "valid": false + } + ] + }, + + { + "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum < minimum", + "schema": { + "properties": { + "smaller": {}, + "larger": { + "exclusiveMinimum": 2.5, + "minimum": { "$data": "1/smaller" } + } + } + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": { + "smaller": 3, + "larger": 4 + }, + "valid": true + }, + { + "description": "equal to the minimum is valid", + "data": { + "smaller": 3, + "larger": 3 + }, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": { + "smaller": 3, + "larger": 2.8 + }, + "valid": false + } + ] + }, + { + "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum = minimum", + "schema": { + "properties": { + "smaller": {}, + "larger": { + "exclusiveMinimum": 3, + "minimum": { "$data": "1/smaller" } + } + } + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": { + "smaller": 3, + "larger": 4 + }, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": { + "smaller": 3, + "larger": 3 + }, + "valid": false + }, + { + "description": "below the minimum is invalid", + "data": { + "smaller": 3, + "larger": 2 + }, + "valid": false + } + ] + }, + { + "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum > minimum", + "schema": { + "properties": { + "smaller": {}, + "larger": { + "exclusiveMinimum": 3.5, + "minimum": { "$data": "1/smaller" } + } + } + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": { + "smaller": 3, + "larger": 4 + }, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": { + "smaller": 3, + "larger": 3.5 + }, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": { + "smaller": 3, + "larger": 3.3 + }, + "valid": false + } + ] + }, + + { + "description": "exclusiveMinimum and minimum as $data, exclusiveMinimum < minimum", + "schema": { + "properties": { + "smaller": {}, + "smallerExclusive": {}, + "larger": { + "exclusiveMinimum": { "$data": "1/smallerExclusive" }, + "minimum": { "$data": "1/smaller" } + } + } + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": { + "smaller": 3, + "smallerExclusive": 2.5, + "larger": 4 + }, + "valid": true + }, + { + "description": "equal to the minimum is valid", + "data": { + "smaller": 3, + "smallerExclusive": 2.5, + "larger": 3 + }, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": { + "smaller": 3, + "smallerExclusive": 2.5, + "larger": 2.8 + }, + "valid": false + } + ] + }, + { + "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum = minimum", + "schema": { + "properties": { + "smaller": {}, + "smallerExclusive": {}, + "larger": { + "exclusiveMinimum": { "$data": "1/smallerExclusive" }, + "minimum": { "$data": "1/smaller" } + } + } + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": { + "smaller": 3, + "smallerExclusive": 3, + "larger": 4 + }, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": { + "smaller": 3, + "smallerExclusive": 3, + "larger": 3 + }, + "valid": false + }, + { + "description": "below the minimum is invalid", + "data": { + "smaller": 3, + "smallerExclusive": 3, + "larger": 2 + }, + "valid": false + } + ] + }, + { + "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum > minimum", + "schema": { + "properties": { + "smaller": {}, + "smallerExclusive": {}, + "larger": { + "exclusiveMinimum": { "$data": "1/smallerExclusive" }, + "minimum": { "$data": "1/smaller" } + } + } + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": { + "smaller": 3, + "smallerExclusive": 3.5, + "larger": 4 + }, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": { + "smaller": 3, + "smallerExclusive": 3.5, + "larger": 3.5 + }, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": { + "smaller": 3, + "smallerExclusive": 3.5, + "larger": 3.3 + }, + "valid": false + } + ] + }, + + { + "description": "items in array are > than their indeces", + "schema": { + "items": { + "exclusiveMinimum": { "$data": "0#" } + } + }, + "tests": [ + { + "description": "valid array", + "data": [1, 2, 3, 4, 5, 6], + "valid": true + }, + { + "description": "invalid array (1=1)", + "data": [2, 1], + "valid": false + } + ] + } +] diff --git a/spec/v5/$data/format.json b/spec/extras/$data/format.json similarity index 100% rename from spec/v5/$data/format.json rename to spec/extras/$data/format.json diff --git a/spec/v5/$data/maxItems.json b/spec/extras/$data/maxItems.json similarity index 100% rename from spec/v5/$data/maxItems.json rename to spec/extras/$data/maxItems.json diff --git a/spec/v5/$data/maxLength.json b/spec/extras/$data/maxLength.json similarity index 100% rename from spec/v5/$data/maxLength.json rename to spec/extras/$data/maxLength.json diff --git a/spec/v5/$data/maxProperties.json b/spec/extras/$data/maxProperties.json similarity index 100% rename from spec/v5/$data/maxProperties.json rename to spec/extras/$data/maxProperties.json diff --git a/spec/v5/$data/maximum.json b/spec/extras/$data/maximum.json similarity index 100% rename from spec/v5/$data/maximum.json rename to spec/extras/$data/maximum.json diff --git a/spec/v5/$data/minItems.json b/spec/extras/$data/minItems.json similarity index 100% rename from spec/v5/$data/minItems.json rename to spec/extras/$data/minItems.json diff --git a/spec/v5/$data/minLength.json b/spec/extras/$data/minLength.json similarity index 100% rename from spec/v5/$data/minLength.json rename to spec/extras/$data/minLength.json diff --git a/spec/v5/$data/minProperties.json b/spec/extras/$data/minProperties.json similarity index 100% rename from spec/v5/$data/minProperties.json rename to spec/extras/$data/minProperties.json diff --git a/spec/v5/$data/minimum.json b/spec/extras/$data/minimum.json similarity index 100% rename from spec/v5/$data/minimum.json rename to spec/extras/$data/minimum.json diff --git a/spec/v5/$data/multipleOf.json b/spec/extras/$data/multipleOf.json similarity index 100% rename from spec/v5/$data/multipleOf.json rename to spec/extras/$data/multipleOf.json diff --git a/spec/v5/$data/pattern.json b/spec/extras/$data/pattern.json similarity index 100% rename from spec/v5/$data/pattern.json rename to spec/extras/$data/pattern.json diff --git a/spec/v5/$data/required.json b/spec/extras/$data/required.json similarity index 100% rename from spec/v5/$data/required.json rename to spec/extras/$data/required.json diff --git a/spec/v5/$data/uniqueItems.json b/spec/extras/$data/uniqueItems.json similarity index 100% rename from spec/v5/$data/uniqueItems.json rename to spec/extras/$data/uniqueItems.json diff --git a/spec/v5/constant.json b/spec/extras/const.json similarity index 76% rename from spec/v5/constant.json rename to spec/extras/const.json index f6fc913..a58429f 100644 --- a/spec/v5/constant.json +++ b/spec/extras/const.json @@ -1,7 +1,7 @@ [ { - "description": "constant keyword requires the value to be equal to some constant", - "schema": { "constant": 2 }, + "description": "const keyword requires the value to be equal to some constant", + "schema": { "const": 2 }, "tests": [ { "description": "same value is valid", @@ -21,8 +21,8 @@ ] }, { - "description": "constant keyword requires the value to be equal to some object", - "schema": { "constant": { "foo": "bar", "baz": "bax" } }, + "description": "const keyword requires the value to be equal to some object", + "schema": { "const": { "foo": "bar", "baz": "bax" } }, "tests": [ { "description": "same object is valid", @@ -47,8 +47,8 @@ ] }, { - "description": "constant keyword with null", - "schema": { "constant": null }, + "description": "const keyword with null", + "schema": { "const": null }, "tests": [ { "description": "null is valid", diff --git a/spec/v5/contains.json b/spec/extras/contains.json similarity index 80% rename from spec/v5/contains.json rename to spec/extras/contains.json index 1a9c42b..369c10a 100644 --- a/spec/v5/contains.json +++ b/spec/extras/contains.json @@ -20,6 +20,11 @@ "data": [1, 2, 3, 4], "valid": false }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, { "description": "not array is valid", "data": {}, @@ -28,9 +33,9 @@ ] }, { - "description": "contains keyword with constant keyword requires a specific item to be present", + "description": "contains keyword with const keyword requires a specific item to be present", "schema": { - "contains": { "constant": 5 } + "contains": { "const": 5 } }, "tests": [ { diff --git a/spec/extras/exclusiveMaximum.json b/spec/extras/exclusiveMaximum.json new file mode 100644 index 0000000..e4e7f0b --- /dev/null +++ b/spec/extras/exclusiveMaximum.json @@ -0,0 +1,97 @@ +[ + { + "description": "exclusiveMaximum as number", + "schema": { + "exclusiveMaximum": 3.0 + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": 3.2, + "valid": false + } + ] + }, + { + "description": "both exclusiveMaximum and maximum are numbers, exclusiveMaximum > maximum", + "schema": { + "exclusiveMaximum": 3.0, + "maximum": 2.0 + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 2.0, + "valid": true + }, + { + "description": "above maximum is invalid", + "data": 2.2, + "valid": false + } + ] + }, + { + "description": "both exclusiveMaximum and maximum are numbers, exclusiveMaximum = maximum", + "schema": { + "exclusiveMaximum": 3.0, + "maximum": 3.0 + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "above maximum is invalid", + "data": 3.2, + "valid": false + } + ] + }, + { + "description": "both exclusiveMaximum and maximum are numbers, exclusiveMaximum < maximum", + "schema": { + "exclusiveMaximum": 2.0, + "maximum": 3.0 + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 2.0, + "valid": false + }, + { + "description": "above exclusiveMaximum is invalid", + "data": 2.2, + "valid": false + } + ] + } +] diff --git a/spec/extras/exclusiveMinimum.json b/spec/extras/exclusiveMinimum.json new file mode 100644 index 0000000..510fb95 --- /dev/null +++ b/spec/extras/exclusiveMinimum.json @@ -0,0 +1,97 @@ +[ + { + "description": "exclusiveMinimum as number", + "schema": { + "exclusiveMinimum": 1.1 + }, + "tests": [ + { + "description": "above the exclusiveMinimum is still valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "below exclusiveMinimum is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "both exclusiveMinimum and minimum are numbers, exclusiveMinimum < minimum", + "schema": { + "exclusiveMinimum": 2.0, + "minimum": 3.0 + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": 3.2, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "below minimum is invalid", + "data": 2.2, + "valid": false + } + ] + }, + { + "description": "both exclusiveMinimum and minimum are numbers, exclusiveMinimum = minimum", + "schema": { + "exclusiveMinimum": 3.0, + "minimum": 3.0 + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": 3.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "below minimum is invalid", + "data": 2.2, + "valid": false + } + ] + }, + { + "description": "both exclusiveMinimum and minimum are numbers, exclusiveMinimum > minimum", + "schema": { + "exclusiveMinimum": 3.0, + "minimum": 2.0 + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": 3.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "below exclusiveMinimum is invalid", + "data": 2.2, + "valid": false + } + ] + } +] diff --git a/spec/v5/patternGroups.json b/spec/extras/patternGroups.json similarity index 100% rename from spec/v5/patternGroups.json rename to spec/extras/patternGroups.json diff --git a/spec/extras/propertyNames.json b/spec/extras/propertyNames.json new file mode 100644 index 0000000..745530f --- /dev/null +++ b/spec/extras/propertyNames.json @@ -0,0 +1,32 @@ +[ + { + "description": "propertyNames validation", + "schema": { + "type": "object", + "propertyNames": { "format": "email" } + }, + "tests": [ + { + "description": "all property names valid", + "data": { + "foo@example.com": {}, + "bar.baz@email.example.com": {} + }, + "valid": true + }, + { + "description": "some property names invalid", + "data": { + "foo": {}, + "bar.baz@email.example.com": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + } + ] + } +] diff --git a/spec/issues.spec.js b/spec/issues.spec.js index 9aa675e..d4b39ed 100644 --- a/spec/issues.spec.js +++ b/spec/issues.spec.js @@ -106,14 +106,14 @@ describe('issue #182, NaN validation', function() { }); -describe('issue #204, options schemas and v5 used together', function() { +describe('issue #204, options schemas and $data used together', function() { it('should use v5 metaschemas by default', function() { var ajv = new Ajv({ - v5: true, schemas: [{id: 'str', type: 'string'}], + $data: true }); - var schema = { constant: 42 }; + var schema = { const: 42 }; var validate = ajv.compile(schema); validate(42) .should.equal(true); @@ -156,7 +156,7 @@ describe('issue #181, custom keyword is not validated in allErrors mode if there }); function testCustomKeywordErrors(def) { - var ajv = new Ajv({ allErrors: true, beautify: true }); + var ajv = new Ajv({ allErrors: true }); ajv.addKeyword('alwaysFails', def); @@ -256,7 +256,7 @@ describe('issue #210, mutual recursive $refs that are schema fragments', functio describe('issue #240, mutually recursive fragment refs reference a common schema', function() { var apiSchema = { - $schema: 'http://json-schema.org/draft-04/schema#', + $schema: 'http://json-schema.org/draft-06/schema#', id: 'schema://api.schema#', resource: { id: '#resource', @@ -274,7 +274,7 @@ describe('issue #240, mutually recursive fragment refs reference a common schema }; var domainSchema = { - $schema: 'http://json-schema.org/draft-04/schema#', + $schema: 'http://json-schema.org/draft-06/schema#', id: 'schema://domain.schema#', properties: { data: { @@ -290,7 +290,7 @@ describe('issue #240, mutually recursive fragment refs reference a common schema var ajv = new Ajv; var librarySchema = { - $schema: 'http://json-schema.org/draft-04/schema#', + $schema: 'http://json-schema.org/draft-06/schema#', id: 'schema://library.schema#', properties: { name: { type: 'string' }, @@ -322,7 +322,7 @@ describe('issue #240, mutually recursive fragment refs reference a common schema }; var catalogItemSchema = { - $schema: 'http://json-schema.org/draft-04/schema#', + $schema: 'http://json-schema.org/draft-06/schema#', id: 'schema://catalog_item.schema#', properties: { name: { type: 'string' }, @@ -351,7 +351,7 @@ describe('issue #240, mutually recursive fragment refs reference a common schema }; var catalogItemResourceIdentifierSchema = { - $schema: 'http://json-schema.org/draft-04/schema#', + $schema: 'http://json-schema.org/draft-06/schema#', id: 'schema://catalog_item_resource_identifier.schema#', allOf: [ { @@ -381,7 +381,7 @@ describe('issue #240, mutually recursive fragment refs reference a common schema var ajv = new Ajv; var librarySchema = { - $schema: 'http://json-schema.org/draft-04/schema#', + $schema: 'http://json-schema.org/draft-06/schema#', id: 'schema://library.schema#', properties: { name: { type: 'string' }, @@ -413,7 +413,7 @@ describe('issue #240, mutually recursive fragment refs reference a common schema }; var catalogItemSchema = { - $schema: 'http://json-schema.org/draft-04/schema#', + $schema: 'http://json-schema.org/draft-06/schema#', id: 'schema://catalog_item.schema#', properties: { name: { type: 'string' }, @@ -463,12 +463,40 @@ describe('issue #240, mutually recursive fragment refs reference a common schema describe('issue #259, support validating [meta-]schemas against themselves', function() { it('should add schema before validation if "id" is the same as "$schema"', function() { var ajv = new Ajv; + ajv.addMetaSchema(require('../lib/refs/json-schema-draft-04.json')); var hyperSchema = require('./remotes/hyper-schema.json'); ajv.addMetaSchema(hyperSchema); }); }); +describe.skip('issue #273, schemaPath in error in referenced schema', function() { + it('should have canonic reference with hash after file name', function() { + test(new Ajv); + test(new Ajv({inlineRefs: false})); + + function test(ajv) { + var schema = { + "properties": { + "a": { "$ref": "int" } + } + }; + + var referencedSchema = { + "id": "int", + "type": "integer" + }; + + ajv.addSchema(referencedSchema); + var validate = ajv.compile(schema); + + validate({ "a": "foo" }) .should.equal(false); + validate.errors[0].schemaPath .should.equal('int#/type'); + } + }); +}); + + describe('issue #342, support uniqueItems with some non-JSON objects', function() { var validate; @@ -496,3 +524,27 @@ describe('issue #342, support uniqueItems with some non-JSON objects', function( validate([{foo: undefined}, {foo: undefined}]) .should.equal(false); }); }); + + +describe('issue #388, code clean-up not working', function() { + it('should remove assignement to rootData if it is not used', function() { + var ajv = new Ajv; + var validate = ajv.compile({ + type: 'object', + properties: { + foo: { type: 'string' } + } + }); + var code = validate.toString(); + code.match(/rootData/g).length .should.equal(1); + }); + + it('should remove assignement to errors if they are not used', function() { + var ajv = new Ajv; + var validate = ajv.compile({ + type: 'object' + }); + var code = validate.toString(); + should.equal(code.match(/[^\.]errors|vErrors/g), null); + }); +}); diff --git a/spec/json-schema.spec.js b/spec/json-schema.spec.js index 6ab14ad..bde31a2 100644 --- a/spec/json-schema.spec.js +++ b/spec/json-schema.spec.js @@ -6,45 +6,47 @@ var jsonSchemaTest = require('json-schema-test') , suite = require('./browser_test_suite') , after = require('./after_test'); -var instances = getAjvInstances(options); - var remoteRefs = { 'http://localhost:1234/integer.json': require('./JSON-Schema-Test-Suite/remotes/integer.json'), 'http://localhost:1234/subSchemas.json': require('./JSON-Schema-Test-Suite/remotes/subSchemas.json'), 'http://localhost:1234/folder/folderInteger.json': require('./JSON-Schema-Test-Suite/remotes/folder/folderInteger.json'), }; -instances.forEach(addRemoteRefs); +runTest(getAjvInstances(options, {meta: false}), 4, typeof window == 'object' + ? suite(require('./JSON-Schema-Test-Suite/tests/draft4/{**/,}*.json', {mode: 'list'})) + : './JSON-Schema-Test-Suite/tests/draft4/{**/,}*.json'); + +runTest(getAjvInstances(options), 6, typeof window == 'object' + ? suite(require('./JSON-Schema-Test-Suite/tests/draft6/{**/,}*.json', {mode: 'list'})) + : './JSON-Schema-Test-Suite/tests/draft6/{**/,}*.json'); -jsonSchemaTest(instances, { - description: 'JSON-Schema Test Suite: ' + instances.length + ' ajv instances with different options', - suites: { - 'JSON-Schema tests draft4': - typeof window == 'object' - ? suite(require('./JSON-Schema-Test-Suite/tests/draft4/{**/,}*.json', {mode: 'list'})) - : './JSON-Schema-Test-Suite/tests/draft4/{**/,}*.json' - }, - only: [ - // 'type', 'not', 'allOf', 'anyOf', 'oneOf', 'enum', - // 'maximum', 'minimum', 'multipleOf', 'maxLength', 'minLength', 'pattern', - // 'properties', 'patternProperties', 'additionalProperties', - // 'dependencies', 'required', - // 'maxProperties', 'minProperties', 'maxItems', 'minItems', - // 'items', 'additionalItems', 'uniqueItems', - // 'optional/format', 'optional/bignum', - // 'ref', 'refRemote', 'definitions', - ], - skip: [ 'optional/zeroTerminatedFloats' ], - assert: require('./chai').assert, - afterError: after.error, - afterEach: after.each, - cwd: __dirname, - hideFolder: 'draft4/', - timeout: 120000 -}); +function runTest(instances, draft, tests) { + instances.forEach(function (ajv) { + ajv.addMetaSchema(require('../lib/refs/json-schema-draft-04.json')); + if (draft == 4) ajv._opts.defaultMeta = 'http://json-schema.org/draft-04/schema#'; + for (var id in remoteRefs) ajv.addSchema(remoteRefs[id], id); + }); - -function addRemoteRefs(ajv) { - for (var id in remoteRefs) ajv.addSchema(remoteRefs[id], id); + jsonSchemaTest(instances, { + description: 'JSON-Schema Test Suite draft-0' + draft + ': ' + instances.length + ' ajv instances with different options', + suites: {tests: tests}, + only: [ + // 'type', 'not', 'allOf', 'anyOf', 'oneOf', 'enum', + // 'maximum', 'minimum', 'multipleOf', 'maxLength', 'minLength', 'pattern', + // 'properties', 'patternProperties', 'additionalProperties', + // 'dependencies', 'required', + // 'maxProperties', 'minProperties', 'maxItems', 'minItems', + // 'items', 'additionalItems', 'uniqueItems', + // 'optional/format', 'optional/bignum', + // 'ref', 'refRemote', 'definitions', + ], + skip: ['optional/zeroTerminatedFloats'], + assert: require('./chai').assert, + afterError: after.error, + afterEach: after.each, + cwd: __dirname, + hideFolder: 'draft' + draft + '/', + timeout: 120000 + }); } diff --git a/spec/options.spec.js b/spec/options.spec.js index 20d456c..dc58f42 100644 --- a/spec/options.spec.js +++ b/spec/options.spec.js @@ -85,77 +85,200 @@ describe('Ajv Options', function () { describe('ownProperties', function() { - it('should only validate against own properties of data if specified', function() { - var ajv = new Ajv({ ownProperties: true }); - var validate = ajv.compile({ - properties: { c: { type: 'number' } }, + var ajv, ajvOP, ajvOP1; + + beforeEach(function() { + ajv = new Ajv({ allErrors: true }); + ajvOP = new Ajv({ ownProperties: true, allErrors: true }); + ajvOP1 = new Ajv({ ownProperties: true }); + }); + + it('should only validate own properties with additionalProperties', function() { + var schema = { + properties: { a: { type: 'number' } }, additionalProperties: false - }); + }; - var triangle = { a: 1, b: 2 }; - function ColoredTriangle() { this.c = 3; } - ColoredTriangle.prototype = triangle; - var object = new ColoredTriangle(); - - validate(object).should.equal(true); - should.equal(validate.errors, null); + var obj = { a: 1 }; + var proto = { b: 2 }; + test(schema, obj, proto); }); - it('should only validate against own properties when using patternProperties', function() { - var ajv = new Ajv({ allErrors: true, ownProperties: true }); - var validate = ajv.compile({ + it('should only validate own properties with properties keyword', function() { + var schema = { + properties: { + a: { type: 'number' }, + b: { type: 'number' } + } + }; + + var obj = { a: 1 }; + var proto = { b: 'not a number' }; + test(schema, obj, proto); + }); + + it('should only validate own properties with required keyword', function() { + var schema = { + required: ['a', 'b'] + }; + + var obj = { a: 1 }; + var proto = { b: 2 }; + test(schema, obj, proto, 1, true); + }); + + it('should only validate own properties with required keyword - many properties', function() { + ajv = new Ajv({ allErrors: true, loopRequired: 1 }); + ajvOP = new Ajv({ ownProperties: true, allErrors: true, loopRequired: 1 }); + ajvOP1 = new Ajv({ ownProperties: true, loopRequired: 1 }); + + var schema = { + required: ['a', 'b', 'c', 'd'] + }; + + var obj = { a: 1, b: 2 }; + var proto = { c: 3, d: 4 }; + test(schema, obj, proto, 2, true); + }); + + it('should only validate own properties with required keyword as $data', function() { + ajv = new Ajv({ allErrors: true, $data: true }); + ajvOP = new Ajv({ ownProperties: true, allErrors: true, $data: true }); + ajvOP1 = new Ajv({ ownProperties: true, $data: true }); + + var schema = { + required: { $data: '0/req' }, + properties: { + req: { + type: 'array', + items: { type: 'string' } + } + } + }; + + var obj = { + req: ['a', 'b'], + a: 1 + }; + var proto = { b: 2 }; + test(schema, obj, proto, 1, true); + }); + + it('should only validate own properties with properties and required keyword', function() { + var schema = { + properties: { + a: { type: 'number' }, + b: { type: 'number' } + }, + required: ['a', 'b'] + }; + + var obj = { a: 1 }; + var proto = { b: 2 }; + test(schema, obj, proto, 1, true); + }); + + it('should only validate own properties with dependencies keyword', function() { + var schema = { + dependencies: { + a: ['c'], + b: ['d'] + } + }; + + var obj = { a: 1, c: 3 }; + var proto = { b: 2 }; + test(schema, obj, proto); + + obj = { a: 1, b: 2, c: 3 }; + proto = { d: 4 }; + test(schema, obj, proto, 1, true); + }); + + it('should only validate own properties with schema dependencies', function() { + var schema = { + dependencies: { + a: { not: { required: ['c'] } }, + b: { not: { required: ['d'] } } + } + }; + + var obj = { a: 1, d: 3 }; + var proto = { b: 2 }; + test(schema, obj, proto); + + obj = { a: 1, b: 2 }; + proto = { d: 4 }; + test(schema, obj, proto); + }); + + it('should only validate own properties with patternProperties', function() { + var schema = { patternProperties: { 'f.*o': { type: 'integer' } }, - }); + }; - var baz = { foooo: false, fooooooo: 42.31 }; - function FooThing() { this.foo = 'not a number'; } - FooThing.prototype = baz; - var object = new FooThing(); - - validate(object).should.equal(false); - validate.errors.should.have.length(1); + var obj = { fooo: 1 }; + var proto = { foo: 'not a number' }; + test(schema, obj, proto); }); - it('should only validate against own properties when using patternGroups', function() { - var ajv = new Ajv({ v5: true, allErrors: true, ownProperties: true }); - var validate = ajv.compile({ + it('should only validate own properties with patternGroups', function() { + ajv = new Ajv({ allErrors: true, patternGroups: true }); + ajvOP = new Ajv({ ownProperties: true, allErrors: true, patternGroups: true }); + + var schema = { patternGroups: { 'f.*o': { schema: { type: 'integer' } } } - }); + }; - var baz = { foooo: false, fooooooo: 42.31 }; - function FooThing() { this.foo = 'not a number'; } - FooThing.prototype = baz; - var object = new FooThing(); - - validate(object).should.equal(false); - validate.errors.should.have.length(1); + var obj = { fooo: 1 }; + var proto = { foo: 'not a number' }; + test(schema, obj, proto); }); - it('should only validate against own properties when using patternRequired', function() { - var ajv = new Ajv({ v5: true, allErrors: true, ownProperties: true }); - var validate = ajv.compile({ - patternRequired: [ 'f.*o' ] - }); + it('should only validate own properties with propertyNames', function() { + var schema = { + propertyNames: { + format: 'email' + } + }; - var baz = { foooo: false, fooooooo: 42.31 }; - function FooThing() { this.bar = 123; } - FooThing.prototype = baz; - var object = new FooThing(); - - validate(object).should.equal(false); - validate.errors.should.have.length(1); + var obj = { 'e@example.com': 2 }; + var proto = { 'not email': 1 }; + test(schema, obj, proto, 2); }); + + function test(schema, obj, proto, errors, reverse) { + errors = errors || 1; + var validate = ajv.compile(schema); + var validateOP = ajvOP.compile(schema); + var validateOP1 = ajvOP1.compile(schema); + var data = Object.create(proto); + for (var key in obj) data[key] = obj[key]; + + if (reverse) { + validate(data) .should.equal(true); + validateOP(data) .should.equal(false); + validateOP.errors .should.have.length(errors); + validateOP1(data) .should.equal(false); + validateOP1.errors .should.have.length(1); + } else { + validate(data) .should.equal(false); + validate.errors .should.have.length(errors); + validateOP(data) .should.equal(true); + validateOP1(data) .should.equal(true); + } + } }); describe('meta and validateSchema', function() { - it('should add draft-4 meta schema by default', function() { + it('should add draft-6 meta schema by default', function() { testOptionMeta(new Ajv); testOptionMeta(new Ajv({ meta: true })); function testOptionMeta(ajv) { - ajv.getSchema('http://json-schema.org/draft-04/schema') .should.be.a('function'); + ajv.getSchema('http://json-schema.org/draft-06/schema') .should.be.a('function'); ajv.validateSchema({ type: 'integer' }) .should.equal(true); ajv.validateSchema({ type: 123 }) .should.equal(false); should.not.throw(function() { ajv.addSchema({ type: 'integer' }); }); @@ -165,8 +288,8 @@ describe('Ajv Options', function () { it('should throw if meta: false and validateSchema: true', function() { var ajv = new Ajv({ meta: false }); - should.not.exist(ajv.getSchema('http://json-schema.org/draft-04/schema')); - should.throw(function() { ajv.addSchema({ type: 'integer' }, 'integer'); }); + should.not.exist(ajv.getSchema('http://json-schema.org/draft-06/schema')); + should.not.throw(function() { ajv.addSchema({ type: 'wrong_type' }, 'integer'); }); }); it('should skip schema validation with validateSchema: false', function() { @@ -188,24 +311,23 @@ describe('Ajv Options', function () { var ajv = new Ajv({ validateSchema: 'log' }); should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); loggedError .should.equal(true); - console.error = logError; + loggedError = false; ajv = new Ajv({ validateSchema: 'log', meta: false }); - should.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); + should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); + loggedError .should.equal(false); + console.error = logError; }); - it('should validate v5 schema', function() { - var ajv = new Ajv({ v5: true }); + it('should validate v6 schema', function() { + var ajv = new Ajv; ajv.validateSchema({ contains: { minimum: 2 } }) .should.equal(true); ajv.validateSchema({ contains: 2 }). should.equal(false); - - ajv = new Ajv; - ajv.validateSchema({ contains: 2 }). should.equal(true); }); it('should use option meta as default meta schema', function() { var meta = { - $schema: 'http://json-schema.org/draft-04/schema', + $schema: 'http://json-schema.org/draft-06/schema', properties: { myKeyword: { type: 'boolean' } } @@ -214,7 +336,7 @@ describe('Ajv Options', function () { ajv.validateSchema({ myKeyword: true }) .should.equal(true); ajv.validateSchema({ myKeyword: 2 }) .should.equal(false); ajv.validateSchema({ - $schema: 'http://json-schema.org/draft-04/schema', + $schema: 'http://json-schema.org/draft-06/schema', myKeyword: 2 }) .should.equal(true); @@ -263,20 +385,6 @@ describe('Ajv Options', function () { ajv.validate(schema, invalideDateTime) .should.equal(false); ajvFF.validate(schema, invalideDateTime) .should.equal(true); }); - - it('should not validate formatMaximum/Minimum if option format == false', function() { - var ajv = new Ajv({ v5: true, allErrors: true }) - , ajvFF = new Ajv({ v5: true, allErrors: true, format: false }); - - var schema = { - format: 'date', - formatMaximum: '2015-08-01' - }; - - var date = '2015-09-01'; - ajv.validate(schema, date) .should.equal(false); - ajvFF.validate(schema, date) .should.equal(true); - }); }); @@ -726,58 +834,57 @@ describe('Ajv Options', function () { describe('extendRefs', function() { - describe('= true and default', function() { + describe('= true', function() { it('should allow extending $ref with other keywords', function() { - test(new Ajv, true); test(new Ajv({ extendRefs: true }), true); }); - it('should log warning when other keywords are used with $ref', function() { - testWarning(new Ajv, /keywords\sused/); - }); - it('should NOT log warning if extendRefs is true', function() { testWarning(new Ajv({ extendRefs: true })); }); }); - describe('= "ignore"', function() { + describe('= "ignore" and default', function() { it('should ignore other keywords when $ref is used', function() { + test(new Ajv); test(new Ajv({ extendRefs: 'ignore' }), false); }); it('should log warning when other keywords are used with $ref', function() { + testWarning(new Ajv, /keywords\signored/); testWarning(new Ajv({ extendRefs: 'ignore' }), /keywords\signored/); }); }); describe('= "fail"', function() { it('should fail schema compilation if other keywords are used with $ref', function() { - var ajv = new Ajv({ extendRefs: 'fail' }); + testFail(new Ajv({ extendRefs: 'fail' })); - should.throw(function() { - var schema = { - "definitions": { - "int": { "type": "integer" } - }, - "$ref": "#/definitions/int", - "minimum": 10 - }; - ajv.compile(schema); - }); + function testFail(ajv) { + should.throw(function() { + var schema = { + "definitions": { + "int": { "type": "integer" } + }, + "$ref": "#/definitions/int", + "minimum": 10 + }; + ajv.compile(schema); + }); - should.not.throw(function() { - var schema = { - "definitions": { - "int": { "type": "integer" } - }, - "allOf": [ - { "$ref": "#/definitions/int" }, - { "minimum": 10 } - ] - }; - ajv.compile(schema); - }); + should.not.throw(function() { + var schema = { + "definitions": { + "int": { "type": "integer" } + }, + "allOf": [ + { "$ref": "#/definitions/int" }, + { "minimum": 10 } + ] + }; + ajv.compile(schema); + }); + } }); }); @@ -822,9 +929,9 @@ describe('Ajv Options', function () { function testWarning(ajv, msgPattern) { var oldConsole; try { - oldConsole = console.log; + oldConsole = console.warn; var consoleMsg; - console.log = function() { + console.warn = function() { consoleMsg = Array.prototype.join.call(arguments, ' '); }; @@ -840,38 +947,43 @@ describe('Ajv Options', function () { if (msgPattern) consoleMsg .should.match(msgPattern); else should.not.exist(consoleMsg); } finally { - console.log = oldConsole; + console.warn = oldConsole; } } }); describe('sourceCode', function() { - describe('= true and default', function() { - it('should add sourceCode property', function() { - test(new Ajv); + describe('= true', function() { + it('should add source.code property', function() { test(new Ajv({sourceCode: true})); function test(ajv) { var validate = ajv.compile({ "type": "number" }); - validate.sourceCode .should.be.a('string'); + validate.source.code .should.be.a('string'); } }); }); - describe('= false', function() { - it('should not add sourceCode property', function() { - var ajv = new Ajv({sourceCode: false}); - var validate = ajv.compile({ "type": "number" }); - should.not.exist(validate.sourceCode); + describe('= false and default', function() { + it('should not add source and sourceCode properties', function() { + test(new Ajv); + test(new Ajv({sourceCode: false})); + + function test(ajv) { + var validate = ajv.compile({ "type": "number" }); + should.not.exist(validate.source); + should.not.exist(validate.sourceCode); + } }); }); }); describe('unknownFormats', function() { - describe('= true (will be default in 5.0.0)', function() { + describe('= true (default)', function() { it('should fail schema compilation if unknown format is used', function() { + test(new Ajv); test(new Ajv({unknownFormats: true})); function test(ajv) { @@ -882,7 +994,8 @@ describe('Ajv Options', function () { }); it('should fail validation if unknown format is used via $data', function() { - test(new Ajv({v5: true, unknownFormats: true})); + test(new Ajv({$data: true})); + test(new Ajv({$data: true, unknownFormats: true})); function test(ajv) { var validate = ajv.compile({ @@ -892,7 +1005,7 @@ describe('Ajv Options', function () { } }); - validate({foo: 1, bar: 'unknown'}) .should.equal(true); + validate({foo: 1, bar: 'unknown'}) .should.equal(false); validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true); validate({foo: '20161016', bar: 'date'}) .should.equal(false); validate({foo: '20161016'}) .should.equal(true); @@ -902,9 +1015,8 @@ describe('Ajv Options', function () { }); }); - describe('= "ignore (default)"', function() { + describe('= "ignore (default before 5.0.0)"', function() { it('should pass schema compilation and be valid if unknown format is used', function() { - test(new Ajv); test(new Ajv({unknownFormats: 'ignore'})); function test(ajv) { @@ -914,8 +1026,7 @@ describe('Ajv Options', function () { }); it('should be valid if unknown format is used via $data', function() { - test(new Ajv({v5: true})); - test(new Ajv({v5: true, unknownFormats: 'ignore'})); + test(new Ajv({$data: true, unknownFormats: 'ignore'})); function test(ajv) { var validate = ajv.compile({ @@ -949,7 +1060,7 @@ describe('Ajv Options', function () { }); it('should be valid if whitelisted unknown format is used via $data', function() { - test(new Ajv({v5: true, unknownFormats: ['allowed']})); + test(new Ajv({$data: true, unknownFormats: ['allowed']})); function test(ajv) { var validate = ajv.compile({ @@ -960,7 +1071,7 @@ describe('Ajv Options', function () { }); validate({foo: 1, bar: 'allowed'}) .should.equal(true); - validate({foo: 1, bar: 'unknown'}) .should.equal(true); + validate({foo: 1, bar: 'unknown'}) .should.equal(false); validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true); validate({foo: '20161016', bar: 'date'}) .should.equal(false); validate({foo: '20161016'}) .should.equal(true); @@ -971,4 +1082,141 @@ describe('Ajv Options', function () { }); }); }); + + + describe('processCode', function() { + it('should process generated code', function() { + var ajv = new Ajv; + var validate = ajv.compile({type: 'string'}); + validate.toString().split('\n').length .should.equal(1); + + var beautify = require('js-beautify').js_beautify; + var ajvPC = new Ajv({processCode: beautify}); + validate = ajvPC.compile({type: 'string'}); + validate.toString().split('\n').length .should.be.above(1); + validate('foo') .should.equal(true); + validate(1) .should.equal(false); + }); + }); + + + describe('serialize', function() { + var serializeCalled; + + it('should use custom function to serialize schema to string', function() { + serializeCalled = undefined; + var ajv = new Ajv({ serialize: serialize }); + ajv.addSchema({ type: 'string' }); + should.equal(serializeCalled, true); + }); + + function serialize(schema) { + serializeCalled = true; + return JSON.stringify(schema); + } + }); + + + describe('patternGroups without draft-06 meta-schema', function() { + it('should use default meta-schema', function() { + var ajv = new Ajv({ + patternGroups: true, + meta: require('../lib/refs/json-schema-draft-04.json') + }); + + ajv.compile({ + patternGroups: { + '^foo': { + schema: { type: 'number' }, + minimum: 1 + } + } + }); + + should.throw(function() { + ajv.compile({ + patternGroups: { + '^foo': { + schema: { type: 'wrong_type' }, + minimum: 1 + } + } + }); + }); + }); + + it('should not use meta-schema if not available', function() { + var ajv = new Ajv({ + patternGroups: true, + meta: false + }); + + ajv.compile({ + patternGroups: { + '^foo': { + schema: { type: 'number' }, + minimum: 1 + } + } + }); + + ajv.compile({ + patternGroups: { + '^foo': { + schema: { type: 'wrong_type' }, + minimum: 1 + } + } + }); + }); + }); + + + describe('schemaId', function() { + describe('= undefined (default)', function() { + it('should throw if both id and $id are available and different', function() { + var ajv = new Ajv; + + ajv.compile({ + id: 'mySchema', + $id: 'mySchema' + }); + + should.throw(function() { + ajv.compile({ + id: 'mySchema1', + $id: 'mySchema2' + }); + }); + }); + }); + + describe('= "id"', function() { + it('should use id and ignore $id', function() { + var ajv = new Ajv({schemaId: 'id'}); + + ajv.addSchema({ id: 'mySchema1', type: 'string' }); + var validate = ajv.getSchema('mySchema1'); + validate('foo') .should.equal(true); + validate(1) .should.equal(false); + + validate = ajv.compile({ $id: 'mySchema2', type: 'string' }); + should.not.exist(ajv.getSchema('mySchema2')); + }); + }); + + describe('= "$id"', function() { + it('should use $id and ignore id', function() { + var ajv = new Ajv({schemaId: '$id'}); + + ajv.addSchema({ $id: 'mySchema1', type: 'string' }); + var validate = ajv.getSchema('mySchema1'); + validate('foo') .should.equal(true); + validate(1) .should.equal(false); + + validate = ajv.compile({ id: 'mySchema2', type: 'string' }); + should.not.exist(ajv.getSchema('mySchema2')); + }); + }); + }); }); diff --git a/spec/resolve.spec.js b/spec/resolve.spec.js index d3ec647..d1033d9 100644 --- a/spec/resolve.spec.js +++ b/spec/resolve.spec.js @@ -219,7 +219,7 @@ describe('resolve', function () { var ajv = new Ajv({ verbose: true }); var schemaMessage = { - $schema: "http://json-schema.org/draft-04/schema#", + $schema: "http://json-schema.org/draft-06/schema#", id: "http://e.com/message.json#", type: "object", required: ["header"], @@ -235,7 +235,7 @@ describe('resolve', function () { // header schema var schemaHeader = { - $schema: "http://json-schema.org/draft-04/schema#", + $schema: "http://json-schema.org/draft-06/schema#", id: "http://e.com/header.json#", type: "object", properties: { diff --git a/spec/schema-tests.spec.js b/spec/schema-tests.spec.js index 4dc46ee..bcca994 100644 --- a/spec/schema-tests.spec.js +++ b/spec/schema-tests.spec.js @@ -48,6 +48,7 @@ jsonSchemaTest(instances, { function addRemoteRefs(ajv) { + ajv.addMetaSchema(require('../lib/refs/json-schema-draft-04.json')); for (var id in remoteRefs) ajv.addSchema(remoteRefs[id], id); ajv.addSchema(remoteRefsWithIds); } diff --git a/spec/tests/issues/13_root_ref_in_ref_in_remote_ref.json b/spec/tests/issues/13_root_ref_in_ref_in_remote_ref.json index 07e14c2..5947cc5 100644 --- a/spec/tests/issues/13_root_ref_in_ref_in_remote_ref.json +++ b/spec/tests/issues/13_root_ref_in_ref_in_remote_ref.json @@ -1,13 +1,22 @@ [ { "description": "root ref in remote ref (#13)", - "schema": { - "id": "http://localhost:1234/object", - "type": "object", - "properties": { - "name": { "$ref": "name.json#/definitions/orNull" } + "schemas": [ + { + "id": "http://localhost:1234/issue13_1", + "type": "object", + "properties": { + "name": { "$ref": "name.json#/definitions/orNull" } + } + }, + { + "$id": "http://localhost:1234/issue13_2", + "type": "object", + "properties": { + "name": { "$ref": "name.json#/definitions/orNull" } + } } - }, + ], "tests": [ { "description": "string is valid", diff --git a/spec/tests/issues/14_ref_in_remote_ref_with_id.json b/spec/tests/issues/14_ref_in_remote_ref_with_id.json index 26e1a17..ae9f3f8 100644 --- a/spec/tests/issues/14_ref_in_remote_ref_with_id.json +++ b/spec/tests/issues/14_ref_in_remote_ref_with_id.json @@ -1,11 +1,18 @@ [ { "description": "ref in remote ref with ids", - "schema": { - "id": "http://localhost:1234/issue14a.json", - "type": "array", - "items": { "$ref": "foo.json" } - }, + "schemas": [ + { + "id": "http://localhost:1234/issue14a_1.json", + "type": "array", + "items": { "$ref": "foo.json" } + }, + { + "$id": "http://localhost:1234/issue14a_2.json", + "type": "array", + "items": { "$ref": "foo.json" } + } + ], "tests": [ { "description": "string is valid", @@ -29,11 +36,18 @@ }, { "description": "remote ref in definitions in remote ref with ids (#14)", - "schema": { - "id": "http://localhost:1234/issue14b.json", - "type": "array", - "items": { "$ref": "buu.json#/definitions/buu" } - }, + "schemas": [ + { + "id": "http://localhost:1234/issue14b_1.json", + "type": "array", + "items": { "$ref": "buu.json#/definitions/buu" } + }, + { + "$id": "http://localhost:1234/issue14b_2.json", + "type": "array", + "items": { "$ref": "buu.json#/definitions/buu" } + } + ], "tests": [ { "description": "string is valid", diff --git a/spec/tests/issues/170_ref_and_id_in_sibling.json b/spec/tests/issues/170_ref_and_id_in_sibling.json index 57e7f9c..eb48768 100644 --- a/spec/tests/issues/170_ref_and_id_in_sibling.json +++ b/spec/tests/issues/170_ref_and_id_in_sibling.json @@ -1,21 +1,38 @@ [ { "description": "sibling property has id (#170)", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "http://example.com/base_object", - "type": "object", - "properties": { - "title": { - "id": "http://example.com/title", - "type": "string" + "schemas": [ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://example.com/base_object_1", + "type": "object", + "properties": { + "title": { + "id": "http://example.com/title", + "type": "string" + }, + "file": { "$ref": "#/definitions/file-entry" } }, - "file": { "$ref": "#/definitions/file-entry" } + "definitions": { + "file-entry": { "type": "string" } + } }, - "definitions": { - "file-entry": { "type": "string" } + { + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://example.com/base_object_2", + "type": "object", + "properties": { + "title": { + "$id": "http://example.com/title", + "type": "string" + }, + "file": { "$ref": "#/definitions/file-entry" } + }, + "definitions": { + "file-entry": { "type": "string" } + } } - }, + ], "tests": [ { "description": "valid object", @@ -37,21 +54,38 @@ }, { "description": "sibling item has id", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "http://example.com/base_array", - "type": "array", - "items": [ - { - "id": "http://example.com/0", - "type": "string" - }, - { "$ref": "#/definitions/file-entry" } - ], - "definitions": { - "file-entry": { "type": "string" } + "schemas": [ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://example.com/base_array_1", + "type": "array", + "items": [ + { + "id": "http://example.com/0", + "type": "string" + }, + { "$ref": "#/definitions/file-entry" } + ], + "definitions": { + "file-entry": { "type": "string" } + } + }, + { + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://example.com/base_array_2", + "type": "array", + "items": [ + { + "$id": "http://example.com/0", + "type": "string" + }, + { "$ref": "#/definitions/file-entry" } + ], + "definitions": { + "file-entry": { "type": "string" } + } } - }, + ], "tests": [ { "description": "valid array", @@ -67,20 +101,36 @@ }, { "description": "sibling schema in anyOf has id", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "http://example.com/base_anyof", - "anyOf": [ - { - "id": "http://example.com/0", - "type": "number" - }, - { "$ref": "#/definitions/def" } - ], - "definitions": { - "def": { "type": "string" } + "schemas": [ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://example.com/base_anyof_1", + "anyOf": [ + { + "id": "http://example.com/0", + "type": "number" + }, + { "$ref": "#/definitions/def" } + ], + "definitions": { + "def": { "type": "string" } + } + }, + { + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://example.com/base_anyof_2", + "anyOf": [ + { + "$id": "http://example.com/0", + "type": "number" + }, + { "$ref": "#/definitions/def" } + ], + "definitions": { + "def": { "type": "string" } + } } - }, + ], "tests": [ { "description": "valid string", @@ -101,20 +151,36 @@ }, { "description": "sibling schema in oneOf has id", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "http://example.com/base_oneof", - "oneOf": [ - { - "id": "http://example.com/0", - "type": "number" - }, - { "$ref": "#/definitions/def" } - ], - "definitions": { - "def": { "type": "string" } + "schemas": [ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://example.com/base_oneof_1", + "oneOf": [ + { + "id": "http://example.com/0", + "type": "number" + }, + { "$ref": "#/definitions/def" } + ], + "definitions": { + "def": { "type": "string" } + } + }, + { + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://example.com/base_oneof_2", + "oneOf": [ + { + "$id": "http://example.com/0", + "type": "number" + }, + { "$ref": "#/definitions/def" } + ], + "definitions": { + "def": { "type": "string" } + } } - }, + ], "tests": [ { "description": "valid string", @@ -135,21 +201,38 @@ }, { "description": "sibling schema in allOf has id", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "http://example.com/base_allof", - "allOf": [ - { - "id": "http://example.com/0", - "type": "string", - "maxLength": 3 - }, - { "$ref": "#/definitions/def" } - ], - "definitions": { - "def": { "type": "string" } + "schemas": [ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://example.com/base_allof_1", + "allOf": [ + { + "id": "http://example.com/0", + "type": "string", + "maxLength": 3 + }, + { "$ref": "#/definitions/def" } + ], + "definitions": { + "def": { "type": "string" } + } + }, + { + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://example.com/base_allof_2", + "allOf": [ + { + "$id": "http://example.com/0", + "type": "string", + "maxLength": 3 + }, + { "$ref": "#/definitions/def" } + ], + "definitions": { + "def": { "type": "string" } + } } - }, + ], "tests": [ { "description": "valid string", @@ -165,21 +248,38 @@ }, { "description": "sibling schema in dependencies has id", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "http://example.com/base_dependencies", - "type": "object", - "dependencies": { - "foo": { - "id": "http://example.com/foo", - "required": [ "bar" ] + "schemas": [ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://example.com/base_dependencies_1", + "type": "object", + "dependencies": { + "foo": { + "id": "http://example.com/foo", + "required": [ "bar" ] + }, + "bar": { "$ref": "#/definitions/def" } }, - "bar": { "$ref": "#/definitions/def" } + "definitions": { + "def": { "required": [ "baz" ] } + } }, - "definitions": { - "def": { "required": [ "baz" ] } + { + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://example.com/base_dependencies_2", + "type": "object", + "dependencies": { + "foo": { + "$id": "http://example.com/foo", + "required": [ "bar" ] + }, + "bar": { "$ref": "#/definitions/def" } + }, + "definitions": { + "def": { "required": [ "baz" ] } + } } - }, + ], "tests": [ { "description": "valid object", diff --git a/spec/tests/issues/1_ids_in_refs.json b/spec/tests/issues/1_ids_in_refs.json index eeddc08..5c6eed1 100644 --- a/spec/tests/issues/1_ids_in_refs.json +++ b/spec/tests/issues/1_ids_in_refs.json @@ -1,15 +1,26 @@ [ { "description": "IDs in refs without root id (#1)", - "schema": { - "definitions": { - "int": { - "id": "#int", - "type": "integer" - } + "schemas": [ + { + "definitions": { + "int": { + "id": "#int", + "type": "integer" + } + }, + "$ref": "#int" }, - "$ref": "#int" - }, + { + "definitions": { + "int": { + "$id": "#int", + "type": "integer" + } + }, + "$ref": "#int" + } + ], "tests": [ { "description": "valid", "data": 1, "valid": true }, { "description": "invalid", "data": "foo", "valid": false } @@ -17,16 +28,28 @@ }, { "description": "IDs in refs with root id", - "schema": { - "id": "http://example.com/int.json", - "definitions": { - "int": { - "id": "#int", - "type": "integer" - } + "schemas": [ + { + "id": "http://example.com/int_1.json", + "definitions": { + "int": { + "id": "#int", + "type": "integer" + } + }, + "$ref": "#int" }, - "$ref": "#int" - }, + { + "$id": "http://example.com/int_2.json", + "definitions": { + "int": { + "$id": "#int", + "type": "integer" + } + }, + "$ref": "#int" + } + ], "tests": [ { "description": "valid", "data": 1, "valid": true }, { "description": "invalid", "data": "foo", "valid": false } diff --git a/spec/tests/issues/27_recursive_reference.json b/spec/tests/issues/27_recursive_reference.json index cf3f952..fb9a542 100644 --- a/spec/tests/issues/27_recursive_reference.json +++ b/spec/tests/issues/27_recursive_reference.json @@ -1,33 +1,62 @@ [ { "description": "Recursive reference (#27)", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "testrec", - "type": "object", - "properties": { - "layout": { - "id": "layout", - "type": "object", - "properties": { - "layout": { "type": "string" }, - "panels": { - "type": "array", - "items": { - "anyOf": [ - { "type": "string" }, - { "$ref": "layout" } - ] + "schemas": [ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "testrec_1", + "type": "object", + "properties": { + "layout": { + "id": "layout", + "type": "object", + "properties": { + "layout": { "type": "string" }, + "panels": { + "type": "array", + "items": { + "anyOf": [ + { "type": "string" }, + { "$ref": "layout" } + ] + } } - } - }, - "required": [ - "layout", - "panels" - ] + }, + "required": [ + "layout", + "panels" + ] + } + } + }, + { + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "testrec_2", + "type": "object", + "properties": { + "layout": { + "$id": "layout", + "type": "object", + "properties": { + "layout": { "type": "string" }, + "panels": { + "type": "array", + "items": { + "anyOf": [ + { "type": "string" }, + { "$ref": "layout" } + ] + } + } + }, + "required": [ + "layout", + "panels" + ] + } } } - }, + ], "tests": [ { "description": "empty object is valid", diff --git a/spec/tests/issues/311_quotes_in_refs.json b/spec/tests/issues/311_quotes_in_refs.json index a248a19..04de75d 100644 --- a/spec/tests/issues/311_quotes_in_refs.json +++ b/spec/tests/issues/311_quotes_in_refs.json @@ -3,20 +3,10 @@ "description": "quotes in refs (#311)", "schema": { "properties": { - "foo\"bar": { "$ref": "#/definitions/foo\"bar" }, - "foo\\bar": { "$ref": "#/definitions/foo\\bar" }, - "foo\nbar": { "$ref": "#/definitions/foo\nbar" }, - "foo\rbar": { "$ref": "#/definitions/foo\rbar" }, - "foo\tbar": { "$ref": "#/definitions/foo\tbar" }, - "foo\fbar": { "$ref": "#/definitions/foo\fbar" } + "foo\"bar": { "$ref": "#/definitions/foo\"bar" } }, "definitions": { - "foo\"bar": { "type": "number" }, - "foo\\bar": { "type": "number" }, - "foo\nbar": { "type": "number" }, - "foo\rbar": { "type": "number" }, - "foo\tbar": { "type": "number" }, - "foo\fbar": { "type": "number" } + "foo\"bar": { "type": "number" } } }, "tests": [ diff --git a/spec/tests/issues/63_id_property_not_in_schema.json b/spec/tests/issues/63_id_property_not_in_schema.json index 73c16df..8d9d36e 100644 --- a/spec/tests/issues/63_id_property_not_in_schema.json +++ b/spec/tests/issues/63_id_property_not_in_schema.json @@ -1,12 +1,20 @@ [ { "description": "id property in referenced schema in object that is not a schema (#63)", - "schema": { - "type" : "object", - "properties": { - "title": { "$ref": "http://json-schema.org/draft-04/schema#/properties/title" } + "schemas": [ + { + "type" : "object", + "properties": { + "title": { "$ref": "http://json-schema.org/draft-04/schema#/properties/title" } + } + }, + { + "type" : "object", + "properties": { + "title": { "$ref": "http://json-schema.org/draft-06/schema#/properties/title" } + } } - }, + ], "tests": [ { "description": "empty object is valid", diff --git a/spec/tests/issues/70_1_recursive_hash_ref_in_remote_ref.json b/spec/tests/issues/70_1_recursive_hash_ref_in_remote_ref.json index 371c40f..6f2c211 100644 --- a/spec/tests/issues/70_1_recursive_hash_ref_in_remote_ref.json +++ b/spec/tests/issues/70_1_recursive_hash_ref_in_remote_ref.json @@ -2,7 +2,7 @@ { "description": "hash ref inside hash ref in remote ref (#70, was passing)", "schema": { - "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" }, "tests": [ { "data": 1, "valid": true, "description": "positive integer is valid" }, @@ -12,10 +12,16 @@ }, { "description": "hash ref inside hash ref in remote ref with id (#70, was passing)", - "schema": { - "id": "http://example.com/my_schema.json", + "schemas": [ + { + "id": "http://example.com/my_schema_1.json", "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" - }, + }, + { + "$id": "http://example.com/my_schema_2.json", + "$ref": "http://json-schema.org/draft-06/schema#/definitions/nonNegativeIntegerDefault0" + } + ], "tests": [ { "data": 1, "valid": true, "description": "positive integer is valid" }, { "data": 0, "valid": true, "description": "zero is valid" }, diff --git a/spec/tests/rules/dependencies.json b/spec/tests/rules/dependencies.json new file mode 100644 index 0000000..9a3f616 --- /dev/null +++ b/spec/tests/rules/dependencies.json @@ -0,0 +1,27 @@ +[ + { + "description": "dependencies keyword with empty array", + "schema": { + "dependencies": { + "foo": [] + } + }, + "tests": [ + { + "description": "object with property is valid", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + } +] diff --git a/spec/tests/rules/format.json b/spec/tests/rules/format.json index d0b0334..4eb9c9d 100644 --- a/spec/tests/rules/format.json +++ b/spec/tests/rules/format.json @@ -47,9 +47,27 @@ "valid": true }, { - "description": "valid relative uri", + "description": "invalid relative uri-reference", "data": "/abc", + "valid": false + } + ] + }, + { + "description": "format: uri-template", + "schema": { + "format": "uri-template" + }, + "tests": [ + { + "description": "valid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term}", "valid": true + }, + { + "description": "invalid uri-template", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": false } ] }, @@ -71,6 +89,387 @@ } ] }, + { + "description": "validation of URL strings", + "schema": {"format": "url"}, + "tests": [ + { + "data": "http://foo.com/blah_blah", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://foo.com/blah_blah/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://foo.com/blah_blah_(wikipedia)", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://foo.com/blah_blah_(wikipedia)_(again)", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://www.example.com/wpstyle/?p=364", + "description": "a valid URL string", + "valid": true + }, + { + "data": "https://www.example.com/foo/?bar=baz&inga=42&quux", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://✪df.ws/123", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://userid:password@example.com:8080", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://userid:password@example.com:8080/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://userid@example.com", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://userid@example.com/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://userid@example.com:8080", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://userid@example.com:8080/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://userid:password@example.com", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://userid:password@example.com/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://142.42.1.1/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://142.42.1.1:8080/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://➡.ws/䨹", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://⌘.ws", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://⌘.ws/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://foo.com/blah_(wikipedia)#cite-1", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://foo.com/unicode_(✪)_in_parens", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://foo.com/(something)?after=parens", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://☺.damowmow.com/", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://code.google.com/events/#&product=browser", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://j.mp", + "description": "a valid URL string", + "valid": true + }, + { + "data": "ftp://foo.bar/baz", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://مثال.إختبار", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://例子.测试", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://उदाहरण.परीक्षा", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://1337.net", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://a.b-c.de", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://223.255.255.254", + "description": "a valid URL string", + "valid": true + }, + { + "data": "http://", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://.", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://..", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://../", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://?", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://??", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://??/", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://#", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://##", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://##/", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://foo.bar?q=Spaces should be encoded", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "//", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "//a", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "///a", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "///", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http:///a", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "foo.com", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "rdar://1234", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "h://test", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http:// shouldfail.com", + "description": "an invalid URL string", + "valid": false + }, + { + "data": ":// should fail", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://foo.bar/foo(bar)baz quux", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "ftps://foo.bar/", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://-error-.invalid/", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://a.b--c.de/", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://-a.b.co", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://a.b-.co", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://0.0.0.0", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://10.1.1.0", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://10.1.1.255", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://224.1.1.1", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://1.1.1.1.1", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://123.123.123", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://3628126748", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://.www.foo.bar/", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://www.foo.bar./", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://.www.foo.bar./", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://10.1.1.1", + "description": "an invalid URL string", + "valid": false + }, + { + "data": "http://10.1.1.254", + "description": "an invalid URL string", + "valid": false + } + ] + }, { "description": "validation of date strings", "schema": {"format": "date"}, diff --git a/spec/tests/rules/required.json b/spec/tests/rules/required.json new file mode 100644 index 0000000..c84f4fd --- /dev/null +++ b/spec/tests/rules/required.json @@ -0,0 +1,25 @@ +[ + { + "description": "required keyword with empty array", + "schema": { + "required": [] + }, + "tests": [ + { + "description": "object with property is valid", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + } +] diff --git a/spec/v5.spec.js b/spec/v5.spec.js deleted file mode 100644 index 66cc567..0000000 --- a/spec/v5.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -var jsonSchemaTest = require('json-schema-test') - , getAjvInstances = require('./ajv_instances') - , options = require('./ajv_options') - , suite = require('./browser_test_suite') - , after = require('./after_test'); - -var instances = getAjvInstances(options, { v5: true, unknownFormats: ['allowedUnknown'] }); - - -jsonSchemaTest(instances, { - description: 'v5 schemas tests of ' + instances.length + ' ajv instances with different options', - suites: { - 'v5 proposals': typeof window == 'object' - ? suite(require('./v5/{**/,}*.json', {mode: 'list'})) - : './v5/{**/,}*.json' - }, - assert: require('./chai').assert, - afterError: after.error, - afterEach: after.each, - cwd: __dirname, - hideFolder: 'v5/', - timeout: 90000 -}); diff --git a/spec/v5/$data/formatMaximum.json b/spec/v5/$data/formatMaximum.json deleted file mode 100644 index c01e5c5..0000000 --- a/spec/v5/$data/formatMaximum.json +++ /dev/null @@ -1,595 +0,0 @@ -[ - { - "description": "formatMaximum in the property", - "schema": { - "properties": { - "finalDate": { - "format": "date", - "formatMaximum": { "$data": "1/beforeThan" } - }, - "beforeThan": {} - } - }, - "tests": [ - { - "description": "after the maximum date is invalid", - "data": { - "finalDate": "2015-11-09", - "beforeThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "finalDate": "2015-08-17", - "beforeThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "before the maximum date is valid", - "data": { - "finalDate": "2014-12-03", - "beforeThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "ignores non-strings", - "data": { - "finalDate": 2015, - "beforeThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "fails if value of formatMaximum is not string", - "data": { - "finalDate": "2014-12-03", - "beforeThan": 2015 - }, - "valid": false - }, - { - "description": "valid if value of formatMaximum is undefined", - "data": { - "finalDate": "2014-12-03" - }, - "valid": true - } - ] - }, - { - "description": "formatMaximum in the property with formatExclusiveMaximum", - "schema": { - "properties": { - "finalDate": { - "format": "date", - "formatMaximum": { "$data": "1/beforeThan" }, - "formatExclusiveMaximum": true - }, - "beforeThan": {} - } - }, - "tests": [ - { - "description": "after the maximum date is still invalid", - "data": { - "finalDate": "2015-11-09", - "beforeThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "finalDate": "2015-08-17", - "beforeThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "before the maximum date is still valid", - "data": { - "finalDate": "2014-12-03", - "beforeThan": "2015-08-17" - }, - "valid": true - } - ] - }, - - { - "description": "formatMaximum with format in the property", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMaximum": "2015-08-17" - }, - "whenFormat": {} - } - }, - "tests": [ - { - "description": "after the maximum date is invalid", - "data": { - "when": "2015-11-09", - "whenFormat": "date" - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "when": "2015-08-17", - "whenFormat": "date" - }, - "valid": true - }, - { - "description": "before the maximum date is valid", - "data": { - "when": "2014-12-03", - "whenFormat": "date" - }, - "valid": true - }, - { - "description": "ignores non-strings", - "data": { - "when": 2015, - "whenFormat": "date" - }, - "valid": true - } - ] - }, - { - "description": "formatMaximum with format in the property and with formatExclusiveMaximum", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMaximum": "2015-08-17", - "formatExclusiveMaximum": true - }, - "whenFormat": {} - } - }, - "tests": [ - { - "description": "after the maximum date is still invalid", - "data": { - "when": "2015-11-09", - "whenFormat": "date" - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "when": "2015-08-17", - "whenFormat": "date" - }, - "valid": false - }, - { - "description": "before the maximum date is still valid", - "data": { - "when": "2014-12-03", - "whenFormat": "date" - }, - "valid": true - } - ] - }, - - { - "description": "formatMaximum and format in the properties", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMaximum": { "$data": "1/beforeThan" } - }, - "whenFormat": {}, - "beforeThan": {} - } - }, - "tests": [ - { - "description": "after the maximum date is invalid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "beforeThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "beforeThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "before the maximum date is valid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "beforeThan": "2015-08-17" - }, - "valid": true - } - ] - }, - { - "description": "formatMaximum and format in the properties with formatExclusiveMaximum", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMaximum": { "$data": "1/beforeThan" }, - "formatExclusiveMaximum": true - }, - "whenFormat": {}, - "beforeThan": {} - } - }, - "tests": [ - { - "description": "after the maximum date is still invalid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "beforeThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "beforeThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "before the maximum date is still valid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "beforeThan": "2015-08-17" - }, - "valid": true - } - ] - }, - - { - "description": "formatExclusiveMaximum in the property", - "schema": { - "properties": { - "finalDate": { - "format": "date", - "formatMaximum": "2015-08-17", - "formatExclusiveMaximum": { "$data": "1/maxIsExclusive" } - }, - "maxIsExclusive": {} - } - }, - "tests": [ - { - "description": "after the maximum date is invalid", - "data": { - "finalDate": "2015-11-09", - "maxIsExclusive": false - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "finalDate": "2015-08-17" - }, - "valid": true - }, - { - "description": "before the maximum date is valid", - "data": { - "finalDate": "2014-12-03" - }, - "valid": true - }, - { - "description": "after the maximum date is still invalid", - "data": { - "finalDate": "2015-11-09", - "maxIsExclusive": true - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "finalDate": "2015-08-17", - "maxIsExclusive": true - }, - "valid": false - }, - { - "description": "before the maximum date is still valid", - "data": { - "finalDate": "2014-12-03", - "maxIsExclusive": true - }, - "valid": true - }, - { - "description": "fails if value of formatExclusiveMaximum is not boolean", - "data": { - "finalDate": "2014-12-03", - "maxIsExclusive": "false" - }, - "valid": false - } - ] - }, - - { - "description": "formatMaximum and formatExclusiveMaximum in the properties", - "schema": { - "properties": { - "finalDate": { - "format": "date", - "formatMaximum": { "$data": "1/beforeThan" }, - "formatExclusiveMaximum": { "$data": "1/maxIsExclusive" } - }, - "beforeThan": {}, - "maxIsExclusive": {} - } - }, - "tests": [ - { - "description": "after the maximum date is invalid", - "data": { - "finalDate": "2015-11-09", - "beforeThan": "2015-08-17", - "maxIsExclusive": false - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "finalDate": "2015-08-17", - "beforeThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "before the maximum date is valid", - "data": { - "finalDate": "2014-12-03", - "beforeThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "after the maximum date is still invalid", - "data": { - "finalDate": "2015-11-09", - "beforeThan": "2015-08-17", - "maxIsExclusive": true - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "finalDate": "2015-08-17", - "beforeThan": "2015-08-17", - "maxIsExclusive": true - }, - "valid": false - }, - { - "description": "before the maximum date is still valid", - "data": { - "finalDate": "2014-12-03", - "beforeThan": "2015-08-17", - "maxIsExclusive": true - }, - "valid": true - }, - { - "description": "fails if value of formatExclusiveMaximum is not boolean", - "data": { - "finalDate": "2014-12-03", - "beforeThan": "2015-08-17", - "maxIsExclusive": "false" - }, - "valid": false - }, - { - "description": "fails if value of formatMaximum is not string", - "data": { - "finalDate": "2014-12-03", - "beforeThan": 2015, - "maxIsExclusive": false - }, - "valid": false - } - ] - }, - - { - "description": "formatMaximum with format and formatExclusiveMaximum in the properties", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMaximum": "2015-08-17", - "formatExclusiveMaximum": { "$data": "1/maxIsExclusive" } - }, - "whenFormat": {}, - "maxIsExclusive": {} - } - }, - "tests": [ - { - "description": "after the maximum date is invalid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "maxIsExclusive": false - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "when": "2015-08-17", - "whenFormat": "date" - }, - "valid": true - }, - { - "description": "before the maximum date is valid", - "data": { - "when": "2014-12-03", - "whenFormat": "date" - }, - "valid": true - }, - { - "description": "after the maximum date is still invalid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "maxIsExclusive": true - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "maxIsExclusive": true - }, - "valid": false - }, - { - "description": "before the maximum date is still valid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "maxIsExclusive": true - }, - "valid": true - }, - { - "description": "fails if value of formatExclusiveMaximum is not boolean", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "maxIsExclusive": "false" - }, - "valid": false - } - ] - }, - - { - "description": "formatMaximum, format and formatExclusiveMaximum in the properties", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMaximum": { "$data": "1/beforeThan" }, - "formatExclusiveMaximum": { "$data": "1/maxIsExclusive" } - }, - "beforeThan": {}, - "whenFormat": {}, - "maxIsExclusive": {} - } - }, - "tests": [ - { - "description": "after the maximum date is invalid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "beforeThan": "2015-08-17", - "maxIsExclusive": false - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "beforeThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "before the maximum date is valid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "beforeThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "after the maximum date is still invalid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "beforeThan": "2015-08-17", - "maxIsExclusive": true - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "beforeThan": "2015-08-17", - "maxIsExclusive": true - }, - "valid": false - }, - { - "description": "before the maximum date is still valid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "beforeThan": "2015-08-17", - "maxIsExclusive": true - }, - "valid": true - }, - { - "description": "fails if value of formatExclusiveMaximum is not boolean", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "beforeThan": "2015-08-17", - "maxIsExclusive": "false" - }, - "valid": false - } - ] - } -] diff --git a/spec/v5/$data/formatMinimum.json b/spec/v5/$data/formatMinimum.json deleted file mode 100644 index a3d7c87..0000000 --- a/spec/v5/$data/formatMinimum.json +++ /dev/null @@ -1,588 +0,0 @@ -[ - { - "description": "formatMinimum in the property", - "schema": { - "properties": { - "finalDate": { - "format": "date", - "formatMinimum": { "$data": "1/laterThan" } - }, - "laterThan": {} - } - }, - "tests": [ - { - "description": "before the minimum date is invalid", - "data": { - "finalDate": "2014-12-03", - "laterThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "finalDate": "2015-08-17", - "laterThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "after the minimum date is valid", - "data": { - "finalDate": "2015-11-09", - "laterThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "ignores non-strings", - "data": { - "finalDate": 2015, - "laterThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "fails if value of formatMinimum is not string", - "data": { - "finalDate": "2016-11-09", - "laterThan": 2015 - }, - "valid": false - } - ] - }, - { - "description": "formatMinimum in the property with formatExclusiveMinimum", - "schema": { - "properties": { - "finalDate": { - "format": "date", - "formatMinimum": { "$data": "1/laterThan" }, - "formatExclusiveMinimum": true - }, - "laterThan": {} - } - }, - "tests": [ - { - "description": "before the minimum date is still invalid", - "data": { - "finalDate": "2014-12-03", - "laterThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "finalDate": "2015-08-17", - "laterThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "after the minimum date is still valid", - "data": { - "finalDate": "2015-11-09", - "laterThan": "2015-08-17" - }, - "valid": true - } - ] - }, - - { - "description": "formatMinimum with format in the property", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMinimum": "2015-08-17" - }, - "whenFormat": {} - } - }, - "tests": [ - { - "description": "before the minimum date is invalid", - "data": { - "when": "2014-12-03", - "whenFormat": "date" - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "when": "2015-08-17", - "whenFormat": "date" - }, - "valid": true - }, - { - "description": "after the minimum date is valid", - "data": { - "when": "2015-11-09", - "whenFormat": "date" - }, - "valid": true - }, - { - "description": "ignores non-strings", - "data": { - "when": 2015, - "whenFormat": "date" - }, - "valid": true - } - ] - }, - { - "description": "formatMinimum with format in the property and with formatExclusiveMinimum", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMinimum": "2015-08-17", - "formatExclusiveMinimum": true - }, - "whenFormat": {} - } - }, - "tests": [ - { - "description": "before the minimum date is still invalid", - "data": { - "when": "2014-12-03", - "whenFormat": "date" - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "when": "2015-08-17", - "whenFormat": "date" - }, - "valid": false - }, - { - "description": "after the minimum date is still valid", - "data": { - "when": "2015-11-09", - "whenFormat": "date" - }, - "valid": true - } - ] - }, - - { - "description": "formatMinimum and format in the properties", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMinimum": { "$data": "1/laterThan" } - }, - "whenFormat": {}, - "laterThan": {} - } - }, - "tests": [ - { - "description": "before the minimum date is invalid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "laterThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "laterThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "after the minimum date is valid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "laterThan": "2015-08-17" - }, - "valid": true - } - ] - }, - { - "description": "formatMinimum and format in the properties with formatExclusiveMinimum", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMinimum": { "$data": "1/laterThan" }, - "formatExclusiveMinimum": true - }, - "whenFormat": {}, - "laterThan": {} - } - }, - "tests": [ - { - "description": "before the minimum date is still invalid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "laterThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "laterThan": "2015-08-17" - }, - "valid": false - }, - { - "description": "after the minimum date is still valid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "laterThan": "2015-08-17" - }, - "valid": true - } - ] - }, - - { - "description": "formatExclusiveMinimum in the property", - "schema": { - "properties": { - "finalDate": { - "format": "date", - "formatMinimum": "2015-08-17", - "formatExclusiveMinimum": { "$data": "1/minIsExclusive" } - }, - "minIsExclusive": {} - } - }, - "tests": [ - { - "description": "before the minimum date is invalid", - "data": { - "finalDate": "2014-12-03", - "minIsExclusive": false - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "finalDate": "2015-08-17" - }, - "valid": true - }, - { - "description": "after the minimum date is valid", - "data": { - "finalDate": "2015-11-09" - }, - "valid": true - }, - { - "description": "before the minimum date is still invalid", - "data": { - "finalDate": "2014-12-03", - "minIsExclusive": true - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "finalDate": "2015-08-17", - "minIsExclusive": true - }, - "valid": false - }, - { - "description": "after the minimum date is still valid", - "data": { - "finalDate": "2015-11-09", - "minIsExclusive": true - }, - "valid": true - }, - { - "description": "fails if value of formatExclusiveMinimum is not boolean", - "data": { - "finalDate": "2015-11-09", - "minIsExclusive": "false" - }, - "valid": false - } - ] - }, - - { - "description": "formatMinimum and formatExclusiveMinimum in the properties", - "schema": { - "properties": { - "finalDate": { - "format": "date", - "formatMinimum": { "$data": "1/laterThan" }, - "formatExclusiveMinimum": { "$data": "1/minIsExclusive" } - }, - "laterThan": {}, - "minIsExclusive": {} - } - }, - "tests": [ - { - "description": "before the minimum date is invalid", - "data": { - "finalDate": "2014-12-03", - "laterThan": "2015-08-17", - "minIsExclusive": false - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "finalDate": "2015-08-17", - "laterThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "after the minimum date is valid", - "data": { - "finalDate": "2015-11-09", - "laterThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "before the minimum date is still invalid", - "data": { - "finalDate": "2014-12-03", - "laterThan": "2015-08-17", - "minIsExclusive": true - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "finalDate": "2015-08-17", - "laterThan": "2015-08-17", - "minIsExclusive": true - }, - "valid": false - }, - { - "description": "after the minimum date is still valid", - "data": { - "finalDate": "2015-11-09", - "laterThan": "2015-08-17", - "minIsExclusive": true - }, - "valid": true - }, - { - "description": "fails if value of formatExclusiveMinimum is not boolean", - "data": { - "finalDate": "2015-11-09", - "laterThan": "2015-08-17", - "minIsExclusive": "false" - }, - "valid": false - }, - { - "description": "fails if value of formatMinimum is not string", - "data": { - "finalDate": "2016-11-09", - "laterThan": 2015, - "minIsExclusive": false - }, - "valid": false - } - ] - }, - - { - "description": "formatMinimum with format and formatExclusiveMinimum in the properties", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMinimum": "2015-08-17", - "formatExclusiveMinimum": { "$data": "1/minIsExclusive" } - }, - "whenFormat": {}, - "minIsExclusive": {} - } - }, - "tests": [ - { - "description": "before the minimum date is invalid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "minIsExclusive": false - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "when": "2015-08-17", - "whenFormat": "date" - }, - "valid": true - }, - { - "description": "after the minimum date is valid", - "data": { - "when": "2015-11-09", - "whenFormat": "date" - }, - "valid": true - }, - { - "description": "before the minimum date is still invalid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "minIsExclusive": true - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "minIsExclusive": true - }, - "valid": false - }, - { - "description": "after the minimum date is still valid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "minIsExclusive": true - }, - "valid": true - }, - { - "description": "fails if value of formatExclusiveMinimum is not boolean", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "minIsExclusive": "false" - }, - "valid": false - } - ] - }, - - { - "description": "formatMinimum, format and formatExclusiveMinimum in the properties", - "schema": { - "properties": { - "when": { - "format": { "$data": "1/whenFormat" }, - "formatMinimum": { "$data": "1/laterThan" }, - "formatExclusiveMinimum": { "$data": "1/minIsExclusive" } - }, - "laterThan": {}, - "whenFormat": {}, - "minIsExclusive": {} - } - }, - "tests": [ - { - "description": "before the minimum date is invalid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "laterThan": "2015-08-17", - "minIsExclusive": false - }, - "valid": false - }, - { - "description": "boundary point is valid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "laterThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "after the minimum date is valid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "laterThan": "2015-08-17" - }, - "valid": true - }, - { - "description": "before the minimum date is still invalid", - "data": { - "when": "2014-12-03", - "whenFormat": "date", - "laterThan": "2015-08-17", - "minIsExclusive": true - }, - "valid": false - }, - { - "description": "boundary point is invalid", - "data": { - "when": "2015-08-17", - "whenFormat": "date", - "laterThan": "2015-08-17", - "minIsExclusive": true - }, - "valid": false - }, - { - "description": "after the minimum date is still valid", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "laterThan": "2015-08-17", - "minIsExclusive": true - }, - "valid": true - }, - { - "description": "fails if value of formatExclusiveMinimum is not boolean", - "data": { - "when": "2015-11-09", - "whenFormat": "date", - "laterThan": "2015-08-17", - "minIsExclusive": "false" - }, - "valid": false - } - ] - } -] diff --git a/spec/v5/formatMaximum.json b/spec/v5/formatMaximum.json deleted file mode 100644 index 9e0e6c9..0000000 --- a/spec/v5/formatMaximum.json +++ /dev/null @@ -1,220 +0,0 @@ -[ - { - "description": "formatMaximum validation with date format", - "schema": { - "format": "date", - "formatMaximum": "2015-08-17" - }, - "tests": [ - { - "description": "after the maximum date is invalid", - "data": "2015-11-09", - "valid": false - }, - { - "description": "boundary point is valid", - "data": "2015-08-17", - "valid": true - }, - { - "description": "before the maximum date is valid", - "data": "2014-12-03", - "valid": true - }, - { - "description": "ignores non-strings", - "data": 2015, - "valid": true - } - ] - }, - { - "description": "formatExclusiveMaximum validation with date format", - "schema": { - "format": "date", - "formatMaximum": "2015-08-17", - "formatExclusiveMaximum": true - }, - "tests": [ - { - "description": "after the maximum date is still invalid", - "data": "2015-11-09", - "valid": false - }, - { - "description": "boundary point is invalid", - "data": "2015-08-17", - "valid": false - }, - { - "description": "before the maximum date is still valid", - "data": "2014-12-03", - "valid": true - } - ] - }, - - { - "description": "formatMaximum validation with time format", - "schema": { - "format": "time", - "formatMaximum": "13:15:17.000Z" - }, - "tests": [ - { - "description": "time after the maximum time is invalid", - "data": "15:11:09.000Z", - "valid": false - }, - { - "description": "boundary point is valid", - "data": "13:15:17.000Z", - "valid": true - }, - { - "description": "boundary point is valid, timezone is ignored", - "data": "13:15:17.000+01:00", - "valid": true - }, - { - "description": "boundary point is valid, no timezone is ok too", - "data": "13:15:17.000", - "valid": true - }, - { - "description": "time before the maximum time is valid", - "data": "10:33:55.000Z", - "valid": true - }, - { - "description": "ignores non-strings", - "data": 1315, - "valid": true - } - ] - }, - { - "description": "formatExclusiveMaximum validation with time format", - "schema": { - "format": "time", - "formatMaximum": "13:15:17.000Z", - "formatExclusiveMaximum": true - }, - "tests": [ - { - "description": "time after the maximum time is still invalid", - "data": "15:11:09.000Z", - "valid": false - }, - { - "description": "boundary point is invalid", - "data": "13:15:17.000Z", - "valid": false - }, - { - "description": "boundary point is invalid, timezone is ignored", - "data": "13:15:17.000+01:00", - "valid": false - }, - { - "description": "boundary point is invalid, no timezone is ok too", - "data": "13:15:17.000", - "valid": false - }, - { - "description": "time before the maximum time is still valid", - "data": "10:33:55.000Z", - "valid": true - } - ] - }, - - { - "description": "formatMaximum validation with date-time format", - "schema": { - "format": "date-time", - "formatMaximum": "2015-08-17T13:15:17.000Z" - }, - "tests": [ - { - "description": "date after the maximum date is invalid", - "data": "2015-11-09T13:15:17.000Z", - "valid": false - }, - { - "description": "same date, time after the maximum time is invalid", - "data": "2015-08-17T15:11:09.000Z", - "valid": false - }, - { - "description": "boundary point is valid", - "data": "2015-08-17T13:15:17.000Z", - "valid": true - }, - { - "description": "same date, time before the maximum time is valid", - "data": "2015-08-17T10:33:55.000Z", - "valid": true - }, - { - "description": "date before the maximum date is valid", - "data": "2014-12-03T13:15:17.000Z", - "valid": true - }, - { - "description": "ignores non-strings", - "data": 2015, - "valid": true - } - ] - }, - { - "description": "formatExclusiveMaximum validation with date-time format", - "schema": { - "format": "date-time", - "formatMaximum": "2015-08-17T13:15:17.000Z", - "formatExclusiveMaximum": true - }, - "tests": [ - { - "description": "date after the maximum date is still invalid", - "data": "2015-11-09T13:15:17.000Z", - "valid": false - }, - { - "description": "same date, time after the maximum time is still invalid", - "data": "2015-08-17T15:11:09.000Z", - "valid": false - }, - { - "description": "boundary point is invalid", - "data": "2015-08-17T13:15:17.000Z", - "valid": false - }, - { - "description": "same date, time before the maximum time is still valid", - "data": "2015-08-17T10:33:55.000Z", - "valid": true - }, - { - "description": "date before the maximum date is still valid", - "data": "2014-12-03T13:15:17.000Z", - "valid": true - } - ] - }, - { - "description": "formatMaximum is valid with whitelisted unknown format", - "schema": { - "format": "allowedUnknown", - "formatMaximum": "2015" - }, - "tests": [ - { - "description": "valid string", - "data": "2016", - "valid": true - } - ] - } -] diff --git a/spec/v5/formatMinimum.json b/spec/v5/formatMinimum.json deleted file mode 100644 index e6b3e63..0000000 --- a/spec/v5/formatMinimum.json +++ /dev/null @@ -1,206 +0,0 @@ -[ - { - "description": "formatMinimum validation with date format", - "schema": { - "format": "date", - "formatMinimum": "2015-08-17" - }, - "tests": [ - { - "description": "after the minimum date is valid", - "data": "2015-11-09", - "valid": true - }, - { - "description": "boundary point is valid", - "data": "2015-08-17", - "valid": true - }, - { - "description": "before the minimum date is invalid", - "data": "2014-12-03", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 2015, - "valid": true - } - ] - }, - { - "description": "formatExclusiveMinimum validation with date format", - "schema": { - "format": "date", - "formatMinimum": "2015-08-17", - "formatExclusiveMinimum": true - }, - "tests": [ - { - "description": "after the minimum date is still valid", - "data": "2015-11-09", - "valid": true - }, - { - "description": "boundary point is invalid", - "data": "2015-08-17", - "valid": false - }, - { - "description": "before the minimum date is still invalid", - "data": "2014-12-03", - "valid": false - } - ] - }, - - { - "description": "formatMinimum validation with time format", - "schema": { - "format": "time", - "formatMinimum": "13:15:17.000Z" - }, - "tests": [ - { - "description": "time after the minimum time is valid", - "data": "15:11:09.000Z", - "valid": true - }, - { - "description": "boundary point is valid", - "data": "13:15:17.000Z", - "valid": true - }, - { - "description": "boundary point is valid, timezone is ignored", - "data": "13:15:17.000+01:00", - "valid": true - }, - { - "description": "boundary point is valid, no timezone is ok too", - "data": "13:15:17.000", - "valid": true - }, - { - "description": "time before the minimum time is invalid", - "data": "10:33:55.000Z", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 1315, - "valid": true - } - ] - }, - { - "description": "formatExclusiveMinimum validation with time format", - "schema": { - "format": "time", - "formatMinimum": "13:15:17.000Z", - "formatExclusiveMinimum": true - }, - "tests": [ - { - "description": "time after the minimum time is still valid", - "data": "15:11:09.000Z", - "valid": true - }, - { - "description": "boundary point is invalid", - "data": "13:15:17.000Z", - "valid": false - }, - { - "description": "boundary point is invalid, timezone is ignored", - "data": "13:15:17.000+01:00", - "valid": false - }, - { - "description": "boundary point is invalid, no timezone is ok too", - "data": "13:15:17.000", - "valid": false - }, - { - "description": "time before the minimum time is still invalid", - "data": "10:33:55.000Z", - "valid": false - } - ] - }, - - { - "description": "formatMinimum validation with date-time format", - "schema": { - "format": "date-time", - "formatMinimum": "2015-08-17T13:15:17.000Z" - }, - "tests": [ - { - "description": "date after the minimum date is valid", - "data": "2015-11-09T13:15:17.000Z", - "valid": true - }, - { - "description": "same date, time after the minimum time is valid", - "data": "2015-08-17T15:11:09.000Z", - "valid": true - }, - { - "description": "boundary point is valid", - "data": "2015-08-17T13:15:17.000Z", - "valid": true - }, - { - "description": "same date, time before the minimum time is invalid", - "data": "2015-08-17T10:33:55.000Z", - "valid": false - }, - { - "description": "date before the minimum date is invalid", - "data": "2014-12-03T13:15:17.000Z", - "valid": false - }, - { - "description": "ignores non-strings", - "data": 2015, - "valid": true - } - ] - }, - { - "description": "formatExclusiveMinimum validation with date-time format", - "schema": { - "format": "date-time", - "formatMinimum": "2015-08-17T13:15:17.000Z", - "formatExclusiveMinimum": true - }, - "tests": [ - { - "description": "date after the minimum date is still valid", - "data": "2015-11-09T13:15:17.000Z", - "valid": true - }, - { - "description": "same date, time after the minimum time is still valid", - "data": "2015-08-17T15:11:09.000Z", - "valid": true - }, - { - "description": "boundary point is invalid", - "data": "2015-08-17T13:15:17.000Z", - "valid": false - }, - { - "description": "same date, time before the minimum time is stillinvalid", - "data": "2015-08-17T10:33:55.000Z", - "valid": false - }, - { - "description": "date before the minimum date is still invalid", - "data": "2014-12-03T13:15:17.000Z", - "valid": false - } - ] - } -] diff --git a/spec/v5/patternRequired.json b/spec/v5/patternRequired.json deleted file mode 100644 index ef672d0..0000000 --- a/spec/v5/patternRequired.json +++ /dev/null @@ -1,86 +0,0 @@ -[ - { - "description": "patternRequired requires that some property matching pattern is present", - "schema": { - "patternRequired": [ "f.*o" ] - }, - "tests": [ - { - "description": "property matching required pattern is valid", - "data": { "foo": 1 }, - "valid": true - }, - { - "description": "one of properties matching required pattern is valid", - "data": { "foo": 1, "bar": 2 }, - "valid": true - }, - { - "description": "non-present property matching required pattern is invalid", - "data": { "bar": 1 }, - "valid": false - } - ] - }, - { - "description": "multiple patterns in patternRequired", - "schema": { - "patternRequired": [ "a+", "b+" ] - }, - "tests": [ - { - "description": "both patterns matched with one property is valid", - "data": { "ab": 2 }, - "valid": true - }, - { - "description": "both patterns matched with separate properties is valid", - "data": { "aa": 1, "bb": 2 }, - "valid": true - }, - { - "description": "both patterns matched with multiple properties is valid", - "data": { "a": 1, "aa": 2, "ab": 3, "b": 4, "bb": 5 }, - "valid": true - }, - { - "description": "one pattern not matched is invalid", - "data": { "aa": 1 }, - "valid": false - }, - { - "description": "another pattern not matched is invalid", - "data": { "bb": 2 }, - "valid": false - }, - { - "description": "both patterns not matched is invalid", - "data": { "c": 3 }, - "valid": false - } - ] - }, - { - "description": "regexes in patternRequired are not anchored by default and are case sensitive", - "schema": { - "patternRequired": [ "X_[0-9]{2,}" ] - }, - "tests": [ - { - "description": "regexes are not anchored", - "data": { "aX_25b": 1 }, - "valid": true - }, - { - "description": "regexes are case sensitive", - "data": { "X_25": 2 }, - "valid": true - }, - { - "description": "regexes are case sensitive, 2", - "data": { "x_25": 3 }, - "valid": false - } - ] - } -] diff --git a/spec/v5/switch.json b/spec/v5/switch.json deleted file mode 100644 index 8c7dc01..0000000 --- a/spec/v5/switch.json +++ /dev/null @@ -1,255 +0,0 @@ -[ - { - "description": "switch keyword validation", - "schema": { - "switch": [ - { "if": { "minimum": 10 }, "then": { "multipleOf": 2 } } - ] - }, - "tests": [ - { - "description": ">= 10 and even is valid", - "data": 12, - "valid": true - }, - { - "description": ">= 10 and odd is invalid", - "data": 11, - "valid": false - }, - { - "description": "< 10 is valid", - "data": 9, - "valid": true - } - ] - }, - { - "description": "switch keyword with then clause boolean", - "schema": { - "switch": [ - { "if": { "minimum": 10 }, "then": { "multipleOf": 2 } }, - { "if": { "maximum": 4 }, "then": false } - ] - }, - "tests": [ - { - "description": ">=10 and even is valid", - "data": 12, - "valid": true - }, - { - "description": ">=10 and odd is invalid", - "data": 11, - "valid": false - }, - { - "description": "<10 is valid", - "data": 7, - "valid": true - }, - { - "description": "<4 is invalid", - "data": 3, - "valid": false - } - ] - }, - { - "description": "switch keyword with single then clause schema", - "schema": { - "switch": [ - { "if": { "maximum": 10 }, "then": { "multipleOf": 2 } }, - { "then": { "multipleOf": 5 } } - ] - }, - "tests": [ - { - "description": "<=10 and even is valid", - "data": 8, - "valid": true - }, - { - "description": "<=10 and odd is invalid", - "data": 7, - "valid": false - }, - { - "description": ">10 and mulitple of 5 is valid", - "data": 15, - "valid": true - }, - { - "description": ">10 and not mulitple of 5 is invalid", - "data": 17, - "valid": false - } - ] - }, - { - "description": "switch keyword with single then clause boolean", - "schema": { - "description": "positive integer <=1000 with one non-zero digit", - "switch": [ - { "if": { "not": { "minimum": 1 } }, "then": false }, - { "if": { "maximum": 10 }, "then": true }, - { "if": { "maximum": 100 }, "then": { "multipleOf": 10 } }, - { "if": { "maximum": 1000 }, "then": { "multipleOf": 100 } }, - { "then": false } - ] - }, - "tests": [ - { - "description": "1 is valid", - "data": 1, - "valid": true - }, - { - "description": "0 is invalid (no positive)", - "data": 0, - "valid": false - }, - { - "description": "-1 is invalid (negative)", - "data": -1, - "valid": false - }, - { - "description": "8 is valid", - "data": 8, - "valid": true - }, - { - "description": "9 is valid", - "data": 9, - "valid": true - }, - { - "description": "10 is valid", - "data": 10, - "valid": true - }, - { - "description": "80 is valid", - "data": 80, - "valid": true - }, - { - "description": "90 is valid", - "data": 90, - "valid": true - }, - { - "description": "100 is valid", - "data": 100, - "valid": true - }, - { - "description": "85 is invalid (not multiple of 10)", - "data": 85, - "valid": false - }, - { - "description": "800 is valid", - "data": 800, - "valid": true - }, - { - "description": "900 is valid", - "data": 900, - "valid": true - }, - { - "description": "1000 is valid", - "data": 1000, - "valid": true - }, - { - "description": "850 is invalid (not multiple of 100)", - "data": 850, - "valid": false - }, - { - "description": "> 1000 is invalid", - "data": 1001, - "valid": false - } - ] - }, - { - "description": "switch keyword with continue clause", - "schema": { - "switch": [ - { "if": { "minimum": 10 }, "then": { "multipleOf": 2 }, "continue": true }, - { "if": { "minimum": 20 }, "then": { "multipleOf": 5 } } - ] - }, - "tests": [ - { - "description": "<10 is valid", - "data": 7, - "valid": true - }, - { - "description": ">=10, <20 and even is valid", - "data": 12, - "valid": true - }, - { - "description": ">=10, <20 and odd is invalid", - "data": 11, - "valid": false - }, - { - "description": ">=20 and multiple of 2 and 5 is valid", - "data": 30, - "valid": true - }, - { - "description": ">=20 and not multiple of 2 is invalid", - "data": 35, - "valid": false - }, - { - "description": ">=20 and not multiple of 5 is invalid", - "data": 36, - "valid": false - } - ] - }, - { - "description": "switch keyword with id in sibling subschema", - "schema": { - "id": "http://example.com/base_switch", - "switch": [ - { - "if": { - "id": "http://example.com/if", - "minimum": 10 - }, - "then": { "$ref": "#/definitions/def" } - } - ], - "definitions": { - "def": { "multipleOf": 2 } - } - }, - "tests": [ - { - "description": ">= 10 and even is valid", - "data": 12, - "valid": true - }, - { - "description": ">= 10 and odd is invalid", - "data": 11, - "valid": false - }, - { - "description": "< 10 is valid", - "data": 9, - "valid": true - } - ] - } -]