From af7b5b3fc07f2abd69bd7991762e6a2da67a18d6 Mon Sep 17 00:00:00 2001 From: Roshin Rajan Panackal Date: Tue, 18 Nov 2025 17:40:24 +0100 Subject: [PATCH 1/2] Propagating Import and Type mappings --- datamodel/openapi/openapi-api-sample/pom.xml | 1 + .../openapi/sample/api/SodasApi.java | 62 +++++++++++++++++++ .../src/main/resources/sodastore.yaml | 23 +++++++ .../sample/api/DeserializationTest.java | 20 ++++++ .../generator/DataModelGeneratorMojo.java | 31 ++++++++++ .../DataModelGeneratorMojoUnitTest.java | 25 ++++++++ .../testInvocationWithAllParameters/pom.xml | 9 ++- .../testMappingsEdgeCases/pom.xml | 38 ++++++++++++ .../GenerationConfigurationConverter.java | 2 + .../model/GenerationConfiguration.java | 6 ++ .../GenerationConfigurationConverterTest.java | 20 ++++++ 11 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testMappingsEdgeCases/pom.xml diff --git a/datamodel/openapi/openapi-api-sample/pom.xml b/datamodel/openapi/openapi-api-sample/pom.xml index d373bb45e..a0a372908 100644 --- a/datamodel/openapi/openapi-api-sample/pom.xml +++ b/datamodel/openapi/openapi-api-sample/pom.xml @@ -123,6 +123,7 @@ ${project.basedir}/src/main/resources/sodastore.yaml com.sap.cloud.sdk.datamodel.openapi.sample.api com.sap.cloud.sdk.datamodel.openapi.sample.model + File=byte[] create diff --git a/datamodel/openapi/openapi-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/SodasApi.java b/datamodel/openapi/openapi-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/SodasApi.java index a512ff249..c06633a74 100644 --- a/datamodel/openapi/openapi-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/SodasApi.java +++ b/datamodel/openapi/openapi-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/SodasApi.java @@ -57,6 +57,68 @@ public SodasApi( @Nonnull final ApiClient apiClient ) super(apiClient); } + /** + *

+ * Download soda product data as binary + *

+ *

+ *

+ *

+ * 200 - Successful response + *

+ * 404 - Soda product not found + * + * @param id + * ID of the soda product to download + * @return byte[] + * @throws OpenApiRequestException + * if an error occurs while attempting to invoke the API + */ + @Nonnull + public byte[] sodasDownloadIdGet( @Nonnull final Long id ) + throws OpenApiRequestException + { + final Object localVarPostBody = null; + + // verify the required parameter 'id' is set + if( id == null ) { + throw new OpenApiRequestException("Missing the required parameter 'id' when calling sodasDownloadIdGet"); + } + + // create path and map variables + final Map localVarPathParams = new HashMap(); + localVarPathParams.put("id", id); + final String localVarPath = + UriComponentsBuilder.fromPath("/sodas/download/{id}").buildAndExpand(localVarPathParams).toUriString(); + + final MultiValueMap localVarQueryParams = new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarFormParams = new LinkedMultiValueMap(); + + final String[] localVarAccepts = { "application/octet-stream" }; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = {}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + final String[] localVarAuthNames = new String[] { "apiKeyAuth", "bearerAuth" }; + + final ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference() + { + }; + return apiClient + .invokeAPI( + localVarPath, + HttpMethod.GET, + localVarQueryParams, + localVarPostBody, + localVarHeaderParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + localVarReturnType); + } + /** *

* Get all soda products diff --git a/datamodel/openapi/openapi-api-sample/src/main/resources/sodastore.yaml b/datamodel/openapi/openapi-api-sample/src/main/resources/sodastore.yaml index 1fa8ce09a..bf2b80a7f 100644 --- a/datamodel/openapi/openapi-api-sample/src/main/resources/sodastore.yaml +++ b/datamodel/openapi/openapi-api-sample/src/main/resources/sodastore.yaml @@ -258,6 +258,29 @@ paths: $ref: '#/components/schemas/SodaWithId' '404': description: Soda product not found + /sodas/download/{id}: + get: + summary: Download soda product data as binary + tags: + - Sodas + parameters: + - name: id + in: path + description: ID of the soda product to download + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Successful response + content: + application/octet-stream: + schema: + type: string + format: binary + '404': + description: Soda product not found /orders: post: summary: Create a new order diff --git a/datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java b/datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java index db7e50500..9ad0e7801 100644 --- a/datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java +++ b/datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java @@ -115,6 +115,26 @@ void testUnexpectedAdditionalField() assertThat(actual.getCustomField("unexpectedField")).asInstanceOf(InstanceOfAssertFactories.LIST).isEmpty(); } + @Test + void testBinaryResponse() + { + final byte[] binaryData = "binary file content".getBytes(); + WireMock + .stubFor( + WireMock + .get(WireMock.urlMatching("/sodas/download/\\d+")) + .willReturn( + WireMock + .aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/octet-stream") + .withBody(binaryData))); + + byte[] result = sut.sodasDownloadIdGet(1L); + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(binaryData); + } + private void stub( String responseBody ) { WireMock.stubFor(WireMock.get(WireMock.anyUrl()).willReturn(okJson(responseBody))); diff --git a/datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java b/datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java index 7cde3a40f..7c573c3b4 100644 --- a/datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java +++ b/datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java @@ -1,7 +1,12 @@ package com.sap.cloud.sdk.datamodel.openapi.generator; import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -126,6 +131,18 @@ public class DataModelGeneratorMojo extends AbstractMojo @Parameter( property = "openapi.generate.generateApis", defaultValue = "true" ) private boolean generateApis; + /** + * Type mappings to override OpenAPI specification types and the types used in your generated code. + */ + @Parameter( property = "openapi.generate.typeMappings" ) + private List typeMappings; + + /** + * Import mappings to specify alternative imports statement to use for a given class name. + */ + @Parameter( property = "openapi.generate.importMappings" ) + private List importMappings; + /** * Defines a list of additional properties that will be passed to the Java generator. */ @@ -189,6 +206,8 @@ Try retrieveGenerationConfiguration() .oneOfAnyOfGenerationEnabled(enableOneOfAnyOfGeneration) .generateModels(generateModels) .generateApis(generateApis) + .typeMappings(parseMapping(typeMappings)) + .importMappings(parseMapping(importMappings)) .build()); } @@ -198,4 +217,16 @@ void setOutputDirectory( final String outputDirectory ) this.outputDirectory = outputDirectory; } + @Nonnull + private Map parseMapping( @Nonnull List mappings ) + { + return mappings + .stream() + .filter(Objects::nonNull) + .filter(line -> line.contains("=")) + .map(line -> line.split("=", 2)) + .map(parts -> new String[] { parts[0].trim(), parts[1].trim() }) + .filter(parts -> !parts[0].isEmpty() && !parts[1].isEmpty()) + .collect(Collectors.toMap(parts -> parts[0], parts -> parts[1])); + } } diff --git a/datamodel/openapi/openapi-generator-maven-plugin/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojoUnitTest.java b/datamodel/openapi/openapi-generator-maven-plugin/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojoUnitTest.java index aa632f7a6..f50b73d61 100644 --- a/datamodel/openapi/openapi-generator-maven-plugin/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojoUnitTest.java +++ b/datamodel/openapi/openapi-generator-maven-plugin/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojoUnitTest.java @@ -30,6 +30,7 @@ class DataModelGeneratorMojoUnitTest RESOURCE_PATH + "/testInvocationWithUnexpectedApiMaturity/pom.xml"; private static final String ADDITIONAL_PROPERTIES_POM = RESOURCE_PATH + "/testAdditionalPropertiesAndEnablingAnyOfOneOf/pom.xml"; + private static final String MAPPINGS_EDGE_CASES_POM = RESOURCE_PATH + "/testMappingsEdgeCases/pom.xml"; @TempDir File outputDirectory; @@ -50,6 +51,11 @@ void testInvocationWithAllParameters( DataModelGeneratorMojo mojo ) assertThat(configuration.getApiPackage()).isEqualTo("com.sap.cloud.sdk.datamodel.rest.test.api"); assertThat(configuration.deleteOutputDirectory()).isTrue(); assertThat(configuration.isOneOfAnyOfGenerationEnabled()).isFalse(); + assertThat(configuration.getTypeMappings()) + .containsEntry("binary", "org.springframework.core.io.Resource") + .containsEntry("file", "org.springframework.core.io.Resource"); + assertThat(configuration.getImportMappings()) + .containsEntry("org.springframework.core.io.Resource", "org.springframework.core.io.Resource"); mojo.setOutputDirectory(outputDirectory.getAbsolutePath()); @@ -71,6 +77,8 @@ void testInvocationWithMandatoryParameters( DataModelGeneratorMojo mojo ) assertThat(configuration.getModelPackage()).isEqualTo("com.sap.cloud.sdk.datamodel.rest.test.model"); assertThat(configuration.getApiPackage()).isEqualTo("com.sap.cloud.sdk.datamodel.rest.test.api"); assertThat(configuration.deleteOutputDirectory()).isFalse(); + assertThat(configuration.getTypeMappings()).isEmpty(); + assertThat(configuration.getImportMappings()).isEmpty(); mojo.setOutputDirectory(outputDirectory.getAbsolutePath()); @@ -126,4 +134,21 @@ void testAdditionalPropertiesAndEnablingAnyOfOneOf( DataModelGeneratorMojo mojo assertThat(mojo.retrieveGenerationConfiguration().get().isOneOfAnyOfGenerationEnabled()).isTrue(); } + + @Test + @InjectMojo( goal = "generate", pom = MAPPINGS_EDGE_CASES_POM ) + void testMappingsEdgeCases( DataModelGeneratorMojo mojo ) + throws Throwable + { + final GenerationConfiguration configuration = mojo.retrieveGenerationConfiguration().get(); + + assertThat(configuration.getTypeMappings()) + .hasSize(2) // Only valid mappings should remain + .containsEntry("File", "byte[]") + .containsEntry("binary", "org.springframework.core.io.Resource"); + + assertThat(configuration.getImportMappings()) + .hasSize(1) + .containsEntry("Resource", "org.springframework.core.io.Resource"); + } } diff --git a/datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testInvocationWithAllParameters/pom.xml b/datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testInvocationWithAllParameters/pom.xml index 2ee5a9eab..589a32cbe 100644 --- a/datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testInvocationWithAllParameters/pom.xml +++ b/datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testInvocationWithAllParameters/pom.xml @@ -20,7 +20,14 @@ true output-directory true - + + binary=org.springframework.core.io.Resource + file=org.springframework.core.io.Resource + + + org.springframework.core.io.Resource=org.springframework.core.io.Resource + + diff --git a/datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testMappingsEdgeCases/pom.xml b/datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testMappingsEdgeCases/pom.xml new file mode 100644 index 000000000..a5cff880b --- /dev/null +++ b/datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testMappingsEdgeCases/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + com.company + application + 5.25.0-SNAPSHOT + + + + + com.sap.cloud.sdk.datamodel + openapi-generator-maven-plugin + + DataModelGeneratorMojoUnitTest/testMappingsEdgeCases/input/sodastore.yaml + com.sap.cloud.sdk.datamodel.rest.test.api + com.sap.cloud.sdk.datamodel.rest.test.model + released + output-directory + + File=byte[] + binary = org.springframework.core.io.Resource + no-equals-sign + =missing-key + missing-value= + = + + + + Resource=org.springframework.core.io.Resource + no-equals-sign + + + + + + diff --git a/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverter.java b/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverter.java index d3406ad4a..596d8545c 100644 --- a/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverter.java +++ b/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverter.java @@ -65,6 +65,8 @@ static ClientOptInput convertGenerationConfiguration( config.setModelPackage(generationConfiguration.getModelPackage()); config.setTemplateDir(TEMPLATE_DIRECTORY); config.additionalProperties().putAll(getAdditionalProperties(generationConfiguration)); + config.typeMapping().putAll(generationConfiguration.getTypeMappings()); + config.importMapping().putAll(generationConfiguration.getImportMappings()); final var openAPI = parseOpenApiSpec(inputSpecFile, generationConfiguration); diff --git a/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/model/GenerationConfiguration.java b/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/model/GenerationConfiguration.java index eb1c49b48..7629f856a 100644 --- a/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/model/GenerationConfiguration.java +++ b/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/model/GenerationConfiguration.java @@ -61,6 +61,12 @@ public class GenerationConfiguration @Builder.Default boolean debugModels = false; + @Singular( ignoreNullCollections = true ) + Map typeMappings; + + @Singular( ignoreNullCollections = true ) + Map importMappings; + /** * Indicates whether to use the default SAP copyright header for generated files. * diff --git a/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverterTest.java b/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverterTest.java index 9ad40b528..7918cc837 100644 --- a/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverterTest.java +++ b/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverterTest.java @@ -4,11 +4,13 @@ import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Map; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.openapitools.codegen.ClientOptInput; import com.sap.cloud.sdk.datamodel.openapi.generator.model.GenerationConfiguration; @@ -57,4 +59,22 @@ private String getCopyrightHeaderFromConfig( final GenerationConfiguration confi .get(GenerationConfigurationConverter.COPYRIGHT_PROPERTY_KEY); return maybeHeader != null ? maybeHeader.toString() : null; } + + @Test + @SuppressWarnings( "deprecation" ) + void testTypeMappingsAndImportMappingsAreApplied() + { + final Map typeMappings = + Map.of("File", "byte[]", "binary", "org.springframework.core.io.Resource"); + final Map importMappings = Map.of("Resource", "org.springframework.core.io.Resource"); + + final GenerationConfiguration config = + createBasicConfig().typeMappings(typeMappings).importMappings(importMappings).build(); + + final ClientOptInput result = + GenerationConfigurationConverter.convertGenerationConfiguration(config, Paths.get(config.getInputSpec())); + + assertThat(result.getConfig().typeMapping()).containsAllEntriesOf(typeMappings); + assertThat(result.getConfig().importMapping()).containsAllEntriesOf(importMappings); + } } From 25c9fd24e2ba736d38bf4485c77f6e98bb7d5c95 Mon Sep 17 00:00:00 2001 From: Roshin Rajan Panackal Date: Wed, 19 Nov 2025 13:04:31 +0100 Subject: [PATCH 2/2] pmd fix --- .../sdk/datamodel/openapi/sample/api/DeserializationTest.java | 2 +- .../sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java b/datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java index 9ad0e7801..dfe1c7744 100644 --- a/datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java +++ b/datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java @@ -130,7 +130,7 @@ void testBinaryResponse() .withHeader("Content-Type", "application/octet-stream") .withBody(binaryData))); - byte[] result = sut.sodasDownloadIdGet(1L); + final byte[] result = sut.sodasDownloadIdGet(1L); assertThat(result).isNotNull(); assertThat(result).isEqualTo(binaryData); } diff --git a/datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java b/datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java index 7c573c3b4..d5ad79e03 100644 --- a/datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java +++ b/datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java @@ -218,7 +218,7 @@ void setOutputDirectory( final String outputDirectory ) } @Nonnull - private Map parseMapping( @Nonnull List mappings ) + private Map parseMapping( @Nonnull final List mappings ) { return mappings .stream()