Skip to content

Commit c84b494

Browse files
andurairajfrantuma
andurairaj
authored andcommitted
Add ref handling for callbacks
Add tests for ref fixes
1 parent 0c2cec6 commit c84b494

31 files changed

+1508
-50
lines changed

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ExternalRefProcessor.java

+72-49
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import io.swagger.v3.parser.ResolverCache;
3535
import io.swagger.v3.parser.models.RefFormat;
3636
import io.swagger.v3.parser.models.RefType;
37+
3738
import org.apache.commons.io.FilenameUtils;
3839
import org.apache.commons.lang3.StringUtils;
3940
import org.slf4j.LoggerFactory;
@@ -325,71 +326,75 @@ public PathItem processRefToExternalPathItem(String $ref, RefFormat refFormat) {
325326
cache.putRenamedRef($ref, newRef);
326327

327328
if(pathItem != null) {
328-
if(pathItem.readOperationsMap() != null) {
329-
final Map<PathItem.HttpMethod, Operation> operationMap = pathItem.readOperationsMap();
330-
for (PathItem.HttpMethod httpMethod : operationMap.keySet()) {
331-
Operation operation = operationMap.get(httpMethod);
332-
if (operation.getResponses() != null) {
333-
final Map<String, ApiResponse> responses = operation.getResponses();
334-
if (responses != null) {
335-
for (String responseCode : responses.keySet()) {
336-
ApiResponse response = responses.get(responseCode);
337-
if (response != null) {
338-
Schema schema = null;
339-
if (response.getContent() != null) {
340-
Map<String, MediaType> content = response.getContent();
341-
for (String mediaName : content.keySet()) {
342-
MediaType mediaType = content.get(mediaName);
343-
if (mediaType.getSchema() != null) {
344-
schema = mediaType.getSchema();
345-
if (schema != null) {
346-
processRefSchemaObject(mediaType.getSchema(), $ref);
347-
}
348-
if (mediaType.getExamples() != null) {
349-
processRefExamples(mediaType.getExamples(), $ref);
350-
}
329+
processPathItem(pathItem, $ref);
330+
}
331+
332+
return pathItem;
333+
}
351334

335+
private void processPathItem(PathItem pathItem, String $ref) {
336+
if(pathItem.readOperationsMap() != null) {
337+
final Map<PathItem.HttpMethod, Operation> operationMap = pathItem.readOperationsMap();
338+
for (PathItem.HttpMethod httpMethod : operationMap.keySet()) {
339+
Operation operation = operationMap.get(httpMethod);
340+
if (operation.getResponses() != null) {
341+
final Map<String, ApiResponse> responses = operation.getResponses();
342+
if (responses != null) {
343+
for (String responseCode : responses.keySet()) {
344+
ApiResponse response = responses.get(responseCode);
345+
if (response != null) {
346+
Schema schema = null;
347+
if (response.getContent() != null) {
348+
Map<String, MediaType> content = response.getContent();
349+
for (String mediaName : content.keySet()) {
350+
MediaType mediaType = content.get(mediaName);
351+
if (mediaType.getSchema() != null) {
352+
schema = mediaType.getSchema();
353+
if (schema != null) {
354+
processRefSchemaObject(mediaType.getSchema(), $ref);
355+
}
356+
if (mediaType.getExamples() != null) {
357+
processRefExamples(mediaType.getExamples(), $ref);
352358
}
359+
353360
}
354361
}
355-
if (response.getLinks() != null) {
356-
processRefLinks(response.getLinks(), $ref);
357-
}
358-
if (response.getHeaders() != null) {
359-
processRefHeaders(response.getHeaders(), $ref);
360-
}
362+
}
363+
if (response.getLinks() != null) {
364+
processRefLinks(response.getLinks(), $ref);
365+
}
366+
if (response.getHeaders() != null) {
367+
processRefHeaders(response.getHeaders(), $ref);
361368
}
362369
}
363370
}
364371
}
365-
if (operation.getRequestBody() != null) {
366-
RequestBody body = operation.getRequestBody();
367-
if (body.getContent() != null) {
368-
Schema schema;
369-
Map<String, MediaType> content = body.getContent();
370-
for (String mediaName : content.keySet()) {
371-
MediaType mediaType = content.get(mediaName);
372-
if (mediaType.getSchema() != null) {
373-
schema = mediaType.getSchema();
374-
if (schema != null) {
375-
processRefSchemaObject(mediaType.getSchema(), $ref);
376-
}
372+
}
373+
if (operation.getRequestBody() != null) {
374+
RequestBody body = operation.getRequestBody();
375+
if (body.getContent() != null) {
376+
Schema schema;
377+
Map<String, MediaType> content = body.getContent();
378+
for (String mediaName : content.keySet()) {
379+
MediaType mediaType = content.get(mediaName);
380+
if (mediaType.getSchema() != null) {
381+
schema = mediaType.getSchema();
382+
if (schema != null) {
383+
processRefSchemaObject(mediaType.getSchema(), $ref);
377384
}
378385
}
379386
}
380387
}
388+
}
381389

382-
final List<Parameter> parameters = operation.getParameters();
383-
if (parameters != null) {
384-
parameters.stream()
385-
.filter(parameter -> parameter.getSchema() != null)
386-
.forEach(parameter -> this.processRefSchemaObject(parameter.getSchema(), $ref));
387-
}
390+
final List<Parameter> parameters = operation.getParameters();
391+
if (parameters != null) {
392+
parameters.stream()
393+
.filter(parameter -> parameter.getSchema() != null)
394+
.forEach(parameter -> this.processRefSchemaObject(parameter.getSchema(), $ref));
388395
}
389396
}
390397
}
391-
392-
return pathItem;
393398
}
394399

395400
private void processDiscriminator(Discriminator d, String file) {
@@ -927,6 +932,24 @@ public String processRefToExternalCallback(String $ref, RefFormat refFormat) {
927932
processRefToExternalCallback(file + callback.get$ref(), RefFormat.RELATIVE);
928933
}
929934
}
935+
} else {
936+
for (String path : callback.keySet()) {
937+
PathItem pathItem = callback.get(path);
938+
if(pathItem != null) {
939+
if (pathItem.get$ref() != null) {
940+
RefFormat pathRefFormat = computeRefFormat(pathItem.get$ref());
941+
String path$ref = pathItem.get$ref();
942+
if (isAnExternalRefFormat(refFormat)) {
943+
pathItem = this.processRefToExternalPathItem(path$ref, pathRefFormat);
944+
} else {
945+
pathItem = cache.loadRef(pathItem.get$ref(), refFormat, PathItem.class);
946+
}
947+
callback.put(path, pathItem);
948+
} else {
949+
this.processPathItem(pathItem, $ref);
950+
}
951+
}
952+
}
930953
}
931954
}
932955

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/PathsProcessor.java

+7
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
import io.swagger.v3.parser.models.RefFormat;
2020

2121
import java.util.ArrayList;
22+
import java.util.Collection;
23+
import java.util.HashMap;
2224
import java.util.List;
2325
import java.util.Map;
26+
import java.util.Objects;
27+
import java.util.stream.Stream;
2428

2529
import static io.swagger.v3.parser.util.RefUtils.computeRefFormat;
2630
import static io.swagger.v3.parser.util.RefUtils.isAnExternalRefFormat;
@@ -155,6 +159,9 @@ protected void updateRefs(PathItem path, String pathRef) {
155159
for (String name : callbacks.keySet()) {
156160
Callback callback = callbacks.get(name);
157161
if (callback != null) {
162+
if(callback.get$ref() != null) {
163+
callback.set$ref(computeRef(callback.get$ref(), pathRef));
164+
}
158165
for(String callbackName : callback.keySet()) {
159166
PathItem pathItem = callback.get(callbackName);
160167
updateRefs(pathItem,pathRef);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package io.swagger.v3.parser.test;
2+
3+
4+
import java.util.Collection;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.Set;
8+
import java.util.stream.Collectors;
9+
10+
import org.junit.Before;
11+
import org.junit.Test;
12+
import org.testng.Assert;
13+
14+
import io.swagger.v3.oas.models.OpenAPI;
15+
import io.swagger.v3.oas.models.Operation;
16+
import io.swagger.v3.oas.models.examples.Example;
17+
import io.swagger.v3.oas.models.media.MediaType;
18+
import io.swagger.v3.oas.models.media.Schema;
19+
import io.swagger.v3.parser.OpenAPIV3Parser;
20+
import io.swagger.v3.parser.core.models.ParseOptions;
21+
22+
import static io.swagger.v3.oas.models.Components.COMPONENTS_SCHEMAS_REF;
23+
import static org.testng.Assert.assertEquals;
24+
import static org.testng.Assert.assertNotNull;
25+
import static org.testng.Assert.assertTrue;
26+
27+
28+
public class OpenAPIV3RefTest {
29+
private OpenAPI oas;
30+
@Before
31+
public void parseOASSpec() {
32+
ParseOptions options = new ParseOptions();
33+
options.setResolve(true);
34+
oas = new OpenAPIV3Parser().read("oas3-refs-test/openapi.json", null, options);
35+
}
36+
@Test
37+
public void testParameterExampleRefProcessed() {
38+
String paramName = "correlation_id";
39+
Map<String, Example> paramExamples = oas.getComponents().getParameters().get(paramName).getExamples();
40+
Assert.assertEquals(paramExamples.size(), 1, "Parameter has an example");
41+
Assert.assertTrue(paramExamples.values()
42+
.stream().allMatch(e -> e.get$ref().equalsIgnoreCase("#/components/examples/" + paramName)),
43+
"Examples are referenced");
44+
Assert.assertEquals(oas.getComponents().getExamples().get(paramName).getValue(), "7758b780aaaca",
45+
"Examples are processed");
46+
}
47+
48+
@Test
49+
public void testDiscriminatorMappingRefsUpdated() {
50+
Schema reqSchema = oas.getPaths().values().stream()
51+
.findFirst()
52+
.map(path -> path.getPost().getRequestBody().getContent().getOrDefault("application/json", null))
53+
.map(MediaType::getSchema)
54+
.orElseThrow(() -> new IllegalStateException("Path not processed!"));
55+
Collection<String> discriminator = reqSchema.getDiscriminator().getMapping().values();
56+
Set<String> allOfs = ((List<Schema>)reqSchema.getAnyOf())
57+
.stream().map(Schema::get$ref).collect(Collectors.toSet());
58+
assertTrue(allOfs.stream().allMatch(s -> s.contains(COMPONENTS_SCHEMAS_REF)),"Schema mappings are processed");
59+
assertTrue(allOfs.containsAll(discriminator),"Discriminator mappings are updated");
60+
61+
}
62+
63+
@Test
64+
public void testCallbackRef() {
65+
assertEquals(oas.getComponents().getCallbacks().size(), 1, "Callbacks processed");
66+
Operation cbOperation = oas.getComponents().getCallbacks()
67+
.get("vaccination_complete")
68+
.get("{$request.body#/callback_url}/pets/{$request.path.id}/vaccinations").getPut();
69+
assertNotNull(cbOperation);
70+
assertEquals(cbOperation.getRequestBody().getContent().get("application/json").getSchema().get$ref(),
71+
COMPONENTS_SCHEMAS_REF + "vaccination_record", "Callback Request body processed");
72+
assertEquals(cbOperation.getResponses().get("400").getContent().get("application/json").getSchema().get$ref(),
73+
COMPONENTS_SCHEMAS_REF + "error" , "Callback responses Processed");
74+
}
75+
76+
@Test
77+
public void testComposedArrayItemsRef() {
78+
Schema adoptionRequest = oas.getComponents()
79+
.getSchemas().get("adoption_request");
80+
assertEquals(adoptionRequest.getTitle(), "Adoption Request", "Processed ref Schemas");
81+
assertEquals(((Schema) adoptionRequest.getProperties().get("adopter_alias")).get$ref(),
82+
COMPONENTS_SCHEMAS_REF + "alias_array", "Processed ref added to Schemas");
83+
Schema adopterAlias = ((Schema) oas.getComponents().getSchemas().get("alias_array"));
84+
assertEquals(adopterAlias.getTitle(),"AdopterAlias", "Processed Schemas");
85+
assertEquals(adopterAlias.getItems().getAllOf().size(), 2, "Processed schemas items");
86+
}
87+
}

modules/swagger-parser-v3/src/test/resources/callbacks-issue/domain.yaml

+12-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,15 @@ components:
3131
$ref: '#/components/schemas/WebhookVerification'
3232
responses:
3333
'202':
34-
description: Your server returns this code if it accepts the message it was sent
34+
description: Your server returns this code if it accepts the message it was sent
35+
schemas:
36+
WebhookVerification:
37+
type: string
38+
parameters:
39+
x-api-key:
40+
name: Correlation-Id
41+
in: header
42+
description: description
43+
required: false
44+
schema:
45+
type: string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
{
2+
"{$request.body#/callback_url}/pets/{$request.path.id}/vaccinations": {
3+
"put": {
4+
"summary": "Vaccination record sent on completion",
5+
"description": "New vaccination record for a pet sent after vaccination order is complete.",
6+
"x-slo": {
7+
"response_time_95th_percentile": 100,
8+
"error_rate": 0.001
9+
},
10+
"x-visibility": {
11+
"extent": "INTERNAL"
12+
},
13+
"requestBody": {
14+
"required": true,
15+
"content": {
16+
"application/json": {
17+
"schema": {
18+
"$ref": "../../../schemas/vaccination_record.json"
19+
},
20+
"examples": {
21+
"vaccination": {
22+
"summary": "Vaccination with ID",
23+
"description": "Vaccination record for pet with vaccination id.",
24+
"value": {
25+
"id": "8ce1edf4-3014-4b5c-b50d-9ed697768ead",
26+
"vaccine_name": "Feline Viral Rhinotracheitis",
27+
"administration_date": "2023-12-20"
28+
}
29+
}
30+
}
31+
}
32+
}
33+
},
34+
"responses": {
35+
"204": {
36+
"description": "Your server implementation should return this HTTP status code if the data was received successfully."
37+
},
38+
"400": {
39+
"description": "Your server should return this HTTP status code if the request is malformed.",
40+
"content": {
41+
"application/json": {
42+
"schema": {
43+
"$ref": "../../../schemas/error.json"
44+
},
45+
"examples": {
46+
"bad_vaccine_name": {
47+
"summary": "Vaccine name is invalid.",
48+
"value": {
49+
"name": "INVALID_REQUEST",
50+
"debug_id": "b1d1f06c7246c",
51+
"message": "Request is not well-formed, syntactically incorrect, or violates schema.",
52+
"details": [
53+
{
54+
"field": "/vaccine_name",
55+
"location": "body",
56+
"value": "#bad-value#",
57+
"issue": "INVALID_PARAMETER_SYNTAX",
58+
"description": "The value of a field does not conform to the expected format."
59+
}
60+
]
61+
}
62+
}
63+
}
64+
}
65+
}
66+
},
67+
"404": {
68+
"description": "Your server should return this HTTP status code if the pet id is not found.",
69+
"content": {
70+
"application/json": {
71+
"schema": {
72+
"$ref": "../../../schemas/error.json"
73+
},
74+
"examples": {
75+
"generic": {
76+
"summary": "Pet 'Not Found' error.",
77+
"value": {
78+
"name": "RESOURCE_NOT_FOUND",
79+
"debug_id": "b1d1f06c7246c",
80+
"message": "The specified resource does not exist."
81+
}
82+
}
83+
}
84+
}
85+
}
86+
},
87+
"500": {
88+
"description": "Your server should return this HTTP status code if there is an internal error processing the request.",
89+
"content": {
90+
"application/json": {
91+
"schema": {
92+
"$ref": "../../../schemas/error.json"
93+
},
94+
"examples": {
95+
"generic": {
96+
"summary": "Generic internal server error.",
97+
"value": {
98+
"name": "INTERNAL_SERVER_ERROR",
99+
"debug_id": "b1d1f06c7246c",
100+
"message": "An internal server error has occurred."
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}
107+
}
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)