388 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			388 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'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;
 | 
						|
}
 |