Merge pull request #464 from epoberezkin/5.0.0

version 5.0.0
master
Evgeny Poberezkin 2017-04-17 15:02:19 +01:00 committed by GitHub
commit fde7030a19
110 changed files with 4816 additions and 3924 deletions

View File

@ -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"
}

28
.eslintrc.yml Normal file
View File

@ -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

View File

@ -1,8 +0,0 @@
{
"laxcomma": true,
"laxbreak": true,
"curly": false,
"-W058": true,
"eqeqeq": false,
"node": true
}

View File

@ -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.

View File

@ -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:

228
README.md
View File

@ -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<br>speed*|run-time<br>speed*|bundle<br>size|
|---|:-:|:-:|:-:|
|es7 async<br>(native)|-|0.75|-|
|generators<br>(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)
##### <a name="api-compileAsync"></a>.compileAsync(Object schema [, Boolean meta] [, Function callback]) -&gt; 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) -&gt; 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`.
##### <a name="api-validateschema"></a>.validateSchema(Object schema) -&gt; 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
##### <a name="api-addformat"></a>.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)

49
lib/$data.js Normal file
View File

@ -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<keywordsJsonPointers.length; i++) {
metaSchema = JSON.parse(JSON.stringify(metaSchema));
var segments = keywordsJsonPointers[i].split('/');
var keywords = metaSchema;
var j;
for (j=1; j<segments.length; j++)
keywords = keywords[segments[j]];
for (j=0; j<KEYWORDS.length; j++) {
var key = KEYWORDS[j];
var schema = keywords[key];
if (schema) {
keywords[key] = {
anyOf: [
schema,
{ $ref: 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#' }
]
};
}
}
}
return metaSchema;
};

84
lib/ajv.d.ts vendored
View File

@ -7,26 +7,28 @@ declare namespace ajv {
interface Ajv {
/**
* 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
* 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 by default).
* @param {String|Object|Boolean} 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`).
*/
validate(schemaKeyRef: Object | string, data: any): boolean;
validate(schemaKeyRef: Object | string | boolean, data: any): boolean | Thenable<any>;
/**
* 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<ValidateFunction>} 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<ValidateFunction>;
/**
* 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<any>,
parentDataProperty?: string | number,
rootData?: Object | Array<any>
): boolean | Thenable<boolean>;
): boolean | Thenable<any>;
errors?: Array<ErrorObject>;
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<string>;
unknownFormats?: true | string[] | 'ignore';
schemas?: Array<Object> | 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<Object | boolean>;
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<string>;
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<any>,
parentDataProperty?: string | number
): boolean | Thenable<boolean>;
parentDataProperty?: string | number,
rootData?: Object | Array<any>
): boolean | Thenable<any>;
errors?: Array<ErrorObject>;
}
@ -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;
}

View File

@ -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<schema.length; i++) this.addSchema(schema[i], undefined, _skipValidation, _meta);
return;
}
var id = this._getId(schema);
if (id !== undefined && typeof id != 'string')
throw new Error('schema id must be string');
key = resolve.normalizeId(key || id);
checkUnique(this, key);
this._schemas[key] = this._addSchema(schema, _skipValidation, _meta, true);
}
/**
* 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.
* @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<schema.length; i++) addSchema(schema[i], undefined, _skipValidation, _meta);
return;
}
// can key/id have # inside?
key = resolve.normalizeId(key || schema.id);
checkUnique(key);
self._schemas[key] = _addSchema(schema, _skipValidation, _meta, true);
/**
* Add schema that will be used to validate other schemas
* options in META_IGNORE_OPTIONS are alway set to false
* @this Ajv
* @param {Object} schema schema object
* @param {String} key optional schema key
* @param {Boolean} skipValidation true to skip schema validation, can be used to override validateSchema option for meta-schema
*/
function addMetaSchema(schema, key, skipValidation) {
this.addSchema(schema, key, skipValidation, true);
}
/**
* Validate schema
* @this Ajv
* @param {Object} schema schema to validate
* @param {Boolean} throwOrLogError pass true to throw (or log) an error if invalid
* @return {Boolean} true if schema is valid
*/
function validateSchema(schema, throwOrLogError) {
var $schema = schema.$schema;
if ($schema !== undefined && typeof $schema != 'string')
throw new Error('$schema must be a string');
$schema = $schema || this._opts.defaultMeta || defaultMeta(this);
if (!$schema) {
console.warn('meta-schema not available');
this.errors = null;
return true;
}
/**
* Add schema that will be used to validate other schemas
* options in META_IGNORE_OPTIONS are alway set to false
* @param {Object} schema schema object
* @param {String} key optional schema key
* @param {Boolean} skipValidation true to skip schema validation, can be used to override validateSchema option for meta-schema
*/
function addMetaSchema(schema, key, skipValidation) {
addSchema(schema, key, skipValidation, true);
var currentUriFormat = this._formats.uri;
this._formats.uri = typeof currentUriFormat == 'function'
? this._schemaUriFormatFunc
: this._schemaUriFormat;
var valid;
try { valid = this.validate($schema, schema); }
finally { this._formats.uri = currentUriFormat; }
if (!valid && throwOrLogError) {
var message = 'schema is invalid: ' + this.errorsText();
if (this._opts.validateSchema == 'log') console.error(message);
else throw new Error(message);
}
return valid;
}
/**
* Validate schema
* @param {Object} schema schema to validate
* @param {Boolean} throwOrLogError pass true to throw (or log) an error if invalid
* @return {Boolean} true if schema is valid
*/
function validateSchema(schema, throwOrLogError) {
var $schema = schema.$schema || self._opts.defaultMeta || defaultMeta();
var currentUriFormat = self._formats.uri;
self._formats.uri = typeof currentUriFormat == 'function'
? SCHEMA_URI_FORMAT_FUNC
: SCHEMA_URI_FORMAT;
var valid;
try { valid = validate($schema, schema); }
finally { self._formats.uri = currentUriFormat; }
if (!valid && throwOrLogError) {
var message = 'schema is invalid: ' + errorsText();
if (self._opts.validateSchema == 'log') console.error(message);
else throw new Error(message);
}
return valid;
}
function defaultMeta(self) {
var meta = self._opts.meta;
self._opts.defaultMeta = typeof meta == 'object'
? self._getId(meta) || meta
: self.getSchema(META_SCHEMA_ID)
? META_SCHEMA_ID
: undefined;
return self._opts.defaultMeta;
}
function defaultMeta() {
var meta = self._opts.meta;
self._opts.defaultMeta = typeof meta == 'object'
? meta.id || meta
: self._opts.v5
? v5.META_SCHEMA_ID
: META_SCHEMA_ID;
return self._opts.defaultMeta;
}
/**
* 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).
* @return {Function} schema validating function (with property `schema`).
*/
function getSchema(keyRef) {
var schemaObj = _getSchemaObj(keyRef);
switch (typeof schemaObj) {
case 'object': return schemaObj.validate || _compile(schemaObj);
case 'string': return getSchema(schemaObj);
case 'undefined': return _getSchemaFragment(keyRef);
}
}
function _getSchemaFragment(ref) {
var res = resolve.schema.call(self, { schema: {} }, ref);
if (res) {
var schema = res.schema
, root = res.root
, baseId = res.baseId;
var v = compileSchema.call(self, schema, root, undefined, baseId);
self._fragments[ref] = new SchemaObject({
ref: ref,
fragment: true,
schema: schema,
root: root,
baseId: baseId,
validate: v
});
return v;
}
}
function _getSchemaObj(keyRef) {
keyRef = resolve.normalizeId(keyRef);
return self._schemas[keyRef] || self._refs[keyRef] || self._fragments[keyRef];
}
/**
* Remove cached schema(s).
* 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
*/
function removeSchema(schemaKeyRef) {
if (schemaKeyRef instanceof RegExp) {
_removeAllSchemas(self._schemas, schemaKeyRef);
_removeAllSchemas(self._refs, schemaKeyRef);
return;
}
switch (typeof schemaKeyRef) {
case 'undefined':
_removeAllSchemas(self._schemas);
_removeAllSchemas(self._refs);
self._cache.clear();
return;
case 'string':
var schemaObj = _getSchemaObj(schemaKeyRef);
if (schemaObj) self._cache.del(schemaObj.jsonStr);
delete self._schemas[schemaKeyRef];
delete self._refs[schemaKeyRef];
return;
case 'object':
var jsonStr = stableStringify(schemaKeyRef);
self._cache.del(jsonStr);
var id = schemaKeyRef.id;
if (id) {
id = resolve.normalizeId(id);
delete self._schemas[id];
delete self._refs[id];
}
}
}
function _removeAllSchemas(schemas, regex) {
for (var keyRef in schemas) {
var schemaObj = schemas[keyRef];
if (!schemaObj.meta && (!regex || regex.test(keyRef))) {
self._cache.del(schemaObj.jsonStr);
delete schemas[keyRef];
}
}
}
function _addSchema(schema, skipValidation, meta, shouldAddSchema) {
if (typeof schema != 'object') throw new Error('schema should be object');
var jsonStr = stableStringify(schema);
var cached = self._cache.get(jsonStr);
if (cached) return cached;
shouldAddSchema = shouldAddSchema || self._opts.addUsedSchema !== false;
var id = resolve.normalizeId(schema.id);
if (id && shouldAddSchema) checkUnique(id);
var willValidate = self._opts.validateSchema !== false && !skipValidation;
var recursiveMeta;
if (willValidate && !(recursiveMeta = schema.id && schema.id == schema.$schema))
validateSchema(schema, true);
var localRefs = resolve.ids.call(self, schema);
var schemaObj = new SchemaObject({
id: id,
schema: schema,
localRefs: localRefs,
jsonStr: jsonStr,
meta: meta
});
if (id[0] != '#' && shouldAddSchema) self._refs[id] = schemaObj;
self._cache.put(jsonStr, schemaObj);
if (willValidate && recursiveMeta) validateSchema(schema, true);
return schemaObj;
}
function _compile(schemaObj, root) {
if (schemaObj.compiling) {
schemaObj.validate = callValidate;
callValidate.schema = schemaObj.schema;
callValidate.errors = null;
callValidate.root = root ? root : callValidate;
if (schemaObj.schema.$async === true)
callValidate.$async = true;
return callValidate;
}
schemaObj.compiling = true;
var currentOpts;
if (schemaObj.meta) {
currentOpts = self._opts;
self._opts = self._metaOpts;
}
var v;
try { v = compileSchema.call(self, schemaObj.schema, root, schemaObj.localRefs); }
finally {
schemaObj.compiling = false;
if (schemaObj.meta) self._opts = currentOpts;
}
schemaObj.validate = v;
schemaObj.refs = v.refs;
schemaObj.refVal = v.refVal;
schemaObj.root = v.root;
return v;
function callValidate() {
var _validate = schemaObj.validate;
var result = _validate.apply(null, arguments);
callValidate.errors = _validate.errors;
return result;
}
}
/**
* Convert array of error message objects to string
* @param {Array<Object>} 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.length; i++) {
var e = errors[i];
if (e) text += dataVar + e.dataPath + ' ' + e.message + separator;
}
return text.slice(0, -separator.length);
}
/**
* Add custom format
* @param {String} name format name
* @param {String|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid)
*/
function addFormat(name, format) {
if (typeof format == 'string') format = new RegExp(format);
self._formats[name] = format;
}
function addDraft4MetaSchema() {
if (self._opts.meta !== false) {
var metaSchema = require('./refs/json-schema-draft-04.json');
addMetaSchema(metaSchema, META_SCHEMA_ID, true);
self._refs['http://json-schema.org/schema'] = META_SCHEMA_ID;
}
}
function addInitialSchemas() {
var optsSchemas = self._opts.schemas;
if (!optsSchemas) return;
if (Array.isArray(optsSchemas)) addSchema(optsSchemas);
else for (var key in optsSchemas) addSchema(optsSchemas[key], key);
}
function addInitialFormats() {
for (var name in self._opts.formats) {
var format = self._opts.formats[name];
addFormat(name, format);
}
}
function checkUnique(id) {
if (self._schemas[id] || self._refs[id])
throw new Error('schema with key or id "' + id + '" already exists');
}
function getMetaSchemaOptions() {
var metaOpts = util.copy(self._opts);
for (var i=0; i<META_IGNORE_OPTIONS.length; i++)
delete metaOpts[META_IGNORE_OPTIONS[i]];
return metaOpts;
/**
* Get compiled schema from the instance by `key` or `ref`.
* @this Ajv
* @param {String} keyRef `key` that was passed to `addSchema` or full schema reference (`schema.id` or resolved id).
* @return {Function} schema validating function (with property `schema`).
*/
function getSchema(keyRef) {
var schemaObj = _getSchemaObj(this, keyRef);
switch (typeof schemaObj) {
case 'object': return schemaObj.validate || this._compile(schemaObj);
case 'string': return this.getSchema(schemaObj);
case 'undefined': return _getSchemaFragment(this, keyRef);
}
}
function _getSchemaFragment(self, ref) {
var res = resolve.schema.call(self, { schema: {} }, ref);
if (res) {
var schema = res.schema
, root = res.root
, baseId = res.baseId;
var v = compileSchema.call(self, schema, root, undefined, baseId);
self._fragments[ref] = new SchemaObject({
ref: ref,
fragment: true,
schema: schema,
root: root,
baseId: baseId,
validate: v
});
return v;
}
}
function _getSchemaObj(self, keyRef) {
keyRef = resolve.normalizeId(keyRef);
return self._schemas[keyRef] || self._refs[keyRef] || self._fragments[keyRef];
}
/**
* Remove cached schema(s).
* 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.
* @this Ajv
* @param {String|Object|RegExp} schemaKeyRef key, ref, pattern to match key/ref or schema object
*/
function removeSchema(schemaKeyRef) {
if (schemaKeyRef instanceof RegExp) {
_removeAllSchemas(this, this._schemas, schemaKeyRef);
_removeAllSchemas(this, this._refs, schemaKeyRef);
return;
}
switch (typeof schemaKeyRef) {
case 'undefined':
_removeAllSchemas(this, this._schemas);
_removeAllSchemas(this, this._refs);
this._cache.clear();
return;
case 'string':
var schemaObj = _getSchemaObj(this, schemaKeyRef);
if (schemaObj) this._cache.del(schemaObj.cacheKey);
delete this._schemas[schemaKeyRef];
delete this._refs[schemaKeyRef];
return;
case 'object':
var serialize = this._opts.serialize;
var cacheKey = serialize ? serialize(schemaKeyRef) : schemaKeyRef;
this._cache.del(cacheKey);
var id = this._getId(schemaKeyRef);
if (id) {
id = resolve.normalizeId(id);
delete this._schemas[id];
delete this._refs[id];
}
}
}
function _removeAllSchemas(self, schemas, regex) {
for (var keyRef in schemas) {
var schemaObj = schemas[keyRef];
if (!schemaObj.meta && (!regex || regex.test(keyRef))) {
self._cache.del(schemaObj.cacheKey);
delete schemas[keyRef];
}
}
}
/* @this Ajv */
function _addSchema(schema, skipValidation, meta, shouldAddSchema) {
if (typeof schema != 'object' && typeof schema != 'boolean')
throw new Error('schema should be object or boolean');
var serialize = this._opts.serialize;
var cacheKey = serialize ? serialize(schema) : schema;
var cached = this._cache.get(cacheKey);
if (cached) return cached;
shouldAddSchema = shouldAddSchema || this._opts.addUsedSchema !== false;
var id = resolve.normalizeId(this._getId(schema));
if (id && shouldAddSchema) checkUnique(this, id);
var willValidate = this._opts.validateSchema !== false && !skipValidation;
var recursiveMeta;
if (willValidate && !(recursiveMeta = id && id == resolve.normalizeId(schema.$schema)))
this.validateSchema(schema, true);
var localRefs = resolve.ids.call(this, schema);
var schemaObj = new SchemaObject({
id: id,
schema: schema,
localRefs: localRefs,
cacheKey: cacheKey,
meta: meta
});
if (id[0] != '#' && shouldAddSchema) this._refs[id] = schemaObj;
this._cache.put(cacheKey, schemaObj);
if (willValidate && recursiveMeta) this.validateSchema(schema, true);
return schemaObj;
}
/* @this Ajv */
function _compile(schemaObj, root) {
if (schemaObj.compiling) {
schemaObj.validate = callValidate;
callValidate.schema = schemaObj.schema;
callValidate.errors = null;
callValidate.root = root ? root : callValidate;
if (schemaObj.schema.$async === true)
callValidate.$async = true;
return callValidate;
}
schemaObj.compiling = true;
var currentOpts;
if (schemaObj.meta) {
currentOpts = this._opts;
this._opts = this._metaOpts;
}
var v;
try { v = compileSchema.call(this, schemaObj.schema, root, schemaObj.localRefs); }
finally {
schemaObj.compiling = false;
if (schemaObj.meta) this._opts = currentOpts;
}
schemaObj.validate = v;
schemaObj.refs = v.refs;
schemaObj.refVal = v.refVal;
schemaObj.root = v.root;
return v;
function callValidate() {
var _validate = schemaObj.validate;
var result = _validate.apply(null, arguments);
callValidate.errors = _validate.errors;
return result;
}
}
function chooseGetId(opts) {
switch (opts.schemaId) {
case '$id': return _get$Id;
case 'id': return _getId;
default: return _get$IdOrId;
}
}
function _getId(schema) {
if (schema.$id) console.warn('schema $id ignored', schema.$id);
return schema.id;
}
function _get$Id(schema) {
if (schema.id) console.warn('schema id ignored', schema.id);
return schema.$id;
}
function _get$IdOrId(schema) {
if (schema.$id && schema.id && schema.$id != schema.id)
throw new Error('schema $id is different from id');
return schema.$id || schema.id;
}
/**
* Convert array of error message objects to string
* @this Ajv
* @param {Array<Object>} 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<errors.length; i++) {
var e = errors[i];
if (e) text += dataVar + e.dataPath + ' ' + e.message + separator;
}
return text.slice(0, -separator.length);
}
/**
* Add custom format
* @this Ajv
* @param {String} name format name
* @param {String|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid)
*/
function addFormat(name, format) {
if (typeof format == 'string') format = new RegExp(format);
this._formats[name] = format;
}
function addDraft6MetaSchema(self) {
var $dataSchema;
if (self._opts.$data) {
$dataSchema = require('./refs/$data.json');
self.addMetaSchema($dataSchema, $dataSchema.$id, true);
}
if (self._opts.meta === false) return;
var metaSchema = require('./refs/json-schema-draft-06.json');
if (self._opts.$data) metaSchema = $dataMetaSchema(metaSchema, META_SUPPORT_DATA);
self.addMetaSchema(metaSchema, META_SCHEMA_ID, true);
self._refs['http://json-schema.org/schema'] = META_SCHEMA_ID;
}
function addInitialSchemas(self) {
var optsSchemas = self._opts.schemas;
if (!optsSchemas) return;
if (Array.isArray(optsSchemas)) self.addSchema(optsSchemas);
else for (var key in optsSchemas) self.addSchema(optsSchemas[key], key);
}
function addInitialFormats(self) {
for (var name in self._opts.formats) {
var format = self._opts.formats[name];
self.addFormat(name, format);
}
}
function checkUnique(self, id) {
if (self._schemas[id] || self._refs[id])
throw new Error('schema with key or id "' + id + '" already exists');
}
function getMetaSchemaOptions(self) {
var metaOpts = util.copy(self._opts);
for (var i=0; i<META_IGNORE_OPTIONS.length; i++)
delete metaOpts[META_IGNORE_OPTIONS[i]];
return metaOpts;
}

View File

@ -1,218 +0,0 @@
'use strict';
module.exports = {
setup: setupAsync,
compile: compileAsync
};
var util = require('./compile/util');
var ASYNC = {
'*': checkGenerators,
'co*': checkGenerators,
'es7': checkAsyncFunction
};
var TRANSPILE = {
'nodent': getNodent,
'regenerator': getRegenerator
};
var MODES = [
{ async: 'co*' },
{ async: 'es7', transpile: 'nodent' },
{ async: 'co*', transpile: 'regenerator' }
];
var regenerator, nodent;
function setupAsync(opts, required) {
if (required !== false) required = true;
var async = opts.async
, transpile = opts.transpile
, check;
switch (typeof transpile) {
case 'string':
var get = TRANSPILE[transpile];
if (!get) throw new Error('bad transpiler: ' + transpile);
return (opts._transpileFunc = get(opts, required));
case 'undefined':
case 'boolean':
if (typeof async == 'string') {
check = ASYNC[async];
if (!check) throw new Error('bad async mode: ' + async);
return (opts.transpile = check(opts, required));
}
for (var i=0; i<MODES.length; i++) {
var _opts = MODES[i];
if (setupAsync(_opts, false)) {
util.copy(_opts, opts);
return opts.transpile;
}
}
/* istanbul ignore next */
throw new Error('generators, nodent and regenerator are not available');
case 'function':
return (opts._transpileFunc = opts.transpile);
default:
throw new Error('bad transpiler: ' + transpile);
}
}
function checkGenerators(opts, required) {
/* jshint evil: true */
try {
(new Function('(function*(){})()'))();
return true;
} catch(e) {
/* istanbul ignore next */
if (required) throw new Error('generators not supported');
}
}
function checkAsyncFunction(opts, required) {
/* jshint evil: true */
try {
(new Function('(async function(){})()'))();
/* istanbul ignore next */
return true;
} catch(e) {
if (required) throw new Error('es7 async functions not supported');
}
}
function getRegenerator(opts, required) {
try {
if (!regenerator) {
var name = 'regenerator';
regenerator = require(name);
regenerator.runtime();
}
if (!opts.async || opts.async === true)
opts.async = 'es7';
return regeneratorTranspile;
} catch(e) {
/* istanbul ignore next */
if (required) throw new Error('regenerator not available');
}
}
function regeneratorTranspile(code) {
return regenerator.compile(code).code;
}
function getNodent(opts, required) {
/* jshint evil: true */
try {
if (!nodent) {
var name = 'nodent';
nodent = require(name)({ log: false, dontInstallRequireHook: true });
}
if (opts.async != 'es7') {
if (opts.async && opts.async !== true) console.warn('nodent transpiles only es7 async functions');
opts.async = 'es7';
}
return nodentTranspile;
} catch(e) {
/* istanbul ignore next */
if (required) throw new Error('nodent not available');
}
}
function nodentTranspile(code) {
return nodent.compile(code, '', { promises: true, sourcemap: false }).code;
}
/**
* 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.
*/
function compileAsync(schema, callback) {
/* eslint no-shadow: 0 */
/* jshint validthis: true */
var schemaObj;
var self = this;
try {
schemaObj = this._addSchema(schema);
} catch(e) {
setTimeout(function() { callback(e); });
return;
}
if (schemaObj.validate) {
setTimeout(function() { callback(null, schemaObj.validate); });
} else {
if (typeof this._opts.loadSchema != 'function')
throw new Error('options.loadSchema should be a function');
_compileAsync(schema, callback, true);
}
function _compileAsync(schema, callback, firstCall) {
var validate;
try { validate = self.compile(schema); }
catch(e) {
if (e.missingSchema) loadMissingSchema(e);
else deferCallback(e);
return;
}
deferCallback(null, validate);
function loadMissingSchema(e) {
var ref = e.missingSchema;
if (self._refs[ref] || self._schemas[ref])
return callback(new Error('Schema ' + ref + ' is loaded but ' + e.missingRef + ' cannot be resolved'));
var _callbacks = self._loadingSchemas[ref];
if (_callbacks) {
if (typeof _callbacks == 'function')
self._loadingSchemas[ref] = [_callbacks, schemaLoaded];
else
_callbacks[_callbacks.length] = schemaLoaded;
} else {
self._loadingSchemas[ref] = schemaLoaded;
self._opts.loadSchema(ref, function (err, sch) {
var _callbacks = self._loadingSchemas[ref];
delete self._loadingSchemas[ref];
if (typeof _callbacks == 'function') {
_callbacks(err, sch);
} else {
for (var i=0; i<_callbacks.length; i++)
_callbacks[i](err, sch);
}
});
}
function schemaLoaded(err, sch) {
if (err) return callback(err);
if (!(self._refs[ref] || self._schemas[ref])) {
try {
self.addSchema(sch, ref);
} catch(e) {
callback(e);
return;
}
}
_compileAsync(schema, callback);
}
}
function deferCallback(err, validate) {
if (firstCall) setTimeout(function() { callback(err, validate); });
else return callback(err, validate);
}
}
}

View File

@ -5,6 +5,8 @@ module.exports = {
'$ref': require('../dotjs/ref'),
allOf: require('../dotjs/allOf'),
anyOf: require('../dotjs/anyOf'),
const: require('../dotjs/const'),
contains: require('../dotjs/contains'),
dependencies: require('../dotjs/dependencies'),
'enum': require('../dotjs/enum'),
format: require('../dotjs/format'),
@ -22,6 +24,7 @@ module.exports = {
oneOf: require('../dotjs/oneOf'),
pattern: require('../dotjs/pattern'),
properties: require('../dotjs/properties'),
propertyNames: require('../dotjs/propertyNames'),
required: require('../dotjs/required'),
uniqueItems: require('../dotjs/uniqueItems'),
validate: require('../dotjs/validate')

90
lib/compile/async.js Normal file
View File

@ -0,0 +1,90 @@
'use strict';
var MissingRefError = require('./error_classes').MissingRef;
module.exports = compileAsync;
/**
* Creates validating function for passed schema with asynchronous loading of missing schemas.
* `loadSchema` option should be a function that accepts schema uri and returns promise that resolves with the schema.
* @this Ajv
* @param {Object} schema schema object
* @param {Boolean} meta optional true to compile meta-schema; this parameter can be skipped
* @param {Function} callback an optional node-style callback, it is called with 2 parameters: error (or null) and validating function.
* @return {Promise} promise that resolves with a validating function.
*/
function compileAsync(schema, meta, callback) {
/* eslint no-shadow: 0 */
/* global Promise */
/* jshint validthis: true */
var self = this;
if (typeof this._opts.loadSchema != 'function')
throw new Error('options.loadSchema should be a function');
if (typeof meta == 'function') {
callback = meta;
meta = undefined;
}
var p = loadMetaSchemaOf(schema).then(function () {
var schemaObj = self._addSchema(schema, undefined, meta);
return schemaObj.validate || _compileAsync(schemaObj);
});
if (callback) {
p.then(
function(v) { callback(null, v); },
callback
);
}
return p;
function loadMetaSchemaOf(sch) {
var $schema = sch.$schema;
return $schema && !self.getSchema($schema)
? compileAsync.call(self, { $ref: $schema }, true)
: Promise.resolve();
}
function _compileAsync(schemaObj) {
try { return self._compile(schemaObj); }
catch(e) {
if (e instanceof MissingRefError) return loadMissingSchema(e);
throw e;
}
function loadMissingSchema(e) {
var ref = e.missingSchema;
if (added(ref)) throw new Error('Schema ' + ref + ' is loaded but ' + e.missingRef + ' cannot be resolved');
var schemaPromise = self._loadingSchemas[ref];
if (!schemaPromise) {
schemaPromise = self._loadingSchemas[ref] = self._opts.loadSchema(ref);
schemaPromise.then(removePromise, removePromise);
}
return schemaPromise.then(function (sch) {
if (!added(ref)) {
return loadMetaSchemaOf(sch).then(function () {
if (!added(ref)) self.addSchema(sch, ref, undefined, meta);
});
}
}).then(function() {
return _compileAsync(schemaObj);
});
function removePromise() {
delete self._loadingSchemas[ref];
}
function added(ref) {
return self._refs[ref] || self._schemas[ref];
}
}
}
}

View File

@ -0,0 +1,34 @@
'use strict';
var resolve = require('./resolve');
module.exports = {
Validation: errorSubclass(ValidationError),
MissingRef: errorSubclass(MissingRefError)
};
function ValidationError(errors) {
this.message = 'validation failed';
this.errors = errors;
this.ajv = this.validation = true;
}
MissingRefError.message = function (baseId, ref) {
return 'can\'t resolve reference ' + ref + ' from id ' + baseId;
};
function MissingRefError(baseId, ref, message) {
this.message = message || MissingRefError.message(baseId, ref);
this.missingRef = resolve.url(baseId, ref);
this.missingSchema = resolve.normalizeId(resolve.fullPath(this.missingRef));
}
function errorSubclass(Subclass) {
Subclass.prototype = Object.create(Error.prototype);
Subclass.prototype.constructor = Subclass;
return Subclass;
}

View File

@ -6,7 +6,15 @@ var DATE = /^\d\d\d\d-(\d\d)-(\d\d)$/;
var DAYS = [0,31,29,31,30,31,30,31,31,30,31,30,31];
var TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d:\d\d)?$/i;
var HOSTNAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*$/i;
var URI = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9a-f]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9a-f]{2})*)?$/i;
var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9a-f]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9a-f]{2})*)?$/i;
var URIREF = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@\/?]|%[0-9a-f]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'"()*+,;=:@\/?]|%[0-9a-f]{2})*)?$/i;
// uri-template: https://tools.ietf.org/html/rfc6570
var URITEMPLATE = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[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]);
}

View File

@ -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 + '];';
}

View File

@ -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<schema.length; i++)
_resolveIds.call(this, schema[i], fullPath+'/'+i, baseId);
} else if (schema && typeof schema == 'object') {
if (typeof schema.id == 'string') {
var id = baseId = baseId
? url.resolve(baseId, schema.id)
: schema.id;
id = normalizeId(id);
var id = this._getId(schema);
if (typeof id == 'string') {
id = baseId = normalizeId(baseId ? url.resolve(baseId, id) : id);
var refVal = this._refs[id];
if (typeof refVal == 'string') refVal = this._refs[refVal];

View File

@ -6,27 +6,43 @@ var ruleModules = require('./_rules')
module.exports = function rules() {
var RULES = [
{ type: 'number',
rules: [ 'maximum', 'minimum', 'multipleOf'] },
rules: [ { 'maximum': ['exclusiveMaximum'] },
{ 'minimum': ['exclusiveMinimum'] }, 'multipleOf', 'format'] },
{ type: 'string',
rules: [ 'maxLength', 'minLength', 'pattern', 'format' ] },
{ type: 'array',
rules: [ 'maxItems', 'minItems', 'uniqueItems', 'items' ] },
rules: [ 'maxItems', 'minItems', 'uniqueItems', 'contains', 'items' ] },
{ type: 'object',
rules: [ 'maxProperties', 'minProperties', 'required', 'dependencies', 'properties' ] },
{ rules: [ '$ref', 'enum', 'not', 'anyOf', 'oneOf', 'allOf' ] }
rules: [ 'maxProperties', 'minProperties', 'required', 'dependencies', 'propertyNames',
{ 'properties': ['additionalProperties', 'patternProperties'] } ] },
{ rules: [ '$ref', 'const', 'enum', 'not', 'anyOf', 'oneOf', 'allOf' ] }
];
var ALL = [ 'type', 'additionalProperties', 'patternProperties' ];
var KEYWORDS = [ 'additionalItems', '$schema', 'id', 'title', 'description', 'default' ];
var ALL = [ 'type' ];
var KEYWORDS = [
'additionalItems', '$schema', 'id', 'title',
'description', 'default', 'definitions'
];
var TYPES = [ 'number', 'integer', 'string', 'array', 'object', 'boolean', 'null' ];
RULES.all = toHash(ALL);
RULES.forEach(function (group) {
group.rules = group.rules.map(function (keyword) {
var implKeywords;
if (typeof keyword == 'object') {
var key = Object.keys(keyword)[0];
implKeywords = keyword[key];
keyword = key;
implKeywords.forEach(function (k) {
ALL.push(k);
RULES.all[k] = true;
});
}
ALL.push(keyword);
var rule = RULES.all[keyword] = {
keyword: keyword,
code: ruleModules[keyword]
code: ruleModules[keyword],
implements: implKeywords
};
return rule;
});

View File

@ -13,10 +13,9 @@ module.exports = {
varOccurences: varOccurences,
varReplace: varReplace,
cleanUpCode: cleanUpCode,
cleanUpVarErrors: cleanUpVarErrors,
finalCleanUpCode: finalCleanUpCode,
schemaHasRules: schemaHasRules,
schemaHasRulesExcept: schemaHasRulesExcept,
stableStringify: require('json-stable-stringify'),
toQuotedString: toQuotedString,
getPathExpr: getPathExpr,
getPath: getPath,
@ -149,26 +148,35 @@ var ERRORS_REGEXP = /[^v\.]errors/g
, REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g
, RETURN_VALID = 'return errors === 0;'
, RETURN_TRUE = 'validate.errors = null; return true;'
, RETURN_ASYNC = /if \(errors === 0\) return true;\s*else throw new ValidationError\(vErrors\);/
, RETURN_TRUE_ASYNC = 'return true;';
, RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/
, RETURN_DATA_ASYNC = 'return data;'
, ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g
, REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/;
function cleanUpVarErrors(out, async) {
function finalCleanUpCode(out, async) {
var matches = out.match(ERRORS_REGEXP);
if (!matches || matches.length !== 2) return out;
return async
if (matches && matches.length == 2) {
out = async
? out.replace(REMOVE_ERRORS_ASYNC, '')
.replace(RETURN_ASYNC, RETURN_TRUE_ASYNC)
.replace(RETURN_ASYNC, RETURN_DATA_ASYNC)
: out.replace(REMOVE_ERRORS, '')
.replace(RETURN_VALID, RETURN_TRUE);
}
matches = out.match(ROOTDATA_REGEXP);
if (!matches || matches.length !== 3) return out;
return out.replace(REMOVE_ROOTDATA, '');
}
function schemaHasRules(schema, rules) {
if (typeof schema == 'boolean') return !schema;
for (var key in schema) if (rules[key]) return true;
}
function schemaHasRulesExcept(schema, rules, exceptKeyword) {
if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not';
for (var key in schema) if (key != exceptKeyword && rules[key]) return true;
}

View File

@ -1,14 +0,0 @@
'use strict';
module.exports = ValidationError;
function ValidationError(errors) {
this.message = 'validation failed';
this.errors = errors;
this.ajv = this.validation = true;
}
ValidationError.prototype = Object.create(Error.prototype);
ValidationError.prototype.constructor = ValidationError;

View File

@ -7,7 +7,7 @@
var $isMax = $keyword == 'maximum'
, $exclusiveKeyword = $isMax ? 'exclusiveMaximum' : 'exclusiveMinimum'
, $schemaExcl = it.schema[$exclusiveKeyword]
, $isDataExcl = it.opts.v5 && $schemaExcl && $schemaExcl.$data
, $isDataExcl = it.opts.$data && $schemaExcl && $schemaExcl.$data
, $op = $isMax ? '<' : '>'
, $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' }}

View File

@ -35,7 +35,7 @@
{{= $closingBraces }}
if (!{{=$valid}}) {
{{# def.addError:'anyOf' }}
{{# def.extraError:'anyOf' }}
} else {
{{# def.resetErrors }}
{{? it.opts.allErrors }} } {{?}}

View File

@ -7,4 +7,5 @@
var schema{{=$lvl}} = validate.schema{{=$schemaPath}};
{{?}}
var {{=$valid}} = equal({{=$data}}, schema{{=$lvl}});
{{# def.checkError:'constant' }}
{{# def.checkError:'const' }}
{{? $breakOnError }} else { {{?}}

55
lib/dot/contains.jst Normal file
View File

@ -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 }}

View File

@ -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 }}

View File

@ -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)}}')
#}}

View File

@ -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);

View File

@ -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: "{}"
} #}}

View File

@ -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');

View File

@ -90,7 +90,6 @@ var {{=$valid}};
$it.errSchemaPath = $errSchemaPath;
}}
{{# def.validateItems: 0 }}
{{# def.ifResultValid }}
{{?}}
{{? $breakOnError }}

View File

@ -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 }}
}
#}}

View File

@ -38,7 +38,7 @@ var {{=$valid}} = false;
{{= $closingBraces }}
if (!{{=$valid}}) {
{{# def.error:'oneOf' }}
{{# def.extraError:'oneOf' }}
} else {
{{# def.resetErrors }}
{{? it.opts.allErrors }} } {{?}}

View File

@ -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}}++;

54
lib/dot/propertyNames.jst Normal file
View File

@ -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}}<errors; {{=$i}}++) {
vErrors[{{=$i}}].propertyName = {{=$key}};
}
{{# def.extraError:'propertyNames' }}
{{? $breakOnError }} break; {{?}}
}
}
{{?}}
{{? $breakOnError }}
{{= $closingBraces }}
if ({{=$errs}} == errors) {
{{?}}
{{# def.cleanUp }}

View File

@ -25,21 +25,16 @@
{{??}}
{{ var $refVal = it.resolveRef(it.baseId, $schema, it.isRoot); }}
{{? $refVal === undefined }}
{{ var $message = 'can\'t resolve reference ' + $schema + ' from id ' + it.baseId; }}
{{ var $message = it.MissingRefError.message(it.baseId, $schema); }}
{{? it.opts.missingRefs == 'fail' }}
{{ console.log($message); }}
{{ console.error($message); }}
{{# def.error:'$ref' }}
{{? $breakOnError }} if (false) { {{?}}
{{?? it.opts.missingRefs == 'ignore' }}
{{ console.log($message); }}
{{ console.warn($message); }}
{{? $breakOnError }} if (true) { {{?}}
{{??}}
{{
var $error = new Error($message);
$error.missingRef = it.resolve.url(it.baseId, $schema);
$error.missingSchema = it.resolve.normalizeId(it.resolve.fullPath($error.missingRef));
throw $error;
}}
{{ throw new it.MissingRefError(it.baseId, $schema, $message); }}
{{?}}
{{?? $refVal.inline }}
{{# def.setupNextLevel }}
@ -68,12 +63,16 @@
{{? $async }}
{{ if (!it.async) throw new Error('async schema referenced by sync schema'); }}
try { {{? $breakOnError }}var {{=$valid}} ={{?}} {{=it.yieldAwait}} {{=__callValidate}}; }
catch (e) {
{{? $breakOnError }} var {{=$valid}}; {{?}}
try {
{{=it.yieldAwait}} {{=__callValidate}};
{{? $breakOnError }} {{=$valid}} = true; {{?}}
} catch (e) {
if (!(e instanceof ValidationError)) throw e;
if (vErrors === null) vErrors = e.errors;
else vErrors = vErrors.concat(e.errors);
errors = vErrors.length;
{{? $breakOnError }} {{=$valid}} = false; {{?}}
}
{{? $breakOnError }} if ({{=$valid}}) { {{?}}
{{??}}

View File

@ -22,6 +22,11 @@
#}}
{{## def.isRequiredOwnProperty:
Object.prototype.hasOwnProperty.call({{=$data}}, {{=$vSchema}}[{{=$i}}])
#}}
{{? !$isData }}
{{? $schema.length < it.opts.loopRequired &&
it.schema.properties && Object.keys(it.schema.properties).length }}
@ -41,7 +46,8 @@
{{? $isData || $required.length }}
{{
var $currentErrorPath = it.errorPath
, $loopRequired = $isData || $required.length >= 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' }}
{{~}}
{{?}}

View File

@ -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' }}
}

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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;
}
}}

View File

@ -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;

36
lib/patternGroups.js Normal file
View File

@ -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');
};

17
lib/refs/$data.json Normal file
View File

@ -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
}

View File

@ -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": {}
}

View File

@ -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": {}
}

View File

@ -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 } }
};
}

View File

@ -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"
}
}

View File

@ -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);

View File

@ -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
}
}

10
spec/.eslintrc.yml Normal file
View File

@ -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

@ -1 +1 @@
Subproject commit f3d5aeb5ffbe9d9a5a0ceb761dc47c7c4c2efa68
Subproject commit 8758156cb3bae615e5e75abcab6e757883d10669

View File

@ -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'
});
});
});
});
});

View File

@ -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) {}
}

View File

@ -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;

View File

@ -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);
});
}
});

126
spec/async/boolean.json Normal file
View File

@ -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
}
]
}
]

View File

@ -137,7 +137,7 @@
"description": "custom keyword in async schema",
"schema": {
"$async": true,
"constant": 5
"const": 5
},
"tests": [
{

View File

@ -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
}
]
}
]

View File

@ -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,

View File

@ -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);
});
}

476
spec/boolean.spec.js Normal file
View File

@ -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);
}
});

View File

@ -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');

View File

@ -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);

29
spec/extras.spec.js Normal file
View File

@ -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
});

View File

@ -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"
}
},

View File

@ -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" } }
}
}
},

View File

@ -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
}
]
}
]

View File

@ -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
}
]
}
]

View File

@ -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",

View File

@ -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": [
{

View File

@ -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
}
]
}
]

View File

@ -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
}
]
}
]

View File

@ -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
}
]
}
]

View File

@ -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);
});
});

View File

@ -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
});
}

View File

@ -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'));
});
});
});
});

View File

@ -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: {

View File

@ -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);
}

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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 }

View File

@ -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",

View File

@ -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": [

View File

@ -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",

View File

@ -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" },

Some files were not shown because too many files have changed in this diff Show More