Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(reference): fix internal/extrernal URL determ for OpenAPI 3.1.0 #3459

Merged
merged 3 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading