'use strict'; var URI = require('uri-js') , equal = require('fast-deep-equal') , util = require('./util') , SchemaObject = require('./schema_obj') , traverse = require('json-schema-traverse'); module.exports = resolve; resolve.normalizeId = normalizeId; resolve.fullPath = getFullPath; resolve.url = resolveUrl; resolve.ids = resolveIds; resolve.inlineRef = inlineRef; resolve.schema = resolveSchema; /** * [resolve and compile the references ($ref)] * @this Ajv * @param {Function} compile reference to schema compilation funciton (localCompile) * @param {Object} root object with information about the root schema for the current schema * @param {String} ref reference to resolve * @return {Object|Function} schema object (if the schema can be inlined) or validation function */ function resolve(compile, root, ref) { /* jshint validthis: true */ var refVal = this._refs[ref]; if (typeof refVal == 'string') { if (this._refs[refVal]) refVal = this._refs[refVal]; else return resolve.call(this, compile, root, refVal); } refVal = refVal || this._schemas[ref]; if (refVal instanceof SchemaObject) { return inlineRef(refVal.schema, this._opts.inlineRefs) ? refVal.schema : refVal.validate || this._compile(refVal); } var res = resolveSchema.call(this, root, ref); var schema, v, baseId; if (res) { schema = res.schema; root = res.root; baseId = res.baseId; } if (schema instanceof SchemaObject) { v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId); } else if (schema !== undefined) { v = inlineRef(schema, this._opts.inlineRefs) ? schema : compile.call(this, schema, root, undefined, baseId); } return v; } /** * Resolve schema, its root and baseId * @this Ajv * @param {Object} root root object with properties schema, refVal, refs * @param {String} ref reference to resolve * @return {Object} object with properties schema, root, baseId */ function resolveSchema(root, ref) { /* jshint validthis: true */ var p = URI.parse(ref) , refPath = _getFullPath(p) , baseId = getFullPath(this._getId(root.schema)); if (Object.keys(root.schema).length === 0 || refPath !== baseId) { var id = normalizeId(refPath); var refVal = this._refs[id]; if (typeof refVal == 'string') { return resolveRecursive.call(this, root, refVal, p); } else if (refVal instanceof SchemaObject) { if (!refVal.validate) this._compile(refVal); root = refVal; } else { refVal = this._schemas[id]; if (refVal instanceof SchemaObject) { if (!refVal.validate) this._compile(refVal); if (id == normalizeId(ref)) return { schema: refVal, root: root, baseId: baseId }; root = refVal; } else { return; } } if (!root.schema) return; baseId = getFullPath(this._getId(root.schema)); } return getJsonPointer.call(this, p, baseId, root.schema, root); } /* @this Ajv */ function resolveRecursive(root, ref, parsedRef) { /* jshint validthis: true */ var res = resolveSchema.call(this, root, ref); if (res) { var schema = res.schema; var baseId = res.baseId; root = res.root; var id = this._getId(schema); if (id) baseId = resolveUrl(baseId, id); return getJsonPointer.call(this, parsedRef, baseId, schema, root); } } var PREVENT_SCOPE_CHANGE = util.toHash(['properties', 'patternProperties', 'enum', 'dependencies', 'definitions']); /* @this Ajv */ function getJsonPointer(parsedRef, baseId, schema, root) { /* jshint validthis: true */ parsedRef.fragment = parsedRef.fragment || ''; if (parsedRef.fragment.slice(0,1) != '/') return; var parts = parsedRef.fragment.split('/'); for (var i = 1; i < parts.length; i++) { var part = parts[i]; if (part) { part = util.unescapeFragment(part); schema = schema[part]; if (schema === undefined) break; var id; if (!PREVENT_SCOPE_CHANGE[part]) { id = this._getId(schema); if (id) baseId = resolveUrl(baseId, id); if (schema.$ref) { var $ref = resolveUrl(baseId, schema.$ref); var res = resolveSchema.call(this, root, $ref); if (res) { schema = res.schema; root = res.root; baseId = res.baseId; } } } } } if (schema !== undefined && schema !== root.schema) return { schema: schema, root: root, baseId: baseId }; } var SIMPLE_INLINED = util.toHash([ 'type', 'format', 'pattern', 'maxLength', 'minLength', 'maxProperties', 'minProperties', 'maxItems', 'minItems', 'maximum', 'minimum', 'uniqueItems', 'multipleOf', 'required', 'enum' ]); function inlineRef(schema, limit) { if (limit === false) return false; if (limit === undefined || limit === true) return checkNoRef(schema); else if (limit) return countKeys(schema) <= limit; } function checkNoRef(schema) { var item; if (Array.isArray(schema)) { for (var i=0; i