Skip to content

Commit

Permalink
Add ref handling for callbacks
Browse files Browse the repository at this point in the history
Add tests for ref fixes
  • Loading branch information
andurairaj authored and anthochristen committed May 29, 2024
1 parent 3d55475 commit 04d7ef2
Show file tree
Hide file tree
Showing 31 changed files with 1,508 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.swagger.v3.parser.ResolverCache;
import io.swagger.v3.parser.models.RefFormat;
import io.swagger.v3.parser.models.RefType;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -325,71 +326,75 @@ public PathItem processRefToExternalPathItem(String $ref, RefFormat refFormat) {
cache.putRenamedRef($ref, newRef);

if(pathItem != null) {
if(pathItem.readOperationsMap() != null) {
final Map<PathItem.HttpMethod, Operation> operationMap = pathItem.readOperationsMap();
for (PathItem.HttpMethod httpMethod : operationMap.keySet()) {
Operation operation = operationMap.get(httpMethod);
if (operation.getResponses() != null) {
final Map<String, ApiResponse> responses = operation.getResponses();
if (responses != null) {
for (String responseCode : responses.keySet()) {
ApiResponse response = responses.get(responseCode);
if (response != null) {
Schema schema = null;
if (response.getContent() != null) {
Map<String, MediaType> content = response.getContent();
for (String mediaName : content.keySet()) {
MediaType mediaType = content.get(mediaName);
if (mediaType.getSchema() != null) {
schema = mediaType.getSchema();
if (schema != null) {
processRefSchemaObject(mediaType.getSchema(), $ref);
}
if (mediaType.getExamples() != null) {
processRefExamples(mediaType.getExamples(), $ref);
}
processPathItem(pathItem, $ref);
}

return pathItem;
}

private void processPathItem(PathItem pathItem, String $ref) {
if(pathItem.readOperationsMap() != null) {
final Map<PathItem.HttpMethod, Operation> operationMap = pathItem.readOperationsMap();
for (PathItem.HttpMethod httpMethod : operationMap.keySet()) {
Operation operation = operationMap.get(httpMethod);
if (operation.getResponses() != null) {
final Map<String, ApiResponse> responses = operation.getResponses();
if (responses != null) {
for (String responseCode : responses.keySet()) {
ApiResponse response = responses.get(responseCode);
if (response != null) {
Schema schema = null;
if (response.getContent() != null) {
Map<String, MediaType> content = response.getContent();
for (String mediaName : content.keySet()) {
MediaType mediaType = content.get(mediaName);
if (mediaType.getSchema() != null) {
schema = mediaType.getSchema();
if (schema != null) {
processRefSchemaObject(mediaType.getSchema(), $ref);
}
if (mediaType.getExamples() != null) {
processRefExamples(mediaType.getExamples(), $ref);
}

}
}
if (response.getLinks() != null) {
processRefLinks(response.getLinks(), $ref);
}
if (response.getHeaders() != null) {
processRefHeaders(response.getHeaders(), $ref);
}
}
if (response.getLinks() != null) {
processRefLinks(response.getLinks(), $ref);
}
if (response.getHeaders() != null) {
processRefHeaders(response.getHeaders(), $ref);
}
}
}
}
if (operation.getRequestBody() != null) {
RequestBody body = operation.getRequestBody();
if (body.getContent() != null) {
Schema schema;
Map<String, MediaType> content = body.getContent();
for (String mediaName : content.keySet()) {
MediaType mediaType = content.get(mediaName);
if (mediaType.getSchema() != null) {
schema = mediaType.getSchema();
if (schema != null) {
processRefSchemaObject(mediaType.getSchema(), $ref);
}
}
if (operation.getRequestBody() != null) {
RequestBody body = operation.getRequestBody();
if (body.getContent() != null) {
Schema schema;
Map<String, MediaType> content = body.getContent();
for (String mediaName : content.keySet()) {
MediaType mediaType = content.get(mediaName);
if (mediaType.getSchema() != null) {
schema = mediaType.getSchema();
if (schema != null) {
processRefSchemaObject(mediaType.getSchema(), $ref);
}
}
}
}
}

final List<Parameter> parameters = operation.getParameters();
if (parameters != null) {
parameters.stream()
.filter(parameter -> parameter.getSchema() != null)
.forEach(parameter -> this.processRefSchemaObject(parameter.getSchema(), $ref));
}
final List<Parameter> parameters = operation.getParameters();
if (parameters != null) {
parameters.stream()
.filter(parameter -> parameter.getSchema() != null)
.forEach(parameter -> this.processRefSchemaObject(parameter.getSchema(), $ref));
}
}
}

return pathItem;
}

private void processDiscriminator(Discriminator d, String file) {
Expand Down Expand Up @@ -927,6 +932,24 @@ public String processRefToExternalCallback(String $ref, RefFormat refFormat) {
processRefToExternalCallback(file + callback.get$ref(), RefFormat.RELATIVE);
}
}
} else {
for (String path : callback.keySet()) {
PathItem pathItem = callback.get(path);
if(pathItem != null) {
if (pathItem.get$ref() != null) {
RefFormat pathRefFormat = computeRefFormat(pathItem.get$ref());
String path$ref = pathItem.get$ref();
if (isAnExternalRefFormat(refFormat)) {
pathItem = this.processRefToExternalPathItem(path$ref, pathRefFormat);
} else {
pathItem = cache.loadRef(pathItem.get$ref(), refFormat, PathItem.class);
}
callback.put(path, pathItem);
} else {
this.processPathItem(pathItem, $ref);
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
import io.swagger.v3.parser.models.RefFormat;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

import static io.swagger.v3.parser.util.RefUtils.computeRefFormat;
import static io.swagger.v3.parser.util.RefUtils.isAnExternalRefFormat;
Expand Down Expand Up @@ -155,6 +159,9 @@ protected void updateRefs(PathItem path, String pathRef) {
for (String name : callbacks.keySet()) {
Callback callback = callbacks.get(name);
if (callback != null) {
if(callback.get$ref() != null) {
callback.set$ref(computeRef(callback.get$ref(), pathRef));
}
for(String callbackName : callback.keySet()) {
PathItem pathItem = callback.get(callbackName);
updateRefs(pathItem,pathRef);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.swagger.v3.parser.test;


import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.junit.Before;
import org.junit.Test;
import org.testng.Assert;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;

import static io.swagger.v3.oas.models.Components.COMPONENTS_SCHEMAS_REF;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;


public class OpenAPIV3RefTest {
private OpenAPI oas;
@Before
public void parseOASSpec() {
ParseOptions options = new ParseOptions();
options.setResolve(true);
oas = new OpenAPIV3Parser().read("oas3-refs-test/openapi.json", null, options);
}
@Test
public void testParameterExampleRefProcessed() {
String paramName = "correlation_id";
Map<String, Example> paramExamples = oas.getComponents().getParameters().get(paramName).getExamples();
Assert.assertEquals(paramExamples.size(), 1, "Parameter has an example");
Assert.assertTrue(paramExamples.values()
.stream().allMatch(e -> e.get$ref().equalsIgnoreCase("#/components/examples/" + paramName)),
"Examples are referenced");
Assert.assertEquals(oas.getComponents().getExamples().get(paramName).getValue(), "7758b780aaaca",
"Examples are processed");
}

@Test
public void testDiscriminatorMappingRefsUpdated() {
Schema reqSchema = oas.getPaths().values().stream()
.findFirst()
.map(path -> path.getPost().getRequestBody().getContent().getOrDefault("application/json", null))
.map(MediaType::getSchema)
.orElseThrow(() -> new IllegalStateException("Path not processed!"));
Collection<String> discriminator = reqSchema.getDiscriminator().getMapping().values();
Set<String> allOfs = ((List<Schema>)reqSchema.getAnyOf())
.stream().map(Schema::get$ref).collect(Collectors.toSet());
assertTrue(allOfs.stream().allMatch(s -> s.contains(COMPONENTS_SCHEMAS_REF)),"Schema mappings are processed");
assertTrue(allOfs.containsAll(discriminator),"Discriminator mappings are updated");

}

@Test
public void testCallbackRef() {
assertEquals(oas.getComponents().getCallbacks().size(), 1, "Callbacks processed");
Operation cbOperation = oas.getComponents().getCallbacks()
.get("vaccination_complete")
.get("{$request.body#/callback_url}/pets/{$request.path.id}/vaccinations").getPut();
assertNotNull(cbOperation);
assertEquals(cbOperation.getRequestBody().getContent().get("application/json").getSchema().get$ref(),
COMPONENTS_SCHEMAS_REF + "vaccination_record", "Callback Request body processed");
assertEquals(cbOperation.getResponses().get("400").getContent().get("application/json").getSchema().get$ref(),
COMPONENTS_SCHEMAS_REF + "error" , "Callback responses Processed");
}

@Test
public void testComposedArrayItemsRef() {
Schema adoptionRequest = oas.getComponents()
.getSchemas().get("adoption_request");
assertEquals(adoptionRequest.getTitle(), "Adoption Request", "Processed ref Schemas");
assertEquals(((Schema) adoptionRequest.getProperties().get("adopter_alias")).get$ref(),
COMPONENTS_SCHEMAS_REF + "alias_array", "Processed ref added to Schemas");
Schema adopterAlias = ((Schema) oas.getComponents().getSchemas().get("alias_array"));
assertEquals(adopterAlias.getTitle(),"AdopterAlias", "Processed Schemas");
assertEquals(adopterAlias.getItems().getAllOf().size(), 2, "Processed schemas items");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,15 @@ components:
$ref: '#/components/schemas/WebhookVerification'
responses:
'202':
description: Your server returns this code if it accepts the message it was sent
description: Your server returns this code if it accepts the message it was sent
schemas:
WebhookVerification:
type: string
parameters:
x-api-key:
name: Correlation-Id
in: header
description: description
required: false
schema:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"{$request.body#/callback_url}/pets/{$request.path.id}/vaccinations": {
"put": {
"summary": "Vaccination record sent on completion",
"description": "New vaccination record for a pet sent after vaccination order is complete.",
"x-slo": {
"response_time_95th_percentile": 100,
"error_rate": 0.001
},
"x-visibility": {
"extent": "INTERNAL"
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "../../../schemas/vaccination_record.json"
},
"examples": {
"vaccination": {
"summary": "Vaccination with ID",
"description": "Vaccination record for pet with vaccination id.",
"value": {
"id": "8ce1edf4-3014-4b5c-b50d-9ed697768ead",
"vaccine_name": "Feline Viral Rhinotracheitis",
"administration_date": "2023-12-20"
}
}
}
}
}
},
"responses": {
"204": {
"description": "Your server implementation should return this HTTP status code if the data was received successfully."
},
"400": {
"description": "Your server should return this HTTP status code if the request is malformed.",
"content": {
"application/json": {
"schema": {
"$ref": "../../../schemas/error.json"
},
"examples": {
"bad_vaccine_name": {
"summary": "Vaccine name is invalid.",
"value": {
"name": "INVALID_REQUEST",
"debug_id": "b1d1f06c7246c",
"message": "Request is not well-formed, syntactically incorrect, or violates schema.",
"details": [
{
"field": "/vaccine_name",
"location": "body",
"value": "#bad-value#",
"issue": "INVALID_PARAMETER_SYNTAX",
"description": "The value of a field does not conform to the expected format."
}
]
}
}
}
}
}
},
"404": {
"description": "Your server should return this HTTP status code if the pet id is not found.",
"content": {
"application/json": {
"schema": {
"$ref": "../../../schemas/error.json"
},
"examples": {
"generic": {
"summary": "Pet 'Not Found' error.",
"value": {
"name": "RESOURCE_NOT_FOUND",
"debug_id": "b1d1f06c7246c",
"message": "The specified resource does not exist."
}
}
}
}
}
},
"500": {
"description": "Your server should return this HTTP status code if there is an internal error processing the request.",
"content": {
"application/json": {
"schema": {
"$ref": "../../../schemas/error.json"
},
"examples": {
"generic": {
"summary": "Generic internal server error.",
"value": {
"name": "INTERNAL_SERVER_ERROR",
"debug_id": "b1d1f06c7246c",
"message": "An internal server error has occurred."
}
}
}
}
}
}
}
}
}
}
Loading

0 comments on commit 04d7ef2

Please sign in to comment.