Skip to content

Commit dc8785e

Browse files
authored
Merge pull request #4762 from swagger-api/Ticket-4737-removeBrokenReferences
refs-#4737 add scanning for other OAS elements besides paths
2 parents 7d5ec61 + 5402f1c commit dc8785e

File tree

4 files changed

+275
-14
lines changed

4 files changed

+275
-14
lines changed

modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java

+84-14
Original file line numberDiff line numberDiff line change
@@ -350,38 +350,98 @@ private void addPathItemSchemaRef(PathItem pathItem, Set<String> referencedDefin
350350
Map<PathItem.HttpMethod, Operation> ops = pathItem.readOperationsMap();
351351
for (Operation op : ops.values()) {
352352
if (op.getRequestBody() != null) {
353-
addContentSchemaRef(op.getRequestBody().getContent(), referencedDefinitions);
353+
addRequestBodySchemaRef(op.getRequestBody(), referencedDefinitions);
354354
}
355355
if (op.getResponses() != null) {
356356
for (String keyResponses : op.getResponses().keySet()) {
357357
ApiResponse response = op.getResponses().get(keyResponses);
358-
if (response.getHeaders() != null) {
359-
for (String keyHeaders : response.getHeaders().keySet()) {
360-
Header header = response.getHeaders().get(keyHeaders);
361-
addSchemaRef(header.getSchema(), referencedDefinitions);
362-
addContentSchemaRef(header.getContent(), referencedDefinitions);
363-
}
364-
}
365-
addContentSchemaRef(response.getContent(), referencedDefinitions);
358+
addApiResponseSchemaRef(response, referencedDefinitions);
366359
}
367360
}
368361
if (op.getParameters() != null) {
369362
for (Parameter parameter : op.getParameters()) {
370-
addSchemaRef(parameter.getSchema(), referencedDefinitions);
371-
addContentSchemaRef(parameter.getContent(), referencedDefinitions);
363+
addParameterSchemaRef(parameter, referencedDefinitions);
372364
}
373365
}
374366
if (op.getCallbacks() != null) {
375367
for (String keyCallback : op.getCallbacks().keySet()) {
376368
Callback callback = op.getCallbacks().get(keyCallback);
377-
for (PathItem callbackPathItem : callback.values()) {
378-
addPathItemSchemaRef(callbackPathItem, referencedDefinitions);
379-
}
369+
addCallbackSchemaRef(callback, referencedDefinitions);
380370
}
381371
}
382372
}
383373
}
384374

375+
private void addApiResponseSchemaRef(ApiResponse response, Set<String> referencedDefinitions) {
376+
if (response.getHeaders() != null) {
377+
for (String keyHeaders : response.getHeaders().keySet()) {
378+
Header header = response.getHeaders().get(keyHeaders);
379+
addHeaderSchemaRef(header, referencedDefinitions);
380+
}
381+
}
382+
addContentSchemaRef(response.getContent(), referencedDefinitions);
383+
}
384+
385+
private void addRequestBodySchemaRef(RequestBody requestBody, Set<String> referencedDefinitions) {
386+
addContentSchemaRef(requestBody.getContent(), referencedDefinitions);
387+
}
388+
389+
private void addParameterSchemaRef(Parameter parameter, Set<String> referencedDefinitions) {
390+
addSchemaRef(parameter.getSchema(), referencedDefinitions);
391+
addContentSchemaRef(parameter.getContent(), referencedDefinitions);
392+
}
393+
394+
private void addHeaderSchemaRef(Header header, Set<String> referencedDefinitions) {
395+
addSchemaRef(header.getSchema(), referencedDefinitions);
396+
addContentSchemaRef(header.getContent(), referencedDefinitions);
397+
}
398+
399+
private void addCallbackSchemaRef(Callback callback, Set<String> referencedDefinitions){
400+
for (PathItem callbackPathItem : callback.values()) {
401+
addPathItemSchemaRef(callbackPathItem, referencedDefinitions);
402+
}
403+
}
404+
405+
private void addComponentsSchemaRef(Components components, Set<String> referencedDefinitions){
406+
407+
if (components.getResponses() != null){
408+
for (String resourcePath : components.getResponses().keySet()) {
409+
ApiResponse apiResponse = components.getResponses().get(resourcePath);
410+
addApiResponseSchemaRef(apiResponse, referencedDefinitions);
411+
}
412+
}
413+
if (components.getRequestBodies() != null){
414+
for (String requestBody : components.getRequestBodies().keySet()) {
415+
RequestBody requestBody1 = components.getRequestBodies().get(requestBody);
416+
addRequestBodySchemaRef(requestBody1, referencedDefinitions);
417+
}
418+
}
419+
if (components.getParameters() != null){
420+
for (String parameter : components.getParameters().keySet()) {
421+
Parameter resourceParam = components.getParameters().get(parameter);
422+
addParameterSchemaRef(resourceParam, referencedDefinitions);
423+
}
424+
}
425+
if (components.getHeaders() != null){
426+
for (String header : components.getHeaders().keySet()) {
427+
Header resourceHeader = components.getHeaders().get(header);
428+
addHeaderSchemaRef(resourceHeader, referencedDefinitions);
429+
}
430+
}
431+
if (components.getCallbacks() != null){
432+
for (String callback : components.getCallbacks().keySet()){
433+
Callback resourceCallback = components.getCallbacks().get(callback);
434+
addCallbackSchemaRef(resourceCallback, referencedDefinitions);
435+
}
436+
}
437+
if (components.getPathItems() != null){
438+
for (String resourcePath : components.getPathItems().keySet()){
439+
PathItem pathItem = components.getPathItems().get(resourcePath);
440+
addPathItemSchemaRef(pathItem, referencedDefinitions);
441+
}
442+
}
443+
}
444+
385445
protected OpenAPI removeBrokenReferenceDefinitions(OpenAPI openApi) {
386446

387447
if (openApi == null || openApi.getComponents() == null || openApi.getComponents().getSchemas() == null) {
@@ -395,6 +455,16 @@ protected OpenAPI removeBrokenReferenceDefinitions(OpenAPI openApi) {
395455
addPathItemSchemaRef(pathItem, referencedDefinitions);
396456
}
397457
}
458+
if (openApi.getWebhooks() != null){
459+
for (String resourcePath : openApi.getWebhooks().keySet()) {
460+
PathItem pathItem = openApi.getWebhooks().get(resourcePath);
461+
addPathItemSchemaRef(pathItem, referencedDefinitions);
462+
}
463+
}
464+
if (openApi.getComponents() != null){
465+
Components components = openApi.getComponents();
466+
addComponentsSchemaRef(components, referencedDefinitions);
467+
}
398468

399469
referencedDefinitions.addAll(resolveAllNestedRefs(referencedDefinitions, referencedDefinitions, openApi));
400470
openApi.getComponents()

modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java

+38
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import io.swagger.v3.core.matchers.SerializationMatchers;
1717
import io.swagger.v3.core.util.Json;
1818
import io.swagger.v3.core.util.Json31;
19+
import io.swagger.v3.core.util.Yaml;
20+
import io.swagger.v3.core.util.Yaml31;
1921
import io.swagger.v3.core.util.ResourceUtils;
2022
import io.swagger.v3.oas.models.Components;
2123
import io.swagger.v3.oas.models.OpenAPI;
@@ -46,6 +48,8 @@ public class SpecFilterTest {
4648
private static final String RESOURCE_RECURSIVE_MODELS = "specFiles/recursivemodels.json";
4749
private static final String RESOURCE_PATH = "specFiles/petstore-3.0-v2.json";
4850
private static final String RESOURCE_PATH_3303 = "specFiles/petstore-3.0-v2-ticket-3303.json";
51+
private static final String RESOURCE_WITH_REF_DEFINITION_4737 = "specFiles/3.1.0/issue-4737-3.1.yaml";
52+
private static final String RESOURCE_WITH_REFERRED_DEFINITIONS= "specFiles/3.1.0/specWithReferredSchemas-3.1.yaml";
4953
private static final String RESOURCE_PATH_LIST = "specFiles/3.1.0/list-3.1.json";
5054
private static final String RESOURCE_PATH_COMPOSED_SCHEMA = "specFiles/3.1.0/composed-schema-3.1.json";
5155
private static final String RESOURCE_REFERRED_SCHEMAS = "specFiles/petstore-3.0-referred-schemas.json";
@@ -450,6 +454,30 @@ public void filterWithNullDefinitions() throws IOException {
450454
assertNotNull(filtered);
451455
}
452456

457+
@Test(description = "RemoveUnreferencedDefinitionsFilter should not remove schema definition if ref used in Webhook")
458+
public void testTicket4737() throws IOException {
459+
final OpenAPI openAPI = getOpenAPIYaml31(RESOURCE_WITH_REF_DEFINITION_4737);
460+
final RemoveUnreferencedDefinitionsFilter remover = new RemoveUnreferencedDefinitionsFilter();
461+
final OpenAPI filtered = new SpecFilter().filter(openAPI, remover, null, null, null);
462+
assertNotNull(filtered.getComponents().getSchemas().get("RequestDto"));
463+
}
464+
465+
@Test
466+
public void shouldNotRemoveUsedDefinitions() throws IOException {
467+
final OpenAPI openAPI = getOpenAPIYaml31(RESOURCE_WITH_REFERRED_DEFINITIONS);
468+
final RemoveUnreferencedDefinitionsFilter remover = new RemoveUnreferencedDefinitionsFilter();
469+
final OpenAPI filtered = new SpecFilter().filter(openAPI, remover, null, null, null);
470+
assertNotNull(filtered.getComponents().getSchemas().get("ResponseDefinition"));
471+
assertNotNull(filtered.getComponents().getSchemas().get("WebhookResponseDefinition"));
472+
assertNotNull(filtered.getComponents().getSchemas().get("WebhookOperationDefinition"));
473+
assertNotNull(filtered.getComponents().getSchemas().get("RequestBodyDefinition"));
474+
assertNotNull(filtered.getComponents().getSchemas().get("ParameterDefinition"));
475+
assertNotNull(filtered.getComponents().getSchemas().get("HeaderDefinition"));
476+
assertNotNull(filtered.getComponents().getSchemas().get("CallbackDefinition"));
477+
assertNotNull(filtered.getComponents().getSchemas().get("PathItemDefinition"));
478+
assertNull(filtered.getComponents().getSchemas().get("UnusedDefinition"));
479+
}
480+
453481
private Set getTagNames(OpenAPI openAPI) {
454482
Set<String> result = new HashSet<>();
455483
if (openAPI.getTags() != null) {
@@ -469,4 +497,14 @@ private OpenAPI getOpenAPI31(String path) throws IOException {
469497
final String json = ResourceUtils.loadClassResource(getClass(), path);
470498
return Json31.mapper().readValue(json, OpenAPI.class);
471499
}
500+
501+
private OpenAPI getOpenAPIYaml(String path) throws IOException {
502+
final String yaml = ResourceUtils.loadClassResource(getClass(), path);
503+
return Yaml.mapper().readValue(yaml, OpenAPI.class);
504+
}
505+
506+
private OpenAPI getOpenAPIYaml31(String path) throws IOException {
507+
final String yaml = ResourceUtils.loadClassResource(getClass(), path);
508+
return Yaml31.mapper().readValue(yaml, OpenAPI.class);
509+
}
472510
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
openapi: 3.1.0
2+
info:
3+
title: OpenAPI definition
4+
version: v0
5+
servers:
6+
- url: http://localhost
7+
description: Generated server url
8+
paths: {}
9+
components:
10+
schemas:
11+
RequestDto:
12+
properties:
13+
personalNumber:
14+
type: string
15+
webhooks:
16+
newPet:
17+
post:
18+
requestBody:
19+
description: Information about a new pet in the system
20+
content:
21+
application/json:
22+
schema:
23+
$ref: '#/components/schemas/RequestDto'
24+
description: Webhook Pet
25+
responses:
26+
'200':
27+
description: >-
28+
Return a 200 status to indicate that the data was received
29+
successfully
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
openapi: 3.1.0
2+
info:
3+
title: OpenAPI definition
4+
version: v0
5+
servers:
6+
- url: http://localhost
7+
description: Generated server url
8+
paths:
9+
/users:
10+
get:
11+
summary: Get list of all users
12+
operationId: usersList
13+
responses:
14+
200:
15+
description: OK
16+
content:
17+
application/json:
18+
schema:
19+
type: string
20+
maxLength: 100
21+
minLength: 1
22+
components:
23+
schemas:
24+
ResponseDefinition:
25+
properties:
26+
personalNumber:
27+
type: string
28+
WebhookResponseDefinition:
29+
properties:
30+
personalNumber:
31+
type: integer
32+
WebhookOperationDefinition:
33+
properties:
34+
personalNumber:
35+
type: number
36+
RequestBodyDefinition:
37+
properties:
38+
personalNumber:
39+
type: string
40+
ParameterDefinition:
41+
properties:
42+
parameterName:
43+
type: string
44+
HeaderDefinition:
45+
type: string
46+
description: header ref
47+
CallbackDefinition:
48+
description: callback ref
49+
type: string
50+
PathItemDefinition:
51+
description: pathItem ref
52+
type: string
53+
UnusedDefinition:
54+
description: unused definition
55+
type: string
56+
minLength: 1
57+
maxLength: 100
58+
responses:
59+
OK:
60+
description: OK
61+
content:
62+
application/json:
63+
schema:
64+
$ref: "#/components/schemas/ResponseDefinition"
65+
requestBodies:
66+
requestBody1:
67+
description: request body
68+
content:
69+
application/json:
70+
schema:
71+
$ref: "#/components/schemas/RequestBodyDefinition"
72+
parameters:
73+
parameter1:
74+
description: request body
75+
name: parameter
76+
in: query
77+
content:
78+
application/json:
79+
schema:
80+
$ref: "#/components/schemas/ParameterDefinition"
81+
headers:
82+
header1:
83+
content:
84+
application/json:
85+
schema:
86+
$ref: "#/components/schemas/HeaderDefinition"
87+
callbacks:
88+
myEvent: # Event name
89+
"{$request.body#/callbackUrl}":
90+
post:
91+
requestBody:
92+
required: true
93+
content:
94+
application/json:
95+
schema:
96+
$ref: "#/components/schemas/CallbackDefinition"
97+
pathItems:
98+
pathItem1:
99+
get:
100+
summary: Get list of all users
101+
operationId: usersList
102+
responses:
103+
200:
104+
description: OK
105+
content:
106+
application/json:
107+
schema:
108+
$ref: "#/components/schemas/PathItemDefinition"
109+
webhooks:
110+
newPet:
111+
post:
112+
requestBody:
113+
description: Information about a new pet in the system
114+
content:
115+
application/json:
116+
schema:
117+
$ref: '#/components/schemas/WebhookOperationDefinition'
118+
description: Webhook Pet
119+
responses:
120+
'200':
121+
content:
122+
application/json:
123+
schema:
124+
$ref: '#/components/schemas/WebhookResponseDefinition'

0 commit comments

Comments
 (0)