'use strict' ;
var compileSchema = require ( './compile' )
, 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' )
, v5 = require ( './v5' )
, util = require ( './compile/util' )
, async = require ( './async' )
, co = require ( 'co' ) ;
module . exports = Ajv ;
Ajv . prototype . compileAsync = async . compile ;
var customKeyword = require ( './keyword' ) ;
Ajv . prototype . addKeyword = customKeyword . add ;
Ajv . prototype . getKeyword = customKeyword . get ;
Ajv . prototype . removeKeyword = customKeyword . remove ;
Ajv . ValidationError = require ( './compile/validation_error' ) ;
var META_SCHEMA_ID = 'http://json-schema.org/draft-04/schema' ;
var SCHEMA_URI_FORMAT = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/\/)?[^\s]*$/i ;
function SCHEMA_URI_FORMAT_FUNC ( str ) {
return SCHEMA_URI_FORMAT . test ( str ) ;
}
var META_IGNORE_OPTIONS = [ 'removeAdditional' , 'useDefaults' , 'coerceTypes' ] ;
/ * *
* Creates validator instance .
* Usage : ` Ajv(opts) `
* @ param { Object } opts optional options
* @ return { Object } ajv instance
* /
function Ajv ( opts ) {
if ( ! ( this instanceof Ajv ) ) return new Ajv ( opts ) ;
var self = this ;
opts = this . _ opts = util . copy ( opts ) || { } ;
this . _ schemas = { } ;
this . _ refs = { } ;
this . _ fragments = { } ;
this . _ formats = formats ( opts . format ) ;
this . _ cache = opts . cache || new Cache ;
this . _ loadingSchemas = { } ;
this . _ compilations = [ ] ;
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 ;
this . _ addSchema = _ addSchema ;
this . _ compile = _ compile ;
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 ( ) ;
if ( opts . formats ) addInitialFormats ( ) ;
addDraft4MetaSchema ( ) ;
if ( opts . v5 ) v5 . enable ( this ) ;
if ( typeof opts . meta == 'object' ) addMetaSchema ( opts . meta ) ;
addInitialSchemas ( ) ;
/ * *
* 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 ) ;
}
var valid = v ( data ) ;
if ( v . $async === true )
return self . _ opts . async == '*' ? co ( valid ) : valid ;
self . errors = v . errors ;
return valid ;
}
/ * *
* Create validating function for passed schema .
* @ param { Object } schema schema object
* @ param { Boolean } _ meta true if schema is a meta - schema . Used internally to compile meta schemas of custom keywords .
* @ return { Function } validating function
* /
function compile ( schema , _ meta ) {
var schemaObj = _ addSchema ( schema , undefined , _ meta ) ;
return schemaObj . validate || _ compile ( schemaObj ) ;
}
/ * *
* Adds schema to the instance .
* @ param { Object | Array } schema schema or array of schemas . If array is passed , ` key ` and other parameters 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 ` .
* @ 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 .
* /
function addSchema ( schema , key , _ skipValidation , _ meta ) {
if ( Array . isArray ( schema ) ) {
for ( var i = 0 ; i < schema . length ; i ++ ) addSchema ( schema [ i ] , undefined , _ skipValidation , _ meta ) ;
return ;
}
// can key/id have # inside?
key = resolve . normalizeId ( key || schema . id ) ;
checkUnique ( key ) ;
self . _ schemas [ key ] = _ addSchema ( schema , _ skipValidation , _ meta , true ) ;
}
/ * *
* Add schema that will be used to validate other schemas
* options in META_IGNORE_OPTIONS are alway set to false
* @ param { Object } schema schema object
* @ param { String } key optional schema key
* @ param { Boolean } skipValidation true to skip schema validation , can be used to override validateSchema option for meta - schema
* /
function addMetaSchema ( schema , key , skipValidation ) {
addSchema ( schema , key , skipValidation , true ) ;
}
/ * *
* Validate schema
* @ param { Object } schema schema to validate
* @ param { Boolean } throwOrLogError pass true to throw ( or log ) an error if invalid
* @ return { Boolean } true if schema is valid
* /
function validateSchema ( schema , throwOrLogError ) {
var $schema = schema . $schema || self . _ opts . defaultMeta || defaultMeta ( ) ;
var currentUriFormat = self . _ formats . uri ;
self . _ formats . uri = typeof currentUriFormat == 'function'
? SCHEMA_URI_FORMAT_FUNC
: SCHEMA_URI_FORMAT ;
var valid ;
try { valid = validate ( $schema , schema ) ; }
finally { 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 ;
}
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 ;
}
/ * *
* 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 ) ;
case 'undefined' : return _ getSchemaFragment ( keyRef ) ;
}
}
function _ getSchemaFragment ( ref ) {
var res = resolve . schema . call ( self , { schema : { } } , ref ) ;
if ( res ) {
var schema = res . schema
, root = res . root
, baseId = res . baseId ;
var v = compileSchema . call ( self , schema , root , undefined , baseId ) ;
self . _ fragments [ ref ] = new SchemaObject ( {
ref : ref ,
fragment : true ,
schema : schema ,
root : root ,
baseId : baseId ,
validate : v
} ) ;
return v ;
}
}
function _ getSchemaObj ( keyRef ) {
keyRef = resolve . normalizeId ( keyRef ) ;
return self . _ schemas [ keyRef ] || self . _ refs [ keyRef ] || self . _ fragments [ keyRef ] ;
}
/ * *
* 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
* /
function removeSchema ( schemaKeyRef ) {
if ( schemaKeyRef instanceof RegExp ) {
_ removeAllSchemas ( self . _ schemas , schemaKeyRef ) ;
_ removeAllSchemas ( self . _ refs , schemaKeyRef ) ;
return ;
}
switch ( typeof schemaKeyRef ) {
case 'undefined' :
_ removeAllSchemas ( self . _ schemas ) ;
_ removeAllSchemas ( self . _ refs ) ;
self . _ cache . clear ( ) ;
return ;
case 'string' :
var schemaObj = _ getSchemaObj ( schemaKeyRef ) ;
if ( schemaObj ) self . _ cache . del ( schemaObj . jsonStr ) ;
delete self . _ schemas [ schemaKeyRef ] ;
delete self . _ refs [ schemaKeyRef ] ;
return ;
case 'object' :
var jsonStr = stableStringify ( schemaKeyRef ) ;
self . _ cache . del ( jsonStr ) ;
var id = schemaKeyRef . id ;
if ( id ) {
id = resolve . normalizeId ( id ) ;
delete self . _ schemas [ id ] ;
delete self . _ refs [ id ] ;
}
}
}
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 ] ;
}
}
}
function _ addSchema ( schema , skipValidation , meta , shouldAddSchema ) {
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 ;
shouldAddSchema = shouldAddSchema || self . _ opts . addUsedSchema !== false ;
var id = resolve . normalizeId ( schema . id ) ;
if ( id && shouldAddSchema ) checkUnique ( id ) ;
var willValidate = self . _ opts . validateSchema !== false && ! skipValidation ;
var recursiveMeta ;
if ( willValidate && ! ( recursiveMeta = schema . id && schema . id == schema . $schema ) )
validateSchema ( schema , true ) ;
var localRefs = resolve . ids . call ( self , schema ) ;
var schemaObj = new SchemaObject ( {
id : id ,
schema : schema ,
localRefs : localRefs ,
jsonStr : jsonStr ,
meta : meta
} ) ;
if ( id [ 0 ] != '#' && shouldAddSchema ) self . _ refs [ id ] = schemaObj ;
self . _ cache . put ( jsonStr , schemaObj ) ;
if ( willValidate && recursiveMeta ) validateSchema ( schema , true ) ;
return schemaObj ;
}
function _ compile ( schemaObj , root ) {
if ( schemaObj . compiling ) {
schemaObj . validate = callValidate ;
callValidate . schema = schemaObj . schema ;
callValidate . errors = null ;
callValidate . root = root ? root : callValidate ;
if ( schemaObj . schema . $async === true )
callValidate . $async = true ;
return callValidate ;
}
schemaObj . compiling = true ;
var currentOpts ;
if ( schemaObj . meta ) {
currentOpts = self . _ opts ;
self . _ opts = self . _ metaOpts ;
}
var v ;
try { v = compileSchema . call ( self , schemaObj . schema , root , schemaObj . localRefs ) ; }
finally {
schemaObj . compiling = false ;
if ( schemaObj . meta ) self . _ opts = currentOpts ;
}
schemaObj . validate = v ;
schemaObj . refs = v . refs ;
schemaObj . refVal = v . refVal ;
schemaObj . root = v . root ;
return v ;
function callValidate ( ) {
var _ validate = schemaObj . validate ;
var result = _ validate . apply ( null , arguments ) ;
callValidate . errors = _ validate . errors ;
return result ;
}
}
/ * *
* 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 .
* @ param { Object } options optional options with properties ` separator ` and ` dataVar ` .
* @ return { String } human readable string with all errors descriptions
* /
function errorsText ( errors , options ) {
errors = errors || self . errors ;
if ( ! errors ) return 'No errors' ;
options = options || { } ;
var separator = options . separator === undefined ? ', ' : options . separator ;
var dataVar = options . dataVar === undefined ? 'data' : options . dataVar ;
var text = '' ;
for ( var i = 0 ; i < errors . length ; i ++ ) {
var e = errors [ i ] ;
if ( e ) text += dataVar + e . dataPath + ' ' + e . message + separator ;
}
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 ;
}
function addDraft4MetaSchema ( ) {
if ( self . _ opts . meta !== false ) {
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 ;
}
}
function addInitialSchemas ( ) {
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 ( ) {
for ( var name in self . _ opts . formats ) {
var format = self . _ opts . formats [ name ] ;
addFormat ( name , format ) ;
}
}
function checkUnique ( id ) {
if ( self . _ schemas [ id ] || self . _ refs [ id ] )
throw new Error ( 'schema with key or id "' + id + '" already exists' ) ;
}
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 ;
}
}