2015-05-20 03:55:53 +03:00
'use strict' ;
2015-11-22 05:10:53 +03:00
var compileSchema = require ( './compile' )
2015-11-22 05:00:34 +03:00
, resolve = require ( './compile/resolve' )
, Cache = require ( './cache' )
, SchemaObject = require ( './compile/schema_obj' )
, stableStringify = require ( 'json-stable-stringify' )
, formats = require ( './compile/formats' )
, rules = require ( './compile/rules' )
2016-01-21 01:55:14 +03:00
, v5 = require ( './v5' )
2016-01-27 02:56:38 +03:00
, util = require ( './compile/util' )
2016-02-03 02:42:07 +03:00
, async = require ( './async' )
, co = require ( 'co' ) ;
2015-05-20 03:55:53 +03:00
2015-05-30 01:32:47 +03:00
module . exports = Ajv ;
2015-05-20 03:55:53 +03:00
2016-01-27 02:56:38 +03:00
Ajv . prototype . compileAsync = async . compile ;
2015-11-22 05:10:53 +03:00
Ajv . prototype . addKeyword = require ( './keyword' ) ;
2016-01-31 01:13:00 +03:00
Ajv . ValidationError = require ( './compile/validation_error' ) ;
2015-11-22 05:10:53 +03:00
2015-06-07 14:46:53 +03:00
var META _SCHEMA _ID = 'http://json-schema.org/draft-04/schema' ;
2015-06-18 00:06:07 +03:00
var SCHEMA _URI _FORMAT = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/\/)?[^\s]*$/i ;
function SCHEMA _URI _FORMAT _FUNC ( str ) {
2015-11-22 05:00:34 +03:00
return SCHEMA _URI _FORMAT . test ( str ) ;
2015-06-18 00:06:07 +03:00
}
2015-06-07 14:46:53 +03:00
2016-01-17 04:05:06 +03:00
var META _IGNORE _OPTIONS = [ 'removeAdditional' , 'useDefaults' , 'coerceTypes' ] ;
2015-05-20 03:55:53 +03:00
/ * *
* Creates validator instance .
2015-06-20 20:40:13 +03:00
* Usage : ` Ajv(opts) `
2015-05-20 03:55:53 +03:00
* @ param { Object } opts optional options
2015-06-20 20:40:13 +03:00
* @ return { Object } ajv instance
2015-05-20 03:55:53 +03:00
* /
2015-05-30 01:32:47 +03:00
function Ajv ( opts ) {
2015-11-22 05:00:34 +03:00
if ( ! ( this instanceof Ajv ) ) return new Ajv ( opts ) ;
var self = this ;
2016-01-27 02:56:38 +03:00
opts = this . _opts = util . copy ( opts ) || { } ;
2015-11-22 05:00:34 +03:00
this . _schemas = { } ;
this . _refs = { } ;
2016-01-27 02:56:38 +03:00
this . _formats = formats ( opts . format ) ;
this . _cache = opts . cache || new Cache ;
2015-11-22 05:00:34 +03:00
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
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 ;
2015-11-22 05:10:53 +03:00
this . _addSchema = _addSchema ;
2015-11-22 05:00:34 +03:00
this . _compile = _compile ;
2016-03-20 21:12:14 +03:00
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 ( ) ;
2016-01-27 02:56:38 +03:00
if ( opts . formats ) addInitialFormats ( ) ;
2016-06-01 08:58:29 +03:00
addDraft4MetaSchema ( ) ;
2016-01-27 02:56:38 +03:00
if ( opts . v5 ) v5 . enable ( this ) ;
2016-03-20 21:12:14 +03:00
if ( typeof opts . meta == 'object' ) addMetaSchema ( opts . meta ) ;
2016-06-01 08:58:29 +03:00
addInitialSchemas ( ) ;
2015-12-26 02:23:42 +03:00
2015-11-22 05:00:34 +03:00
/ * *
* 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 ) ;
2015-05-26 04:11:36 +03:00
}
2015-11-22 05:00:34 +03:00
var valid = v ( data ) ;
2016-04-16 00:20:40 +03:00
if ( v . $async === true )
2016-04-11 22:40:31 +03:00
return self . _opts . async == '*' ? co ( valid ) : valid ;
2015-11-22 05:00:34 +03:00
self . errors = v . errors ;
return valid ;
}
/ * *
* Create validating function for passed schema .
2016-02-02 21:55:02 +03:00
* @ param { Object } schema schema object
2015-11-22 05:00:34 +03:00
* @ return { Function } validating function
* /
function compile ( schema ) {
var schemaObj = _addSchema ( schema ) ;
return schemaObj . validate || _compile ( schemaObj ) ;
}
/ * *
* Adds schema to the instance .
2016-02-02 21:55:02 +03:00
* @ param { Object | Array } schema schema or array of schemas . If array is passed , ` key ` and other parameters will be ignored .
2015-11-22 05:00:34 +03:00
* @ 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 ` .
2016-02-02 21:55:02 +03:00
* @ 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 .
2015-11-22 05:00:34 +03:00
* /
function addSchema ( schema , key , _skipValidation , _meta ) {
if ( Array . isArray ( schema ) ) {
2016-03-10 02:20:35 +03:00
for ( var i = 0 ; i < schema . length ; i ++ ) addSchema ( schema [ i ] , undefined , _skipValidation , _meta ) ;
2015-11-22 05:00:34 +03:00
return ;
2015-06-07 14:46:53 +03:00
}
2015-11-22 05:00:34 +03:00
// can key/id have # inside?
key = resolve . normalizeId ( key || schema . id ) ;
checkUnique ( key ) ;
2016-01-15 09:54:53 +03:00
var schemaObj = self . _schemas [ key ] = _addSchema ( schema , _skipValidation , true ) ;
2015-11-22 05:00:34 +03:00
schemaObj . meta = _meta ;
}
/ * *
* Add schema that will be used to validate other schemas
2016-01-17 04:05:06 +03:00
* options in META _IGNORE _OPTIONS are alway set to false
2016-02-02 21:55:02 +03:00
* @ param { Object } schema schema object
2015-11-22 05:00:34 +03:00
* @ param { String } key optional schema key
2016-02-02 21:55:02 +03:00
* @ param { Boolean } skipValidation true to skip schema validation , can be used to override validateSchema option for meta - schema
2015-11-22 05:00:34 +03:00
* /
2016-02-02 21:55:02 +03:00
function addMetaSchema ( schema , key , skipValidation ) {
addSchema ( schema , key , skipValidation , true ) ;
2015-11-22 05:00:34 +03:00
}
/ * *
* Validate schema
* @ param { Object } schema schema to validate
2015-12-27 01:27:13 +03:00
* @ param { Boolean } throwOrLogError pass true to throw ( or log ) an error if invalid
2016-02-02 21:55:02 +03:00
* @ return { Boolean } true if schema is valid
2015-11-22 05:00:34 +03:00
* /
function validateSchema ( schema , throwOrLogError ) {
2016-03-14 23:53:00 +03:00
var $schema = schema . $schema || self . _opts . defaultMeta || defaultMeta ( ) ;
2015-11-22 05:00:34 +03:00
var currentUriFormat = self . _formats . uri ;
self . _formats . uri = typeof currentUriFormat == 'function'
? SCHEMA _URI _FORMAT _FUNC
: SCHEMA _URI _FORMAT ;
var valid = validate ( $schema , schema ) ;
self . _formats . uri = currentUriFormat ;
if ( ! valid && throwOrLogError ) {
var message = 'schema is invalid:' + errorsText ( ) ;
2016-01-21 01:55:14 +03:00
if ( self . _opts . validateSchema == 'log' ) console . error ( message ) ;
2015-11-22 05:00:34 +03:00
else throw new Error ( message ) ;
2015-06-07 14:46:53 +03:00
}
2015-11-22 05:00:34 +03:00
return valid ;
}
2016-03-14 23:53:00 +03:00
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 ;
}
2015-11-22 05:00:34 +03:00
/ * *
* 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 ) ;
2015-08-09 02:54:06 +03:00
}
2015-11-22 05:00:34 +03:00
}
function _getSchemaObj ( keyRef ) {
keyRef = resolve . normalizeId ( keyRef ) ;
return self . _schemas [ keyRef ] || self . _refs [ keyRef ] ;
}
/ * *
2016-02-28 04:16:48 +03:00
* 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
2015-11-22 05:00:34 +03:00
* /
function removeSchema ( schemaKeyRef ) {
2016-04-18 23:15:38 +03:00
if ( schemaKeyRef instanceof RegExp ) {
_removeAllSchemas ( self . _schemas , schemaKeyRef ) ;
_removeAllSchemas ( self . _refs , schemaKeyRef ) ;
return ;
}
2015-11-22 05:00:34 +03:00
switch ( typeof schemaKeyRef ) {
2016-02-28 04:16:48 +03:00
case 'undefined' :
_removeAllSchemas ( self . _schemas ) ;
_removeAllSchemas ( self . _refs ) ;
self . _cache . clear ( ) ;
return ;
2015-11-22 05:00:34 +03:00
case 'string' :
var schemaObj = _getSchemaObj ( schemaKeyRef ) ;
2016-02-13 23:14:04 +03:00
if ( schemaObj ) self . _cache . del ( schemaObj . jsonStr ) ;
2015-11-22 05:00:34 +03:00
delete self . _schemas [ schemaKeyRef ] ;
delete self . _refs [ schemaKeyRef ] ;
2016-02-28 04:16:48 +03:00
return ;
2015-11-22 05:00:34 +03:00
case 'object' :
var jsonStr = stableStringify ( schemaKeyRef ) ;
self . _cache . del ( jsonStr ) ;
var id = schemaKeyRef . id ;
if ( id ) {
id = resolve . normalizeId ( id ) ;
2016-02-28 04:16:48 +03:00
delete self . _schemas [ id ] ;
2015-11-22 05:00:34 +03:00
delete self . _refs [ id ] ;
2015-06-20 20:40:13 +03:00
}
2015-05-26 04:11:36 +03:00
}
2016-02-28 04:16:48 +03:00
}
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 ] ;
}
}
2015-11-22 05:00:34 +03:00
}
2015-05-26 04:11:36 +03:00
2016-01-15 09:54:53 +03:00
function _addSchema ( schema , skipValidation , shouldAddSchema ) {
2015-11-22 05:00:34 +03:00
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 ;
2015-07-04 02:48:32 +03:00
2016-01-21 01:55:14 +03:00
shouldAddSchema = shouldAddSchema || self . _opts . addUsedSchema !== false ;
2016-01-15 09:54:53 +03:00
2015-11-22 05:00:34 +03:00
var id = resolve . normalizeId ( schema . id ) ;
2016-01-15 09:54:53 +03:00
if ( id && shouldAddSchema ) checkUnique ( id ) ;
2015-07-04 02:48:32 +03:00
2016-01-21 01:55:14 +03:00
if ( self . _opts . validateSchema !== false && ! skipValidation )
2015-11-22 05:00:34 +03:00
validateSchema ( schema , true ) ;
2015-07-04 02:48:32 +03:00
2015-11-22 05:00:34 +03:00
var localRefs = resolve . ids . call ( self , schema ) ;
2015-08-09 02:54:06 +03:00
2015-11-22 05:00:34 +03:00
var schemaObj = new SchemaObject ( {
id : id ,
schema : schema ,
localRefs : localRefs ,
2016-02-02 21:55:02 +03:00
jsonStr : jsonStr
2015-11-22 05:00:34 +03:00
} ) ;
2015-08-09 02:54:06 +03:00
2016-01-15 09:54:53 +03:00
if ( id [ 0 ] != '#' && shouldAddSchema ) self . _refs [ id ] = schemaObj ;
2015-11-22 05:00:34 +03:00
self . _cache . put ( jsonStr , schemaObj ) ;
2015-07-04 02:48:32 +03:00
2015-11-22 05:00:34 +03:00
return schemaObj ;
}
2015-06-07 14:46:53 +03:00
2015-11-22 05:00:34 +03:00
function _compile ( schemaObj , root ) {
if ( schemaObj . compiling ) {
schemaObj . validate = callValidate ;
callValidate . schema = schemaObj . schema ;
callValidate . errors = null ;
callValidate . root = root ? root : callValidate ;
2016-01-28 02:43:28 +03:00
if ( schemaObj . schema . $async === true )
2016-04-10 22:17:06 +03:00
callValidate . $async = true ;
2015-11-22 05:00:34 +03:00
return callValidate ;
2015-06-17 04:05:52 +03:00
}
2015-11-22 05:00:34 +03:00
schemaObj . compiling = true ;
2016-03-20 21:12:14 +03:00
var currentOpts ;
2016-01-09 04:07:33 +03:00
if ( schemaObj . meta ) {
2016-03-20 21:12:14 +03:00
currentOpts = self . _opts ;
self . _opts = self . _metaOpts ;
2016-01-09 04:07:33 +03:00
}
2016-01-17 04:05:06 +03:00
2015-11-22 05:00:34 +03:00
var v ;
try { v = compileSchema . call ( self , schemaObj . schema , root , schemaObj . localRefs ) ; }
finally {
schemaObj . compiling = false ;
2016-03-20 21:12:14 +03:00
if ( schemaObj . meta ) self . _opts = currentOpts ;
2015-06-13 15:42:58 +03:00
}
2015-11-22 05:00:34 +03:00
schemaObj . validate = v ;
schemaObj . refs = v . refs ;
schemaObj . refVal = v . refVal ;
schemaObj . root = v . root ;
return v ;
2015-06-13 15:42:58 +03:00
2015-11-11 10:01:27 +03:00
2015-11-22 05:00:34 +03:00
function callValidate ( ) {
2016-02-05 01:24:59 +03:00
var _validate = schemaObj . validate ;
var result = _validate . apply ( null , arguments ) ;
callValidate . errors = _validate . errors ;
2015-11-22 05:00:34 +03:00
return result ;
2015-11-11 10:01:27 +03:00
}
2015-11-22 05:00:34 +03:00
}
/ * *
* 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 .
2016-02-05 01:24:59 +03:00
* @ param { Object } options optional options with properties ` separator ` and ` dataVar ` .
2016-02-02 21:55:02 +03:00
* @ return { String } human readable string with all errors descriptions
2015-11-22 05:00:34 +03:00
* /
2016-02-05 01:24:59 +03:00
function errorsText ( errors , options ) {
2015-11-22 05:00:34 +03:00
errors = errors || self . errors ;
if ( ! errors ) return 'No errors' ;
2016-02-05 01:24:59 +03:00
options = options || { } ;
2016-03-24 02:25:54 +03:00
var separator = options . separator === undefined ? ', ' : options . separator ;
var dataVar = options . dataVar === undefined ? 'data' : options . dataVar ;
2015-11-22 05:00:34 +03:00
2015-11-22 05:10:53 +03:00
var text = '' ;
for ( var i = 0 ; i < errors . length ; i ++ ) {
var e = errors [ i ] ;
if ( e ) text += dataVar + e . dataPath + ' ' + e . message + separator ;
}
2015-11-22 05:00:34 +03:00
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 ;
}
2016-06-01 08:58:29 +03:00
function addDraft4MetaSchema ( ) {
2016-01-21 01:55:14 +03:00
if ( self . _opts . meta !== false ) {
2015-11-22 05:00:34 +03:00
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 ;
2015-11-15 01:05:37 +03:00
}
2016-06-01 08:58:29 +03:00
}
2015-11-15 01:05:37 +03:00
2016-06-01 08:58:29 +03:00
function addInitialSchemas ( ) {
2016-01-21 01:55:14 +03:00
var optsSchemas = self . _opts . schemas ;
2015-11-22 05:00:34 +03:00
if ( ! optsSchemas ) return ;
if ( Array . isArray ( optsSchemas ) ) addSchema ( optsSchemas ) ;
else for ( var key in optsSchemas ) addSchema ( optsSchemas [ key ] , key ) ;
}
2015-11-11 10:01:27 +03:00
2015-11-22 05:00:34 +03:00
function addInitialFormats ( ) {
2016-01-21 01:55:14 +03:00
for ( var name in self . _opts . formats ) {
var format = self . _opts . formats [ name ] ;
2015-11-22 05:00:34 +03:00
addFormat ( name , format ) ;
2015-06-13 15:42:58 +03:00
}
2015-11-22 05:00:34 +03:00
}
2015-06-13 15:42:58 +03:00
2015-11-22 05:00:34 +03:00
function checkUnique ( id ) {
if ( self . _schemas [ id ] || self . _refs [ id ] )
throw new Error ( 'schema with key or id "' + id + '" already exists' ) ;
}
2016-03-20 21:12:14 +03:00
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 ;
}
2015-05-20 03:55:53 +03:00
}