Skip to content

Commit

Permalink
Remove duplicate elements from final OpenApi object. (#1212)
Browse files Browse the repository at this point in the history
Fixed #1204
  • Loading branch information
altro3 authored Sep 20, 2023
1 parent 294897a commit 79b0dc2
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,10 @@ public static Optional<Map<String, Object>> resolveExtensions(JsonNode jn) {
*/
public static SecurityRequirement mapToSecurityRequirement(AnnotationValue<io.swagger.v3.oas.annotations.security.SecurityRequirement> r) {
String name = r.getRequiredValue("name", String.class);
List<String> scopes = r.get("scopes", String[].class).map(Arrays::asList).orElse(Collections.emptyList());
SecurityRequirement securityRequirement = new SecurityRequirement();
List<String> scopes = r.get("scopes", String[].class)
.map(Arrays::asList)
.orElse(Collections.emptyList());
var securityRequirement = new SecurityRequirement();
securityRequirement.addList(name, scopes);
return securityRequirement;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.Set;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -83,15 +84,6 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;

import static io.micronaut.openapi.visitor.OpenApiConfigProperty.ALL;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_ADDITIONAL_FILES;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_CONTEXT_SERVER_PATH;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_FILENAME;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_JSON_FORMAT;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_PROPERTY_NAMING_STRATEGY;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_TARGET_FILE;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_VIEWS_DEST_DIR;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_VIEWS_SPEC;
import static io.micronaut.openapi.visitor.ConfigUtils.endpointsConfiguration;
import static io.micronaut.openapi.visitor.ConfigUtils.getConfigProperty;
import static io.micronaut.openapi.visitor.ConfigUtils.getEnv;
Expand All @@ -106,6 +98,15 @@
import static io.micronaut.openapi.visitor.FileUtils.EXT_YML;
import static io.micronaut.openapi.visitor.FileUtils.createDirectories;
import static io.micronaut.openapi.visitor.FileUtils.resolve;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.ALL;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_ADDITIONAL_FILES;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_CONTEXT_SERVER_PATH;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_FILENAME;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_JSON_FORMAT;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_PROPERTY_NAMING_STRATEGY;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_TARGET_FILE;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_VIEWS_DEST_DIR;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_VIEWS_SPEC;
import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SIMPLE_SCHEMA;
import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT;
import static io.micronaut.openapi.visitor.SchemaUtils.getOperationOnPathItem;
Expand Down Expand Up @@ -772,12 +773,117 @@ private OpenAPI postProcessOpenApi(OpenAPI openApi, VisitorContext context) {
}

removeEmtpyComponents(openApi);
findAndRemoveDuplicates(openApi);

openApi = resolvePropertyPlaceHolders(openApi, context);

return openApi;
}

private <T> List<T> findAndRemoveDuplicates(List<T> elements, BiPredicate<T, T> predicate) {
if (CollectionUtils.isEmpty(elements)) {
return elements;
}
var result = new ArrayList<T>();
for (var element : elements) {
boolean found = false;
for (var el : result) {
if (predicate.test(element, el)) {
found = true;
break;
}
}
if (!found) {
result.add(element);
}
}
if (result.size() != elements.size()) {
return result;
}
return elements;
}

void findAndRemoveDuplicates(OpenAPI openApi) {
openApi.setTags(findAndRemoveDuplicates(openApi.getTags(), (el1, el2) -> el1.getName() != null && el1.getName().equals(el2.getName())));
openApi.setServers(findAndRemoveDuplicates(openApi.getServers(), (el1, el2) -> el1.getUrl() != null && el1.getUrl().equals(el2.getUrl())));
openApi.setSecurity(findAndRemoveDuplicates(openApi.getSecurity(), (el1, el2) -> el1 != null && el1.equals(el2)));
if (CollectionUtils.isNotEmpty(openApi.getPaths())) {
for (var path : openApi.getPaths().values()) {
path.setServers(findAndRemoveDuplicates(path.getServers(), (el1, el2) -> el1.getUrl() != null && el1.getUrl().equals(el2.getUrl())));
path.setParameters(findAndRemoveDuplicates(path.getParameters(), (el1, el2) -> el1.getName() != null && el1.getName().equals(el2.getName())
&& el1.getIn() != null && el1.getIn().equals(el2.getIn())));
findAndRemoveDuplicates(path.getGet());
findAndRemoveDuplicates(path.getPut());
findAndRemoveDuplicates(path.getPost());
findAndRemoveDuplicates(path.getDelete());
findAndRemoveDuplicates(path.getOptions());
findAndRemoveDuplicates(path.getHead());
findAndRemoveDuplicates(path.getPatch());
findAndRemoveDuplicates(path.getTrace());
}
}
if (openApi.getComponents() != null) {
if (CollectionUtils.isNotEmpty(openApi.getComponents().getSchemas())) {
for (var schema : openApi.getComponents().getSchemas().values()) {
findAndRemoveDuplicates(schema);
}
}
}
if (openApi.getComponents() != null) {
if (CollectionUtils.isNotEmpty(openApi.getComponents().getSchemas())) {
for (var schema : openApi.getComponents().getSchemas().values()) {
findAndRemoveDuplicates(schema);
}
}
}
}

private void findAndRemoveDuplicates(Schema schema) {
if (schema == null) {
return;
}
schema.setRequired(findAndRemoveDuplicates(schema.getRequired(), (el1, el2) -> el1 != null && el1.equals(el2)));
schema.setPrefixItems(findAndRemoveDuplicates(schema.getPrefixItems(), (el1, el2) -> el1 != null && el1.equals(el2)));
schema.setAllOf(findAndRemoveDuplicates(schema.getAllOf(), (el1, el2) -> el1 != null && el1.equals(el2)));
schema.setAnyOf(findAndRemoveDuplicates(schema.getAnyOf(), (el1, el2) -> el1 != null && el1.equals(el2)));
schema.setOneOf(findAndRemoveDuplicates(schema.getOneOf(), (el1, el2) -> el1 != null && el1.equals(el2)));
}

private void findAndRemoveDuplicates(Operation operation) {
if (operation == null) {
return;
}
operation.setTags(findAndRemoveDuplicates(operation.getTags(), (el1, el2) -> el1 != null && el1.equals(el2)));
operation.setServers(findAndRemoveDuplicates(operation.getServers(), (el1, el2) -> el1.getUrl() != null && el1.getUrl().equals(el2.getUrl())));
operation.setSecurity(findAndRemoveDuplicates(operation.getSecurity(), (el1, el2) -> el1 != null && el1.equals(el2)));
if (CollectionUtils.isNotEmpty(operation.getParameters())) {
for (var param : operation.getParameters()) {
findAndRemoveDuplicates(param.getContent());
findAndRemoveDuplicates(param.getSchema());
}
operation.setParameters(findAndRemoveDuplicates(operation.getParameters(), (el1, el2) -> el1.getName() != null && el1.getName().equals(el2.getName())
&& el1.getIn() != null && el1.getIn().equals(el2.getIn())));
}

if (operation.getRequestBody() != null) {
findAndRemoveDuplicates(operation.getRequestBody().getContent());
}
if (CollectionUtils.isNotEmpty(operation.getResponses())) {
for (var response : operation.getResponses().values()) {
findAndRemoveDuplicates(response.getContent());
}
}
}

private void findAndRemoveDuplicates(Content content) {
if (CollectionUtils.isEmpty(content)) {
return;
}
for (var mediaType : content.values()) {
findAndRemoveDuplicates(mediaType.getSchema());
}
}

private void removeEmtpyComponents(OpenAPI openAPI) {
Components components = openAPI.getComponents();
if (components == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.micronaut.openapi.visitor

import io.micronaut.openapi.AbstractOpenApiTypeElementSpec
import io.swagger.v3.oas.models.OpenAPI

class OpenApiDuplicteElementsSpec extends AbstractOpenApiTypeElementSpec {

void "test duplicate elements removed"() {

when:
def visitor = new OpenApiApplicationVisitor()
def openApi = ConvertUtils.yamlMapper.readValue('''
openapi: 3.0.0
info:
description: This is a sample server Petstore server.
version: 1.0.0
title: Swagger Petstore
tags:
- name: pet
description: Pet resource
- name: pet
description: Store resource
paths:
/pets:
post:
tags:
- pet
- pet
- pet
parameters:
- in: query
name: status
description: Status values that need to be considered for filter
schema:
type: string
- in: query
name: status
description: Status values that need to be considered for filter
schema:
type: string
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
name:
description: Updated name of the pet
type: string
status:
description: Updated status of the pet
type: string
required:
- name
- name
- name
responses:
"405":
description: Invalid input
security:
- petstore_auth:
- write_pets
- read_pets
- petstore_auth:
- write_pets
- read_pets
- petstore_auth:
- write_pets
- read_pets
servers:
- url: http://petstore.swagger.io/v2
- url: http://petstore.swagger.io/v2
- url: http://petstore.swagger.io/v2
components:
schemas:
Pet:
required:
- id
- id
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
''', OpenAPI);
visitor.findAndRemoveDuplicates(openApi)

then:

openApi.tags.size() == 1
openApi.servers.size() == 1
openApi.components.schemas.Pet.required.size() == 1
openApi.paths.'/pets'.post.requestBody.content.'application/x-www-form-urlencoded'.schema.required.size() == 1
openApi.paths.'/pets'.post.tags.size() == 1
openApi.paths.'/pets'.post.parameters.size() == 1
openApi.paths.'/pets'.post.security.size() == 1
}

}

0 comments on commit 79b0dc2

Please sign in to comment.