From 5402f1cf6edd337fa542d2c1153db8b514149b9b Mon Sep 17 00:00:00 2001 From: MicRyc Date: Fri, 18 Oct 2024 15:06:42 +0200 Subject: [PATCH] refs-#4737 add scanning for other OAS elements besides paths --- .../io/swagger/v3/core/filter/SpecFilter.java | 98 ++++++++++++-- .../v3/core/filter/SpecFilterTest.java | 38 ++++++ .../specFiles/3.1.0/issue-4737-3.1.yaml | 29 ++++ .../3.1.0/specWithReferredSchemas-3.1.yaml | 124 ++++++++++++++++++ 4 files changed, 275 insertions(+), 14 deletions(-) create mode 100644 modules/swagger-core/src/test/resources/specFiles/3.1.0/issue-4737-3.1.yaml create mode 100644 modules/swagger-core/src/test/resources/specFiles/3.1.0/specWithReferredSchemas-3.1.yaml diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java index aef8b69384..e0f15c8679 100755 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/filter/SpecFilter.java @@ -350,38 +350,98 @@ private void addPathItemSchemaRef(PathItem pathItem, Set referencedDefin Map ops = pathItem.readOperationsMap(); for (Operation op : ops.values()) { if (op.getRequestBody() != null) { - addContentSchemaRef(op.getRequestBody().getContent(), referencedDefinitions); + addRequestBodySchemaRef(op.getRequestBody(), referencedDefinitions); } if (op.getResponses() != null) { for (String keyResponses : op.getResponses().keySet()) { ApiResponse response = op.getResponses().get(keyResponses); - if (response.getHeaders() != null) { - for (String keyHeaders : response.getHeaders().keySet()) { - Header header = response.getHeaders().get(keyHeaders); - addSchemaRef(header.getSchema(), referencedDefinitions); - addContentSchemaRef(header.getContent(), referencedDefinitions); - } - } - addContentSchemaRef(response.getContent(), referencedDefinitions); + addApiResponseSchemaRef(response, referencedDefinitions); } } if (op.getParameters() != null) { for (Parameter parameter : op.getParameters()) { - addSchemaRef(parameter.getSchema(), referencedDefinitions); - addContentSchemaRef(parameter.getContent(), referencedDefinitions); + addParameterSchemaRef(parameter, referencedDefinitions); } } if (op.getCallbacks() != null) { for (String keyCallback : op.getCallbacks().keySet()) { Callback callback = op.getCallbacks().get(keyCallback); - for (PathItem callbackPathItem : callback.values()) { - addPathItemSchemaRef(callbackPathItem, referencedDefinitions); - } + addCallbackSchemaRef(callback, referencedDefinitions); } } } } + private void addApiResponseSchemaRef(ApiResponse response, Set referencedDefinitions) { + if (response.getHeaders() != null) { + for (String keyHeaders : response.getHeaders().keySet()) { + Header header = response.getHeaders().get(keyHeaders); + addHeaderSchemaRef(header, referencedDefinitions); + } + } + addContentSchemaRef(response.getContent(), referencedDefinitions); + } + + private void addRequestBodySchemaRef(RequestBody requestBody, Set referencedDefinitions) { + addContentSchemaRef(requestBody.getContent(), referencedDefinitions); + } + + private void addParameterSchemaRef(Parameter parameter, Set referencedDefinitions) { + addSchemaRef(parameter.getSchema(), referencedDefinitions); + addContentSchemaRef(parameter.getContent(), referencedDefinitions); + } + + private void addHeaderSchemaRef(Header header, Set referencedDefinitions) { + addSchemaRef(header.getSchema(), referencedDefinitions); + addContentSchemaRef(header.getContent(), referencedDefinitions); + } + + private void addCallbackSchemaRef(Callback callback, Set referencedDefinitions){ + for (PathItem callbackPathItem : callback.values()) { + addPathItemSchemaRef(callbackPathItem, referencedDefinitions); + } + } + + private void addComponentsSchemaRef(Components components, Set referencedDefinitions){ + + if (components.getResponses() != null){ + for (String resourcePath : components.getResponses().keySet()) { + ApiResponse apiResponse = components.getResponses().get(resourcePath); + addApiResponseSchemaRef(apiResponse, referencedDefinitions); + } + } + if (components.getRequestBodies() != null){ + for (String requestBody : components.getRequestBodies().keySet()) { + RequestBody requestBody1 = components.getRequestBodies().get(requestBody); + addRequestBodySchemaRef(requestBody1, referencedDefinitions); + } + } + if (components.getParameters() != null){ + for (String parameter : components.getParameters().keySet()) { + Parameter resourceParam = components.getParameters().get(parameter); + addParameterSchemaRef(resourceParam, referencedDefinitions); + } + } + if (components.getHeaders() != null){ + for (String header : components.getHeaders().keySet()) { + Header resourceHeader = components.getHeaders().get(header); + addHeaderSchemaRef(resourceHeader, referencedDefinitions); + } + } + if (components.getCallbacks() != null){ + for (String callback : components.getCallbacks().keySet()){ + Callback resourceCallback = components.getCallbacks().get(callback); + addCallbackSchemaRef(resourceCallback, referencedDefinitions); + } + } + if (components.getPathItems() != null){ + for (String resourcePath : components.getPathItems().keySet()){ + PathItem pathItem = components.getPathItems().get(resourcePath); + addPathItemSchemaRef(pathItem, referencedDefinitions); + } + } + } + protected OpenAPI removeBrokenReferenceDefinitions(OpenAPI openApi) { if (openApi == null || openApi.getComponents() == null || openApi.getComponents().getSchemas() == null) { @@ -395,6 +455,16 @@ protected OpenAPI removeBrokenReferenceDefinitions(OpenAPI openApi) { addPathItemSchemaRef(pathItem, referencedDefinitions); } } + if (openApi.getWebhooks() != null){ + for (String resourcePath : openApi.getWebhooks().keySet()) { + PathItem pathItem = openApi.getWebhooks().get(resourcePath); + addPathItemSchemaRef(pathItem, referencedDefinitions); + } + } + if (openApi.getComponents() != null){ + Components components = openApi.getComponents(); + addComponentsSchemaRef(components, referencedDefinitions); + } referencedDefinitions.addAll(resolveAllNestedRefs(referencedDefinitions, referencedDefinitions, openApi)); openApi.getComponents() diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java index f04c9b56e7..355d4722e2 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/filter/SpecFilterTest.java @@ -16,6 +16,8 @@ import io.swagger.v3.core.matchers.SerializationMatchers; import io.swagger.v3.core.util.Json; import io.swagger.v3.core.util.Json31; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.core.util.Yaml31; import io.swagger.v3.core.util.ResourceUtils; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; @@ -46,6 +48,8 @@ public class SpecFilterTest { private static final String RESOURCE_RECURSIVE_MODELS = "specFiles/recursivemodels.json"; private static final String RESOURCE_PATH = "specFiles/petstore-3.0-v2.json"; private static final String RESOURCE_PATH_3303 = "specFiles/petstore-3.0-v2-ticket-3303.json"; + private static final String RESOURCE_WITH_REF_DEFINITION_4737 = "specFiles/3.1.0/issue-4737-3.1.yaml"; + private static final String RESOURCE_WITH_REFERRED_DEFINITIONS= "specFiles/3.1.0/specWithReferredSchemas-3.1.yaml"; private static final String RESOURCE_PATH_LIST = "specFiles/3.1.0/list-3.1.json"; private static final String RESOURCE_PATH_COMPOSED_SCHEMA = "specFiles/3.1.0/composed-schema-3.1.json"; private static final String RESOURCE_REFERRED_SCHEMAS = "specFiles/petstore-3.0-referred-schemas.json"; @@ -450,6 +454,30 @@ public void filterWithNullDefinitions() throws IOException { assertNotNull(filtered); } + @Test(description = "RemoveUnreferencedDefinitionsFilter should not remove schema definition if ref used in Webhook") + public void testTicket4737() throws IOException { + final OpenAPI openAPI = getOpenAPIYaml31(RESOURCE_WITH_REF_DEFINITION_4737); + final RemoveUnreferencedDefinitionsFilter remover = new RemoveUnreferencedDefinitionsFilter(); + final OpenAPI filtered = new SpecFilter().filter(openAPI, remover, null, null, null); + assertNotNull(filtered.getComponents().getSchemas().get("RequestDto")); + } + + @Test + public void shouldNotRemoveUsedDefinitions() throws IOException { + final OpenAPI openAPI = getOpenAPIYaml31(RESOURCE_WITH_REFERRED_DEFINITIONS); + final RemoveUnreferencedDefinitionsFilter remover = new RemoveUnreferencedDefinitionsFilter(); + final OpenAPI filtered = new SpecFilter().filter(openAPI, remover, null, null, null); + assertNotNull(filtered.getComponents().getSchemas().get("ResponseDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("WebhookResponseDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("WebhookOperationDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("RequestBodyDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("ParameterDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("HeaderDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("CallbackDefinition")); + assertNotNull(filtered.getComponents().getSchemas().get("PathItemDefinition")); + assertNull(filtered.getComponents().getSchemas().get("UnusedDefinition")); + } + private Set getTagNames(OpenAPI openAPI) { Set result = new HashSet<>(); if (openAPI.getTags() != null) { @@ -469,4 +497,14 @@ private OpenAPI getOpenAPI31(String path) throws IOException { final String json = ResourceUtils.loadClassResource(getClass(), path); return Json31.mapper().readValue(json, OpenAPI.class); } + + private OpenAPI getOpenAPIYaml(String path) throws IOException { + final String yaml = ResourceUtils.loadClassResource(getClass(), path); + return Yaml.mapper().readValue(yaml, OpenAPI.class); + } + + private OpenAPI getOpenAPIYaml31(String path) throws IOException { + final String yaml = ResourceUtils.loadClassResource(getClass(), path); + return Yaml31.mapper().readValue(yaml, OpenAPI.class); + } } diff --git a/modules/swagger-core/src/test/resources/specFiles/3.1.0/issue-4737-3.1.yaml b/modules/swagger-core/src/test/resources/specFiles/3.1.0/issue-4737-3.1.yaml new file mode 100644 index 0000000000..1e1615a4f2 --- /dev/null +++ b/modules/swagger-core/src/test/resources/specFiles/3.1.0/issue-4737-3.1.yaml @@ -0,0 +1,29 @@ +openapi: 3.1.0 +info: + title: OpenAPI definition + version: v0 +servers: + - url: http://localhost + description: Generated server url +paths: {} +components: + schemas: + RequestDto: + properties: + personalNumber: + type: string +webhooks: + newPet: + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: '#/components/schemas/RequestDto' + description: Webhook Pet + responses: + '200': + description: >- + Return a 200 status to indicate that the data was received + successfully \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/specFiles/3.1.0/specWithReferredSchemas-3.1.yaml b/modules/swagger-core/src/test/resources/specFiles/3.1.0/specWithReferredSchemas-3.1.yaml new file mode 100644 index 0000000000..bfc9e05cf4 --- /dev/null +++ b/modules/swagger-core/src/test/resources/specFiles/3.1.0/specWithReferredSchemas-3.1.yaml @@ -0,0 +1,124 @@ +openapi: 3.1.0 +info: + title: OpenAPI definition + version: v0 +servers: + - url: http://localhost + description: Generated server url +paths: + /users: + get: + summary: Get list of all users + operationId: usersList + responses: + 200: + description: OK + content: + application/json: + schema: + type: string + maxLength: 100 + minLength: 1 +components: + schemas: + ResponseDefinition: + properties: + personalNumber: + type: string + WebhookResponseDefinition: + properties: + personalNumber: + type: integer + WebhookOperationDefinition: + properties: + personalNumber: + type: number + RequestBodyDefinition: + properties: + personalNumber: + type: string + ParameterDefinition: + properties: + parameterName: + type: string + HeaderDefinition: + type: string + description: header ref + CallbackDefinition: + description: callback ref + type: string + PathItemDefinition: + description: pathItem ref + type: string + UnusedDefinition: + description: unused definition + type: string + minLength: 1 + maxLength: 100 + responses: + OK: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ResponseDefinition" + requestBodies: + requestBody1: + description: request body + content: + application/json: + schema: + $ref: "#/components/schemas/RequestBodyDefinition" + parameters: + parameter1: + description: request body + name: parameter + in: query + content: + application/json: + schema: + $ref: "#/components/schemas/ParameterDefinition" + headers: + header1: + content: + application/json: + schema: + $ref: "#/components/schemas/HeaderDefinition" + callbacks: + myEvent: # Event name + "{$request.body#/callbackUrl}": + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CallbackDefinition" + pathItems: + pathItem1: + get: + summary: Get list of all users + operationId: usersList + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PathItemDefinition" +webhooks: + newPet: + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookOperationDefinition' + description: Webhook Pet + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponseDefinition'