Path: blob/master/node_modules/ajv/lib/compile/resolve.js
1126 views
'use strict';12var URI = require('uri-js')3, equal = require('fast-deep-equal')4, util = require('./util')5, SchemaObject = require('./schema_obj')6, traverse = require('json-schema-traverse');78module.exports = resolve;910resolve.normalizeId = normalizeId;11resolve.fullPath = getFullPath;12resolve.url = resolveUrl;13resolve.ids = resolveIds;14resolve.inlineRef = inlineRef;15resolve.schema = resolveSchema;1617/**18* [resolve and compile the references ($ref)]19* @this Ajv20* @param {Function} compile reference to schema compilation funciton (localCompile)21* @param {Object} root object with information about the root schema for the current schema22* @param {String} ref reference to resolve23* @return {Object|Function} schema object (if the schema can be inlined) or validation function24*/25function resolve(compile, root, ref) {26/* jshint validthis: true */27var refVal = this._refs[ref];28if (typeof refVal == 'string') {29if (this._refs[refVal]) refVal = this._refs[refVal];30else return resolve.call(this, compile, root, refVal);31}3233refVal = refVal || this._schemas[ref];34if (refVal instanceof SchemaObject) {35return inlineRef(refVal.schema, this._opts.inlineRefs)36? refVal.schema37: refVal.validate || this._compile(refVal);38}3940var res = resolveSchema.call(this, root, ref);41var schema, v, baseId;42if (res) {43schema = res.schema;44root = res.root;45baseId = res.baseId;46}4748if (schema instanceof SchemaObject) {49v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId);50} else if (schema !== undefined) {51v = inlineRef(schema, this._opts.inlineRefs)52? schema53: compile.call(this, schema, root, undefined, baseId);54}5556return v;57}585960/**61* Resolve schema, its root and baseId62* @this Ajv63* @param {Object} root root object with properties schema, refVal, refs64* @param {String} ref reference to resolve65* @return {Object} object with properties schema, root, baseId66*/67function resolveSchema(root, ref) {68/* jshint validthis: true */69var p = URI.parse(ref)70, refPath = _getFullPath(p)71, baseId = getFullPath(this._getId(root.schema));72if (Object.keys(root.schema).length === 0 || refPath !== baseId) {73var id = normalizeId(refPath);74var refVal = this._refs[id];75if (typeof refVal == 'string') {76return resolveRecursive.call(this, root, refVal, p);77} else if (refVal instanceof SchemaObject) {78if (!refVal.validate) this._compile(refVal);79root = refVal;80} else {81refVal = this._schemas[id];82if (refVal instanceof SchemaObject) {83if (!refVal.validate) this._compile(refVal);84if (id == normalizeId(ref))85return { schema: refVal, root: root, baseId: baseId };86root = refVal;87} else {88return;89}90}91if (!root.schema) return;92baseId = getFullPath(this._getId(root.schema));93}94return getJsonPointer.call(this, p, baseId, root.schema, root);95}969798/* @this Ajv */99function resolveRecursive(root, ref, parsedRef) {100/* jshint validthis: true */101var res = resolveSchema.call(this, root, ref);102if (res) {103var schema = res.schema;104var baseId = res.baseId;105root = res.root;106var id = this._getId(schema);107if (id) baseId = resolveUrl(baseId, id);108return getJsonPointer.call(this, parsedRef, baseId, schema, root);109}110}111112113var PREVENT_SCOPE_CHANGE = util.toHash(['properties', 'patternProperties', 'enum', 'dependencies', 'definitions']);114/* @this Ajv */115function getJsonPointer(parsedRef, baseId, schema, root) {116/* jshint validthis: true */117parsedRef.fragment = parsedRef.fragment || '';118if (parsedRef.fragment.slice(0,1) != '/') return;119var parts = parsedRef.fragment.split('/');120121for (var i = 1; i < parts.length; i++) {122var part = parts[i];123if (part) {124part = util.unescapeFragment(part);125schema = schema[part];126if (schema === undefined) break;127var id;128if (!PREVENT_SCOPE_CHANGE[part]) {129id = this._getId(schema);130if (id) baseId = resolveUrl(baseId, id);131if (schema.$ref) {132var $ref = resolveUrl(baseId, schema.$ref);133var res = resolveSchema.call(this, root, $ref);134if (res) {135schema = res.schema;136root = res.root;137baseId = res.baseId;138}139}140}141}142}143if (schema !== undefined && schema !== root.schema)144return { schema: schema, root: root, baseId: baseId };145}146147148var SIMPLE_INLINED = util.toHash([149'type', 'format', 'pattern',150'maxLength', 'minLength',151'maxProperties', 'minProperties',152'maxItems', 'minItems',153'maximum', 'minimum',154'uniqueItems', 'multipleOf',155'required', 'enum'156]);157function inlineRef(schema, limit) {158if (limit === false) return false;159if (limit === undefined || limit === true) return checkNoRef(schema);160else if (limit) return countKeys(schema) <= limit;161}162163164function checkNoRef(schema) {165var item;166if (Array.isArray(schema)) {167for (var i=0; i<schema.length; i++) {168item = schema[i];169if (typeof item == 'object' && !checkNoRef(item)) return false;170}171} else {172for (var key in schema) {173if (key == '$ref') return false;174item = schema[key];175if (typeof item == 'object' && !checkNoRef(item)) return false;176}177}178return true;179}180181182function countKeys(schema) {183var count = 0, item;184if (Array.isArray(schema)) {185for (var i=0; i<schema.length; i++) {186item = schema[i];187if (typeof item == 'object') count += countKeys(item);188if (count == Infinity) return Infinity;189}190} else {191for (var key in schema) {192if (key == '$ref') return Infinity;193if (SIMPLE_INLINED[key]) {194count++;195} else {196item = schema[key];197if (typeof item == 'object') count += countKeys(item) + 1;198if (count == Infinity) return Infinity;199}200}201}202return count;203}204205206function getFullPath(id, normalize) {207if (normalize !== false) id = normalizeId(id);208var p = URI.parse(id);209return _getFullPath(p);210}211212213function _getFullPath(p) {214return URI.serialize(p).split('#')[0] + '#';215}216217218var TRAILING_SLASH_HASH = /#\/?$/;219function normalizeId(id) {220return id ? id.replace(TRAILING_SLASH_HASH, '') : '';221}222223224function resolveUrl(baseId, id) {225id = normalizeId(id);226return URI.resolve(baseId, id);227}228229230/* @this Ajv */231function resolveIds(schema) {232var schemaId = normalizeId(this._getId(schema));233var baseIds = {'': schemaId};234var fullPaths = {'': getFullPath(schemaId, false)};235var localRefs = {};236var self = this;237238traverse(schema, {allKeys: true}, function(sch, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex) {239if (jsonPtr === '') return;240var id = self._getId(sch);241var baseId = baseIds[parentJsonPtr];242var fullPath = fullPaths[parentJsonPtr] + '/' + parentKeyword;243if (keyIndex !== undefined)244fullPath += '/' + (typeof keyIndex == 'number' ? keyIndex : util.escapeFragment(keyIndex));245246if (typeof id == 'string') {247id = baseId = normalizeId(baseId ? URI.resolve(baseId, id) : id);248249var refVal = self._refs[id];250if (typeof refVal == 'string') refVal = self._refs[refVal];251if (refVal && refVal.schema) {252if (!equal(sch, refVal.schema))253throw new Error('id "' + id + '" resolves to more than one schema');254} else if (id != normalizeId(fullPath)) {255if (id[0] == '#') {256if (localRefs[id] && !equal(sch, localRefs[id]))257throw new Error('id "' + id + '" resolves to more than one schema');258localRefs[id] = sch;259} else {260self._refs[id] = fullPath;261}262}263}264baseIds[jsonPtr] = baseId;265fullPaths[jsonPtr] = fullPath;266});267268return localRefs;269}270271272