Skip to content

Commit 17e8ece

Browse files
authored
Fix @Nonnull and @Nullable annotations on endpoint methods created by OpenAPI Generator (#511)
1 parent 50a2cf3 commit 17e8ece

File tree

31 files changed

+219
-101
lines changed

31 files changed

+219
-101
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import java.util.List;
88

99
import javax.annotation.Nonnull;
10-
import javax.annotation.Nullable;
1110

1211
import org.springframework.core.ParameterizedTypeReference;
1312
import org.springframework.http.HttpHeaders;
@@ -69,7 +68,7 @@ public OrdersApi( @Nonnull final ApiClient apiClient )
6968
* @throws OpenApiRequestException
7069
* if an error occurs while attempting to invoke the API
7170
*/
72-
@Nullable
71+
@Nonnull
7372
public Order ordersPost( @Nonnull final Order order )
7473
throws OpenApiRequestException
7574
{

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public SodasApi( @Nonnull final ApiClient apiClient )
7070
* @throws OpenApiRequestException
7171
* if an error occurs while attempting to invoke the API
7272
*/
73-
@Nullable
73+
@Nonnull
7474
public List<SodaWithId> sodasGet()
7575
throws OpenApiRequestException
7676
{
@@ -124,7 +124,7 @@ public List<SodaWithId> sodasGet()
124124
* @throws OpenApiRequestException
125125
* if an error occurs while attempting to invoke the API
126126
*/
127-
@Nullable
127+
@Nonnull
128128
public SodaWithId sodasIdGet( @Nonnull final Long id )
129129
throws OpenApiRequestException
130130
{
@@ -179,6 +179,8 @@ public SodaWithId sodasIdGet( @Nonnull final Long id )
179179
* <b>200</b> - The updated soda product
180180
* <p>
181181
* <b>404</b> - Soda product not found
182+
* <p>
183+
* <b>204</b> - Nothing has changed
182184
*
183185
* @param sodaWithId
184186
* The updated soda product

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@
190190
},
191191
"404": {
192192
"description": "Soda product not found"
193+
},
194+
"204": {
195+
"description": "Nothing has changed"
193196
}
194197
}
195198
}

datamodel/openapi/openapi-generator-maven-plugin/src/test/resources/DataModelGeneratorMojoIntegrationTest/sodastore/output/com/sap/cloud/sdk/datamodel/rest/sodastore/api/DefaultApi.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public DefaultApi( @Nonnull final ApiClient apiClient )
7070
* @return Soda
7171
* @throws OpenApiRequestException if an error occurs while attempting to invoke the API
7272
*/
73-
@Nullable
73+
@Nonnull
7474
public Soda addSoda( @Nonnull final NewSoda newSoda) throws OpenApiRequestException {
7575
final Object localVarPostBody = newSoda;
7676

@@ -148,7 +148,7 @@ public OpenApiResponse deleteSodaById( @Nonnull final Long sodaId) throws OpenAp
148148
* @return Soda
149149
* @throws OpenApiRequestException if an error occurs while attempting to invoke the API
150150
*/
151-
@Nullable
151+
@Nonnull
152152
public Soda getSodaById( @Nonnull final Long sodaId) throws OpenApiRequestException {
153153
final Object localVarPostBody = null;
154154

@@ -185,7 +185,7 @@ public Soda getSodaById( @Nonnull final Long sodaId) throws OpenApiRequestExcept
185185
* @return List&lt;Soda&gt;
186186
* @throws OpenApiRequestException if an error occurs while attempting to invoke the API
187187
*/
188-
@Nullable
188+
@Nonnull
189189
public List<Soda> getSodas() throws OpenApiRequestException {
190190
final Object localVarPostBody = null;
191191

@@ -219,7 +219,7 @@ public List<Soda> getSodas() throws OpenApiRequestException {
219219
* @return Soda
220220
* @throws OpenApiRequestException if an error occurs while attempting to invoke the API
221221
*/
222-
@Nullable
222+
@Nonnull
223223
public Soda updateSodaById( @Nonnull final Long sodaId, @Nonnull final UpdateSoda updateSoda) throws OpenApiRequestException {
224224
final Object localVarPostBody = updateSoda;
225225

datamodel/openapi/openapi-generator/pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@
6868
</exclusion>
6969
</exclusions>
7070
</dependency>
71+
<dependency>
72+
<groupId>org.openapitools</groupId>
73+
<artifactId>openapi-generator-core</artifactId>
74+
</dependency>
7175
<dependency>
7276
<groupId>com.fasterxml.jackson.core</groupId>
7377
<artifactId>jackson-core</artifactId>
@@ -106,6 +110,18 @@
106110
<groupId>commons-io</groupId>
107111
<artifactId>commons-io</artifactId>
108112
</dependency>
113+
<dependency>
114+
<groupId>io.swagger.core.v3</groupId>
115+
<artifactId>swagger-models</artifactId>
116+
</dependency>
117+
<dependency>
118+
<groupId>io.swagger.parser.v3</groupId>
119+
<artifactId>swagger-parser</artifactId>
120+
</dependency>
121+
<dependency>
122+
<groupId>io.swagger.parser.v3</groupId>
123+
<artifactId>swagger-parser-core</artifactId>
124+
</dependency>
109125
<!-- scope test -->
110126
<dependency>
111127
<groupId>org.junit.jupiter</groupId>

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

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,28 @@
88
import java.nio.file.Paths;
99
import java.time.Year;
1010
import java.util.HashMap;
11+
import java.util.List;
1112
import java.util.Map;
1213

1314
import javax.annotation.Nonnull;
1415

1516
import org.openapitools.codegen.ClientOptInput;
1617
import org.openapitools.codegen.CodegenConstants;
17-
import org.openapitools.codegen.config.CodegenConfigurator;
18+
import org.openapitools.codegen.CodegenOperation;
19+
import org.openapitools.codegen.config.GeneratorSettings;
1820
import org.openapitools.codegen.config.GlobalSettings;
21+
import org.openapitools.codegen.languages.JavaClientCodegen;
22+
import org.openapitools.codegen.model.ModelMap;
23+
import org.openapitools.codegen.model.OperationsMap;
1924

2025
import com.google.common.base.Strings;
2126
import com.sap.cloud.sdk.datamodel.openapi.generator.model.ApiMaturity;
2227
import com.sap.cloud.sdk.datamodel.openapi.generator.model.GenerationConfiguration;
2328

29+
import io.swagger.parser.OpenAPIParser;
30+
import io.swagger.v3.oas.models.OpenAPI;
31+
import io.swagger.v3.parser.core.models.AuthorizationValue;
32+
import io.swagger.v3.parser.core.models.ParseOptions;
2433
import lombok.extern.slf4j.Slf4j;
2534

2635
/**
@@ -30,7 +39,6 @@
3039
@Slf4j
3140
class GenerationConfigurationConverter
3241
{
33-
private static final String GENERATOR_NAME = "java";
3442
private static final String IS_RELEASED_PROPERTY_KEY = "isReleased";
3543
private static final String JAVA_8_PROPERTY_KEY = "java8";
3644
private static final String DATE_LIBRARY_PROPERTY_KEY = "dateLibrary";
@@ -49,31 +57,40 @@ static ClientOptInput convertGenerationConfiguration(
4957
@Nonnull final GenerationConfiguration generationConfiguration,
5058
@Nonnull final Path inputSpec )
5159
{
52-
final CodegenConfigurator config = new CodegenConfigurator();
53-
54-
config.setVerbose(generationConfiguration.isVerbose());
55-
56-
config.setInputSpec(inputSpec.toString());
57-
58-
config.setGeneratorName(GENERATOR_NAME);
59-
60+
setGlobalSettings();
61+
final var inputSpecFile = inputSpec.toString();
62+
63+
final var config = new JavaClientCodegen()
64+
{
65+
// Custom processor to inject "x-return-nullable" extension
66+
@Override
67+
@Nonnull
68+
public OperationsMap postProcessOperationsWithModels(
69+
@Nonnull final OperationsMap ops,
70+
@Nonnull final List<ModelMap> allModels )
71+
{
72+
for( final CodegenOperation op : ops.getOperations().getOperation() ) {
73+
final var noContent =
74+
op.isResponseOptional
75+
|| op.responses == null
76+
|| op.responses.stream().anyMatch(r -> "204".equals(r.code));
77+
op.vendorExtensions.put("x-return-nullable", op.returnType != null && noContent);
78+
}
79+
return super.postProcessOperationsWithModels(ops, allModels);
80+
}
81+
};
6082
config.setOutputDir(generationConfiguration.getOutputDirectory());
61-
62-
config.setTemplateDir(TEMPLATE_DIRECTORY);
63-
64-
setAdditionalProperties(generationConfiguration, config);
65-
66-
config.setGenerateAliasAsModel(false);
67-
config.setRemoveOperationIdPrefix(true);
68-
6983
config.setLibrary(LIBRARY_NAME);
70-
7184
config.setApiPackage(generationConfiguration.getApiPackage());
7285
config.setModelPackage(generationConfiguration.getModelPackage());
86+
config.setTemplateDir(TEMPLATE_DIRECTORY);
87+
config.additionalProperties().putAll(getAdditionalProperties(generationConfiguration));
7388

74-
setGlobalSettings();
75-
76-
return config.toClientOptInput();
89+
final var clientOptInput = new ClientOptInput();
90+
clientOptInput.config(config);
91+
clientOptInput.generatorSettings(new GeneratorSettings());
92+
clientOptInput.openAPI(parseOpenApiSpec(inputSpecFile));
93+
return clientOptInput;
7794
}
7895

7996
private static void setGlobalSettings()
@@ -88,58 +105,52 @@ private static void setGlobalSettings()
88105
GlobalSettings.setProperty(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString());
89106
}
90107

91-
private static void setAdditionalProperties(
92-
final GenerationConfiguration generationConfiguration,
93-
final CodegenConfigurator config )
108+
private static OpenAPI parseOpenApiSpec( @Nonnull final String inputSpecFile )
94109
{
95-
log.info("Using {} as {}.", ApiMaturity.class.getSimpleName(), generationConfiguration.getApiMaturity());
96-
97-
final Map<String, Object> additionalProperties = new HashMap<>();
110+
final List<AuthorizationValue> authorizationValues = List.of();
111+
final var options = new ParseOptions();
112+
options.setResolve(true);
113+
final var spec = new OpenAPIParser().readLocation(inputSpecFile, authorizationValues, options);
114+
if( !spec.getMessages().isEmpty() ) {
115+
log.warn("Parsing the specification yielded the following messages: {}", spec.getMessages());
116+
}
117+
return spec.getOpenAPI();
118+
}
98119

99-
additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString());
120+
private static Map<String, Object> getAdditionalProperties( @Nonnull final GenerationConfiguration config )
121+
{
122+
log.info("Using {} as {}.", ApiMaturity.class.getSimpleName(), config.getApiMaturity());
100123

101-
switch( generationConfiguration.getApiMaturity() ) {
102-
case RELEASED: {
103-
additionalProperties.put(IS_RELEASED_PROPERTY_KEY, true);
104-
break;
105-
}
124+
final Map<String, Object> result = new HashMap<>();
125+
result.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString());
106126

107-
case BETA: {
108-
additionalProperties.remove(IS_RELEASED_PROPERTY_KEY);
109-
break;
110-
}
127+
switch( config.getApiMaturity() ) {
128+
case RELEASED -> result.put(IS_RELEASED_PROPERTY_KEY, true);
129+
case BETA -> result.remove(IS_RELEASED_PROPERTY_KEY);
111130
}
112131

113-
final String copyrightHeader =
114-
generationConfiguration.useSapCopyrightHeader()
115-
? SAP_COPYRIGHT_HEADER
116-
: generationConfiguration.getCopyrightHeader();
117-
132+
final var copyrightHeader = config.useSapCopyrightHeader() ? SAP_COPYRIGHT_HEADER : config.getCopyrightHeader();
118133
if( !Strings.isNullOrEmpty(copyrightHeader) ) {
119-
additionalProperties.put(COPYRIGHT_PROPERTY_KEY, copyrightHeader);
134+
result.put(COPYRIGHT_PROPERTY_KEY, copyrightHeader);
120135
}
121-
122-
additionalProperties.put(CodegenConstants.SERIALIZABLE_MODEL, "true");
123-
additionalProperties.put(JAVA_8_PROPERTY_KEY, "true");
124-
additionalProperties.put(DATE_LIBRARY_PROPERTY_KEY, "java8");
125-
additionalProperties.put(BOOLEAN_GETTER_PREFIX_PROPERTY_KEY, "is");
126-
additionalProperties.put(SOURCE_FOLDER_PROPERTY_KEY, "");
136+
result.put(CodegenConstants.SERIALIZABLE_MODEL, "true");
137+
result.put(JAVA_8_PROPERTY_KEY, "true");
138+
result.put(DATE_LIBRARY_PROPERTY_KEY, "java8");
139+
result.put(BOOLEAN_GETTER_PREFIX_PROPERTY_KEY, "is");
140+
result.put(SOURCE_FOLDER_PROPERTY_KEY, "");
127141
// this is set to false, to prevent issues with the JsonNullable annotation
128142
// long term fix part of BLI CLOUDECOSYSTEM-9843
129-
additionalProperties.put(OPEN_API_NULLABLE_PROPERTY_KEY, "false");
143+
result.put(OPEN_API_NULLABLE_PROPERTY_KEY, "false");
130144

131145
// this allows the customer to override the default Cloud SDK settings above
132-
generationConfiguration.getAdditionalProperties().forEach(( k, v ) -> {
133-
if( additionalProperties.containsKey(k) ) {
134-
log
135-
.info(
136-
"Replacing default value \"{}\" for additional property \"{}\" with \"{}\" from user provided configuration.",
137-
additionalProperties.get(k),
138-
k,
139-
v);
146+
config.getAdditionalProperties().forEach(( k, v ) -> {
147+
if( result.containsKey(k) ) {
148+
final var msg =
149+
"Replacing default value \"{}\" for additional property \"{}\" with \"{}\" from user provided configuration.";
150+
log.info(msg, result.get(k), k, v);
140151
}
141-
additionalProperties.put(k, v);
152+
result.put(k, v);
142153
});
143-
config.setAdditionalProperties(additionalProperties);
154+
return result;
144155
}
145156
}

datamodel/openapi/openapi-generator/src/main/resources/openapi-generator/mustache-templates/auth/ApiKeyAuth.mustache

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {{invokerPackage}}.Pair;
77
import java.util.Map;
88
import java.util.List;
99

10-
{{>generatedAnnotation}}
1110
public class ApiKeyAuth implements Authentication {
1211
private final String location;
1312
private final String paramName;

datamodel/openapi/openapi-generator/src/main/resources/openapi-generator/mustache-templates/auth/HttpBasicAuth.mustache

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import java.nio.charset.StandardCharsets;
1010
import java.util.Map;
1111
import java.util.List;
1212

13-
{{>generatedAnnotation}}
1413
public class HttpBasicAuth implements Authentication {
1514
private String username;
1615
private String password;

datamodel/openapi/openapi-generator/src/main/resources/openapi-generator/mustache-templates/auth/HttpBearerAuth.mustache

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {{invokerPackage}}.Pair;
77
import java.util.Map;
88
import java.util.List;
99

10-
{{>generatedAnnotation}}
1110
public class HttpBearerAuth implements Authentication {
1211
private final String scheme;
1312
private String bearerToken;

datamodel/openapi/openapi-generator/src/main/resources/openapi-generator/mustache-templates/auth/OAuth.mustache

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {{invokerPackage}}.Pair;
77
import java.util.Map;
88
import java.util.List;
99

10-
{{>generatedAnnotation}}
1110
public class OAuth implements Authentication {
1211
private String accessToken;
1312

0 commit comments

Comments
 (0)