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 }