Initial commit
[yaffs-website] / node_modules / ajv / lib / compile / resolve.js
1 'use strict';
2
3 var url = require('url')
4   , equal = require('./equal')
5   , util = require('./util')
6   , SchemaObject = require('./schema_obj');
7
8 module.exports = resolve;
9
10 resolve.normalizeId = normalizeId;
11 resolve.fullPath = getFullPath;
12 resolve.url = resolveUrl;
13 resolve.ids = resolveIds;
14 resolve.inlineRef = inlineRef;
15 resolve.schema = resolveSchema;
16
17 /**
18  * [resolve and compile the references ($ref)]
19  * @this   Ajv
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
24  */
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);
31   }
32
33   refVal = refVal || this._schemas[ref];
34   if (refVal instanceof SchemaObject) {
35     return inlineRef(refVal.schema, this._opts.inlineRefs)
36             ? refVal.schema
37             : refVal.validate || this._compile(refVal);
38   }
39
40   var res = resolveSchema.call(this, root, ref);
41   var schema, v, baseId;
42   if (res) {
43     schema = res.schema;
44     root = res.root;
45     baseId = res.baseId;
46   }
47
48   if (schema instanceof SchemaObject) {
49     v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId);
50   } else if (schema) {
51     v = inlineRef(schema, this._opts.inlineRefs)
52         ? schema
53         : compile.call(this, schema, root, undefined, baseId);
54   }
55
56   return v;
57 }
58
59
60 /**
61  * Resolve schema, its root and baseId
62  * @this Ajv
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
66  */
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);
79       root = refVal;
80     } else {
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 };
86         root = refVal;
87       } else {
88         return;
89       }
90     }
91     if (!root.schema) return;
92     baseId = getFullPath(root.schema.id);
93   }
94   return getJsonPointer.call(this, p, baseId, root.schema, root);
95 }
96
97
98 /* @this Ajv */
99 function resolveRecursive(root, ref, parsedRef) {
100   /* jshint validthis: true */
101   var res = resolveSchema.call(this, root, ref);
102   if (res) {
103     var schema = res.schema;
104     var baseId = res.baseId;
105     root = res.root;
106     if (schema.id) baseId = resolveUrl(baseId, schema.id);
107     return getJsonPointer.call(this, parsedRef, baseId, schema, root);
108   }
109 }
110
111
112 var PREVENT_SCOPE_CHANGE = util.toHash(['properties', 'patternProperties', 'enum', 'dependencies', 'definitions']);
113 /* @this Ajv */
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('/');
119
120   for (var i = 1; i < parts.length; i++) {
121     var part = parts[i];
122     if (part) {
123       part = util.unescapeFragment(part);
124       schema = schema[part];
125       if (!schema) break;
126       if (schema.id && !PREVENT_SCOPE_CHANGE[part]) baseId = resolveUrl(baseId, schema.id);
127       if (schema.$ref) {
128         var $ref = resolveUrl(baseId, schema.$ref);
129         var res = resolveSchema.call(this, root, $ref);
130         if (res) {
131           schema = res.schema;
132           root = res.root;
133           baseId = res.baseId;
134         }
135       }
136     }
137   }
138   if (schema && schema != root.schema)
139     return { schema: schema, root: root, baseId: baseId };
140 }
141
142
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',
150   'required', 'enum'
151 ]);
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;
156 }
157
158
159 function checkNoRef(schema) {
160   var item;
161   if (Array.isArray(schema)) {
162     for (var i=0; i<schema.length; i++) {
163       item = schema[i];
164       if (typeof item == 'object' && !checkNoRef(item)) return false;
165     }
166   } else {
167     for (var key in schema) {
168       if (key == '$ref') return false;
169       item = schema[key];
170       if (typeof item == 'object' && !checkNoRef(item)) return false;
171     }
172   }
173   return true;
174 }
175
176
177 function countKeys(schema) {
178   var count = 0, item;
179   if (Array.isArray(schema)) {
180     for (var i=0; i<schema.length; i++) {
181       item = schema[i];
182       if (typeof item == 'object') count += countKeys(item);
183       if (count == Infinity) return Infinity;
184     }
185   } else {
186     for (var key in schema) {
187       if (key == '$ref') return Infinity;
188       if (SIMPLE_INLINED[key]) {
189         count++;
190       } else {
191         item = schema[key];
192         if (typeof item == 'object') count += countKeys(item) + 1;
193         if (count == Infinity) return Infinity;
194       }
195     }
196   }
197   return count;
198 }
199
200
201 function getFullPath(id, normalize) {
202   if (normalize !== false) id = normalizeId(id);
203   var p = url.parse(id, false, true);
204   return _getFullPath(p);
205 }
206
207
208 function _getFullPath(p) {
209   var protocolSeparator = p.protocol || p.href.slice(0,2) == '//' ? '//' : '';
210   return (p.protocol||'') + protocolSeparator + (p.host||'') + (p.path||'')  + '#';
211 }
212
213
214 var TRAILING_SLASH_HASH = /#\/?$/;
215 function normalizeId(id) {
216   return id ? id.replace(TRAILING_SLASH_HASH, '') : '';
217 }
218
219
220 function resolveUrl(baseId, id) {
221   id = normalizeId(id);
222   return url.resolve(baseId, id);
223 }
224
225
226 /* @this Ajv */
227 function resolveIds(schema) {
228   /* eslint no-shadow: 0 */
229   /* jshint validthis: true */
230   var id = normalizeId(schema.id);
231   var localRefs = {};
232   _resolveIds.call(this, schema, getFullPath(id, false), id);
233   return localRefs;
234
235   /* @this Ajv */
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)
245                           : schema.id;
246         id = normalizeId(id);
247
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)) {
254           if (id[0] == '#') {
255             if (localRefs[id] && !equal(schema, localRefs[id]))
256               throw new Error('id "' + id + '" resolves to more than one schema');
257             localRefs[id] = schema;
258           } else {
259             this._refs[id] = fullPath;
260           }
261         }
262       }
263       for (var key in schema)
264         _resolveIds.call(this, schema[key], fullPath+'/'+util.escapeFragment(key), baseId);
265     }
266   }
267 }