twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

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 }