diff --git a/.travis.yml b/.travis.yml
index 6ada95b..c69a671 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,5 +5,7 @@ before_script:
node_js:
- "0.10"
- "0.12"
+ - "4"
+ - "5"
after_script:
- codeclimate-test-reporter < coverage/lcov.info
diff --git a/README.md b/README.md
index 438455d..66f70aa 100644
--- a/README.md
+++ b/README.md
@@ -4,23 +4,32 @@ Currently the fastest JSON Schema validator for node.js and browser.
It uses [doT templates](https://github.com/olado/doT) to generate super-fast validating functions.
-[![Build Status](https://travis-ci.org/epoberezkin/ajv.svg?branch=master)](https://travis-ci.org/epoberezkin/ajv)
+[![Build Status](https://travis-ci.org/epoberezkin/ajv.svg)](https://travis-ci.org/epoberezkin/ajv)
[![npm version](https://badge.fury.io/js/ajv.svg)](http://badge.fury.io/js/ajv)
[![Code Climate](https://codeclimate.com/github/epoberezkin/ajv/badges/gpa.svg)](https://codeclimate.com/github/epoberezkin/ajv)
[![Test Coverage](https://codeclimate.com/github/epoberezkin/ajv/badges/coverage.svg)](https://codeclimate.com/github/epoberezkin/ajv/coverage)
-## JSON Schema standard
+NB: [Upgrading to version 2.0.0](https://github.com/epoberezkin/ajv/releases/tag/2.0.0).
-ajv implements full [JSON Schema draft 4](http://json-schema.org/) standard:
-- 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)
-- [asynchronous loading](#asynchronous-compilation) of referenced schemas during compilation.
-- support of circular dependencies between schemas
-- correct string lengths for strings with unicode pairs (can be turned off)
-- formats defined by JSON Schema draft 4 standard and custom formats (can be turned off)
-- BETA: [custom keywords](https://github.com/epoberezkin/ajv/tree/v2.0#defining-custom-keywords) supported starting from version [2.0.0](https://github.com/epoberezkin/ajv/tree/v2.0), `npm install ajv@^2.0.0-beta` to use it
+## Features
+
+- ajv implements full [JSON Schema draft 4](http://json-schema.org/) standard:
+ - 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 dependencies between schemas
+ - correct string lengths for strings with unicode pairs (can be turned off)
+ - [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-5.0
+- [asynchronous loading](#asynchronous-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
+- [filtering data](#filtering-data) from additional properties
+- NEW: [custom keywords](#defining-custom-keywords)
+- NEW: keywords `constant` and `contains` from [JSON-schema v5 proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals) with [option v5](#options)
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).
@@ -53,7 +62,7 @@ The fastest validation call:
```
var Ajv = require('ajv');
-var ajv = Ajv(); // options can be passed
+var ajv = Ajv(); // options can be passed, e.g. {allErrors: true}
var validate = ajv.compile(schema);
var valid = validate(data);
if (!valid) console.log(validate.errors);
@@ -82,7 +91,7 @@ ajv compiles schemas to functions and caches them in all cases (using stringifie
The best performance is achieved when using compiled functions returned by `compile` or `getSchema` methods (there is no additional function call).
-__Please note__: every time validation function or `ajv.validate` are called `errors` property is overwritten. You need to copy `errors` array reference to another variable if you want to use it later (e.g., in the callback).
+__Please note__: every time validation function or `ajv.validate` are called `errors` property is overwritten. You need to copy `errors` array reference to another variable if you want to use it later (e.g., in the callback). See [Validation errors](#validation-errors)
## Using in browser
@@ -123,6 +132,202 @@ You can add additional formats and replace any of the formats above using [addFo
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).
+## Defining custom keywords
+
+Starting from version 2.0 (ajv@^2.0.0-beta) ajv supports custom keyword definitions.
+
+WARNING: The main drawback of extending JSON-schema standard with custom keywords is the loss of portability of your schemas - it may not be possible to support these custom keywords on some other platforms. Also your schemas may be more challenging to read for other people. If portability is important you may prefer using additional validation logic outside of JSON-schema rather than putting it inside your JSON-schema.
+
+The advantages of using custom keywords are:
+- they allow you keeping a larger portion of your validation logic in the schema
+- they make your schemas more expressive and less verbose
+- they are fun to use
+
+You can define custom keywords with [addKeyword](#api-addkeyword) method. Keywords are defined on the `ajv` instance level - new instances will not have previously defined keywords.
+
+Ajv allows defining keywords with:
+- validation function
+- compilation function
+- macro function
+- inline compilation function that should return code (as string) that will be inlined in the currently compiled schema.
+
+
+### Define keyword with validation function (NOT RECOMMENDED)
+
+Validation function will be called during data validation. It will be passed schema, data and parentSchema (if it has 3 arguments) at validation time and it should return validation result as boolean. It can return an array of validation errors via `.errors` property of itself (otherwise a standard error will be used).
+
+This way to define keywords is added as a way to quickly test your keyword and is not recommended because of worse performance than compiling schemas.
+
+
+Example. draft5 `constant` keyword (that is equivalent to `enum` keyword with one item):
+
+```
+ajv.addKeyword('constant', { validate: function (schema, data) {
+ return typeof schema == 'object && schema !== null'
+ ? deepEqual(schema, data)
+ : schema === data;
+} });
+
+var schema = { "constant": 2 };
+var validate = ajv.compile(schema);
+console.log(validate(2)); // true
+console.log(validate(3)); // false
+
+var schema = { "constant": { "foo": "bar" } };
+var validate = ajv.compile(schema);
+console.log(validate({foo: 'bar'})); // true
+console.log(validate({foo: 'baz'})); // false
+```
+
+
+### Define keyword with "compilation" function
+
+Compilation function will be called during schema compilation. It will be passed schema and parent schema and it should return a validation function. This validation function will be passed data during validation; it should return validation result as boolean and it can return an array of validation errors via `.errors` property of itself (otherwise a standard error will be used).
+
+In some cases it is the best approach to define keywords, but it has the performance cost of an extra function call during validation. If keyword logic can be expressed via some other JSON-schema then `macro` keyword definition is more efficient (see below).
+
+Example. `range` and `exclusiveRange` keywords using compiled schema:
+
+```
+ajv.addKeyword('range', { type: 'number', compile: function (sch, parentSchema) {
+ var min = sch[0];
+ var max = sch[1];
+
+ return parentSchema.exclusiveRange === true
+ ? function (data) { return data > min && data < max; }
+ : function (data) { return data >= min && data <= max; }
+} });
+
+var schema = { "range": [2, 4], "exclusiveRange": true };
+var validate = ajv.compile(schema);
+console.log(validate(2.01)); // true
+console.log(validate(3.99)); // true
+console.log(validate(2)); // false
+console.log(validate(4)); // false
+```
+
+
+### Define keyword with "macro" function
+
+"Macro" function is called during schema compilation. It is passed schema and parent schema and it should return another schema that will be applied to the data in addition to the original schema (if schemas have different keys they are merged, otherwise `allOf` keyword is used).
+
+It is the most efficient approach (in cases when the keyword logic can be expressed with another JSON-schema) because it is usually easy to implement and there is no extra function call during validation.
+
+
+`range` and `exclusiveRange` keywords from the previous example defined with macro:
+
+```
+ajv.addKeyword('range', { macro: function (schema, parentSchema) {
+ return {
+ minimum: schema[0],
+ maximum: schema[1],
+ exclusiveMinimum: !!parentSchema.exclusiveRange,
+ exclusiveMaximum: !!parentSchema.exclusiveRange
+ };
+} });
+```
+
+Example draft5 `contains` keyword that requires that the array has at least one item matching schema (see https://github.com/json-schema/json-schema/wiki/contains-(v5-proposal)):
+
+```
+ajv.addKeyword('contains', { macro: function (schema) {
+ return { "not": { "items": { "not": schema } } };
+} });
+
+var schema = {
+ "contains": {
+ "type": "number",
+ "minimum": 4,
+ "exclusiveMinimum": true
+ }
+};
+
+var validate = ajv.compile(schema);
+console.log(validate([1,2,3])); // false
+console.log(validate([2,3,4])); // false
+console.log(validate([3,4,5])); // true, number 5 matches schema inside "contains"
+```
+
+See the example of defining recursive macro keyword `deepProperties` in the [test](https://github.com/epoberezkin/ajv/blob/master/spec/custom.spec.js#L151).
+
+
+### Define keyword with "inline" compilation function
+
+Inline compilation function is called during schema compilation. It is passed three parameters: `it` (the current schema compilation context), `schema` and `parentSchema` and it should return the code (as a string) that will be inlined in the code of compiled schema. This code can be either an expression that evaluates to the validation result (boolean) or a set of statements that assign the validation result to a variable.
+
+While it can be more difficult to define keywords with "inline" functions, it can have the best performance.
+
+Example `even` keyword:
+
+```
+ajv.addKeyword('even', { type: 'number', inline: function (it, schema) {
+ var op = schema ? '===' : '!==';
+ return 'data' + (it.dataLevel || '') + ' % 2 ' + op + ' 0';
+} });
+
+var schema = { "even": true };
+
+var validate = ajv.compile(schema);
+console.log(validate(2)); // true
+console.log(validate(3)); // false
+```
+
+`'data' + (it.dataLevel || '')` in the example above is the reference to the currently validated data. Also note that `schema` (keyword schema) is the same as `it.schema.even`, so schema is not strictly necessary here - it is passed for convenience.
+
+
+Example `range` keyword defined using [doT template](https://github.com/olado/doT):
+
+```
+var doT = require('dot');
+var inlineRangeTemplate = doT.compile("\
+{{ \
+ var $data = 'data' + (it.dataLevel || '') \
+ , $min = it.schema.range[0] \
+ , $max = it.schema.range[1] \
+ , $gt = it.schema.exclusiveRange ? '>' : '>=' \
+ , $lt = it.schema.exclusiveRange ? '<' : '<='; \
+}} \
+var valid{{=it.level}} = {{=$data}} {{=$gt}} {{=$min}} && {{=$data}} {{=$lt}} {{=$max}}; \
+");
+
+ajv.addKeyword('range', {
+ type: 'number',
+ inline: inlineRangeTemplate,
+ statements: true
+});
+```
+
+`'valid' + it.level` in the example above is the expected name of the variable that should be set to the validation result.
+
+Property `statements` in the keyword definition should be set to `true` if the validation code sets the variable instead of evaluating to the validation result.
+
+
+### Defining errors in custom keywords
+
+All custom keywords but macro keywords can create custom error messages.
+
+Validating and compiled keywords should define errors by assigning them to `.errors` property of validation function.
+
+Inline custom keyword should increase error counter `errors` and add error to `vErrors` array (it can be null). See [example range keyword](https://github.com/epoberezkin/ajv/blob/master/spec/custom_rules/range_with_errors.jst).
+
+When inline keyword performes validation Ajv checks whether it created errors by comparing errors count before and after validation. To skip this check add option `errors` to keyword definition:
+
+```
+ajv.addKeyword('range', {
+ type: 'number',
+ inline: inlineRangeTemplate,
+ statements: true,
+ errors: true // keyword should create custom errors when validation fails
+});
+```
+
+Each error object should have properties `keyword`, `message` and `params`, other properties will be added.
+
+Inlined keywords can optionally define `dataPath` property in error objects.
+
+If custom keyword doesn't create errors, the default error will be created in case the keyword fails validation (see [Validation errors](#validation-errors)).
+
+
## Asynchronous compilation
Starting from version 1.3 ajv supports asynchronous compilation when remote references are loaded using supplied function. See `compileAsync` method and `loadSchema` option.
@@ -154,6 +359,8 @@ With [option `removeAdditional`](#options) (added by [andyscott](https://github.
This option modifies original object.
+TODO: example
+
## API
@@ -220,7 +427,7 @@ Adds meta schema that can be used to validate other schemas. That function shoul
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`.
-##### .validateSchema(Object schema) -> Boolean
+##### .validateSchema(Object schema) -> Boolean
Validates schema. This method should be used to validate schemas rather than `validate` due to the inconsistency of `uri` format in JSON-Schema standart.
@@ -256,6 +463,29 @@ Function should return validation result as `true` or `false`.
Custom formats can be also added via `formats` option.
+##### .addKeyword(String keyword, Object definition)
+
+Add custom validation keyword to ajv instance.
+
+Keyword should be a valid JavaScript identifier.
+
+Keyword should be different from all standard JSON schema keywords and different from previously defined keywords. There is no way to redefine keywords or remove keyword definition from the instance.
+
+Keyword definition is an object with the following properties:
+
+- _type_: optional string or array of strings with data type(s) that the keyword will apply to. If keyword is validating another type the validation function will not be called, so there is no need to check for data type inside validation function if `type` property is used.
+- _validate_: validating function
+- _compile_: compiling function
+- _macro_: macro function
+- _inline_: compiling function that returns code (as string)
+
+_validate_, _compile_, _macro_ and _inline_ are mutually exclusive, only one should be used at a time.
+
+With _macro_ function _type_ must not be specified, the types that the keyword will be applied for will be determined by the final schema.
+
+See [Defining custom keywords](#defining-custom-keywords) for more details.
+
+
##### .errorsText([Array<Object> errors [, Object options]]) -> String
Returns the text with all errors in a String.
@@ -284,9 +514,10 @@ Defaults:
unicode: true,
beautify: false,
cache: new Cache,
+ errorDataPath: 'object',
jsonPointers: false,
- i18n: false,
messages: true
+ v5: true
}
```
@@ -305,9 +536,53 @@ Defaults:
- _unicode_: calculate correct length of strings with unicode pairs (true by default). Pass `false` to use `.length` of strings that is faster, but gives "incorrect" lengths of strings with unicode pairs - each unicode pair is counted as two characters.
- _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.
- _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)` and `del(key)`.
-- _jsonPointers_: Output `dataPath` using JSON Pointers instead of JS path notation.
-- _i18n_: Support internationalization of error messages using [ajv-i18n](https://github.com/epoberezkin/ajv-i18n). See its repo for details.
-- _messages_: Include human-readable messages in errors. `true` by default. `messages: false` can be added when internationalization (options `i18n`) is used.
+- _errorDataPath_: set `dataPath` to point to 'object' (default) or to 'property' (default behavior in versions before 2.0) when validating keywords `required`, `additionalProperties` and `dependencies`.
+- _jsonPointers_: set `dataPath` propery of errors using [JSON Pointers](https://tools.ietf.org/html/rfc6901) instead of JavaScript property access notation.
+- _messages_: Include human-readable messages in errors. `true` by default. `messages: false` can be added when custom messages are used (e.g. with [ajv-i18n](https://github.com/epoberezkin/ajv-i18n)).
+- _v5_: add keywords `constant` and `contains` from [JSON-schema v5 proposals](https://github.com/json-schema/json-schema/wiki/v5-Proposals)
+
+
+## Validation errors
+
+In case of validation failure Ajv assigns the array of errors to `.errors` property of validation function (or to `.errors` property of ajv instance in case `validate` or `validateSchema` methods were called).
+
+
+### Error objects
+
+Each error is an object with the following properties:
+
+- _keyword_: validation keyword. For user defined validation keywords it is set to `"custom"` (with the exception of macro keywords and unless keyword definition defines its own errors).
+- _dataPath_: the path to the part of the data that was validated. By default `dataPath` uses JavaScript property access notation (e.g., `".prop[1].subProp"`). When the option `jsonPointers` is true (see [Options](#options)) `dataPath` will be set using JSON pointer standard (e.g., `"/prop/1/subProp"`).
+- _params_: the object with the additional information about error that can be used to create custom error messages (e.g., using [ajv-i18n](https://github.com/epoberezkin/ajv-i18n) package). See below for parameters set by all keywords.
+- _message_: the standard error message (can be excluded with option `messages` set to false).
+- _schema_: the schema of the keyword (added with `verbose` option).
+- _data_: the data validated by the keyword (added with `verbose` option).
+
+
+### Error parameters
+
+Properties of `params` object in errors depend on the keyword that failed validation.
+
+- `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).
+- `dependencies` - properties:
+ - `property` (dependent property),
+ - `missingProperty` (required missing dependency - only the first one is reported currently)
+ - `deps` (required dependencies, comma separated list as a string),
+ - `depsCount` (the number of required dependedncies).
+- `format` - property `format` (the schema of the keyword).
+- `maximum`, `minimum` - properties:
+ - `limit` (number, the schema of the keyword),
+ - `exclusive` (boolean, the schema of `exclusiveMaximum` or `exclusiveMinimum`),
+ - `comparison` (string, comparison operation to compare the data to the limit, with the data on the left and the limit on the right; can be "<", "<=", ">", ">=")
+- `multipleOf` - property `multipleOf` (the schema of the keyword)
+- `pattern` - property `pattern` (the schema of the keyword)
+- `required` - property `missingProperty` (required property that is missing).
+- `type` - property `type` (required type(s), a string, can be a comma-separated list)
+- `uniqueItems` - properties `i` and `j` (indices of duplicate items).
+- `$ref` - property `ref` with the referenced schema URI.
+- custom keywords (in case keyword definition doesn't create errors) - property `keyword` (the keyword name).
## Command line interface
diff --git a/lib/ajv.js b/lib/ajv.js
index b1cef99..63f3c74 100644
--- a/lib/ajv.js
+++ b/lib/ajv.js
@@ -5,7 +5,9 @@ var compileSchema = require('./compile')
, Cache = require('./cache')
, SchemaObject = require('./compile/schema_obj')
, stableStringify = require('json-stable-stringify')
- , formats = require('./compile/formats');
+ , formats = require('./compile/formats')
+ , rules = require('./compile/rules')
+ , v5 = require('./v5');
module.exports = Ajv;
@@ -31,6 +33,7 @@ function Ajv(opts) {
this._formats = formats(this.opts.format);
this._cache = this.opts.cache || new Cache;
this._loadingSchemas = {};
+ 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
@@ -43,6 +46,7 @@ function Ajv(opts) {
this.getSchema = getSchema;
this.removeSchema = removeSchema;
this.addFormat = addFormat;
+ this.addKeyword = addKeyword;
this.errorsText = errorsText;
this._compile = _compile;
@@ -50,6 +54,11 @@ function Ajv(opts) {
addInitialSchemas();
if (this.opts.formats) addInitialFormats();
+ if (this.opts.errorDataPath == 'property')
+ this.opts._errorDataPathProperty = true;
+
+ if (this.opts.v5) v5.enable(this);
+
/**
* Validate data using schema
@@ -196,10 +205,11 @@ function Ajv(opts) {
/**
* Validate schema
- * @param {Object} schema schema to validate
+ * @param {Object} schema schema to validate
+ * @param {Boolean} throwOrLogError pass true to throw on error
* @return {Boolean}
*/
- function validateSchema(schema) {
+ function validateSchema(schema, throwOrLogError) {
var $schema = schema.$schema || META_SCHEMA_ID;
var currentUriFormat = self._formats.uri;
self._formats.uri = typeof currentUriFormat == 'function'
@@ -207,6 +217,11 @@ function Ajv(opts) {
: SCHEMA_URI_FORMAT;
var valid = validate($schema, schema);
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;
}
@@ -265,13 +280,8 @@ function Ajv(opts) {
var id = resolve.normalizeId(schema.id);
if (id) checkUnique(id);
- var ok = skipValidation || self.opts.validateSchema === false
- || validateSchema(schema);
- if (!ok) {
- var message = 'schema is invalid:' + errorsText();
- if (self.opts.validateSchema == 'log') console.error(message);
- else throw new Error(message);
- }
+ if (self.opts.validateSchema !== false && !skipValidation)
+ validateSchema(schema, true);
var localRefs = resolve.ids.call(self, schema);
@@ -324,6 +334,12 @@ function Ajv(opts) {
}
+ /**
+ * Convert array of error message objects to string
+ * @param {Array