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
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-06-07 14:46:53 +03:00
} else v = _addSchema ( schemaKeyRef ) ;
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-05-30 21:11:06 +03:00
return _addSchema ( schema ) ;
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 ) {
2015-08-09 13:44:37 +03:00
if ( Array . isArray ( schema ) ) {
schema . forEach ( function ( sch ) { addSchema ( sch ) ; } ) ;
return ;
}
2015-06-07 14:46:53 +03:00
// can key/id have # inside?
var key = resolve . normalizeId ( key || schema . id ) ;
checkUnique ( key ) ;
2015-08-09 13:44:37 +03:00
self . _schemas [ key ] = _addSchema ( schema , _skipValidation ) ;
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 ) {
var currentRemoveAdditional = self . opts . removeAdditional ;
self . opts . removeAdditional = false ;
2015-08-08 21:34:01 +03:00
var validate = addSchema ( schema , META _SCHEMA _ID , _skipValidation ) ;
2015-08-08 12:55:37 +03:00
self . opts . removeAdditional = currentRemoveAdditional ;
}
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 ) ;
return self . _schemas [ keyRef ] || self . _refs [ keyRef ] ;
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-05-30 11:53:04 +03:00
var str = stableStringify ( schema ) ;
2015-06-20 17:37:29 +03:00
var cached = self . _cache . get ( str ) ;
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-06-24 03:28:40 +03:00
var validate = compileSchema . call ( self , schema , undefined , localRefs ) ;
2015-06-24 02:55:38 +03:00
if ( id [ 0 ] != '#' ) self . _refs [ id ] = validate ;
2015-06-20 17:37:29 +03:00
self . _cache . put ( str , validate ) ;
2015-06-07 14:46:53 +03:00
return validate ;
}
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-08-08 12:55:37 +03:00
if ( self . opts . meta !== false )
2015-08-08 21:34:01 +03:00
addMetaSchema ( require ( './refs/json-schema-draft-04.json' ) , META _SCHEMA _ID , true ) ;
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
}