2015-05-20 03:55:53 +03:00
'use strict' ;
var compileSchema = require ( './compile' )
2015-06-07 14:46:53 +03:00
, resolve = require ( './compile/resolve' )
2015-06-20 17:37:29 +03:00
, Cache = require ( './cache' )
2015-08-09 02:54:06 +03:00
, SchemaObject = require ( './compile/schema_obj' )
2015-05-20 04:02:47 +03:00
, stableStringify = require ( 'json-stable-stringify' )
2015-08-11 21:06:32 +03:00
, formats = require ( './compile/formats' ) ;
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
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 ) {
return SCHEMA _URI _FORMAT . test ( str ) ;
}
2015-06-07 14:46:53 +03:00
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 ) {
if ( ! ( this instanceof Ajv ) ) return new Ajv ( opts ) ;
2015-05-20 03:55:53 +03:00
var self = this ;
2015-05-26 04:11:36 +03:00
this . opts = opts || { } ;
this . _schemas = { } ;
2015-06-07 14:46:53 +03:00
this . _refs = { } ;
2015-06-13 15:42:58 +03:00
this . _formats = formats ( this . opts . format ) ;
2015-06-20 17:37:29 +03:00
this . _cache = this . opts . cache || new Cache ;
2015-09-24 01:42:33 +03:00
this . _loadingSchemas = { } ;
2015-05-20 03:55:53 +03:00
2015-05-26 04:11:36 +03:00
// 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 ;
2015-09-03 01:50:25 +03:00
this . compileAsync = compileAsync ;
2015-05-26 04:11:36 +03:00
this . addSchema = addSchema ;
2015-08-09 14:49:05 +03:00
this . addMetaSchema = addMetaSchema ;
2015-06-18 00:06:07 +03:00
this . validateSchema = validateSchema ;
2015-05-26 04:11:36 +03:00
this . getSchema = getSchema ;
2015-06-20 20:40:13 +03:00
this . removeSchema = removeSchema ;
2015-06-13 15:42:58 +03:00
this . addFormat = addFormat ;
2015-06-17 04:05:52 +03:00
this . errorsText = errorsText ;
2015-06-13 15:42:58 +03:00
2015-07-04 02:48:32 +03:00
this . _compile = _compile ;
2015-06-13 15:42:58 +03:00
addInitialSchemas ( ) ;
2015-08-12 03:00:46 +03:00
if ( this . opts . formats ) addInitialFormats ( ) ;
2015-05-26 04:11:36 +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.
2015-06-07 14:46:53 +03:00
* @ param { String | Object } schemaKeyRef key , ref or schema object
2015-05-26 04:11:36 +03:00
* @ param { Any } data to be validated
2015-06-20 20:40:13 +03:00
* @ return { Boolean } validation result . Errors from the last validation will be available in ` ajv.errors ` ( and also in compiled schema : ` schema.errors ` ) .
2015-05-26 04:11:36 +03:00
* /
2015-06-07 14:46:53 +03:00
function validate ( schemaKeyRef , data ) {
2015-06-20 20:40:13 +03:00
var v ;
2015-06-07 14:46:53 +03:00
if ( typeof schemaKeyRef == 'string' ) {
2015-06-20 20:40:13 +03:00
v = getSchema ( schemaKeyRef ) ;
if ( ! v ) throw new Error ( 'no schema with key or ref "' + schemaKeyRef + '"' ) ;
2015-07-04 02:48:32 +03:00
} else {
2015-08-09 02:54:06 +03:00
var schemaObj = _addSchema ( schemaKeyRef ) ;
v = schemaObj . validate || _compile ( schemaObj ) ;
2015-07-04 02:48:32 +03:00
}
2015-06-07 14:46:53 +03:00
2015-06-08 10:00:35 +03:00
var valid = v ( data ) ;
self . errors = v . errors ;
return valid ;
2015-05-26 04:11:36 +03:00
}
/ * *
2015-09-03 01:50:25 +03:00
* Create validating function for passed schema .
2015-05-26 04:11:36 +03:00
* @ param { String | Object } schema
2015-09-03 01:50:25 +03:00
* @ return { Function } validating function
2015-05-26 04:11:36 +03:00
* /
function compile ( schema ) {
2015-07-04 03:58:08 +03:00
var schemaObj = _addSchema ( schema ) ;
2015-08-09 02:54:06 +03:00
return schemaObj . validate || _compile ( schemaObj ) ;
2015-05-26 04:11:36 +03:00
}
2015-09-03 01:50:25 +03:00
/ * *
* Create 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 .
* @ param { String | Object } schema
* @ param { Function } callback node - style callback , it is always called with 2 parameters : error ( or null ) and validating function .
* /
function compileAsync ( schema , callback ) {
2015-09-24 01:42:33 +03:00
var schemaObj ;
try {
schemaObj = _addSchema ( schema ) ;
} catch ( e ) {
setTimeout ( function ( ) { callback ( e ) ; } ) ;
return ;
}
if ( schemaObj . validate )
setTimeout ( function ( ) { callback ( null , schemaObj . validate ) ; } ) ;
else {
2015-09-03 01:50:25 +03:00
if ( typeof self . opts . loadSchema != 'function' )
throw new Error ( 'options.loadSchema should be a function' ) ;
2015-09-23 23:04:20 +03:00
_compileAsync ( schema , callback , true ) ;
2015-09-03 01:50:25 +03:00
}
}
2015-09-23 23:04:20 +03:00
function _compileAsync ( schema , callback , firstCall ) {
var validate ;
try { validate = compile ( schema ) ; }
2015-09-03 01:50:25 +03:00
catch ( e ) {
if ( e . missingSchema ) loadMissingSchema ( e ) ;
2015-10-16 21:37:43 +03:00
else deferCallback ( e ) ;
2015-09-03 01:50:25 +03:00
return ;
}
2015-10-16 21:37:43 +03:00
deferCallback ( null , validate ) ;
2015-09-03 01:50:25 +03:00
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' ) ) ;
2015-09-24 01:42:33 +03:00
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 ) {
2015-09-03 01:50:25 +03:00
if ( err ) callback ( err ) ;
else {
2015-09-24 01:42:33 +03:00
if ( ! ( self . _refs [ ref ] || self . _schemas [ ref ] ) ) {
try {
addSchema ( sch , ref ) ;
} catch ( e ) {
callback ( e ) ;
return ;
}
}
2015-09-03 01:50:25 +03:00
_compileAsync ( schema , callback ) ;
}
2015-09-24 01:42:33 +03:00
}
2015-09-03 01:50:25 +03:00
}
2015-10-16 21:37:43 +03:00
function deferCallback ( err , validate ) {
if ( firstCall ) setTimeout ( function ( ) { callback ( err , validate ) ; } ) ;
else callback ( err , validate ) ;
}
2015-09-03 01:50:25 +03:00
}
2015-05-26 04:11:36 +03:00
/ * *
* Adds schema to the instance .
2015-06-07 14:46:53 +03:00
* @ param { Object | Array } schema schema or array of schemas . If array is passed , ` key ` 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 ` .
2015-05-26 04:11:36 +03:00
* /
2015-08-09 02:54:06 +03:00
function addSchema ( schema , key , _skipValidation , _meta ) {
2015-08-17 01:00:43 +03:00
if ( Array . isArray ( schema ) ) {
2015-10-10 22:50:57 +03:00
for ( var i = 0 ; i < schema . length ; i ++ ) addSchema ( schema [ i ] ) ;
2015-08-09 13:44:37 +03:00
return ;
}
2015-06-07 14:46:53 +03:00
// can key/id have # inside?
2015-08-11 21:06:32 +03:00
key = resolve . normalizeId ( key || schema . id ) ;
2015-06-07 14:46:53 +03:00
checkUnique ( key ) ;
2015-08-09 02:54:06 +03:00
var schemaObj = self . _schemas [ key ] = _addSchema ( schema , _skipValidation ) ;
schemaObj . meta = _meta ;
2015-05-26 04:11:36 +03:00
}
2015-08-08 12:55:37 +03:00
/ * *
* Add schema that will be used to validate other schemas
* removeAdditional option is alway set to false
* @ param { Object } schema
* @ param { String } key optional schema key
* /
function addMetaSchema ( schema , key , _skipValidation ) {
2015-08-09 02:54:06 +03:00
addSchema ( schema , key , _skipValidation , true ) ;
2015-08-08 12:55:37 +03:00
}
2015-06-18 00:06:07 +03:00
/ * *
* Validate schema
* @ param { Object } schema schema to validate
* @ return { Boolean }
* /
2015-06-07 14:46:53 +03:00
function validateSchema ( schema ) {
var $schema = schema . $schema || META _SCHEMA _ID ;
2015-06-18 00:06:07 +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 ;
return valid ;
2015-06-07 14:46:53 +03:00
}
/ * *
2015-06-20 20:40:13 +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 ) .
2015-06-07 14:46:53 +03:00
* @ return { Function } schema validating function ( with property ` schema ` ) .
* /
2015-06-20 20:40:13 +03:00
function getSchema ( keyRef ) {
2015-08-09 02:54:06 +03:00
var schemaObj = _getSchemaObj ( keyRef ) ;
2015-07-04 02:48:32 +03:00
switch ( typeof schemaObj ) {
2015-08-09 02:54:06 +03:00
case 'object' : return schemaObj . validate || _compile ( schemaObj ) ;
2015-07-04 02:48:32 +03:00
case 'string' : return getSchema ( schemaObj ) ;
}
2015-06-07 14:46:53 +03:00
}
2015-08-09 02:54:06 +03:00
function _getSchemaObj ( keyRef ) {
keyRef = resolve . normalizeId ( keyRef ) ;
return self . _schemas [ keyRef ] || self . _refs [ keyRef ] ;
}
2015-05-26 04:11:36 +03:00
/ * *
2015-06-20 20:40:13 +03:00
* Remove cached schema
* Even if schema is referenced by other schemas it still can be removed as other schemas have local references
* @ param { String | Object } schemaKeyRef key , ref or schema object
2015-05-26 04:11:36 +03:00
* /
2015-06-20 20:40:13 +03:00
function removeSchema ( schemaKeyRef ) {
2015-08-09 02:54:06 +03:00
switch ( typeof schemaKeyRef ) {
case 'string' :
var schemaObj = _getSchemaObj ( schemaKeyRef ) ;
self . _cache . del ( schemaObj . jsonStr ) ;
delete self . _schemas [ schemaKeyRef ] ;
delete self . _refs [ schemaKeyRef ] ;
break ;
case 'object' :
var jsonStr = stableStringify ( schemaKeyRef ) ;
self . _cache . del ( jsonStr ) ;
var id = schemaKeyRef . id ;
if ( id ) {
id = resolve . normalizeId ( id ) ;
delete self . _refs [ id ] ;
}
2015-06-20 20:40:13 +03:00
}
2015-05-26 04:11:36 +03:00
}
2015-06-07 14:46:53 +03:00
function _addSchema ( schema , skipValidation ) {
if ( typeof schema != 'object' ) throw new Error ( 'schema should be object' ) ;
2015-07-04 02:48:32 +03:00
var jsonStr = stableStringify ( schema ) ;
var cached = self . _cache . get ( jsonStr ) ;
2015-06-20 17:37:29 +03:00
if ( cached ) return cached ;
2015-06-07 14:46:53 +03:00
var id = resolve . normalizeId ( schema . id ) ;
if ( id ) checkUnique ( id ) ;
2015-06-17 04:05:52 +03:00
var ok = skipValidation || self . opts . validateSchema === false
|| validateSchema ( schema ) ;
2015-06-18 00:06:07 +03:00
if ( ! ok ) {
var message = 'schema is invalid:' + errorsText ( ) ;
if ( self . opts . validateSchema == 'log' ) console . error ( message ) ;
else throw new Error ( message ) ;
}
2015-06-07 14:46:53 +03:00
2015-06-24 03:28:40 +03:00
var localRefs = resolve . ids . call ( self , schema ) ;
2015-06-07 14:46:53 +03:00
2015-08-09 02:54:06 +03:00
var schemaObj = new SchemaObject ( {
2015-07-04 02:48:32 +03:00
id : id ,
schema : schema ,
localRefs : localRefs ,
jsonStr : jsonStr ,
2015-08-09 02:54:06 +03:00
} ) ;
2015-06-07 14:46:53 +03:00
2015-07-04 02:48:32 +03:00
if ( id [ 0 ] != '#' ) self . _refs [ id ] = schemaObj ;
self . _cache . put ( jsonStr , schemaObj ) ;
return schemaObj ;
}
function _compile ( schemaObj , root ) {
if ( schemaObj . compiling ) {
2015-08-09 02:54:06 +03:00
schemaObj . validate = callValidate ;
2015-07-04 02:48:32 +03:00
callValidate . schema = schemaObj . schema ;
callValidate . errors = null ;
callValidate . root = root ? root : callValidate ;
return callValidate ;
}
schemaObj . compiling = true ;
2015-08-09 02:54:06 +03:00
var currentRA = self . opts . removeAdditional ;
if ( currentRA && schemaObj . meta ) self . opts . removeAdditional = false ;
2015-09-23 23:04:20 +03:00
var v ;
try { v = compileSchema . call ( self , schemaObj . schema , root , schemaObj . localRefs ) ; }
2015-09-03 01:50:25 +03:00
finally {
schemaObj . compiling = false ;
if ( currentRA ) self . opts . removeAdditional = currentRA ;
}
2015-08-09 02:54:06 +03:00
schemaObj . validate = v ;
schemaObj . refs = v . refs ;
schemaObj . refVal = v . refVal ;
schemaObj . root = v . root ;
return v ;
2015-07-04 02:48:32 +03:00
function callValidate ( ) {
var v = schemaObj . validate ;
var result = v . apply ( null , arguments ) ;
callValidate . errors = v . errors ;
return result ;
}
2015-06-07 14:46:53 +03:00
}
2015-06-17 04:05:52 +03:00
function errorsText ( errors , opts ) {
errors = errors || self . errors ;
if ( ! errors ) return 'No errors' ;
opts = opts || { } ;
var separator = opts . separator || ', ' ;
var dataVar = opts . dataVar || 'data' ;
var text = errors . reduce ( function ( txt , e ) {
2015-09-23 00:47:54 +03:00
return e ? txt + dataVar + e . dataPath + ' ' + e . message + separator : txt ;
2015-06-17 04:05:52 +03:00
} , '' ) ;
return text . slice ( 0 , - separator . length ) ;
}
2015-06-13 15:42:58 +03:00
function addFormat ( name , format ) {
if ( typeof format == 'string' ) format = new RegExp ( format ) ;
self . _formats [ name ] = format ;
}
function addInitialSchemas ( ) {
2015-08-21 20:14:36 +03:00
if ( self . opts . meta !== false ) {
var metaSchema = require ( './refs/json-schema-draft-04.json' ) ;
addMetaSchema ( metaSchema , META _SCHEMA _ID , true ) ;
2015-08-22 01:11:18 +03:00
self . _refs [ 'http://json-schema.org/schema' ] = META _SCHEMA _ID ;
2015-08-21 20:14:36 +03:00
}
2015-06-13 15:42:58 +03:00
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 ( ) {
2015-08-12 03:00:46 +03:00
for ( var name in self . opts . formats ) {
var format = self . opts . formats [ name ] ;
2015-06-13 15:42:58 +03:00
addFormat ( name , format ) ;
}
}
2015-06-07 14:46:53 +03:00
function checkUnique ( id ) {
2015-06-17 00:19:00 +03:00
if ( self . _schemas [ id ] || self . _refs [ id ] )
2015-06-07 14:46:53 +03:00
throw new Error ( 'schema with key or id "' + id + '" already exists' ) ;
2015-05-26 04:11:36 +03:00
}
2015-05-20 03:55:53 +03:00
}