Skip to content

Commit

Permalink
fix(reference): fix handling cycles in all dereference strategies
Browse files Browse the repository at this point in the history
  • Loading branch information
char0n committed Nov 7, 2023
1 parent 65dcd0e commit a49ea11
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
isStringElement,
visit,
toValue,
refractorPluginElementIdentity,
} from '@swagger-api/apidom-core';
import { ApiDOMError } from '@swagger-api/apidom-error';
import { evaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
Expand All @@ -19,6 +20,7 @@ import {
isChannelItemElementExternal,
isReferenceElementExternal,
isReferenceLikeElement,
isBooleanJsonSchemaElement,
keyMap,
ReferenceElement,
} from '@swagger-api/apidom-ns-asyncapi-2';
Expand All @@ -34,6 +36,24 @@ import Reference from '../../../Reference';
// @ts-ignore
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];

/**
* Predicate for detecting if element was created by merging referencing
* element with particular element identity with a referenced element.
*/
const wasReferencedBy =
<T extends Element, U extends Element>(referencingElement: T) =>
(element: U) => {
// @ts-ignore
return (
element.meta.hasKey('ref-referencing-element-id') &&
element.meta.get('ref-referencing-element-id').equals(toValue(referencingElement.id))
);
};

// initialize element identity plugin
const elementIdentity = refractorPluginElementIdentity()();
elementIdentity.pre();

const AsyncApi2DereferenceVisitor = stampit({
props: {
indirections: [],
Expand All @@ -55,7 +75,7 @@ const AsyncApi2DereferenceVisitor = stampit({
* Compute full ancestors lineage.
* Ancestors are flatten to unwrap all Element instances.
*/
const directAncestors = new WeakSet(ancestors.filter(isElement));
const directAncestors = new Set<Element>(ancestors.filter(isElement));
const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors);

return [ancestorsLineage, directAncestors];
Expand Down Expand Up @@ -174,6 +194,24 @@ const AsyncApi2DereferenceVisitor = stampit({

this.indirections.pop();

// Boolean JSON Schemas
if (isBooleanJsonSchemaElement(referencedElement)) {
const booleanJsonSchemaElement = cloneDeep(referencedElement);
// annotate referenced element with info about original referencing element
booleanJsonSchemaElement.setMetaProperty('ref-fields', {
$ref: 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',
cloneDeep(referencingElement.id),
);

return booleanJsonSchemaElement;
}

const mergeAndAnnotateReferencedElement = <T extends Element>(refedElement: T): T => {
const copy = cloneShallow(refedElement);

Expand All @@ -187,14 +225,24 @@ const AsyncApi2DereferenceVisitor = stampit({
return copy;
};

// assigning element identity if not already assigned
if (referencingElement.id.equals('')) {
elementIdentity.visitor.enter(referencingElement);
}

// attempting to create cycle
if (ancestorsLineage.includes(referencedElement)) {
if (
ancestorsLineage.includes(referencingElement) ||
ancestorsLineage.includes(referencedElement)
) {
const replaceWith =
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
mergeAndAnnotateReferencedElement(referencedElement);
if (isMemberElement(parent)) {
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent.value = replaceWith; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
}

return false;
}

Expand Down Expand Up @@ -301,14 +349,24 @@ const AsyncApi2DereferenceVisitor = stampit({
return mergedElement;
};

// assigning element identity if not already assigned
if (referencingElement.id.equals('')) {
elementIdentity.visitor.enter(referencingElement);
}

// attempting to create cycle
if (ancestorsLineage.includes(referencedElement)) {
if (
ancestorsLineage.includes(referencingElement) ||
ancestorsLineage.includes(referencedElement)
) {
const replaceWith =
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
mergeAndAnnotateReferencedElement(referencedElement);
if (isMemberElement(parent)) {
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent.value = replaceWith; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
cloneDeep,
toValue,
isMemberElement,
refractorPluginElementIdentity,
} from '@swagger-api/apidom-core';
import { ApiDOMError } from '@swagger-api/apidom-error';
import { evaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
Expand Down Expand Up @@ -41,6 +42,24 @@ import Reference from '../../../Reference';
// @ts-ignore
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];

/**
* Predicate for detecting if element was created by merging referencing
* element with particular element identity with a referenced element.
*/
const wasReferencedBy =
<T extends Element, U extends Element>(referencingElement: T) =>
(element: U) => {
// @ts-ignore
return (
element.meta.hasKey('ref-referencing-element-id') &&
element.meta.get('ref-referencing-element-id').equals(toValue(referencingElement.id))
);
};

// initialize element identity plugin
const elementIdentity = refractorPluginElementIdentity()();
elementIdentity.pre();

// eslint-disable-next-line @typescript-eslint/naming-convention
const OpenApi3_0DereferenceVisitor = stampit({
props: {
Expand Down Expand Up @@ -97,7 +116,7 @@ const OpenApi3_0DereferenceVisitor = stampit({
* Compute full ancestors lineage.
* Ancestors are flatten to unwrap all Element instances.
*/
const directAncestors = new WeakSet(ancestors.filter(isElement));
const directAncestors = new Set<Element>(ancestors.filter(isElement));
const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors);

return [ancestorsLineage, directAncestors];
Expand Down Expand Up @@ -196,14 +215,24 @@ const OpenApi3_0DereferenceVisitor = stampit({
return copy;
};

// assigning element identity if not already assigned
if (referencingElement.id.equals('')) {
elementIdentity.visitor.enter(referencingElement);
}

// attempting to create cycle
if (ancestorsLineage.includes(referencedElement)) {
if (
ancestorsLineage.includes(referencingElement) ||
ancestorsLineage.includes(referencedElement)
) {
const replaceWith =
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
mergeAndAnnotateReferencedElement(referencedElement);
if (isMemberElement(parent)) {
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent.value = replaceWith; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
}

return false;
}

Expand Down Expand Up @@ -310,14 +339,24 @@ const OpenApi3_0DereferenceVisitor = stampit({
return mergedElement;
};

// assigning element identity if not already assigned
if (referencingElement.id.equals('')) {
elementIdentity.visitor.enter(referencingElement);
}

// attempting to create cycle
if (ancestorsLineage.includes(referencedElement)) {
if (
ancestorsLineage.includes(referencingElement) ||
ancestorsLineage.includes(referencedElement)
) {
const replaceWith =
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
mergeAndAnnotateReferencedElement(referencedElement);
if (isMemberElement(parent)) {
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent.value = replaceWith; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
cloneDeep,
toValue,
Element,
refractorPluginElementIdentity,
} from '@swagger-api/apidom-core';
import { ApiDOMError } from '@swagger-api/apidom-error';
import { evaluate as jsonPointerEvaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
Expand Down Expand Up @@ -52,6 +53,24 @@ import EvaluationJsonSchemaUriError from '../../../errors/EvaluationJsonSchemaUr
// @ts-ignore
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];

/**
* Predicate for detecting if element was created by merging referencing
* element with particular element identity with a referenced element.
*/
const wasReferencedBy =
<T extends Element, U extends Element>(referencingElement: T) =>
(element: U) => {
// @ts-ignore
return (
element.meta.hasKey('ref-referencing-element-id') &&
element.meta.get('ref-referencing-element-id').equals(toValue(referencingElement.id))
);
};

// initialize element identity plugin
const elementIdentity = refractorPluginElementIdentity()();
elementIdentity.pre();

// eslint-disable-next-line @typescript-eslint/naming-convention
const OpenApi3_1DereferenceVisitor = stampit({
props: {
Expand Down Expand Up @@ -111,7 +130,7 @@ const OpenApi3_1DereferenceVisitor = stampit({
* Compute full ancestors lineage.
* Ancestors are flatten to unwrap all Element instances.
*/
const directAncestors = new WeakSet(ancestors.filter(isElement));
const directAncestors = new Set<Element>(ancestors.filter(isElement));
const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors);

return [ancestorsLineage, directAncestors];
Expand Down Expand Up @@ -228,14 +247,24 @@ const OpenApi3_1DereferenceVisitor = stampit({
return copy;
};

// assigning element identity if not already assigned
if (referencingElement.id.equals('')) {
elementIdentity.visitor.enter(referencingElement);
}

// attempting to create cycle
if (ancestorsLineage.includes(referencedElement)) {
if (
ancestorsLineage.includes(referencingElement) ||
ancestorsLineage.includes(referencedElement)
) {
const replaceWith =
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
mergeAndAnnotateReferencedElement(referencedElement);
if (isMemberElement(parent)) {
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent.value = replaceWith; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
}

return false;
}

Expand Down Expand Up @@ -342,14 +371,24 @@ const OpenApi3_1DereferenceVisitor = stampit({
return mergedElement;
};

// assigning element identity if not already assigned
if (referencingElement.id.equals('')) {
elementIdentity.visitor.enter(referencingElement);
}

// attempting to create cycle
if (ancestorsLineage.includes(referencedElement)) {
if (
ancestorsLineage.includes(referencingElement) ||
ancestorsLineage.includes(referencedElement)
) {
const replaceWith =
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
mergeAndAnnotateReferencedElement(referencedElement);
if (isMemberElement(parent)) {
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent.value = replaceWith; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
}

return false;
}

Expand Down Expand Up @@ -592,6 +631,12 @@ const OpenApi3_1DereferenceVisitor = stampit({
});
// 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',
cloneDeep(referencingElement.id),
);

return booleanJsonSchemaElement;
}

Expand All @@ -616,22 +661,36 @@ const OpenApi3_1DereferenceVisitor = stampit({
});
// 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',
cloneDeep(referencingElement.id),
);

return mergedElement;
};

// assigning element identity if not already assigned
if (referencingElement.id.equals('')) {
elementIdentity.visitor.enter(referencingElement);
}

// attempting to create cycle
if (ancestorsLineage.includes(referencedElement)) {
if (
ancestorsLineage.includes(referencingElement) ||
ancestorsLineage.includes(referencedElement)
) {
const replaceWith =
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
mergeAndAnnotateReferencedElement(referencedElement);
if (isMemberElement(parent)) {
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent.value = replaceWith; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
}

return false;
}

// transclude referencing element with merged referenced element
return mergeAndAnnotateReferencedElement(referencedElement);
},
},
Expand Down
Loading

0 comments on commit a49ea11

Please sign in to comment.