diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3Service.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3Service.java index 6dc85e9c2e..1f6ec5587a 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3Service.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3Service.java @@ -16,10 +16,14 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.swagger.v3.core.jackson.mixin.MediaTypeMixin; +import io.swagger.v3.core.jackson.mixin.SchemaMixin; import io.swagger.v3.oas.models.ExternalDocumentation; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.tags.Tag; @@ -47,11 +51,8 @@ public class ApiDocV3Service extends AbstractApiDocService { @Value("${gateway.scheme.external:https}") private String scheme; - private final ObjectMapper mapper; - public ApiDocV3Service(GatewayClient gatewayClient) { super(gatewayClient); - mapper = initializeObjectMapper(); } public String transformApiDoc(String serviceId, ApiDocInfo apiDocInfo) { @@ -79,7 +80,7 @@ public String transformApiDoc(String serviceId, ApiDocInfo apiDocInfo) { updateExternalDoc(openAPI, apiDocInfo); try { - return mapper.writeValueAsString(openAPI); + return objectMapper().writeValueAsString(openAPI); } catch (JsonProcessingException e) { log.debug("Could not convert OpenAPI to JSON", e); throw new ApiDocTransformationException("Could not convert Swagger to JSON"); @@ -195,17 +196,13 @@ private boolean isHidden(List tags) { return tags != null && tags.stream().anyMatch(tag -> tag.getName().equals(HIDDEN_TAG)); } - private ObjectMapper initializeObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - - SimpleModule simpleModule = new SimpleModule(); - simpleModule.addSerializer(SecurityScheme.class, new SecuritySchemeSerializer()); - - objectMapper.registerModule(simpleModule); - objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); - - objectMapper.registerModule(new JavaTimeModule()); - return objectMapper; + private ObjectMapper objectMapper() { + return new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .registerModule(new SimpleModule().addSerializer(SecurityScheme.class, new SecuritySchemeSerializer())) + .registerModule(new JavaTimeModule()) + .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) + .addMixIn(Schema.class, SchemaMixin.class) + .addMixIn(MediaType.class, MediaTypeMixin.class); } } diff --git a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiTransformationConfig.java b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiTransformationConfig.java index c48707f62f..02525deda4 100644 --- a/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiTransformationConfig.java +++ b/api-catalog-services/src/main/java/org/zowe/apiml/apicatalog/swagger/api/ApiTransformationConfig.java @@ -23,6 +23,7 @@ import org.zowe.apiml.product.gateway.GatewayClient; import jakarta.validation.UnexpectedTypeException; + import java.io.IOException; import java.util.function.Function; @@ -42,8 +43,8 @@ public class ApiTransformationConfig { @Bean @Scope(value = "prototype") public AbstractApiDocService abstractApiDocService(String content) { - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); try { ObjectNode objectNode = mapper.readValue(content, ObjectNode.class); JsonNode openApiNode = objectNode.get("openapi"); diff --git a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3ServiceTest.java b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3ServiceTest.java index 0f28b38c4a..26c13ee806 100644 --- a/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3ServiceTest.java +++ b/api-catalog-services/src/test/java/org/zowe/apiml/apicatalog/swagger/api/ApiDocV3ServiceTest.java @@ -34,6 +34,7 @@ import org.zowe.apiml.product.routing.RoutedServices; import jakarta.validation.UnexpectedTypeException; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -88,7 +89,7 @@ void givenOpenApiValidJson() { RoutedServices routedServices = new RoutedServices(); routedServices.addRoutedService(routedService); routedServices.addRoutedService(routedService2); - ApiInfo apiInfo = new ApiInfo(API_ID, "api/v1", API_VERSION, "https://localhost:10014/apicatalog/api-doc",null, "https://www.zowe.org"); + ApiInfo apiInfo = new ApiInfo(API_ID, "api/v1", API_VERSION, "https://localhost:10014/apicatalog/api-doc", null, "https://www.zowe.org"); ApiDocInfo apiDocInfo = new ApiDocInfo(apiInfo, apiDocContent, routedServices); String actualContent = apiDocV3Service.transformApiDoc(SERVICE_ID, apiDocInfo); @@ -175,7 +176,7 @@ class ThenThrowException { @Test void givenEmptyJson() { String invalidJson = ""; - ApiInfo apiInfo = new ApiInfo(API_ID, "api/v1", API_VERSION, "https://localhost:10014/apicatalog/api-doc",null, "https://www.zowe.org"); + ApiInfo apiInfo = new ApiInfo(API_ID, "api/v1", API_VERSION, "https://localhost:10014/apicatalog/api-doc", null, "https://www.zowe.org"); ApiDocInfo apiDocInfo = new ApiDocInfo(apiInfo, invalidJson, null); Exception exception = assertThrows(UnexpectedTypeException.class, () -> apiDocV3Service.transformApiDoc(SERVICE_ID, apiDocInfo)); @@ -187,7 +188,7 @@ void givenInvalidJson() { String invalidJson = "nonsense"; String error = "The OpenAPI for service 'serviceId' was retrieved but was not a valid JSON document. '[Cannot construct instance of `java.util.LinkedHashMap` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('nonsense')\n" + " at [Source: UNKNOWN; byte offset: #UNKNOWN]]'"; - ApiInfo apiInfo = new ApiInfo(API_ID, "api/v1", API_VERSION, "https://localhost:10014/apicatalog/api-doc",null, "https://www.zowe.org"); + ApiInfo apiInfo = new ApiInfo(API_ID, "api/v1", API_VERSION, "https://localhost:10014/apicatalog/api-doc", null, "https://www.zowe.org"); ApiDocInfo apiDocInfo = new ApiDocInfo(apiInfo, invalidJson, null); Exception exception = assertThrows(UnexpectedTypeException.class, () -> apiDocV3Service.transformApiDoc(SERVICE_ID, apiDocInfo)); @@ -239,14 +240,28 @@ protected void updateExternalDoc(OpenAPI openAPI, ApiDocInfo apiDocInfo) { } }; String transformed = apiDocV3Service.transformApiDoc("serviceId", new ApiDocInfo( - mock(ApiInfo.class), - IOUtils.toString(new ClassPathResource("swagger/openapi3.json").getInputStream(), StandardCharsets.UTF_8), - mock(RoutedServices.class) + mock(ApiInfo.class), + IOUtils.toString(new ClassPathResource("swagger/openapi3.json").getInputStream(), StandardCharsets.UTF_8), + mock(RoutedServices.class) )); assertNotNull(transformed); verifyOpenApi3(openApiHolder.get()); } + @Test + void givenValidApiDoc_thenDoNotLeakExampleSetFlag() throws IOException { + ApiInfo apiInfo = new ApiInfo("zowe.apiml.apicatalog", "api/v1", API_VERSION, "https://localhost:10014/apicatalog/v3/api-docs", null, "https://www.zowe.org"); + String content = IOUtils.toString(new ClassPathResource("swagger/openapi3.json").getInputStream(), StandardCharsets.UTF_8); + + RoutedServices routedServices = new RoutedServices(); + routedServices.addRoutedService(new RoutedService("api-v1", "api/v1", "/apicatalog")); + + ApiDocInfo info = new ApiDocInfo(apiInfo, content, routedServices); + assertThat(content, containsString("\"exampleSetFlag\":")); + + String actualContent = apiDocV3Service.transformApiDoc(SERVICE_ID, info); + assertThat(actualContent, not(containsString("\"exampleSetFlag\":"))); + } } private String convertOpenApiToJson(OpenAPI openApi) {