3 var url = require('url')
4 , equal = require('./equal')
5 , util = require('./util')
6 , SchemaObject = require('./schema_obj');
8 module.exports = resolve;
10 resolve.normalizeId = normalizeId;
11 resolve.fullPath = getFullPath;
12 resolve.url = resolveUrl;
13 resolve.ids = resolveIds;
14 resolve.inlineRef = inlineRef;
15 resolve.schema = resolveSchema;
18 * [resolve and compile the references ($ref)]
20 * @param {Function} compile reference to schema compilation funciton (localCompile)
21 * @param {Object} root object with information about the root schema for the current schema
22 * @param {String} ref reference to resolve
23 * @return {Object|Function} schema object (if the schema can be inlined) or validation function
25 function resolve(compile, root, ref) {
26 /* jshint validthis: true */
27 var refVal = this._refs[ref];
28 if (typeof refVal == 'string') {
29 if (this._refs[refVal]) refVal = this._refs[refVal];
30 else return resolve.call(this, compile, root, refVal);
33 refVal = refVal || this._schemas[ref];
34 if (refVal instanceof SchemaObject) {
35 return inlineRef(refVal.schema, this._opts.inlineRefs)
37 : refVal.validate || this._compile(refVal);
40 var res = resolveSchema.call(this, root, ref);
41 var schema, v, baseId;
48 if (schema instanceof SchemaObject) {
49 v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId);
51 v = inlineRef(schema, this._opts.inlineRefs)
53 : compile.call(this, schema, root, undefined, baseId);
61 * Resolve schema, its root and baseId
63 * @param {Object} root root object with properties schema, refVal, refs
64 * @param {String} ref reference to resolve
65 * @return {Object} object with properties schema, root, baseId
67 function resolveSchema(root, ref) {
68 /* jshint validthis: true */
69 var p = url.parse(ref, false, true)
70 , refPath = _getFullPath(p)
71 , baseId = getFullPath(root.schema.id);
72 if (refPath !== baseId) {
73 var id = normalizeId(refPath);
74 var refVal = this._refs[id];
75 if (typeof refVal == 'string') {
76 return resolveRecursive.call(this, root, refVal, p);
77 } else if (refVal instanceof SchemaObject) {
78 if (!refVal.validate) this._compile(refVal);
81 refVal = this._schemas[id];
82 if (refVal instanceof SchemaObject) {
83 if (!refVal.validate) this._compile(refVal);
84 if (id == normalizeId(ref))
85 return { schema: refVal, root: root, baseId: baseId };
91 if (!root.schema) return;
92 baseId = getFullPath(root.schema.id);
94 return getJsonPointer.call(this, p, baseId, root.schema, root);
99 function resolveRecursive(root, ref, parsedRef) {
100 /* jshint validthis: true */
101 var res = resolveSchema.call(this, root, ref);
103 var schema = res.schema;
104 var baseId = res.baseId;
106 if (schema.id) baseId = resolveUrl(baseId, schema.id);
107 return getJsonPointer.call(this, parsedRef, baseId, schema, root);
112 var PREVENT_SCOPE_CHANGE = util.toHash(['properties', 'patternProperties', 'enum', 'dependencies', 'definitions']);
114 function getJsonPointer(parsedRef, baseId, schema, root) {
115 /* jshint validthis: true */
116 parsedRef.hash = parsedRef.hash || '';
117 if (parsedRef.hash.slice(0,2) != '#/') return;
118 var parts = parsedRef.hash.split('/');
120 for (var i = 1; i < parts.length; i++) {
123 part = util.unescapeFragment(part);
124 schema = schema[part];
126 if (schema.id && !PREVENT_SCOPE_CHANGE[part]) baseId = resolveUrl(baseId, schema.id);
128 var $ref = resolveUrl(baseId, schema.$ref);
129 var res = resolveSchema.call(this, root, $ref);
138 if (schema && schema != root.schema)
139 return { schema: schema, root: root, baseId: baseId };
143 var SIMPLE_INLINED = util.toHash([
144 'type', 'format', 'pattern',
145 'maxLength', 'minLength',
146 'maxProperties', 'minProperties',
147 'maxItems', 'minItems',
148 'maximum', 'minimum',
149 'uniqueItems', 'multipleOf',
152 function inlineRef(schema, limit) {
153 if (limit === false) return false;
154 if (limit === undefined || limit === true) return checkNoRef(schema);
155 else if (limit) return countKeys(schema) <= limit;
159 function checkNoRef(schema) {
161 if (Array.isArray(schema)) {
162 for (var i=0; i<schema.length; i++) {
164 if (typeof item == 'object' && !checkNoRef(item)) return false;
167 for (var key in schema) {
168 if (key == '$ref') return false;
170 if (typeof item == 'object' && !checkNoRef(item)) return false;
177 function countKeys(schema) {
179 if (Array.isArray(schema)) {
180 for (var i=0; i<schema.length; i++) {
182 if (typeof item == 'object') count += countKeys(item);
183 if (count == Infinity) return Infinity;
186 for (var key in schema) {
187 if (key == '$ref') return Infinity;
188 if (SIMPLE_INLINED[key]) {
192 if (typeof item == 'object') count += countKeys(item) + 1;
193 if (count == Infinity) return Infinity;
201 function getFullPath(id, normalize) {
202 if (normalize !== false) id = normalizeId(id);
203 var p = url.parse(id, false, true);
204 return _getFullPath(p);
208 function _getFullPath(p) {
209 var protocolSeparator = p.protocol || p.href.slice(0,2) == '//' ? '//' : '';
210 return (p.protocol||'') + protocolSeparator + (p.host||'') + (p.path||'') + '#';
214 var TRAILING_SLASH_HASH = /#\/?$/;
215 function normalizeId(id) {
216 return id ? id.replace(TRAILING_SLASH_HASH, '') : '';
220 function resolveUrl(baseId, id) {
221 id = normalizeId(id);
222 return url.resolve(baseId, id);
227 function resolveIds(schema) {
228 /* eslint no-shadow: 0 */
229 /* jshint validthis: true */
230 var id = normalizeId(schema.id);
232 _resolveIds.call(this, schema, getFullPath(id, false), id);
236 function _resolveIds(schema, fullPath, baseId) {
237 /* jshint validthis: true */
238 if (Array.isArray(schema)) {
239 for (var i=0; i<schema.length; i++)
240 _resolveIds.call(this, schema[i], fullPath+'/'+i, baseId);
241 } else if (schema && typeof schema == 'object') {
242 if (typeof schema.id == 'string') {
243 var id = baseId = baseId
244 ? url.resolve(baseId, schema.id)
246 id = normalizeId(id);
248 var refVal = this._refs[id];
249 if (typeof refVal == 'string') refVal = this._refs[refVal];
250 if (refVal && refVal.schema) {
251 if (!equal(schema, refVal.schema))
252 throw new Error('id "' + id + '" resolves to more than one schema');
253 } else if (id != normalizeId(fullPath)) {
255 if (localRefs[id] && !equal(schema, localRefs[id]))
256 throw new Error('id "' + id + '" resolves to more than one schema');
257 localRefs[id] = schema;
259 this._refs[id] = fullPath;
263 for (var key in schema)
264 _resolveIds.call(this, schema[key], fullPath+'/'+util.escapeFragment(key), baseId);