Skip to content

Commit

Permalink
fix(reference): fix internal/extrernal URL determ for OpenAPI 3.1.0 (#…
Browse files Browse the repository at this point in the history
…3459)

This change will handle cases where the referenced data is served
from the external URL, but the definition is served on the
the same external URL as well.

Refs #3451
  • Loading branch information
char0n committed Nov 28, 2023
1 parent a2c65e2 commit 2215480
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 84 deletions.
1 change: 0 additions & 1 deletion packages/apidom-ns-openapi-3-1/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export {
isContactElement,
isExampleElement,
isLinkElement,
isLinkElementExternal,
isRequestBodyElement,
isPathsElement,
} from './predicates';
Expand Down
27 changes: 12 additions & 15 deletions packages/apidom-ns-openapi-3-1/src/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,21 +133,6 @@ export const isLinkElement = createPredicate(
},
);

export const isLinkElementExternal: ElementPredicate<LinkElement> = (
element: unknown,
): element is LinkElement => {
if (!isLinkElement(element)) {
return false;
}
if (!isStringElement(element.operationRef)) {
return false;
}

const value = toValue(element.operationRef);

return typeof value === 'string' && value.length > 0 && !value.startsWith('#');
};

export const isOpenapiElement = createPredicate(
({ hasBasicElementProps, isElementType, primitiveEq }) => {
return (element: unknown): element is OpenapiElement =>
Expand Down Expand Up @@ -200,6 +185,12 @@ export const isPathItemElement = createPredicate(
},
);

/**
* @deprecated
* Determining whether a PathItemElement is external or internal is not possible by just looking
* at value of the $ref fixed field. The value of the $ref field needs to be resolved in runtime
* using the referring document as the Base URI.
*/
export const isPathItemElementExternal: ElementPredicate<PathItemElement> = (
element: unknown,
): element is PathItemElement => {
Expand Down Expand Up @@ -235,6 +226,12 @@ export const isReferenceElement = createPredicate(
},
);

/**
* @deprecated
* Determining whether a ReferenceElement is external or internal is not possible by just looking
* at value of the $ref fixed field. The value of the $ref field needs to be resolved in runtime
* using the referring document as the Base URI.
*/
export const isReferenceElementExternal: ElementPredicate<ReferenceElement> = (
element: unknown,
): element is ReferenceElement => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ import {
OperationElement,
ExampleElement,
SchemaElement,
isReferenceElementExternal,
isPathItemElementExternal,
isLinkElementExternal,
isOperationElement,
isBooleanJsonSchemaElement,
} from '@swagger-api/apidom-ns-openapi-3-1';
Expand Down Expand Up @@ -147,13 +144,15 @@ const OpenApi3_1DereferenceVisitor = stampit({
return false;
}

const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));

// ignore resolving external Reference Objects
if (!this.options.resolve.external && isReferenceElementExternal(referencingElement)) {
if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
// skip traversing this reference element and all it's child elements
return false;
}

const reference = await this.toReference(toValue(referencingElement.$ref));
const { uri: retrievalURI } = reference;
const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref));

this.indirections.push(referencingElement);
Expand Down Expand Up @@ -288,13 +287,15 @@ const OpenApi3_1DereferenceVisitor = stampit({
return false;
}

// ignore resolving external Path Item Elements
if (!this.options.resolve.external && isPathItemElementExternal(referencingElement)) {
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));

// ignore resolving external Path Item Objects
if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
// skip traversing this Path Item element but traverse all it's child elements
return undefined;
}

const reference = await this.toReference(toValue(referencingElement.$ref));
const { uri: retrievalURI } = reference;
const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref));

this.indirections.push(referencingElement);
Expand Down Expand Up @@ -399,11 +400,6 @@ const OpenApi3_1DereferenceVisitor = stampit({
return undefined;
}

// ignore resolving external Path Item Elements
if (!this.options.resolve.external && isLinkElementExternal(linkElement)) {
return undefined;
}

// operationRef and operationId fields are mutually exclusive
if (isStringElement(linkElement.operationRef) && isStringElement(linkElement.operationId)) {
throw new ApiDOMError(
Expand All @@ -416,7 +412,16 @@ const OpenApi3_1DereferenceVisitor = stampit({
if (isStringElement(linkElement.operationRef)) {
// possibly non-semantic referenced element
const jsonPointer = uriToPointer(toValue(linkElement.operationRef));
const retrievalURI = this.toBaseURI(toValue(linkElement.operationRef));

// ignore resolving external Operation Object reference
if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
// skip traversing this Link element but traverse all it's child elements
return undefined;
}

const reference = await this.toReference(toValue(linkElement.operationRef));

operationElement = jsonPointerEvaluate(jsonPointer, reference.value.result);
// applying semantics to a referenced element
if (isPrimitiveElement(operationElement)) {
Expand Down Expand Up @@ -472,18 +477,21 @@ const OpenApi3_1DereferenceVisitor = stampit({
return false;
}

// ignore resolving ExampleElement externalValue
if (!this.options.resolve.external && isStringElement(exampleElement.externalValue)) {
return undefined;
}

// value and externalValue fields are mutually exclusive
if (exampleElement.hasKey('value') && isStringElement(exampleElement.externalValue)) {
throw new ApiDOMError(
'ExampleElement value and externalValue fields are mutually exclusive.',
);
}

const retrievalURI = this.toBaseURI(toValue(exampleElement.externalValue));

// ignore resolving external Example Objects
if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
// skip traversing this Example element but traverse all it's child elements
return undefined;
}

const reference = await this.toReference(toValue(exampleElement.externalValue));

// shallow clone of the referenced element
Expand Down Expand Up @@ -524,13 +532,7 @@ const OpenApi3_1DereferenceVisitor = stampit({
const file = File({ uri: $refBaseURIStrippedHash });
const isUnknownURI = none((r: IResolver) => r.canRead(file), this.options.resolve.resolvers);
const isURL = !isUnknownURI;
const isExternal = isURL && retrievalURI !== $refBaseURIStrippedHash;

// ignore resolving external Schema Objects
if (!this.options.resolve.external && isExternal) {
// skip traversing this schema but traverse all it's child schemas
return undefined;
}
const isExternalURL = (uri: string) => url.stripHash(this.reference.uri) !== uri;

this.indirections.push(referencingElement);

Expand All @@ -548,6 +550,14 @@ const OpenApi3_1DereferenceVisitor = stampit({
);
} else {
// we're assuming here that we're dealing with JSON Pointer here
retrievalURI = this.toBaseURI(toValue($refBaseURI));

// ignore resolving external Schema Objects
if (!this.options.resolve.external && isExternalURL(retrievalURI)) {
// skip traversing this schema element but traverse all it's child elements
return undefined;
}

reference = await this.toReference(url.unsanitize($refBaseURI));
const selector = uriToPointer($refBaseURI);
referencedElement = maybeRefractToSchemaElement(
Expand All @@ -563,8 +573,15 @@ const OpenApi3_1DereferenceVisitor = stampit({
if (isURL && error instanceof EvaluationJsonSchemaUriError) {
if (isAnchor(uriToAnchor($refBaseURI))) {
// we're dealing with JSON Schema $anchor here
retrievalURI = this.toBaseURI(toValue($refBaseURI));

// ignore resolving external Schema Objects
if (!this.options.resolve.external && isExternalURL(retrievalURI)) {
// skip traversing this schema element but traverse all it's child elements
return undefined;
}

reference = await this.toReference(url.unsanitize($refBaseURI));
retrievalURI = reference.uri;
const selector = uriToAnchor($refBaseURI);
referencedElement = $anchorEvaluate(
selector,
Expand All @@ -573,8 +590,15 @@ const OpenApi3_1DereferenceVisitor = stampit({
);
} else {
// we're assuming here that we're dealing with JSON Pointer here
retrievalURI = this.toBaseURI(toValue($refBaseURI));

// ignore resolving external Schema Objects
if (!this.options.resolve.external && isExternalURL(retrievalURI)) {
// skip traversing this schema element but traverse all it's child elements
return undefined;
}

reference = await this.toReference(url.unsanitize($refBaseURI));
retrievalURI = reference.uri;
const selector = uriToPointer($refBaseURI);
referencedElement = maybeRefractToSchemaElement(
// @ts-ignore
Expand Down

0 comments on commit 2215480

Please sign in to comment.