'use strict'; var resolve = require('./resolve') , util = require('./util') , stableStringify = require('json-stable-stringify') , async = require('../async'); var beautify = (function() { try { return require('' + 'js-beautify').js_beautify; } catch(e) {/*empty*/} })(); var validateGenerator = require('../dotjs/validate'); 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 formats = this._formats; var RULES = this.RULES; return localCompile(schema, root, localRefs, baseId); 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; if ($async && !opts.transpile) async.setup(opts); var sourceCode = validateGenerator({ isTop: true, schema: _schema, isRoot: isRoot, baseId: baseId, root: _root, schemaPath: '', errSchemaPath: '#', errorPath: '""', RULES: RULES, validate: validateGenerator, util: util, resolve: resolve, resolveRef: resolveRef, usePattern: usePattern, useDefault: useDefault, useCustomRule: useCustomRule, opts: opts, formats: formats, self: self }); sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode) + vars(defaults, defaultCode) + vars(customRules, customRuleCode) + sourceCode; if (opts.beautify) { /* istanbul ignore else */ if (beautify) sourceCode = beautify(sourceCode, opts.beautify); else console.error('"npm install js-beautify" to use beautify option'); } // console.log('\n\n\n *** \n', sourceCode); var validate, validateCode , transpile = opts._transpileFunc; try { validateCode = $async && transpile ? transpile(sourceCode) : sourceCode; eval(validateCode); refVal[0] = validate; } catch(e) { console.error('Error compiling schema, function code:', validateCode); throw e; } validate.schema = _schema; validate.errors = null; validate.refs = refs; validate.refVal = refVal; validate.root = isRoot ? validate : _root; if ($async) validate.$async = true; validate.sourceCode = sourceCode; 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) { 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) { var localSchema = localRefs && localRefs[ref]; if (localSchema) { v = resolve.inlineRef(localSchema, opts.inlineRefs) ? localSchema : compile.call(self, localSchema, root, localRefs, baseId); } } if (v) { 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 replaceLocalRef(ref, v) { var refId = refs[ref]; refVal[refId] = v; } function resolvedRef(refVal, code) { return typeof refVal == 'object' ? { 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) { var compile = rule.definition.compile , inline = rule.definition.inline , macro = rule.definition.macro; var validate; if (compile) { validate = compile.call(self, schema, parentSchema); } else if (macro) { validate = macro.call(self, schema, parentSchema); 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; } var index = customRules.length; customRules[index] = validate; return { code: 'customRule' + index, validate: validate }; } } 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] ? '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