"use strict"; var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default; exports.__esModule = true; exports.mutationReplacer = exports.default = void 0; var _ramda = require("ramda"); var _ramdaAdjunct = require("ramda-adjunct"); var _apidomCore = require("@swagger-api/apidom-core"); var _apidomError = require("@swagger-api/apidom-error"); var _apidomJsonPointer = require("@swagger-api/apidom-json-pointer"); var _apidomNsOpenapi = require("@swagger-api/apidom-ns-openapi-3-1"); var _$anchor = require("./selectors/$anchor.cjs"); var _uri = require("./selectors/uri.cjs"); var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs")); var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs")); var url = _interopRequireWildcard(require("../../../util/url.cjs")); var _index = _interopRequireDefault(require("../../../parse/index.cjs")); var _Reference = _interopRequireDefault(require("../../../Reference.cjs")); var _File = _interopRequireDefault(require("../../../File.cjs")); var _util = require("./util.cjs"); var _util2 = require("../../util.cjs"); var _EvaluationJsonSchemaUriError = _interopRequireDefault(require("../../../errors/EvaluationJsonSchemaUriError.cjs")); // @ts-ignore const visitAsync = _apidomCore.visit[Symbol.for('nodejs.util.promisify.custom')]; // initialize element identity manager const identityManager = new _apidomCore.IdentityManager(); /** * Custom mutation replacer. * @public */ const mutationReplacer = (newElement, oldElement, key, parent) => { if ((0, _apidomCore.isMemberElement)(parent)) { parent.value = newElement; // eslint-disable-line no-param-reassign } else if (Array.isArray(parent)) { parent[key] = newElement; // eslint-disable-line no-param-reassign } }; /** * @public */ exports.mutationReplacer = mutationReplacer; /** * @public */ class OpenAPI3_1DereferenceVisitor { indirections; namespace; reference; options; ancestors; refractCache; constructor({ reference, namespace, options, indirections = [], ancestors = new _util2.AncestorLineage(), refractCache = new Map() }) { this.indirections = indirections; this.namespace = namespace; this.reference = reference; this.options = options; this.ancestors = new _util2.AncestorLineage(...ancestors); this.refractCache = refractCache; } toBaseURI(uri) { return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri))); } async toReference(uri) { // detect maximum depth of resolution if (this.reference.depth >= this.options.resolve.maxDepth) { throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`); } const baseURI = this.toBaseURI(uri); const { refSet } = this.reference; // we've already processed this Reference in past if (refSet.has(baseURI)) { return refSet.find((0, _ramda.propEq)(baseURI, 'uri')); } const parseResult = await (0, _index.default)(url.unsanitize(baseURI), { ...this.options, parse: { ...this.options.parse, mediaType: 'text/plain' } }); // register new mutable reference with a refSet const mutableReference = new _Reference.default({ uri: baseURI, value: (0, _apidomCore.cloneDeep)(parseResult), depth: this.reference.depth + 1 }); refSet.add(mutableReference); if (this.options.dereference.immutable) { // register new immutable reference with a refSet const immutableReference = new _Reference.default({ uri: `immutable://${baseURI}`, value: parseResult, depth: this.reference.depth + 1 }); refSet.add(immutableReference); } return mutableReference; } toAncestorLineage(ancestors) { /** * Compute full ancestors lineage. * Ancestors are flatten to unwrap all Element instances. */ const directAncestors = new Set(ancestors.filter(_apidomCore.isElement)); const ancestorsLineage = new _util2.AncestorLineage(...this.ancestors, directAncestors); return [ancestorsLineage, directAncestors]; } async ReferenceElement(referencingElement, key, parent, path, ancestors, link) { // skip current referencing element as it's already been access if (this.indirections.includes(referencingElement)) { return false; } const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref)); const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; const isExternalReference = !isInternalReference; // ignore resolving internal Reference Objects if (!this.options.resolve.internal && isInternalReference) { // skip traversing this reference element and all it's child elements return false; } // ignore resolving external Reference Objects if (!this.options.resolve.external && isExternalReference) { // skip traversing this reference element and all it's child elements return false; } const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref)); const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref)); this.indirections.push(referencingElement); const jsonPointer = (0, _apidomJsonPointer.uriToPointer)($refBaseURI); // possibly non-semantic fragment let referencedElement = (0, _apidomJsonPointer.evaluate)(jsonPointer, reference.value.result); referencedElement.id = identityManager.identify(referencedElement); // applying semantics to a fragment if ((0, _apidomCore.isPrimitiveElement)(referencedElement)) { const referencedElementType = (0, _apidomCore.toValue)(referencingElement.meta.get('referenced-element')); const cacheKey = `${referencedElementType}-${(0, _apidomCore.toValue)(identityManager.identify(referencedElement))}`; if (this.refractCache.has(cacheKey)) { referencedElement = this.refractCache.get(cacheKey); } else if ((0, _apidomNsOpenapi.isReferenceLikeElement)(referencedElement)) { // handling indirect references referencedElement = _apidomNsOpenapi.ReferenceElement.refract(referencedElement); referencedElement.setMetaProperty('referenced-element', referencedElementType); this.refractCache.set(cacheKey, referencedElement); } else { // handling direct references const ElementClass = this.namespace.getElementClass(referencedElementType); referencedElement = ElementClass.refract(referencedElement); this.refractCache.set(cacheKey, referencedElement); } } // detect direct or indirect reference if (referencingElement === referencedElement) { throw new _apidomError.ApiDOMError('Recursive Reference Object detected'); } // detect maximum depth of dereferencing if (this.indirections.length > this.options.dereference.maxDepth) { throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`); } // detect second deep dive into the same fragment and avoid it if (ancestorsLineage.includes(referencedElement)) { reference.refSet.circular = true; if (this.options.dereference.circular === 'error') { throw new _apidomError.ApiDOMError('Circular reference detected'); } else if (this.options.dereference.circular === 'replace') { var _this$options$derefer, _this$options$derefer2; const refElement = new _apidomCore.RefElement(referencedElement.id, { type: 'reference', uri: reference.uri, $ref: (0, _apidomCore.toValue)(referencingElement.$ref) }); const replacer = (_this$options$derefer = (_this$options$derefer2 = this.options.dereference.strategyOpts['openapi-3-1']) == null ? void 0 : _this$options$derefer2.circularReplacer) != null ? _this$options$derefer : this.options.dereference.circularReplacer; const replacement = replacer(refElement); link.replaceWith(replacement, mutationReplacer); return !parent ? replacement : false; } } /** * Dive deep into the fragment. * * Cases to consider: * 1. We're crossing document boundary * 2. Fragment is from non-root document * 3. Fragment is a Reference Object. We need to follow it to get the eventual value * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode */ const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri; const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular); if ((isExternalReference || isNonRootDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) { // append referencing reference to ancestors lineage directAncestors.add(referencingElement); const visitor = new OpenAPI3_1DereferenceVisitor({ reference, namespace: this.namespace, indirections: [...this.indirections], options: this.options, refractCache: this.refractCache, ancestors: ancestorsLineage }); referencedElement = await visitAsync(referencedElement, visitor, { keyMap: _apidomNsOpenapi.keyMap, nodeTypeGetter: _apidomNsOpenapi.getNodeType }); // remove referencing reference from ancestors lineage directAncestors.delete(referencingElement); } this.indirections.pop(); /** * Creating a new version of referenced element to avoid modifying the original one. */ const mergedElement = (0, _apidomCore.cloneShallow)(referencedElement); // assign unique id to merged element mergedElement.setMetaProperty('id', identityManager.generateId()); // annotate fragment with info about original Reference element mergedElement.setMetaProperty('ref-fields', { $ref: (0, _apidomCore.toValue)(referencingElement.$ref), // @ts-ignore description: (0, _apidomCore.toValue)(referencingElement.description), // @ts-ignore summary: (0, _apidomCore.toValue)(referencingElement.summary) }); // annotate fragment with info about origin mergedElement.setMetaProperty('ref-origin', reference.uri); // annotate fragment with info about referencing element mergedElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement))); // override description and summary (outer has higher priority then inner) if ((0, _apidomCore.isObjectElement)(referencedElement) && (0, _apidomCore.isObjectElement)(mergedElement)) { if (referencingElement.hasKey('description') && 'description' in referencedElement) { mergedElement.remove('description'); mergedElement.set('description', referencingElement.get('description')); } if (referencingElement.hasKey('summary') && 'summary' in referencedElement) { mergedElement.remove('summary'); mergedElement.set('summary', referencingElement.get('summary')); } } /** * Transclude referencing element with merged referenced element. */ link.replaceWith(mergedElement, mutationReplacer); /** * We're at the root of the tree, so we're just replacing the entire tree. */ return !parent ? mergedElement : false; } async PathItemElement(referencingElement, key, parent, path, ancestors, link) { // ignore PathItemElement without $ref field if (!(0, _apidomCore.isStringElement)(referencingElement.$ref)) { return undefined; } // skip current referencing element as it's already been access if (this.indirections.includes(referencingElement)) { return false; } const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref)); const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; const isExternalReference = !isInternalReference; // ignore resolving external Path Item Objects if (!this.options.resolve.internal && isInternalReference) { // skip traversing this Path Item element but traverse all it's child elements return undefined; } // ignore resolving external Path Item Objects if (!this.options.resolve.external && isExternalReference) { // skip traversing this Path Item element but traverse all it's child elements return undefined; } const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref)); const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref)); this.indirections.push(referencingElement); const jsonPointer = (0, _apidomJsonPointer.uriToPointer)($refBaseURI); // possibly non-semantic referenced element let referencedElement = (0, _apidomJsonPointer.evaluate)(jsonPointer, reference.value.result); referencedElement.id = identityManager.identify(referencedElement); /** * Applying semantics to a referenced element if semantics are missing. */ if ((0, _apidomCore.isPrimitiveElement)(referencedElement)) { const cacheKey = `path-item-${(0, _apidomCore.toValue)(identityManager.identify(referencedElement))}`; if (this.refractCache.has(cacheKey)) { referencedElement = this.refractCache.get(cacheKey); } else { referencedElement = _apidomNsOpenapi.PathItemElement.refract(referencedElement); this.refractCache.set(cacheKey, referencedElement); } } // detect direct or indirect reference if (referencingElement === referencedElement) { throw new _apidomError.ApiDOMError('Recursive Path Item Object reference detected'); } // detect maximum depth of dereferencing if (this.indirections.length > this.options.dereference.maxDepth) { throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`); } // detect second deep dive into the same fragment and avoid it if (ancestorsLineage.includes(referencedElement)) { reference.refSet.circular = true; if (this.options.dereference.circular === 'error') { throw new _apidomError.ApiDOMError('Circular reference detected'); } else if (this.options.dereference.circular === 'replace') { var _this$options$derefer3, _this$options$derefer4; const refElement = new _apidomCore.RefElement(referencedElement.id, { type: 'path-item', uri: reference.uri, $ref: (0, _apidomCore.toValue)(referencingElement.$ref) }); const replacer = (_this$options$derefer3 = (_this$options$derefer4 = this.options.dereference.strategyOpts['openapi-3-1']) == null ? void 0 : _this$options$derefer4.circularReplacer) != null ? _this$options$derefer3 : this.options.dereference.circularReplacer; const replacement = replacer(refElement); link.replaceWith(replacement, mutationReplacer); return !parent ? replacement : false; } } /** * Dive deep into the fragment. * * Cases to consider: * 1. We're crossing document boundary * 2. Fragment is from non-root document * 3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode */ const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri; const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular); if ((isExternalReference || isNonRootDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomCore.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) { // append referencing reference to ancestors lineage directAncestors.add(referencingElement); const visitor = new OpenAPI3_1DereferenceVisitor({ reference, namespace: this.namespace, indirections: [...this.indirections], options: this.options, refractCache: this.refractCache, ancestors: ancestorsLineage }); referencedElement = await visitAsync(referencedElement, visitor, { keyMap: _apidomNsOpenapi.keyMap, nodeTypeGetter: _apidomNsOpenapi.getNodeType }); // remove referencing reference from ancestors lineage directAncestors.delete(referencingElement); } this.indirections.pop(); /** * Creating a new version of Path Item by merging fields from referenced Path Item with referencing one. */ if ((0, _apidomNsOpenapi.isPathItemElement)(referencedElement)) { const mergedElement = new _apidomNsOpenapi.PathItemElement([...referencedElement.content], (0, _apidomCore.cloneDeep)(referencedElement.meta), (0, _apidomCore.cloneDeep)(referencedElement.attributes)); // assign unique id to merged element mergedElement.setMetaProperty('id', identityManager.generateId()); // existing keywords from referencing PathItemElement overrides ones from referenced element referencingElement.forEach((value, keyElement, item) => { mergedElement.remove((0, _apidomCore.toValue)(keyElement)); mergedElement.content.push(item); }); mergedElement.remove('$ref'); // annotate referenced element with info about original referencing element mergedElement.setMetaProperty('ref-fields', { $ref: (0, _apidomCore.toValue)(referencingElement.$ref) }); // annotate referenced element with info about origin mergedElement.setMetaProperty('ref-origin', reference.uri); // annotate fragment with info about referencing element mergedElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement))); referencedElement = mergedElement; } /** * Transclude referencing element with merged referenced element. */ link.replaceWith(referencedElement, mutationReplacer); /** * We're at the root of the tree, so we're just replacing the entire tree. */ return !parent ? referencedElement : undefined; } async LinkElement(linkElement, key, parent, path, ancestors, link) { // ignore LinkElement without operationRef or operationId field if (!(0, _apidomCore.isStringElement)(linkElement.operationRef) && !(0, _apidomCore.isStringElement)(linkElement.operationId)) { return undefined; } // operationRef and operationId fields are mutually exclusive if ((0, _apidomCore.isStringElement)(linkElement.operationRef) && (0, _apidomCore.isStringElement)(linkElement.operationId)) { throw new _apidomError.ApiDOMError('LinkElement operationRef and operationId fields are mutually exclusive.'); } let operationElement; if ((0, _apidomCore.isStringElement)(linkElement.operationRef)) { var _linkElementCopy$oper; // possibly non-semantic referenced element const jsonPointer = (0, _apidomJsonPointer.uriToPointer)((0, _apidomCore.toValue)(linkElement.operationRef)); const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(linkElement.operationRef)); const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; const isExternalReference = !isInternalReference; // ignore resolving internal Operation Object reference if (!this.options.resolve.internal && isInternalReference) { // skip traversing this Link element but traverse all it's child elements return undefined; } // ignore resolving external Operation Object reference if (!this.options.resolve.external && isExternalReference) { // skip traversing this Link element but traverse all it's child elements return undefined; } const reference = await this.toReference((0, _apidomCore.toValue)(linkElement.operationRef)); operationElement = (0, _apidomJsonPointer.evaluate)(jsonPointer, reference.value.result); // applying semantics to a referenced element if ((0, _apidomCore.isPrimitiveElement)(operationElement)) { const cacheKey = `operation-${(0, _apidomCore.toValue)(identityManager.identify(operationElement))}`; if (this.refractCache.has(cacheKey)) { operationElement = this.refractCache.get(cacheKey); } else { operationElement = _apidomNsOpenapi.OperationElement.refract(operationElement); this.refractCache.set(cacheKey, operationElement); } } // create shallow clone to be able to annotate with metadata operationElement = (0, _apidomCore.cloneShallow)(operationElement); // annotate operation element with info about origin operationElement.setMetaProperty('ref-origin', reference.uri); const linkElementCopy = (0, _apidomCore.cloneShallow)(linkElement); (_linkElementCopy$oper = linkElementCopy.operationRef) == null || _linkElementCopy$oper.meta.set('operation', operationElement); /** * Transclude Link Object containing Operation Object in its meta. */ link.replaceWith(linkElementCopy, mutationReplacer); /** * We're at the root of the tree, so we're just replacing the entire tree. */ return !parent ? linkElementCopy : undefined; } if ((0, _apidomCore.isStringElement)(linkElement.operationId)) { var _linkElementCopy$oper2; const operationId = (0, _apidomCore.toValue)(linkElement.operationId); const reference = await this.toReference(url.unsanitize(this.reference.uri)); operationElement = (0, _apidomCore.find)(e => (0, _apidomNsOpenapi.isOperationElement)(e) && (0, _apidomCore.isElement)(e.operationId) && e.operationId.equals(operationId), reference.value.result); // OperationElement not found by its operationId if ((0, _ramdaAdjunct.isUndefined)(operationElement)) { throw new _apidomError.ApiDOMError(`OperationElement(operationId=${operationId}) not found.`); } const linkElementCopy = (0, _apidomCore.cloneShallow)(linkElement); (_linkElementCopy$oper2 = linkElementCopy.operationId) == null || _linkElementCopy$oper2.meta.set('operation', operationElement); /** * Transclude Link Object containing Operation Object in its meta. */ link.replaceWith(linkElementCopy, mutationReplacer); /** * We're at the root of the tree, so we're just replacing the entire tree. */ return !parent ? linkElementCopy : undefined; } return undefined; } async ExampleElement(exampleElement, key, parent, path, ancestors, link) { // ignore ExampleElement without externalValue field if (!(0, _apidomCore.isStringElement)(exampleElement.externalValue)) { return undefined; } // value and externalValue fields are mutually exclusive if (exampleElement.hasKey('value') && (0, _apidomCore.isStringElement)(exampleElement.externalValue)) { throw new _apidomError.ApiDOMError('ExampleElement value and externalValue fields are mutually exclusive.'); } const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(exampleElement.externalValue)); const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; const isExternalReference = !isInternalReference; // ignore resolving internal Example Objects if (!this.options.resolve.internal && isInternalReference) { // skip traversing this Example element but traverse all it's child elements return undefined; } // ignore resolving external Example Objects if (!this.options.resolve.external && isExternalReference) { // skip traversing this Example element but traverse all it's child elements return undefined; } const reference = await this.toReference((0, _apidomCore.toValue)(exampleElement.externalValue)); // shallow clone of the referenced element const valueElement = (0, _apidomCore.cloneShallow)(reference.value.result); // annotate operation element with info about origin valueElement.setMetaProperty('ref-origin', reference.uri); const exampleElementCopy = (0, _apidomCore.cloneShallow)(exampleElement); exampleElementCopy.value = valueElement; /** * Transclude Example Object containing external value. */ link.replaceWith(exampleElementCopy, mutationReplacer); /** * We're at the root of the tree, so we're just replacing the entire tree. */ return !parent ? exampleElementCopy : undefined; } async SchemaElement(referencingElement, key, parent, path, ancestors, link) { // skip current referencing schema as $ref keyword was not defined if (!(0, _apidomCore.isStringElement)(referencingElement.$ref)) { return undefined; } // skip current referencing element as it's already been access if (this.indirections.includes(referencingElement)) { return false; } const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); // compute baseURI using rules around $id and $ref keywords let reference = await this.toReference(url.unsanitize(this.reference.uri)); let { uri: retrievalURI } = reference; const $refBaseURI = (0, _util.resolveSchema$refField)(retrievalURI, referencingElement); const $refBaseURIStrippedHash = url.stripHash($refBaseURI); const file = new _File.default({ uri: $refBaseURIStrippedHash }); const isUnknownURI = (0, _ramda.none)(r => r.canRead(file), this.options.resolve.resolvers); const isURL = !isUnknownURI; let isInternalReference = url.stripHash(this.reference.uri) === $refBaseURI; let isExternalReference = !isInternalReference; this.indirections.push(referencingElement); // determining reference, proper evaluation and selection mechanism let referencedElement; try { if (isUnknownURI || isURL) { // we're dealing with canonical URI or URL with possible fragment retrievalURI = this.toBaseURI($refBaseURI); const selector = $refBaseURI; const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result); referencedElement = (0, _uri.evaluate)(selector, referenceAsSchema); referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement); referencedElement.id = identityManager.identify(referencedElement); // ignore resolving internal Schema Objects if (!this.options.resolve.internal && isInternalReference) { // skip traversing this schema element but traverse all it's child elements return undefined; } // ignore resolving external Schema Objects if (!this.options.resolve.external && isExternalReference) { // skip traversing this schema element but traverse all it's child elements return undefined; } } else { // we're assuming here that we're dealing with JSON Pointer here retrievalURI = this.toBaseURI($refBaseURI); isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; isExternalReference = !isInternalReference; // ignore resolving internal Schema Objects if (!this.options.resolve.internal && isInternalReference) { // skip traversing this schema element but traverse all it's child elements return undefined; } // ignore resolving external Schema Objects if (!this.options.resolve.external && isExternalReference) { // skip traversing this schema element but traverse all it's child elements return undefined; } reference = await this.toReference(url.unsanitize($refBaseURI)); const selector = (0, _apidomJsonPointer.uriToPointer)($refBaseURI); const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result); referencedElement = (0, _apidomJsonPointer.evaluate)(selector, referenceAsSchema); referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement); referencedElement.id = identityManager.identify(referencedElement); } } catch (error) { /** * No SchemaElement($id=URL) was not found, so we're going to try to resolve * the URL and assume the returned response is a JSON Schema. */ if (isURL && error instanceof _EvaluationJsonSchemaUriError.default) { if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)($refBaseURI))) { // we're dealing with JSON Schema $anchor here isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; isExternalReference = !isInternalReference; // ignore resolving internal Schema Objects if (!this.options.resolve.internal && isInternalReference) { // skip traversing this schema element but traverse all it's child elements return undefined; } // ignore resolving external Schema Objects if (!this.options.resolve.external && isExternalReference) { // skip traversing this schema element but traverse all it's child elements return undefined; } reference = await this.toReference(url.unsanitize($refBaseURI)); const selector = (0, _$anchor.uriToAnchor)($refBaseURI); const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result); referencedElement = (0, _$anchor.evaluate)(selector, referenceAsSchema); referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement); referencedElement.id = identityManager.identify(referencedElement); } else { // we're assuming here that we're dealing with JSON Pointer here retrievalURI = this.toBaseURI($refBaseURI); isInternalReference = url.stripHash(this.reference.uri) === retrievalURI; isExternalReference = !isInternalReference; // ignore resolving internal Schema Objects if (!this.options.resolve.internal && isInternalReference) { // skip traversing this schema element but traverse all it's child elements return undefined; } // ignore resolving external Schema Objects if (!this.options.resolve.external && isExternalReference) { // skip traversing this schema element but traverse all it's child elements return undefined; } reference = await this.toReference(url.unsanitize($refBaseURI)); const selector = (0, _apidomJsonPointer.uriToPointer)($refBaseURI); const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result); referencedElement = (0, _apidomJsonPointer.evaluate)(selector, referenceAsSchema); referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement); referencedElement.id = identityManager.identify(referencedElement); } } else { throw error; } } // detect direct or indirect reference if (referencingElement === referencedElement) { throw new _apidomError.ApiDOMError('Recursive Schema Object reference detected'); } // detect maximum depth of dereferencing if (this.indirections.length > this.options.dereference.maxDepth) { throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`); } // detect second deep dive into the same fragment and avoid it if (ancestorsLineage.includes(referencedElement)) { reference.refSet.circular = true; if (this.options.dereference.circular === 'error') { throw new _apidomError.ApiDOMError('Circular reference detected'); } else if (this.options.dereference.circular === 'replace') { var _this$options$derefer5, _this$options$derefer6; const refElement = new _apidomCore.RefElement(referencedElement.id, { type: 'json-schema', uri: reference.uri, $ref: (0, _apidomCore.toValue)(referencingElement.$ref) }); const replacer = (_this$options$derefer5 = (_this$options$derefer6 = this.options.dereference.strategyOpts['openapi-3-1']) == null ? void 0 : _this$options$derefer6.circularReplacer) != null ? _this$options$derefer5 : this.options.dereference.circularReplacer; const replacement = replacer(refElement); link.replaceWith(replacement, mutationReplacer); return !parent ? replacement : false; } } /** * Dive deep into the fragment. * * Cases to consider: * 1. We're crossing document boundary * 2. Fragment is from non-root document * 3. Fragment is a Schema Object with $ref field. We need to follow it to get the eventual value * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode */ const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri; const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular); if ((isExternalReference || isNonRootDocument || (0, _apidomNsOpenapi.isSchemaElement)(referencedElement) && (0, _apidomCore.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) { // append referencing reference to ancestors lineage directAncestors.add(referencingElement); const visitor = new OpenAPI3_1DereferenceVisitor({ reference, namespace: this.namespace, indirections: [...this.indirections], options: this.options, refractCache: this.refractCache, ancestors: ancestorsLineage }); referencedElement = await visitAsync(referencedElement, visitor, { keyMap: _apidomNsOpenapi.keyMap, nodeTypeGetter: _apidomNsOpenapi.getNodeType }); // remove referencing reference from ancestors lineage directAncestors.delete(referencingElement); } this.indirections.pop(); // Boolean JSON Schemas if ((0, _apidomNsOpenapi.isBooleanJsonSchemaElement)(referencedElement)) { const booleanJsonSchemaElement = (0, _apidomCore.cloneDeep)(referencedElement); // assign unique id to merged element booleanJsonSchemaElement.setMetaProperty('id', identityManager.generateId()); // annotate referenced element with info about original referencing element booleanJsonSchemaElement.setMetaProperty('ref-fields', { $ref: (0, _apidomCore.toValue)(referencingElement.$ref) }); // annotate referenced element with info about origin booleanJsonSchemaElement.setMetaProperty('ref-origin', reference.uri); // annotate fragment with info about referencing element booleanJsonSchemaElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement))); link.replaceWith(booleanJsonSchemaElement, mutationReplacer); return !parent ? booleanJsonSchemaElement : false; } /** * Creating a new version of Schema Object by merging fields from referenced Schema Object with referencing one. */ if ((0, _apidomNsOpenapi.isSchemaElement)(referencedElement)) { const mergedElement = new _apidomNsOpenapi.SchemaElement([...referencedElement.content], (0, _apidomCore.cloneDeep)(referencedElement.meta), (0, _apidomCore.cloneDeep)(referencedElement.attributes)); // assign unique id to merged element mergedElement.setMetaProperty('id', identityManager.generateId()); // existing keywords from referencing schema overrides ones from referenced schema referencingElement.forEach((value, keyElement, item) => { mergedElement.remove((0, _apidomCore.toValue)(keyElement)); mergedElement.content.push(item); }); mergedElement.remove('$ref'); // annotate referenced element with info about original referencing element mergedElement.setMetaProperty('ref-fields', { $ref: (0, _apidomCore.toValue)(referencingElement.$ref) }); // annotate fragment with info about origin mergedElement.setMetaProperty('ref-origin', reference.uri); // annotate fragment with info about referencing element mergedElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement))); referencedElement = mergedElement; } /** * Transclude referencing element with merged referenced element. */ link.replaceWith(referencedElement, mutationReplacer); /** * We're at the root of the tree, so we're just replacing the entire tree. */ return !parent ? referencedElement : undefined; } } var _default = exports.default = OpenAPI3_1DereferenceVisitor;