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-05-20 04:02:47 +03:00
, stableStringify = require ( 'json-stable-stringify' )
2015-06-13 15:42:58 +03:00
, formats = require ( './compile/formats' )
, util = require ( './compile/util' ) ;
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-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 ;
this . addSchema = addSchema ;
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 ( ) ;
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-07-04 03:58:08 +03:00
v = _addSchema ( schemaKeyRef ) ;
2015-07-04 02:48:32 +03:00
if ( typeof v == 'object' ) v = _compile ( v ) ;
}
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
}
/ * *
* Create validator for passed schema .
* @ param { String | Object } schema
* @ return { Object } validation result { valid : true / false , errors : [ ... ] }
* /
function compile ( schema ) {
2015-07-04 03:58:08 +03:00
var schemaObj = _addSchema ( schema ) ;
2015-07-04 02:48:32 +03:00
return typeof schemaObj == 'function'
? schemaObj
: _compile ( schemaObj ) ;
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-06-07 14:46:53 +03:00
function addSchema ( schema , key , _skipValidation ) {
if ( Array . isArray ( schema ) )
return schema . map ( function ( sch ) { return addSchema ( sch ) ; } ) ;
// can key/id have # inside?
var key = resolve . normalizeId ( key || schema . id ) ;
checkUnique ( key ) ;
2015-07-04 03:58:08 +03:00
self . _schemas [ key ] = _addSchema ( schema , _skipValidation ) ;
2015-05-26 04:11:36 +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 ) {
keyRef = resolve . normalizeId ( keyRef ) ;
2015-07-04 02:48:32 +03:00
var schemaObj = self . _schemas [ keyRef ] || self . _refs [ keyRef ] ;
switch ( typeof schemaObj ) {
case 'function' : return schemaObj ;
case 'object' : return _compile ( schemaObj ) ;
case 'string' : return getSchema ( schemaObj ) ;
}
2015-06-07 14:46:53 +03:00
}
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 ) {
if ( typeof schemaKeyRef == 'string' ) {
schemaKeyRef = resolve . normalizeId ( schemaKeyRef ) ;
var v = self . _schemas [ schemaKeyRef ] || self . _refs [ schemaKeyRef ] ;
delete self . _schemas [ schemaKeyRef ] ;
delete self . _refs [ schemaKeyRef ] ;
var str = stableStringify ( v . schema ) ;
self . _cache . put ( str ) ;
} else {
var str = stableStringify ( schemaKeyRef ) ;
self . _cache . put ( str ) ;
}
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-07-04 02:48:32 +03:00
var schemaObj = {
id : id ,
schema : schema ,
localRefs : localRefs ,
jsonStr : jsonStr ,
} ;
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 ) {
callValidate . schema = schemaObj . schema ;
callValidate . errors = null ;
callValidate . root = root ? root : callValidate ;
return callValidate ;
}
schemaObj . compiling = true ;
var validate = compileSchema . call ( self , schemaObj . schema , root , schemaObj . localRefs ) ;
schemaObj . validate = validate ;
var id = schemaObj . id ;
if ( id [ 0 ] != '#' ) self . _refs [ id ] = validate ;
self . _cache . put ( schemaObj . jsonStr , validate ) ;
2015-06-07 14:46:53 +03:00
return validate ;
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 ) {
return e ? txt + e . keyword + ' ' + dataVar + e . dataPath + ': ' + e . message + separator : txt ;
} , '' ) ;
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-07-31 21:23:35 +03:00
if ( self . opts . meta !== false ) {
var currentRemoveAdditional = self . opts . removeAdditional ;
self . opts . removeAdditional = false ;
2015-06-13 15:42:58 +03:00
addSchema ( require ( './refs/json-schema-draft-04.json' ) , META _SCHEMA _ID , true ) ;
2015-07-31 21:23:35 +03:00
self . opts . removeAdditional = currentRemoveAdditional ;
}
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 ( ) {
var optsFormats = self . opts . formats ;
if ( ! optsFormats ) return ;
for ( var name in optsFormats ) {
var format = optsFormats [ name ] ;
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
}