diff --git a/packages/apidom-ns-openapi-3-1/src/index.ts b/packages/apidom-ns-openapi-3-1/src/index.ts index fae8545ea0..55fa8baf31 100644 --- a/packages/apidom-ns-openapi-3-1/src/index.ts +++ b/packages/apidom-ns-openapi-3-1/src/index.ts @@ -52,7 +52,6 @@ export { isContactElement, isExampleElement, isLinkElement, - isLinkElementExternal, isRequestBodyElement, isPathsElement, } from './predicates'; diff --git a/packages/apidom-ns-openapi-3-1/src/predicates.ts b/packages/apidom-ns-openapi-3-1/src/predicates.ts index 432098f1af..f83a7eaa1c 100644 --- a/packages/apidom-ns-openapi-3-1/src/predicates.ts +++ b/packages/apidom-ns-openapi-3-1/src/predicates.ts @@ -133,21 +133,6 @@ export const isLinkElement = createPredicate( }, ); -export const isLinkElementExternal: ElementPredicate = ( - 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 => @@ -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 = ( element: unknown, ): element is PathItemElement => { @@ -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 = ( element: unknown, ): element is ReferenceElement => { diff --git a/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts b/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts index 928af9b524..9d1f40267b 100644 --- a/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts +++ b/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts @@ -27,9 +27,6 @@ import { OperationElement, ExampleElement, SchemaElement, - isReferenceElementExternal, - isPathItemElementExternal, - isLinkElementExternal, isOperationElement, isBooleanJsonSchemaElement, } from '@swagger-api/apidom-ns-openapi-3-1'; @@ -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); @@ -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); @@ -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( @@ -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)) { @@ -472,11 +477,6 @@ 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( @@ -484,6 +484,14 @@ const OpenApi3_1DereferenceVisitor = stampit({ ); } + 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 @@ -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); @@ -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( @@ -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, @@ -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 diff --git a/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts b/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts index 03d1e45389..617c41e555 100644 --- a/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts +++ b/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts @@ -14,11 +14,8 @@ import { LinkElement, ExampleElement, SchemaElement, - isReferenceElementExternal, isSchemaElement, isPathItemElement, - isPathItemElementExternal, - isLinkElementExternal, } from '@swagger-api/apidom-ns-openapi-3-1'; import { Reference as IReference, Resolver as IResolver } from '../../../types'; @@ -99,16 +96,17 @@ const OpenApi3_1ResolveVisitor = stampit({ }, ReferenceElement(referenceElement: ReferenceElement) { + const uri = toValue(referenceElement.$ref); + const retrievalURI = this.toBaseURI(uri); + // ignore resolving external Reference Objects - if (!this.options.resolve.external && isReferenceElementExternal(referenceElement)) { + 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 uri = toValue(referenceElement.$ref); - const baseURI = this.toBaseURI(uri); - - if (!has(baseURI, this.crawlingMap)) { - this.crawlingMap[baseURI] = this.toReference(uri); + if (!has(retrievalURI, this.crawlingMap)) { + this.crawlingMap[retrievalURI] = this.toReference(uri); } this.crawledElements.push(referenceElement); @@ -121,16 +119,17 @@ const OpenApi3_1ResolveVisitor = stampit({ return undefined; } + const uri = toValue(pathItemElement.$ref); + const retrievalURI = this.toBaseURI(uri); + // ignore resolving external Path Item Objects - if (!this.options.resolve.external && isPathItemElementExternal(pathItemElement)) { + 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 uri = toValue(pathItemElement.$ref); - const baseURI = this.toBaseURI(uri); - - if (!has(baseURI, this.crawlingMap)) { - this.crawlingMap[baseURI] = this.toReference(uri); + if (!has(retrievalURI, this.crawlingMap)) { + this.crawlingMap[retrievalURI] = this.toReference(uri); } this.crawledElements.push(pathItemElement); @@ -143,8 +142,12 @@ const OpenApi3_1ResolveVisitor = stampit({ return undefined; } + const uri = toValue(linkElement.operationRef); + const retrievalURI = this.toBaseURI(uri); + // ignore resolving external Path Item Elements - if (!this.options.resolve.external && isLinkElementExternal(linkElement)) { + const isExternal = url.stripHash(this.reference.uri) !== retrievalURI; + if (!this.options.resolve.external && isExternal) { return undefined; } @@ -153,12 +156,9 @@ const OpenApi3_1ResolveVisitor = stampit({ throw new ApiDOMError('LinkElement operationRef and operationId are mutually exclusive.'); } - if (isLinkElementExternal(linkElement)) { - const uri = toValue(linkElement.operationRef); - const baseURI = this.toBaseURI(uri); - - if (!has(baseURI, this.crawlingMap)) { - this.crawlingMap[baseURI] = this.toReference(uri); + if (isExternal) { + if (!has(retrievalURI, this.crawlingMap)) { + this.crawlingMap[retrievalURI] = this.toReference(uri); } } @@ -171,11 +171,6 @@ const OpenApi3_1ResolveVisitor = stampit({ return undefined; } - // 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( @@ -184,10 +179,16 @@ const OpenApi3_1ResolveVisitor = stampit({ } const uri = toValue(exampleElement.externalValue); - const baseURI = this.toBaseURI(uri); + const retrievalURI = this.toBaseURI(uri); - if (!has(baseURI, this.crawlingMap)) { - this.crawlingMap[baseURI] = this.toReference(uri); + // 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; + } + + if (!has(retrievalURI, this.crawlingMap)) { + this.crawlingMap[retrievalURI] = this.toReference(uri); } return undefined; @@ -211,33 +212,43 @@ const OpenApi3_1ResolveVisitor = stampit({ // compute baseURI using rules around $id and $ref keywords const reference = await this.toReference(url.unsanitize(this.reference.uri)); - const { uri: retrievalURI } = reference; + let { uri: retrievalURI } = reference; const $refBaseURI = resolveSchema$refField(retrievalURI, schemaElement) as string; const $refBaseURIStrippedHash = url.stripHash($refBaseURI); const file = File({ uri: $refBaseURIStrippedHash }); const isUnknownURI = none((r: IResolver) => r.canRead(file), this.options.resolve.resolvers); const isURL = !isUnknownURI; - const isExternal = !isUnknownURI && retrievalURI !== $refBaseURIStrippedHash; - - // ignore resolving external Reference Objects - if (!this.options.resolve.external && isExternal) { - // mark current referencing schema as visited - this.visited.add(schemaElement); - // skip traversing this schema but traverse all it's child schemas - return undefined; - } + const isExternalURL = (uri: string) => url.stripHash(this.reference.uri) !== uri; if (!has($refBaseURIStrippedHash, this.crawlingMap)) { try { if (isUnknownURI || isURL) { this.crawlingMap[$refBaseURIStrippedHash] = reference; } else { + 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 + this.visited.add(schemaElement); + return undefined; + } + this.crawlingMap[$refBaseURIStrippedHash] = this.toReference( url.unsanitize($refBaseURI), ); } } catch (error) { if (isURL && error instanceof EvaluationJsonSchemaUriError) { + retrievalURI = this.toBaseURI(url.unsanitize($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 + this.visited.add(schemaElement); + return undefined; + } + this.crawlingMap[$refBaseURIStrippedHash] = this.toReference( url.unsanitize($refBaseURI), ); @@ -346,12 +357,13 @@ const OpenApi3_1ResolveVisitor = stampit({ async crawlSchemaElement(referencingElement: SchemaElement) { // compute baseURI using rules around $id and $ref keywords let reference = await this.toReference(url.unsanitize(this.reference.uri)); - const { uri: retrievalURI } = reference; + let { uri: retrievalURI } = reference; const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement) as string; const $refBaseURIStrippedHash = url.stripHash($refBaseURI); const file = File({ uri: $refBaseURIStrippedHash }); const isUnknownURI = none((r: IResolver) => r.canRead(file), this.options.resolve.resolvers); const isURL = !isUnknownURI; + const isExternalURL = (uri: string) => url.stripHash(this.reference.uri) !== uri; this.indirections.push(referencingElement); @@ -369,6 +381,14 @@ const OpenApi3_1ResolveVisitor = 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( @@ -384,6 +404,14 @@ const OpenApi3_1ResolveVisitor = 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)); const selector = uriToAnchor($refBaseURI); referencedElement = $anchorEvaluate( @@ -393,6 +421,14 @@ const OpenApi3_1ResolveVisitor = 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( @@ -435,6 +471,8 @@ const OpenApi3_1ResolveVisitor = stampit({ await visitor.crawl(); this.indirections.pop(); + + return undefined; }, async crawl() {