Skip to content

Commit

Permalink
fix(reference): add support for external cycles detection in OpenAPI …
Browse files Browse the repository at this point in the history
…2.0 dereference strategy (#3871)

Refs #3863
  • Loading branch information
char0n committed Feb 28, 2024
1 parent 0735471 commit 5a2141d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 4 deletions.
Expand Up @@ -57,13 +57,22 @@ const OpenApi2DereferenceVisitor = stampit({
reference: null,
options: null,
ancestors: null,
refractCache: null,
},
init({ indirections = [], reference, namespace, options, ancestors = new AncestorLineage() }) {
init({
indirections = [],
reference,
namespace,
options,
ancestors = new AncestorLineage(),
refractCache = new Map(),
}) {
this.indirections = indirections;
this.namespace = namespace;
this.reference = reference;
this.options = options;
this.ancestors = new AncestorLineage(...ancestors);
this.refractCache = refractCache;
},
methods: {
toBaseURI(uri: string): string {
Expand Down Expand Up @@ -149,15 +158,20 @@ const OpenApi2DereferenceVisitor = stampit({
// applying semantics to a fragment
if (isPrimitiveElement(referencedElement)) {
const referencedElementType = toValue(referencingElement.meta.get('referenced-element'));
const cacheKey = `${referencedElementType}-${toValue(identityManager.identify(referencedElement))}`;

if (isReferenceLikeElement(referencedElement)) {
if (this.refractCache.has(cacheKey)) {
referencedElement = this.refractCache.get(cacheKey);
} else if (isReferenceLikeElement(referencedElement)) {
// handling indirect references
referencedElement = 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);
}
}

Expand All @@ -183,6 +197,7 @@ const OpenApi2DereferenceVisitor = stampit({
indirections: [...this.indirections],
options: this.options,
ancestors: ancestorsLineage,
refractCache: this.refractCache,
});
referencedElement = await visitAsync(referencedElement, visitor, {
keyMap,
Expand Down Expand Up @@ -272,7 +287,14 @@ const OpenApi2DereferenceVisitor = stampit({

// applying semantics to a referenced element
if (isPrimitiveElement(referencedElement)) {
referencedElement = PathItemElement.refract(referencedElement);
const cacheKey = `pathItem-${toValue(identityManager.identify(referencedElement))}`;

if (this.refractCache.has(cacheKey)) {
referencedElement = this.refractCache.get(cacheKey);
} else {
referencedElement = PathItemElement.refract(referencedElement);
this.refractCache.set(cacheKey, referencedElement);
}
}

// detect direct or indirect reference
Expand All @@ -297,6 +319,7 @@ const OpenApi2DereferenceVisitor = stampit({
indirections: [...this.indirections],
options: this.options,
ancestors: ancestorsLineage,
refractCache: this.refractCache,
});
referencedElement = await visitAsync(referencedElement, visitor, {
keyMap,
Expand Down Expand Up @@ -394,15 +417,20 @@ const OpenApi2DereferenceVisitor = stampit({
// applying semantics to a fragment
if (isPrimitiveElement(referencedElement)) {
const referencedElementType = toValue(referencingElement.meta.get('referenced-element'));
const cacheKey = `pathItem-${toValue(identityManager.identify(referencedElement))}`;

if (isJSONReferenceLikeElement(referencedElement)) {
if (this.refractCache.has(cacheKey)) {
referencedElement = this.refractCache.get(cacheKey);
} else if (isJSONReferenceLikeElement(referencedElement)) {
// handling indirect references
referencedElement = 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);
}
}

Expand All @@ -428,6 +456,7 @@ const OpenApi2DereferenceVisitor = stampit({
indirections: [...this.indirections],
options: this.options,
ancestors: ancestorsLineage,
refractCache: this.refractCache,
});
referencedElement = await visitAsync(referencedElement, visitor, {
keyMap,
Expand Down
@@ -0,0 +1,13 @@
{
"definitions": {
"externalSchema": {
"type": "object",
"description": "external schema",
"properties": {
"parent": {
"$ref": "#/definitions/externalSchema"
}
}
}
}
}
@@ -0,0 +1,8 @@
{
"swagger": "2.0",
"definitions": {
"schema": {
"$ref": "./ex.json#/definitions/externalSchema"
}
}
}
Expand Up @@ -84,6 +84,24 @@ describe('dereference', function () {
});
});

context('given JSONReference Objects pointing to external cycles', function () {
const fixturePath = path.join(rootFixturePath, 'external-cycle');

specify('should dereference', async function () {
const rootFilePath = path.join(fixturePath, 'root.json');
const dereferenced = await dereference(rootFilePath, {
parse: { mediaType: mediaTypes.latest('json') },
});
const parent = evaluate('/0/definitions/schema/properties', dereferenced);
const cyclicParent = evaluate(
'/0/definitions/schema/properties/parent/properties',
dereferenced,
);

assert.strictEqual(parent, cyclicParent);
});
});

context('given JSONReference Objects pointing to external indirections', function () {
const fixturePath = path.join(rootFixturePath, 'external-indirections');

Expand Down

0 comments on commit 5a2141d

Please sign in to comment.