Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -892,6 +894,8 @@ private void processRefExample(Example example, String externalFile) {
if (format.equals(RefFormat.RELATIVE) && !Objects.equals(subRefExternalPath, externalFile)) {
$ref = join(externalFile, example.get$ref());
example.set$ref($ref);
resolveWholeDocumentRef($ref, this::processRefToExternalExample, example::set$ref,
RefType.COMPONENTS.getInternalPrefix() + "examples/");
}else {
processRefToExternalExample($ref, format);
}
Expand Down Expand Up @@ -955,6 +959,8 @@ private void processRefSchema(Schema subRef, String externalFile) {
if (format.equals(RefFormat.RELATIVE) && !Objects.equals(subRefExternalPath, externalFile)) {
$ref = constructRef(subRef, externalFile);
subRef.set$ref($ref);
resolveWholeDocumentRef($ref, this::processRefToExternalSchema, subRef::set$ref,
RefType.SCHEMAS.getInternalPrefix());
}else {
processRefToExternalSchema($ref, format);
}
Expand All @@ -980,6 +986,8 @@ private void processRefHeader(Header subRef, String externalFile) {
if (format.equals(RefFormat.RELATIVE) && !Objects.equals(subRefExternalPath, externalFile)) {
$ref = join(externalFile, subRef.get$ref());
subRef.set$ref($ref);
resolveWholeDocumentRef($ref, this::processRefToExternalHeader, subRef::set$ref,
RefType.COMPONENTS.getInternalPrefix() + "headers/");
}else {
processRefToExternalHeader($ref, format);
}
Expand All @@ -999,12 +1007,32 @@ private void processRefLink(Link subRef, String externalFile) {
if (format.equals(RefFormat.RELATIVE) && !Objects.equals(subRefExternalPath, externalFile)) {
$ref = join(externalFile, subRef.get$ref());
subRef.set$ref($ref);
resolveWholeDocumentRef($ref, this::processRefToExternalLink, subRef::set$ref,
RefType.COMPONENTS.getInternalPrefix() + "links/");
}else {
processRefToExternalLink($ref, format);
}
}


private static boolean isWholeDocumentRef(String ref) {
return !ref.contains("#");
}

private void resolveWholeDocumentRef(String ref, BiFunction<String, RefFormat, String> processor,
Consumer<String> setRef, String internalPrefix) {
if (!isWholeDocumentRef(ref)) {
return;
}

RefFormat format = computeRefFormat(ref);
processor.apply(ref, format);
String renamedRef = cache.getRenamedRef(ref);
if (renamedRef != null) {
setRef.accept(internalPrefix + renamedRef);
}
}

// visible for testing
public static String join(String source, String fragment) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package io.swagger.v3.parser.test;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.testng.annotations.Test;

import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;

public class Issue1889Test {

private static final String SCHEMA_PREFIX = "#/components/schemas/";
private static final String HEADER_PREFIX = "#/components/headers/";

@Test
public void resolvesNestedRelativeArrayItemReference() {
OpenAPI openAPI = parse("issue-1889/root.yaml");

Schema items = getProductItems(openAPI);
String resolvedName = assertInternalReference(items);

assertNotNull(openAPI.getComponents().getSchemas().get(resolvedName));
}

@Test
public void resolvesNestedRelativeArrayItemReferenceWithoutOverwritingExistingSchema() {
OpenAPI openAPI = parse("issue-1889/root-with-product.yaml");

Schema rootProduct = openAPI.getComponents().getSchemas().get("Product");
assertNotNull(rootProduct);
assertNotNull(rootProduct.getProperties().get("rootOnly"));

Schema items = getProductItems(openAPI);
String resolvedName = assertInternalReference(items);
Schema resolvedProduct = openAPI.getComponents().getSchemas().get(resolvedName);

assertNotNull(resolvedProduct);
assertNotNull(resolvedProduct.getProperties().get("id"));
assertNull(resolvedProduct.getProperties().get("rootOnly"));
}

@Test
public void resolvesNestedRelativeArrayItemReferenceWithFragment() {
OpenAPI openAPI = parse("issue-1889/root-fragment-ref.yaml");

Schema response = openAPI.getComponents().getSchemas().get("GetProductResponse");
assertNotNull(response);
Schema products = (Schema) response.getProperties().get("products");
assertTrue(products instanceof ArraySchema);
Schema items = ((ArraySchema) products).getItems();

assertNotNull(items);
assertNotNull(items.get$ref());
assertTrue(items.get$ref().startsWith(SCHEMA_PREFIX),
"items.$ref should be internal ref, was: " + items.get$ref());
assertFalse(items.get$ref().contains("schemas.yaml"),
"items.$ref should not contain file path, was: " + items.get$ref());

String resolvedName = items.get$ref().substring(SCHEMA_PREFIX.length());
assertNotNull(openAPI.getComponents().getSchemas().get(resolvedName));
}

@Test
public void resolvesThreeLevelChainOfNestedRelativeFileReferences() {
OpenAPI openAPI = parse("issue-1889/root-chain.yaml");

Schema response = openAPI.getComponents().getSchemas().get("GetProductResponse");
assertNotNull(response);
Schema products = (Schema) response.getProperties().get("products");
assertTrue(products instanceof ArraySchema);
Schema productItems = ((ArraySchema) products).getItems();

assertNotNull(productItems);
assertNotNull(productItems.get$ref());
assertTrue(productItems.get$ref().startsWith(SCHEMA_PREFIX),
"productItems.$ref should be internal ref, was: " + productItems.get$ref());

String productName = productItems.get$ref().substring(SCHEMA_PREFIX.length());
Schema productSchema = openAPI.getComponents().getSchemas().get(productName);
assertNotNull(productSchema, "ProductChain schema should be in components");

Schema nestedItems = (Schema) productSchema.getProperties().get("items");
assertNotNull(nestedItems);
assertTrue(nestedItems instanceof ArraySchema);
Schema itemRef = ((ArraySchema) nestedItems).getItems();

assertNotNull(itemRef);
assertNotNull(itemRef.get$ref());
assertTrue(itemRef.get$ref().startsWith(SCHEMA_PREFIX),
"itemRef.$ref should be internal ref, was: " + itemRef.get$ref());
assertFalse(itemRef.get$ref().contains("Item.yaml"),
"itemRef.$ref should not contain file path, was: " + itemRef.get$ref());

String itemName = itemRef.get$ref().substring(SCHEMA_PREFIX.length());
assertNotNull(openAPI.getComponents().getSchemas().get(itemName), "Item schema should be in components");
}

@Test
public void resolvesNestedRelativeHeaderReferenceFromExternalResponse() {
OpenAPI openAPI = parse("issue-1889/root-header.yaml");
ApiResponse response = getProductsResponse(openAPI);
assertNotNull(response.getHeaders(), "response should have headers");

io.swagger.v3.oas.models.headers.Header rateLimitHeader = response.getHeaders().get("X-Rate-Limit");
assertNotNull(rateLimitHeader);
assertNotNull(rateLimitHeader.get$ref());
assertTrue(rateLimitHeader.get$ref().startsWith(HEADER_PREFIX),
"X-Rate-Limit $ref should be internal header ref, was: " + rateLimitHeader.get$ref());
assertFalse(rateLimitHeader.get$ref().contains("RateLimitHeader.yaml"),
"X-Rate-Limit $ref should not contain file path, was: " + rateLimitHeader.get$ref());

String headerName = rateLimitHeader.get$ref().substring(HEADER_PREFIX.length());
assertNotNull(openAPI.getComponents().getHeaders().get(headerName),
"RateLimitHeader should be in components/headers");
}

private OpenAPI parse(String location) {
ParseOptions options = new ParseOptions();
options.setResolve(true);

SwaggerParseResult result = new OpenAPIV3Parser().readLocation(location, null, options);
assertNotNull(result);
assertNotNull(result.getOpenAPI());
return result.getOpenAPI();
}

private Schema getProductItems(OpenAPI openAPI) {
Schema response = openAPI.getComponents().getSchemas().get("GetProductResponse");
assertNotNull(response);
Schema products = (Schema) response.getProperties().get("products");
assertTrue(products instanceof ArraySchema);
return ((ArraySchema) products).getItems();
}

private ApiResponse getProductsResponse(OpenAPI openAPI) {
ApiResponse pathResponse = openAPI.getPaths().get("/products").getGet().getResponses().get("200");
assertNotNull(pathResponse);
assertNotNull(pathResponse.get$ref());
String responsePrefix = "#/components/responses/";
assertTrue(pathResponse.get$ref().startsWith(responsePrefix),
"path response $ref should be internal, was: " + pathResponse.get$ref());

String responseName = pathResponse.get$ref().substring(responsePrefix.length());
ApiResponse response = openAPI.getComponents().getResponses().get(responseName);
assertNotNull(response, "response should be in components/responses");
return response;
}

private String assertInternalReference(Schema items) {
assertNotNull(items);
assertNotNull(items.get$ref());
assertTrue(items.get$ref().startsWith(SCHEMA_PREFIX));
assertFalse(items.get$ref().contains("Product.yaml"));
return items.get$ref().substring(SCHEMA_PREFIX.length());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: Rate limit header
schema:
type: integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: object
properties:
products:
type: array
items:
$ref: Product.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: object
properties:
products:
type: array
items:
$ref: ProductChain.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: object
properties:
products:
type: array
items:
$ref: './schemas.yaml#/Product'
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: object
properties:
id:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: object
properties:
id:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: object
properties:
items:
type: array
items:
$ref: Item.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Product:
type: object
properties:
id:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
description: Products response
headers:
X-Rate-Limit:
$ref: '../headers/RateLimitHeader.yaml'
content:
application/json:
schema:
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
openapi: 3.0.0
info:
title: test
version: 1.0.0

components:
schemas:
GetProductResponse:
$ref: './common/models/GetProductResponseChain.yaml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
openapi: 3.0.0
info:
title: test
version: 1.0.0

components:
schemas:
GetProductResponse:
$ref: './common/models/GetProductResponseFragmentRef.yaml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
openapi: 3.0.0
info:
title: test
version: 1.0.0

paths:
/products:
get:
responses:
'200':
$ref: './common/responses/ProductsResponse.yaml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
openapi: 3.0.0
info:
title: test
version: 1.0.0

components:
schemas:
Product:
type: object
properties:
rootOnly:
type: string
GetProductResponse:
$ref: './common/models/GetProductResponse.yaml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
openapi: 3.0.0
info:
title: test
version: 1.0.0

components:
schemas:
GetProductResponse:
$ref: './common/models/GetProductResponse.yaml'
Loading