twitst4tz

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

resolve.js (7847B)


      1 'use strict';
      2 
      3 var URI = require('uri-js')
      4   , equal = require('fast-deep-equal')
      5   , util = require('./util')
      6   , SchemaObject = require('./schema_obj')
      7   , traverse = require('json-schema-traverse');
      8 
      9 module.exports = resolve;
     10 
     11 resolve.normalizeId = normalizeId;
     12 resolve.fullPath = getFullPath;
     13 resolve.url = resolveUrl;
     14 resolve.ids = resolveIds;
     15 resolve.inlineRef = inlineRef;
     16 resolve.schema = resolveSchema;
     17 
     18 /**
     19  * [resolve and compile the references ($ref)]
     20  * @this   Ajv
     21  * @param  {Function} compile reference to schema compilation funciton (localCompile)
     22  * @param  {Object} root object with information about the root schema for the current schema
     23  * @param  {String} ref reference to resolve
     24  * @return {Object|Function} schema object (if the schema can be inlined) or validation function
     25  */
     26 function resolve(compile, root, ref) {
     27   /* jshint validthis: true */
     28   var refVal = this._refs[ref];
     29   if (typeof refVal == 'string') {
     30     if (this._refs[refVal]) refVal = this._refs[refVal];
     31     else return resolve.call(this, compile, root, refVal);
     32   }
     33 
     34   refVal = refVal || this._schemas[ref];
     35   if (refVal instanceof SchemaObject) {
     36     return inlineRef(refVal.schema, this._opts.inlineRefs)
     37             ? refVal.schema
     38             : refVal.validate || this._compile(refVal);
     39   }
     40 
     41   var res = resolveSchema.call(this, root, ref);
     42   var schema, v, baseId;
     43   if (res) {
     44     schema = res.schema;
     45     root = res.root;
     46     baseId = res.baseId;
     47   }
     48 
     49   if (schema instanceof SchemaObject) {
     50     v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId);
     51   } else if (schema !== undefined) {
     52     v = inlineRef(schema, this._opts.inlineRefs)
     53         ? schema
     54         : compile.call(this, schema, root, undefined, baseId);
     55   }
     56 
     57   return v;
     58 }
     59 
     60 
     61 /**
     62  * Resolve schema, its root and baseId
     63  * @this Ajv
     64  * @param  {Object} root root object with properties schema, refVal, refs
     65  * @param  {String} ref  reference to resolve
     66  * @return {Object} object with properties schema, root, baseId
     67  */
     68 function resolveSchema(root, ref) {
     69   /* jshint validthis: true */
     70   var p = URI.parse(ref)
     71     , refPath = _getFullPath(p)
     72     , baseId = getFullPath(this._getId(root.schema));
     73   if (Object.keys(root.schema).length === 0 || refPath !== baseId) {
     74     var id = normalizeId(refPath);
     75     var refVal = this._refs[id];
     76     if (typeof refVal == 'string') {
     77       return resolveRecursive.call(this, root, refVal, p);
     78     } else if (refVal instanceof SchemaObject) {
     79       if (!refVal.validate) this._compile(refVal);
     80       root = refVal;
     81     } else {
     82       refVal = this._schemas[id];
     83       if (refVal instanceof SchemaObject) {
     84         if (!refVal.validate) this._compile(refVal);
     85         if (id == normalizeId(ref))
     86           return { schema: refVal, root: root, baseId: baseId };
     87         root = refVal;
     88       } else {
     89         return;
     90       }
     91     }
     92     if (!root.schema) return;
     93     baseId = getFullPath(this._getId(root.schema));
     94   }
     95   return getJsonPointer.call(this, p, baseId, root.schema, root);
     96 }
     97 
     98 
     99 /* @this Ajv */
    100 function resolveRecursive(root, ref, parsedRef) {
    101   /* jshint validthis: true */
    102   var res = resolveSchema.call(this, root, ref);
    103   if (res) {
    104     var schema = res.schema;
    105     var baseId = res.baseId;
    106     root = res.root;
    107     var id = this._getId(schema);
    108     if (id) baseId = resolveUrl(baseId, id);
    109     return getJsonPointer.call(this, parsedRef, baseId, schema, root);
    110   }
    111 }
    112 
    113 
    114 var PREVENT_SCOPE_CHANGE = util.toHash(['properties', 'patternProperties', 'enum', 'dependencies', 'definitions']);
    115 /* @this Ajv */
    116 function getJsonPointer(parsedRef, baseId, schema, root) {
    117   /* jshint validthis: true */
    118   parsedRef.fragment = parsedRef.fragment || '';
    119   if (parsedRef.fragment.slice(0,1) != '/') return;
    120   var parts = parsedRef.fragment.split('/');
    121 
    122   for (var i = 1; i < parts.length; i++) {
    123     var part = parts[i];
    124     if (part) {
    125       part = util.unescapeFragment(part);
    126       schema = schema[part];
    127       if (schema === undefined) break;
    128       var id;
    129       if (!PREVENT_SCOPE_CHANGE[part]) {
    130         id = this._getId(schema);
    131         if (id) baseId = resolveUrl(baseId, id);
    132         if (schema.$ref) {
    133           var $ref = resolveUrl(baseId, schema.$ref);
    134           var res = resolveSchema.call(this, root, $ref);
    135           if (res) {
    136             schema = res.schema;
    137             root = res.root;
    138             baseId = res.baseId;
    139           }
    140         }
    141       }
    142     }
    143   }
    144   if (schema !== undefined && schema !== root.schema)
    145     return { schema: schema, root: root, baseId: baseId };
    146 }
    147 
    148 
    149 var SIMPLE_INLINED = util.toHash([
    150   'type', 'format', 'pattern',
    151   'maxLength', 'minLength',
    152   'maxProperties', 'minProperties',
    153   'maxItems', 'minItems',
    154   'maximum', 'minimum',
    155   'uniqueItems', 'multipleOf',
    156   'required', 'enum'
    157 ]);
    158 function inlineRef(schema, limit) {
    159   if (limit === false) return false;
    160   if (limit === undefined || limit === true) return checkNoRef(schema);
    161   else if (limit) return countKeys(schema) <= limit;
    162 }
    163 
    164 
    165 function checkNoRef(schema) {
    166   var item;
    167   if (Array.isArray(schema)) {
    168     for (var i=0; i<schema.length; i++) {
    169       item = schema[i];
    170       if (typeof item == 'object' && !checkNoRef(item)) return false;
    171     }
    172   } else {
    173     for (var key in schema) {
    174       if (key == '$ref') return false;
    175       item = schema[key];
    176       if (typeof item == 'object' && !checkNoRef(item)) return false;
    177     }
    178   }
    179   return true;
    180 }
    181 
    182 
    183 function countKeys(schema) {
    184   var count = 0, item;
    185   if (Array.isArray(schema)) {
    186     for (var i=0; i<schema.length; i++) {
    187       item = schema[i];
    188       if (typeof item == 'object') count += countKeys(item);
    189       if (count == Infinity) return Infinity;
    190     }
    191   } else {
    192     for (var key in schema) {
    193       if (key == '$ref') return Infinity;
    194       if (SIMPLE_INLINED[key]) {
    195         count++;
    196       } else {
    197         item = schema[key];
    198         if (typeof item == 'object') count += countKeys(item) + 1;
    199         if (count == Infinity) return Infinity;
    200       }
    201     }
    202   }
    203   return count;
    204 }
    205 
    206 
    207 function getFullPath(id, normalize) {
    208   if (normalize !== false) id = normalizeId(id);
    209   var p = URI.parse(id);
    210   return _getFullPath(p);
    211 }
    212 
    213 
    214 function _getFullPath(p) {
    215   return URI.serialize(p).split('#')[0] + '#';
    216 }
    217 
    218 
    219 var TRAILING_SLASH_HASH = /#\/?$/;
    220 function normalizeId(id) {
    221   return id ? id.replace(TRAILING_SLASH_HASH, '') : '';
    222 }
    223 
    224 
    225 function resolveUrl(baseId, id) {
    226   id = normalizeId(id);
    227   return URI.resolve(baseId, id);
    228 }
    229 
    230 
    231 /* @this Ajv */
    232 function resolveIds(schema) {
    233   var schemaId = normalizeId(this._getId(schema));
    234   var baseIds = {'': schemaId};
    235   var fullPaths = {'': getFullPath(schemaId, false)};
    236   var localRefs = {};
    237   var self = this;
    238 
    239   traverse(schema, {allKeys: true}, function(sch, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex) {
    240     if (jsonPtr === '') return;
    241     var id = self._getId(sch);
    242     var baseId = baseIds[parentJsonPtr];
    243     var fullPath = fullPaths[parentJsonPtr] + '/' + parentKeyword;
    244     if (keyIndex !== undefined)
    245       fullPath += '/' + (typeof keyIndex == 'number' ? keyIndex : util.escapeFragment(keyIndex));
    246 
    247     if (typeof id == 'string') {
    248       id = baseId = normalizeId(baseId ? URI.resolve(baseId, id) : id);
    249 
    250       var refVal = self._refs[id];
    251       if (typeof refVal == 'string') refVal = self._refs[refVal];
    252       if (refVal && refVal.schema) {
    253         if (!equal(sch, refVal.schema))
    254           throw new Error('id "' + id + '" resolves to more than one schema');
    255       } else if (id != normalizeId(fullPath)) {
    256         if (id[0] == '#') {
    257           if (localRefs[id] && !equal(sch, localRefs[id]))
    258             throw new Error('id "' + id + '" resolves to more than one schema');
    259           localRefs[id] = sch;
    260         } else {
    261           self._refs[id] = fullPath;
    262         }
    263       }
    264     }
    265     baseIds[jsonPtr] = baseId;
    266     fullPaths[jsonPtr] = fullPath;
    267   });
    268 
    269   return localRefs;
    270 }