Skip to content

Commit e5f2840

Browse files
authored
feat: [OpenAPI] Type and Import mapping support (#1010)
1 parent 9d44dd4 commit e5f2840

File tree

11 files changed

+236
-1
lines changed

11 files changed

+236
-1
lines changed

datamodel/openapi/openapi-api-sample/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
<inputSpec>${project.basedir}/src/main/resources/sodastore.yaml</inputSpec>
124124
<apiPackage>com.sap.cloud.sdk.datamodel.openapi.sample.api</apiPackage>
125125
<modelPackage>com.sap.cloud.sdk.datamodel.openapi.sample.model</modelPackage>
126+
<typeMappings>File=byte[]</typeMappings>
126127
<additionalProperties>
127128
<pojoBuilderMethodName>create</pojoBuilderMethodName>
128129
<pojoBuildMethodName />

datamodel/openapi/openapi-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/SodasApi.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,68 @@ public SodasApi( @Nonnull final ApiClient apiClient )
5757
super(apiClient);
5858
}
5959

60+
/**
61+
* <p>
62+
* Download soda product data as binary
63+
* </p>
64+
* <p>
65+
* </p>
66+
* <p>
67+
* <b>200</b> - Successful response
68+
* <p>
69+
* <b>404</b> - Soda product not found
70+
*
71+
* @param id
72+
* ID of the soda product to download
73+
* @return byte[]
74+
* @throws OpenApiRequestException
75+
* if an error occurs while attempting to invoke the API
76+
*/
77+
@Nonnull
78+
public byte[] sodasDownloadIdGet( @Nonnull final Long id )
79+
throws OpenApiRequestException
80+
{
81+
final Object localVarPostBody = null;
82+
83+
// verify the required parameter 'id' is set
84+
if( id == null ) {
85+
throw new OpenApiRequestException("Missing the required parameter 'id' when calling sodasDownloadIdGet");
86+
}
87+
88+
// create path and map variables
89+
final Map<String, Object> localVarPathParams = new HashMap<String, Object>();
90+
localVarPathParams.put("id", id);
91+
final String localVarPath =
92+
UriComponentsBuilder.fromPath("/sodas/download/{id}").buildAndExpand(localVarPathParams).toUriString();
93+
94+
final MultiValueMap<String, String> localVarQueryParams = new LinkedMultiValueMap<String, String>();
95+
final HttpHeaders localVarHeaderParams = new HttpHeaders();
96+
final MultiValueMap<String, Object> localVarFormParams = new LinkedMultiValueMap<String, Object>();
97+
98+
final String[] localVarAccepts = { "application/octet-stream" };
99+
final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
100+
final String[] localVarContentTypes = {};
101+
final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
102+
103+
final String[] localVarAuthNames = new String[] { "apiKeyAuth", "bearerAuth" };
104+
105+
final ParameterizedTypeReference<byte[]> localVarReturnType = new ParameterizedTypeReference<byte[]>()
106+
{
107+
};
108+
return apiClient
109+
.invokeAPI(
110+
localVarPath,
111+
HttpMethod.GET,
112+
localVarQueryParams,
113+
localVarPostBody,
114+
localVarHeaderParams,
115+
localVarFormParams,
116+
localVarAccept,
117+
localVarContentType,
118+
localVarAuthNames,
119+
localVarReturnType);
120+
}
121+
60122
/**
61123
* <p>
62124
* Get all soda products

datamodel/openapi/openapi-api-sample/src/main/resources/sodastore.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,29 @@ paths:
258258
$ref: '#/components/schemas/SodaWithId'
259259
'404':
260260
description: Soda product not found
261+
/sodas/download/{id}:
262+
get:
263+
summary: Download soda product data as binary
264+
tags:
265+
- Sodas
266+
parameters:
267+
- name: id
268+
in: path
269+
description: ID of the soda product to download
270+
required: true
271+
schema:
272+
type: integer
273+
format: int64
274+
responses:
275+
'200':
276+
description: Successful response
277+
content:
278+
application/octet-stream:
279+
schema:
280+
type: string
281+
format: binary
282+
'404':
283+
description: Soda product not found
261284
/orders:
262285
post:
263286
summary: Create a new order

datamodel/openapi/openapi-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/openapi/sample/api/DeserializationTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,26 @@ void testUnexpectedAdditionalField()
115115
assertThat(actual.getCustomField("unexpectedField")).asInstanceOf(InstanceOfAssertFactories.LIST).isEmpty();
116116
}
117117

118+
@Test
119+
void testBinaryResponse()
120+
{
121+
final byte[] binaryData = "binary file content".getBytes();
122+
WireMock
123+
.stubFor(
124+
WireMock
125+
.get(WireMock.urlMatching("/sodas/download/\\d+"))
126+
.willReturn(
127+
WireMock
128+
.aResponse()
129+
.withStatus(200)
130+
.withHeader("Content-Type", "application/octet-stream")
131+
.withBody(binaryData)));
132+
133+
final byte[] result = sut.sodasDownloadIdGet(1L);
134+
assertThat(result).isNotNull();
135+
assertThat(result).isEqualTo(binaryData);
136+
}
137+
118138
private void stub( String responseBody )
119139
{
120140
WireMock.stubFor(WireMock.get(WireMock.anyUrl()).willReturn(okJson(responseBody)));

datamodel/openapi/openapi-generator-maven-plugin/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojo.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package com.sap.cloud.sdk.datamodel.openapi.generator;
22

33
import java.util.Arrays;
4+
import java.util.List;
45
import java.util.Map;
6+
import java.util.Objects;
7+
import java.util.stream.Collectors;
8+
9+
import javax.annotation.Nonnull;
510

611
import org.apache.maven.plugin.AbstractMojo;
712
import org.apache.maven.plugin.MojoExecutionException;
@@ -126,6 +131,18 @@ public class DataModelGeneratorMojo extends AbstractMojo
126131
@Parameter( property = "openapi.generate.generateApis", defaultValue = "true" )
127132
private boolean generateApis;
128133

134+
/**
135+
* Type mappings to override OpenAPI specification types and the types used in your generated code.
136+
*/
137+
@Parameter( property = "openapi.generate.typeMappings" )
138+
private List<String> typeMappings;
139+
140+
/**
141+
* Import mappings to specify alternative imports statement to use for a given class name.
142+
*/
143+
@Parameter( property = "openapi.generate.importMappings" )
144+
private List<String> importMappings;
145+
129146
/**
130147
* Defines a list of additional properties that will be passed to the Java generator.
131148
*/
@@ -189,6 +206,8 @@ Try<GenerationConfiguration> retrieveGenerationConfiguration()
189206
.oneOfAnyOfGenerationEnabled(enableOneOfAnyOfGeneration)
190207
.generateModels(generateModels)
191208
.generateApis(generateApis)
209+
.typeMappings(parseMapping(typeMappings))
210+
.importMappings(parseMapping(importMappings))
192211
.build());
193212
}
194213

@@ -198,4 +217,16 @@ void setOutputDirectory( final String outputDirectory )
198217
this.outputDirectory = outputDirectory;
199218
}
200219

220+
@Nonnull
221+
private Map<String, String> parseMapping( @Nonnull final List<String> mappings )
222+
{
223+
return mappings
224+
.stream()
225+
.filter(Objects::nonNull)
226+
.filter(line -> line.contains("="))
227+
.map(line -> line.split("=", 2))
228+
.map(parts -> new String[] { parts[0].trim(), parts[1].trim() })
229+
.filter(parts -> !parts[0].isEmpty() && !parts[1].isEmpty())
230+
.collect(Collectors.toMap(parts -> parts[0], parts -> parts[1]));
231+
}
201232
}

datamodel/openapi/openapi-generator-maven-plugin/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorMojoUnitTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class DataModelGeneratorMojoUnitTest
3030
RESOURCE_PATH + "/testInvocationWithUnexpectedApiMaturity/pom.xml";
3131
private static final String ADDITIONAL_PROPERTIES_POM =
3232
RESOURCE_PATH + "/testAdditionalPropertiesAndEnablingAnyOfOneOf/pom.xml";
33+
private static final String MAPPINGS_EDGE_CASES_POM = RESOURCE_PATH + "/testMappingsEdgeCases/pom.xml";
3334

3435
@TempDir
3536
File outputDirectory;
@@ -50,6 +51,11 @@ void testInvocationWithAllParameters( DataModelGeneratorMojo mojo )
5051
assertThat(configuration.getApiPackage()).isEqualTo("com.sap.cloud.sdk.datamodel.rest.test.api");
5152
assertThat(configuration.deleteOutputDirectory()).isTrue();
5253
assertThat(configuration.isOneOfAnyOfGenerationEnabled()).isFalse();
54+
assertThat(configuration.getTypeMappings())
55+
.containsEntry("binary", "org.springframework.core.io.Resource")
56+
.containsEntry("file", "org.springframework.core.io.Resource");
57+
assertThat(configuration.getImportMappings())
58+
.containsEntry("org.springframework.core.io.Resource", "org.springframework.core.io.Resource");
5359

5460
mojo.setOutputDirectory(outputDirectory.getAbsolutePath());
5561

@@ -71,6 +77,8 @@ void testInvocationWithMandatoryParameters( DataModelGeneratorMojo mojo )
7177
assertThat(configuration.getModelPackage()).isEqualTo("com.sap.cloud.sdk.datamodel.rest.test.model");
7278
assertThat(configuration.getApiPackage()).isEqualTo("com.sap.cloud.sdk.datamodel.rest.test.api");
7379
assertThat(configuration.deleteOutputDirectory()).isFalse();
80+
assertThat(configuration.getTypeMappings()).isEmpty();
81+
assertThat(configuration.getImportMappings()).isEmpty();
7482

7583
mojo.setOutputDirectory(outputDirectory.getAbsolutePath());
7684

@@ -126,4 +134,21 @@ void testAdditionalPropertiesAndEnablingAnyOfOneOf( DataModelGeneratorMojo mojo
126134

127135
assertThat(mojo.retrieveGenerationConfiguration().get().isOneOfAnyOfGenerationEnabled()).isTrue();
128136
}
137+
138+
@Test
139+
@InjectMojo( goal = "generate", pom = MAPPINGS_EDGE_CASES_POM )
140+
void testMappingsEdgeCases( DataModelGeneratorMojo mojo )
141+
throws Throwable
142+
{
143+
final GenerationConfiguration configuration = mojo.retrieveGenerationConfiguration().get();
144+
145+
assertThat(configuration.getTypeMappings())
146+
.hasSize(2) // Only valid mappings should remain
147+
.containsEntry("File", "byte[]")
148+
.containsEntry("binary", "org.springframework.core.io.Resource");
149+
150+
assertThat(configuration.getImportMappings())
151+
.hasSize(1)
152+
.containsEntry("Resource", "org.springframework.core.io.Resource");
153+
}
129154
}

datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoUnitTest/testInvocationWithAllParameters/pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@
2020
<verbose>true</verbose>
2121
<outputDirectory>output-directory</outputDirectory>
2222
<deleteOutputDirectory>true</deleteOutputDirectory>
23-
</configuration>
23+
<typeMappings>
24+
<typeMapping>binary=org.springframework.core.io.Resource</typeMapping>
25+
<typeMapping>file=org.springframework.core.io.Resource</typeMapping>
26+
</typeMappings>
27+
<importMappings>
28+
<importMapping>org.springframework.core.io.Resource=org.springframework.core.io.Resource</importMapping>
29+
</importMappings>
30+
</configuration>
2431
</plugin>
2532
</plugins>
2633
</build>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
5+
<modelVersion>4.0.0</modelVersion>
6+
<groupId>com.company</groupId>
7+
<artifactId>application</artifactId>
8+
<version>5.25.0-SNAPSHOT</version>
9+
10+
<build>
11+
<plugins>
12+
<plugin>
13+
<groupId>com.sap.cloud.sdk.datamodel</groupId>
14+
<artifactId>openapi-generator-maven-plugin</artifactId>
15+
<configuration>
16+
<inputSpec>DataModelGeneratorMojoUnitTest/testMappingsEdgeCases/input/sodastore.yaml</inputSpec>
17+
<apiPackage>com.sap.cloud.sdk.datamodel.rest.test.api</apiPackage>
18+
<modelPackage>com.sap.cloud.sdk.datamodel.rest.test.model</modelPackage>
19+
<apiMaturity>released</apiMaturity>
20+
<outputDirectory>output-directory</outputDirectory>
21+
<typeMappings>
22+
<typeMapping>File=byte[]</typeMapping>
23+
<typeMapping>binary = org.springframework.core.io.Resource</typeMapping>
24+
<typeMapping>no-equals-sign</typeMapping>
25+
<typeMapping>=missing-key</typeMapping>
26+
<typeMapping>missing-value=</typeMapping>
27+
<typeMapping>=</typeMapping>
28+
<typeMapping></typeMapping><!-- Invalid: empty -->
29+
</typeMappings>
30+
<importMappings>
31+
<importMapping>Resource=org.springframework.core.io.Resource</importMapping>
32+
<importMapping>no-equals-sign</importMapping>
33+
</importMappings>
34+
</configuration>
35+
</plugin>
36+
</plugins>
37+
</build>
38+
</project>

datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/GenerationConfigurationConverter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ static ClientOptInput convertGenerationConfiguration(
6565
config.setModelPackage(generationConfiguration.getModelPackage());
6666
config.setTemplateDir(TEMPLATE_DIRECTORY);
6767
config.additionalProperties().putAll(getAdditionalProperties(generationConfiguration));
68+
config.typeMapping().putAll(generationConfiguration.getTypeMappings());
69+
config.importMapping().putAll(generationConfiguration.getImportMappings());
6870

6971
final var openAPI = parseOpenApiSpec(inputSpecFile, generationConfiguration);
7072

datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/model/GenerationConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ public class GenerationConfiguration
6161
@Builder.Default
6262
boolean debugModels = false;
6363

64+
@Singular( ignoreNullCollections = true )
65+
Map<String, String> typeMappings;
66+
67+
@Singular( ignoreNullCollections = true )
68+
Map<String, String> importMappings;
69+
6470
/**
6571
* Indicates whether to use the default SAP copyright header for generated files.
6672
*

0 commit comments

Comments
 (0)