index.js (10686B)
1 'use strict'; 2 3 var resolve = require('./resolve') 4 , util = require('./util') 5 , errorClasses = require('./error_classes') 6 , stableStringify = require('fast-json-stable-stringify'); 7 8 var validateGenerator = require('../dotjs/validate'); 9 10 /** 11 * Functions below are used inside compiled validations function 12 */ 13 14 var ucs2length = util.ucs2length; 15 var equal = require('fast-deep-equal'); 16 17 // this error is thrown by async schemas to return validation errors via exception 18 var ValidationError = errorClasses.Validation; 19 20 module.exports = compile; 21 22 23 /** 24 * Compiles schema to validation function 25 * @this Ajv 26 * @param {Object} schema schema object 27 * @param {Object} root object with information about the root schema for this schema 28 * @param {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution 29 * @param {String} baseId base ID for IDs in the schema 30 * @return {Function} validation function 31 */ 32 function compile(schema, root, localRefs, baseId) { 33 /* jshint validthis: true, evil: true */ 34 /* eslint no-shadow: 0 */ 35 var self = this 36 , opts = this._opts 37 , refVal = [ undefined ] 38 , refs = {} 39 , patterns = [] 40 , patternsHash = {} 41 , defaults = [] 42 , defaultsHash = {} 43 , customRules = []; 44 45 root = root || { schema: schema, refVal: refVal, refs: refs }; 46 47 var c = checkCompiling.call(this, schema, root, baseId); 48 var compilation = this._compilations[c.index]; 49 if (c.compiling) return (compilation.callValidate = callValidate); 50 51 var formats = this._formats; 52 var RULES = this.RULES; 53 54 try { 55 var v = localCompile(schema, root, localRefs, baseId); 56 compilation.validate = v; 57 var cv = compilation.callValidate; 58 if (cv) { 59 cv.schema = v.schema; 60 cv.errors = null; 61 cv.refs = v.refs; 62 cv.refVal = v.refVal; 63 cv.root = v.root; 64 cv.$async = v.$async; 65 if (opts.sourceCode) cv.source = v.source; 66 } 67 return v; 68 } finally { 69 endCompiling.call(this, schema, root, baseId); 70 } 71 72 /* @this {*} - custom context, see passContext option */ 73 function callValidate() { 74 /* jshint validthis: true */ 75 var validate = compilation.validate; 76 var result = validate.apply(this, arguments); 77 callValidate.errors = validate.errors; 78 return result; 79 } 80 81 function localCompile(_schema, _root, localRefs, baseId) { 82 var isRoot = !_root || (_root && _root.schema == _schema); 83 if (_root.schema != root.schema) 84 return compile.call(self, _schema, _root, localRefs, baseId); 85 86 var $async = _schema.$async === true; 87 88 var sourceCode = validateGenerator({ 89 isTop: true, 90 schema: _schema, 91 isRoot: isRoot, 92 baseId: baseId, 93 root: _root, 94 schemaPath: '', 95 errSchemaPath: '#', 96 errorPath: '""', 97 MissingRefError: errorClasses.MissingRef, 98 RULES: RULES, 99 validate: validateGenerator, 100 util: util, 101 resolve: resolve, 102 resolveRef: resolveRef, 103 usePattern: usePattern, 104 useDefault: useDefault, 105 useCustomRule: useCustomRule, 106 opts: opts, 107 formats: formats, 108 logger: self.logger, 109 self: self 110 }); 111 112 sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode) 113 + vars(defaults, defaultCode) + vars(customRules, customRuleCode) 114 + sourceCode; 115 116 if (opts.processCode) sourceCode = opts.processCode(sourceCode); 117 // console.log('\n\n\n *** \n', JSON.stringify(sourceCode)); 118 var validate; 119 try { 120 var makeValidate = new Function( 121 'self', 122 'RULES', 123 'formats', 124 'root', 125 'refVal', 126 'defaults', 127 'customRules', 128 'equal', 129 'ucs2length', 130 'ValidationError', 131 sourceCode 132 ); 133 134 validate = makeValidate( 135 self, 136 RULES, 137 formats, 138 root, 139 refVal, 140 defaults, 141 customRules, 142 equal, 143 ucs2length, 144 ValidationError 145 ); 146 147 refVal[0] = validate; 148 } catch(e) { 149 self.logger.error('Error compiling schema, function code:', sourceCode); 150 throw e; 151 } 152 153 validate.schema = _schema; 154 validate.errors = null; 155 validate.refs = refs; 156 validate.refVal = refVal; 157 validate.root = isRoot ? validate : _root; 158 if ($async) validate.$async = true; 159 if (opts.sourceCode === true) { 160 validate.source = { 161 code: sourceCode, 162 patterns: patterns, 163 defaults: defaults 164 }; 165 } 166 167 return validate; 168 } 169 170 function resolveRef(baseId, ref, isRoot) { 171 ref = resolve.url(baseId, ref); 172 var refIndex = refs[ref]; 173 var _refVal, refCode; 174 if (refIndex !== undefined) { 175 _refVal = refVal[refIndex]; 176 refCode = 'refVal[' + refIndex + ']'; 177 return resolvedRef(_refVal, refCode); 178 } 179 if (!isRoot && root.refs) { 180 var rootRefId = root.refs[ref]; 181 if (rootRefId !== undefined) { 182 _refVal = root.refVal[rootRefId]; 183 refCode = addLocalRef(ref, _refVal); 184 return resolvedRef(_refVal, refCode); 185 } 186 } 187 188 refCode = addLocalRef(ref); 189 var v = resolve.call(self, localCompile, root, ref); 190 if (v === undefined) { 191 var localSchema = localRefs && localRefs[ref]; 192 if (localSchema) { 193 v = resolve.inlineRef(localSchema, opts.inlineRefs) 194 ? localSchema 195 : compile.call(self, localSchema, root, localRefs, baseId); 196 } 197 } 198 199 if (v === undefined) { 200 removeLocalRef(ref); 201 } else { 202 replaceLocalRef(ref, v); 203 return resolvedRef(v, refCode); 204 } 205 } 206 207 function addLocalRef(ref, v) { 208 var refId = refVal.length; 209 refVal[refId] = v; 210 refs[ref] = refId; 211 return 'refVal' + refId; 212 } 213 214 function removeLocalRef(ref) { 215 delete refs[ref]; 216 } 217 218 function replaceLocalRef(ref, v) { 219 var refId = refs[ref]; 220 refVal[refId] = v; 221 } 222 223 function resolvedRef(refVal, code) { 224 return typeof refVal == 'object' || typeof refVal == 'boolean' 225 ? { code: code, schema: refVal, inline: true } 226 : { code: code, $async: refVal && !!refVal.$async }; 227 } 228 229 function usePattern(regexStr) { 230 var index = patternsHash[regexStr]; 231 if (index === undefined) { 232 index = patternsHash[regexStr] = patterns.length; 233 patterns[index] = regexStr; 234 } 235 return 'pattern' + index; 236 } 237 238 function useDefault(value) { 239 switch (typeof value) { 240 case 'boolean': 241 case 'number': 242 return '' + value; 243 case 'string': 244 return util.toQuotedString(value); 245 case 'object': 246 if (value === null) return 'null'; 247 var valueStr = stableStringify(value); 248 var index = defaultsHash[valueStr]; 249 if (index === undefined) { 250 index = defaultsHash[valueStr] = defaults.length; 251 defaults[index] = value; 252 } 253 return 'default' + index; 254 } 255 } 256 257 function useCustomRule(rule, schema, parentSchema, it) { 258 if (self._opts.validateSchema !== false) { 259 var deps = rule.definition.dependencies; 260 if (deps && !deps.every(function(keyword) { 261 return Object.prototype.hasOwnProperty.call(parentSchema, keyword); 262 })) 263 throw new Error('parent schema must have all required keywords: ' + deps.join(',')); 264 265 var validateSchema = rule.definition.validateSchema; 266 if (validateSchema) { 267 var valid = validateSchema(schema); 268 if (!valid) { 269 var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors); 270 if (self._opts.validateSchema == 'log') self.logger.error(message); 271 else throw new Error(message); 272 } 273 } 274 } 275 276 var compile = rule.definition.compile 277 , inline = rule.definition.inline 278 , macro = rule.definition.macro; 279 280 var validate; 281 if (compile) { 282 validate = compile.call(self, schema, parentSchema, it); 283 } else if (macro) { 284 validate = macro.call(self, schema, parentSchema, it); 285 if (opts.validateSchema !== false) self.validateSchema(validate, true); 286 } else if (inline) { 287 validate = inline.call(self, it, rule.keyword, schema, parentSchema); 288 } else { 289 validate = rule.definition.validate; 290 if (!validate) return; 291 } 292 293 if (validate === undefined) 294 throw new Error('custom keyword "' + rule.keyword + '"failed to compile'); 295 296 var index = customRules.length; 297 customRules[index] = validate; 298 299 return { 300 code: 'customRule' + index, 301 validate: validate 302 }; 303 } 304 } 305 306 307 /** 308 * Checks if the schema is currently compiled 309 * @this Ajv 310 * @param {Object} schema schema to compile 311 * @param {Object} root root object 312 * @param {String} baseId base schema ID 313 * @return {Object} object with properties "index" (compilation index) and "compiling" (boolean) 314 */ 315 function checkCompiling(schema, root, baseId) { 316 /* jshint validthis: true */ 317 var index = compIndex.call(this, schema, root, baseId); 318 if (index >= 0) return { index: index, compiling: true }; 319 index = this._compilations.length; 320 this._compilations[index] = { 321 schema: schema, 322 root: root, 323 baseId: baseId 324 }; 325 return { index: index, compiling: false }; 326 } 327 328 329 /** 330 * Removes the schema from the currently compiled list 331 * @this Ajv 332 * @param {Object} schema schema to compile 333 * @param {Object} root root object 334 * @param {String} baseId base schema ID 335 */ 336 function endCompiling(schema, root, baseId) { 337 /* jshint validthis: true */ 338 var i = compIndex.call(this, schema, root, baseId); 339 if (i >= 0) this._compilations.splice(i, 1); 340 } 341 342 343 /** 344 * Index of schema compilation in the currently compiled list 345 * @this Ajv 346 * @param {Object} schema schema to compile 347 * @param {Object} root root object 348 * @param {String} baseId base schema ID 349 * @return {Integer} compilation index 350 */ 351 function compIndex(schema, root, baseId) { 352 /* jshint validthis: true */ 353 for (var i=0; i<this._compilations.length; i++) { 354 var c = this._compilations[i]; 355 if (c.schema == schema && c.root == root && c.baseId == baseId) return i; 356 } 357 return -1; 358 } 359 360 361 function patternCode(i, patterns) { 362 return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');'; 363 } 364 365 366 function defaultCode(i) { 367 return 'var default' + i + ' = defaults[' + i + '];'; 368 } 369 370 371 function refValCode(i, refVal) { 372 return refVal[i] === undefined ? '' : 'var refVal' + i + ' = refVal[' + i + '];'; 373 } 374 375 376 function customRuleCode(i) { 377 return 'var customRule' + i + ' = customRules[' + i + '];'; 378 } 379 380 381 function vars(arr, statement) { 382 if (!arr.length) return ''; 383 var code = ''; 384 for (var i=0; i<arr.length; i++) 385 code += statement(i, arr); 386 return code; 387 }