You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							387 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
	
	
							387 lines
						
					
					
						
							10 KiB
						
					
					
				'use strict'; | 
						|
 | 
						|
var resolve = require('./resolve') | 
						|
  , util = require('./util') | 
						|
  , errorClasses = require('./error_classes') | 
						|
  , stableStringify = require('fast-json-stable-stringify'); | 
						|
 | 
						|
var validateGenerator = require('../dotjs/validate'); | 
						|
 | 
						|
/** | 
						|
 * Functions below are used inside compiled validations function | 
						|
 */ | 
						|
 | 
						|
var ucs2length = util.ucs2length; | 
						|
var equal = require('fast-deep-equal'); | 
						|
 | 
						|
// this error is thrown by async schemas to return validation errors via exception | 
						|
var ValidationError = errorClasses.Validation; | 
						|
 | 
						|
module.exports = compile; | 
						|
 | 
						|
 | 
						|
/** | 
						|
 * Compiles schema to validation function | 
						|
 * @this   Ajv | 
						|
 * @param  {Object} schema schema object | 
						|
 * @param  {Object} root object with information about the root schema for this schema | 
						|
 * @param  {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution | 
						|
 * @param  {String} baseId base ID for IDs in the schema | 
						|
 * @return {Function} validation function | 
						|
 */ | 
						|
function compile(schema, root, localRefs, baseId) { | 
						|
  /* jshint validthis: true, evil: true */ | 
						|
  /* eslint no-shadow: 0 */ | 
						|
  var self = this | 
						|
    , opts = this._opts | 
						|
    , refVal = [ undefined ] | 
						|
    , refs = {} | 
						|
    , patterns = [] | 
						|
    , patternsHash = {} | 
						|
    , defaults = [] | 
						|
    , defaultsHash = {} | 
						|
    , customRules = []; | 
						|
 | 
						|
  root = root || { schema: schema, refVal: refVal, refs: refs }; | 
						|
 | 
						|
  var c = checkCompiling.call(this, schema, root, baseId); | 
						|
  var compilation = this._compilations[c.index]; | 
						|
  if (c.compiling) return (compilation.callValidate = callValidate); | 
						|
 | 
						|
  var formats = this._formats; | 
						|
  var RULES = this.RULES; | 
						|
 | 
						|
  try { | 
						|
    var v = localCompile(schema, root, localRefs, baseId); | 
						|
    compilation.validate = v; | 
						|
    var cv = compilation.callValidate; | 
						|
    if (cv) { | 
						|
      cv.schema = v.schema; | 
						|
      cv.errors = null; | 
						|
      cv.refs = v.refs; | 
						|
      cv.refVal = v.refVal; | 
						|
      cv.root = v.root; | 
						|
      cv.$async = v.$async; | 
						|
      if (opts.sourceCode) cv.source = v.source; | 
						|
    } | 
						|
    return v; | 
						|
  } finally { | 
						|
    endCompiling.call(this, schema, root, baseId); | 
						|
  } | 
						|
 | 
						|
  /* @this   {*} - custom context, see passContext option */ | 
						|
  function callValidate() { | 
						|
    /* jshint validthis: true */ | 
						|
    var validate = compilation.validate; | 
						|
    var result = validate.apply(this, arguments); | 
						|
    callValidate.errors = validate.errors; | 
						|
    return result; | 
						|
  } | 
						|
 | 
						|
  function localCompile(_schema, _root, localRefs, baseId) { | 
						|
    var isRoot = !_root || (_root && _root.schema == _schema); | 
						|
    if (_root.schema != root.schema) | 
						|
      return compile.call(self, _schema, _root, localRefs, baseId); | 
						|
 | 
						|
    var $async = _schema.$async === true; | 
						|
 | 
						|
    var sourceCode = validateGenerator({ | 
						|
      isTop: true, | 
						|
      schema: _schema, | 
						|
      isRoot: isRoot, | 
						|
      baseId: baseId, | 
						|
      root: _root, | 
						|
      schemaPath: '', | 
						|
      errSchemaPath: '#', | 
						|
      errorPath: '""', | 
						|
      MissingRefError: errorClasses.MissingRef, | 
						|
      RULES: RULES, | 
						|
      validate: validateGenerator, | 
						|
      util: util, | 
						|
      resolve: resolve, | 
						|
      resolveRef: resolveRef, | 
						|
      usePattern: usePattern, | 
						|
      useDefault: useDefault, | 
						|
      useCustomRule: useCustomRule, | 
						|
      opts: opts, | 
						|
      formats: formats, | 
						|
      logger: self.logger, | 
						|
      self: self | 
						|
    }); | 
						|
 | 
						|
    sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode) | 
						|
                   + vars(defaults, defaultCode) + vars(customRules, customRuleCode) | 
						|
                   + sourceCode; | 
						|
 | 
						|
    if (opts.processCode) sourceCode = opts.processCode(sourceCode, _schema); | 
						|
    // console.log('\n\n\n *** \n', JSON.stringify(sourceCode)); | 
						|
    var validate; | 
						|
    try { | 
						|
      var makeValidate = new Function( | 
						|
        'self', | 
						|
        'RULES', | 
						|
        'formats', | 
						|
        'root', | 
						|
        'refVal', | 
						|
        'defaults', | 
						|
        'customRules', | 
						|
        'equal', | 
						|
        'ucs2length', | 
						|
        'ValidationError', | 
						|
        sourceCode | 
						|
      ); | 
						|
 | 
						|
      validate = makeValidate( | 
						|
        self, | 
						|
        RULES, | 
						|
        formats, | 
						|
        root, | 
						|
        refVal, | 
						|
        defaults, | 
						|
        customRules, | 
						|
        equal, | 
						|
        ucs2length, | 
						|
        ValidationError | 
						|
      ); | 
						|
 | 
						|
      refVal[0] = validate; | 
						|
    } catch(e) { | 
						|
      self.logger.error('Error compiling schema, function code:', sourceCode); | 
						|
      throw e; | 
						|
    } | 
						|
 | 
						|
    validate.schema = _schema; | 
						|
    validate.errors = null; | 
						|
    validate.refs = refs; | 
						|
    validate.refVal = refVal; | 
						|
    validate.root = isRoot ? validate : _root; | 
						|
    if ($async) validate.$async = true; | 
						|
    if (opts.sourceCode === true) { | 
						|
      validate.source = { | 
						|
        code: sourceCode, | 
						|
        patterns: patterns, | 
						|
        defaults: defaults | 
						|
      }; | 
						|
    } | 
						|
 | 
						|
    return validate; | 
						|
  } | 
						|
 | 
						|
  function resolveRef(baseId, ref, isRoot) { | 
						|
    ref = resolve.url(baseId, ref); | 
						|
    var refIndex = refs[ref]; | 
						|
    var _refVal, refCode; | 
						|
    if (refIndex !== undefined) { | 
						|
      _refVal = refVal[refIndex]; | 
						|
      refCode = 'refVal[' + refIndex + ']'; | 
						|
      return resolvedRef(_refVal, refCode); | 
						|
    } | 
						|
    if (!isRoot && root.refs) { | 
						|
      var rootRefId = root.refs[ref]; | 
						|
      if (rootRefId !== undefined) { | 
						|
        _refVal = root.refVal[rootRefId]; | 
						|
        refCode = addLocalRef(ref, _refVal); | 
						|
        return resolvedRef(_refVal, refCode); | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    refCode = addLocalRef(ref); | 
						|
    var v = resolve.call(self, localCompile, root, ref); | 
						|
    if (v === undefined) { | 
						|
      var localSchema = localRefs && localRefs[ref]; | 
						|
      if (localSchema) { | 
						|
        v = resolve.inlineRef(localSchema, opts.inlineRefs) | 
						|
            ? localSchema | 
						|
            : compile.call(self, localSchema, root, localRefs, baseId); | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    if (v === undefined) { | 
						|
      removeLocalRef(ref); | 
						|
    } else { | 
						|
      replaceLocalRef(ref, v); | 
						|
      return resolvedRef(v, refCode); | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  function addLocalRef(ref, v) { | 
						|
    var refId = refVal.length; | 
						|
    refVal[refId] = v; | 
						|
    refs[ref] = refId; | 
						|
    return 'refVal' + refId; | 
						|
  } | 
						|
 | 
						|
  function removeLocalRef(ref) { | 
						|
    delete refs[ref]; | 
						|
  } | 
						|
 | 
						|
  function replaceLocalRef(ref, v) { | 
						|
    var refId = refs[ref]; | 
						|
    refVal[refId] = v; | 
						|
  } | 
						|
 | 
						|
  function resolvedRef(refVal, code) { | 
						|
    return typeof refVal == 'object' || typeof refVal == 'boolean' | 
						|
            ? { code: code, schema: refVal, inline: true } | 
						|
            : { code: code, $async: refVal && !!refVal.$async }; | 
						|
  } | 
						|
 | 
						|
  function usePattern(regexStr) { | 
						|
    var index = patternsHash[regexStr]; | 
						|
    if (index === undefined) { | 
						|
      index = patternsHash[regexStr] = patterns.length; | 
						|
      patterns[index] = regexStr; | 
						|
    } | 
						|
    return 'pattern' + index; | 
						|
  } | 
						|
 | 
						|
  function useDefault(value) { | 
						|
    switch (typeof value) { | 
						|
      case 'boolean': | 
						|
      case 'number': | 
						|
        return '' + value; | 
						|
      case 'string': | 
						|
        return util.toQuotedString(value); | 
						|
      case 'object': | 
						|
        if (value === null) return 'null'; | 
						|
        var valueStr = stableStringify(value); | 
						|
        var index = defaultsHash[valueStr]; | 
						|
        if (index === undefined) { | 
						|
          index = defaultsHash[valueStr] = defaults.length; | 
						|
          defaults[index] = value; | 
						|
        } | 
						|
        return 'default' + index; | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  function useCustomRule(rule, schema, parentSchema, it) { | 
						|
    if (self._opts.validateSchema !== false) { | 
						|
      var deps = rule.definition.dependencies; | 
						|
      if (deps && !deps.every(function(keyword) { | 
						|
        return Object.prototype.hasOwnProperty.call(parentSchema, keyword); | 
						|
      })) | 
						|
        throw new Error('parent schema must have all required keywords: ' + deps.join(',')); | 
						|
 | 
						|
      var validateSchema = rule.definition.validateSchema; | 
						|
      if (validateSchema) { | 
						|
        var valid = validateSchema(schema); | 
						|
        if (!valid) { | 
						|
          var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors); | 
						|
          if (self._opts.validateSchema == 'log') self.logger.error(message); | 
						|
          else throw new Error(message); | 
						|
        } | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    var compile = rule.definition.compile | 
						|
      , inline = rule.definition.inline | 
						|
      , macro = rule.definition.macro; | 
						|
 | 
						|
    var validate; | 
						|
    if (compile) { | 
						|
      validate = compile.call(self, schema, parentSchema, it); | 
						|
    } else if (macro) { | 
						|
      validate = macro.call(self, schema, parentSchema, it); | 
						|
      if (opts.validateSchema !== false) self.validateSchema(validate, true); | 
						|
    } else if (inline) { | 
						|
      validate = inline.call(self, it, rule.keyword, schema, parentSchema); | 
						|
    } else { | 
						|
      validate = rule.definition.validate; | 
						|
      if (!validate) return; | 
						|
    } | 
						|
 | 
						|
    if (validate === undefined) | 
						|
      throw new Error('custom keyword "' + rule.keyword + '"failed to compile'); | 
						|
 | 
						|
    var index = customRules.length; | 
						|
    customRules[index] = validate; | 
						|
 | 
						|
    return { | 
						|
      code: 'customRule' + index, | 
						|
      validate: validate | 
						|
    }; | 
						|
  } | 
						|
} | 
						|
 | 
						|
 | 
						|
/** | 
						|
 * Checks if the schema is currently compiled | 
						|
 * @this   Ajv | 
						|
 * @param  {Object} schema schema to compile | 
						|
 * @param  {Object} root root object | 
						|
 * @param  {String} baseId base schema ID | 
						|
 * @return {Object} object with properties "index" (compilation index) and "compiling" (boolean) | 
						|
 */ | 
						|
function checkCompiling(schema, root, baseId) { | 
						|
  /* jshint validthis: true */ | 
						|
  var index = compIndex.call(this, schema, root, baseId); | 
						|
  if (index >= 0) return { index: index, compiling: true }; | 
						|
  index = this._compilations.length; | 
						|
  this._compilations[index] = { | 
						|
    schema: schema, | 
						|
    root: root, | 
						|
    baseId: baseId | 
						|
  }; | 
						|
  return { index: index, compiling: false }; | 
						|
} | 
						|
 | 
						|
 | 
						|
/** | 
						|
 * Removes the schema from the currently compiled list | 
						|
 * @this   Ajv | 
						|
 * @param  {Object} schema schema to compile | 
						|
 * @param  {Object} root root object | 
						|
 * @param  {String} baseId base schema ID | 
						|
 */ | 
						|
function endCompiling(schema, root, baseId) { | 
						|
  /* jshint validthis: true */ | 
						|
  var i = compIndex.call(this, schema, root, baseId); | 
						|
  if (i >= 0) this._compilations.splice(i, 1); | 
						|
} | 
						|
 | 
						|
 | 
						|
/** | 
						|
 * Index of schema compilation in the currently compiled list | 
						|
 * @this   Ajv | 
						|
 * @param  {Object} schema schema to compile | 
						|
 * @param  {Object} root root object | 
						|
 * @param  {String} baseId base schema ID | 
						|
 * @return {Integer} compilation index | 
						|
 */ | 
						|
function compIndex(schema, root, baseId) { | 
						|
  /* jshint validthis: true */ | 
						|
  for (var i=0; i<this._compilations.length; i++) { | 
						|
    var c = this._compilations[i]; | 
						|
    if (c.schema == schema && c.root == root && c.baseId == baseId) return i; | 
						|
  } | 
						|
  return -1; | 
						|
} | 
						|
 | 
						|
 | 
						|
function patternCode(i, patterns) { | 
						|
  return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');'; | 
						|
} | 
						|
 | 
						|
 | 
						|
function defaultCode(i) { | 
						|
  return 'var default' + i + ' = defaults[' + i + '];'; | 
						|
} | 
						|
 | 
						|
 | 
						|
function refValCode(i, refVal) { | 
						|
  return refVal[i] === undefined ? '' : 'var refVal' + i + ' = refVal[' + i + '];'; | 
						|
} | 
						|
 | 
						|
 | 
						|
function customRuleCode(i) { | 
						|
  return 'var customRule' + i + ' = customRules[' + i + '];'; | 
						|
} | 
						|
 | 
						|
 | 
						|
function vars(arr, statement) { | 
						|
  if (!arr.length) return ''; | 
						|
  var code = ''; | 
						|
  for (var i=0; i<arr.length; i++) | 
						|
    code += statement(i, arr); | 
						|
  return code; | 
						|
}
 | 
						|
 |