From 2bd6e2e9b4c1f1b516167dcbe747bf66bfffa16d Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 25 Aug 2026 15:22:24 +0200 Subject: [PATCH 1/4] fix: [OpenAPI] String properties are not mistakenly generated as File anymore --- .../generator/CustomOpenAPINormalizer.java | 3 +- .../DataModelGeneratorIntegrationTest.java | 13 +- .../double-string/input/sodastore.yaml | 43 +++++ .../datamodel/rest/test/api/DefaultApi.java | 135 ++++++++++++++ .../rest/test/model/PredictionConfig.java | 173 ++++++++++++++++++ 5 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/input/sodastore.yaml create mode 100644 datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java create mode 100644 datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java diff --git a/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/CustomOpenAPINormalizer.java b/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/CustomOpenAPINormalizer.java index b4dd6a3a8..2d9d58460 100644 --- a/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/CustomOpenAPINormalizer.java +++ b/datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/CustomOpenAPINormalizer.java @@ -137,7 +137,8 @@ public Schema normalizeSchema( final @Nonnull Schema schema, final @Nonnull Set< } else if( schema.getContentEncoding() != null ) { // Any other content encoding (e.g., "binary") → treat as binary schema.setFormat("binary"); - } else if( schema.getContentMediaType() != null ) { + } else if( schema.getContentMediaType() != null + && !"application/json".equalsIgnoreCase(schema.getContentMediaType()) ) { // contentMediaType without contentEncoding → binary stream schema.setFormat("binary"); } diff --git a/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java b/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java index 0a3d68b90..08bf1c189 100644 --- a/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java +++ b/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java @@ -60,6 +60,17 @@ enum TestCase 6, Map.of(), Map.of()), + DOUBLE_STRING( + "double-string", + "sodastore.yaml", + "com.sap.cloud.sdk.datamodel.rest.test.api", + "com.sap.cloud.sdk.datamodel.rest.test.model", + ApiMaturity.RELEASED, + false, + true, + 2, + Map.of(), + Map.of()), INLINEOBJECT_SCHEMA_NAME( "inlineobject-schemas-enabled", "sodastore.yaml", @@ -257,7 +268,7 @@ enum TestCase } @ParameterizedTest - @EnumSource( value = TestCase.class, mode = EnumSource.Mode.EXCLUDE, names = { "FILE_HANDLING" } ) + @EnumSource( value = TestCase.class, mode = EnumSource.Mode.EXCLUDE, names = { "FILE_HANDLING", "DOUBLE_STRING" } ) void integrationTests( final TestCase testCase, @TempDir final Path path ) throws Throwable { diff --git a/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/input/sodastore.yaml b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/input/sodastore.yaml new file mode 100644 index 000000000..1c330cdee --- /dev/null +++ b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/input/sodastore.yaml @@ -0,0 +1,43 @@ +openapi: 3.1.0 +info: + title: String and File multipart body + version: 1.0.0 + +paths: + /predict_parquet: + post: + summary: Make predictions from Parquet file + operationId: predict_parquet + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/Body_predict_parquet' + encoding: + file: + contentType: application/vnd.apache.parquet + required: true + +components: + schemas: + Body_predict_parquet: + type: object + properties: + file: + type: string + contentMediaType: application/vnd.apache.parquet + title: File + prediction_config: + type: string + title: Prediction Config + description: JSON string containing the prediction configuration (see PredictionConfig schema). + example: { \"target_columns\": [ { \"name\": \"PRICE\",\"prediction_placeholder\": null,\"task_type\": \"regression\" } ] } + contentMediaType: application/json + contentSchema: + $ref: '#/components/schemas/PredictionConfig' + PredictionConfig: + type: object + properties: + test: + type: string + title: Test diff --git a/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java new file mode 100644 index 000000000..09a346c0a --- /dev/null +++ b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ + +package com.sap.cloud.sdk.datamodel.rest.test.api; + +import com.fasterxml.jackson.core.type.TypeReference; + +import com.sap.cloud.sdk.services.openapi.apache.core.OpenApiRequestException; +import com.sap.cloud.sdk.services.openapi.apache.core.OpenApiResponse; +import com.sap.cloud.sdk.services.openapi.apache.apiclient.ApiClient; +import com.sap.cloud.sdk.services.openapi.apache.apiclient.BaseApi; +import com.sap.cloud.sdk.services.openapi.apache.apiclient.Pair; + + +import java.io.File; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.StringJoiner; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; + + +/** + * String and File multipart body in version 1.0.0. + *

+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + */ +public class DefaultApi extends BaseApi { + + /** + * Instantiates this API class to invoke operations on the String and File multipart body. + * + * @param httpDestination The destination that API should be used with + */ + public DefaultApi( @Nonnull final Destination httpDestination ) + { + super(httpDestination); + } + + /** + * Instantiates this API class to invoke operations on the String and File multipart body based on a given {@link ApiClient}. + * + * @param apiClient + * ApiClient to invoke the API on + */ + public DefaultApi(@Nonnull final ApiClient apiClient) { + super(apiClient); + } + + /** + * Creates a new API instance with additional default headers. + * + * @param defaultHeaders Additional headers to include in all requests + * @return A new API instance with the combined headers + */ + public DefaultApi withDefaultHeaders(@Nonnull final Map defaultHeaders) { + final var api = new DefaultApi(apiClient); + api.defaultHeaders.putAll(this.defaultHeaders); + api.defaultHeaders.putAll(defaultHeaders); + return api; + } + + /** + *

Make predictions from Parquet file + *

+ * @param _file (optional) + * The value for the parameter _file + * @param predictionConfig (optional) + * JSON string containing the prediction configuration (see PredictionConfig schema). + * @return An OpenApiResponse containing the status code of the HttpResponse. + * @throws OpenApiRequestException if an error occurs while attempting to invoke the API + */ + @Nonnull + public OpenApiResponse predictParquet(@Nullable final File _file , @Nullable final String predictionConfig ) throws OpenApiRequestException { + + // create path and map variables + final String localVarPath = "/predict_parquet"; + + final StringJoiner localVarQueryStringJoiner = new StringJoiner("&"); + final List localVarQueryParams = new ArrayList(); + final List localVarCollectionQueryParams = new ArrayList(); + final Map localVarHeaderParams = new HashMap(defaultHeaders); + final Map localVarFormParams = new HashMap(); + + if (_file != null) + localVarFormParams.put("file", _file); +if (predictionConfig != null) + localVarFormParams.put("prediction_config", predictionConfig); + + final String[] localVarAccepts = { + + }; + final String localVarAccept = ApiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = { + "multipart/form-data" + }; + final String localVarContentType = ApiClient.selectHeaderContentType(localVarContentTypes); + + final TypeReference localVarReturnType = new TypeReference() {}; + + return apiClient.invokeAPI( + localVarPath, + "POST", + localVarQueryParams, + localVarCollectionQueryParams, + localVarQueryStringJoiner.toString(), + null, + localVarHeaderParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarReturnType + ); + } + + /** + *

Make predictions from Parquet file + *

+ * @return An OpenApiResponse containing the status code of the HttpResponse. + * @throws OpenApiRequestException if an error occurs while attempting to invoke the API + */ + @Nonnull + public OpenApiResponse predictParquet() throws OpenApiRequestException { + return predictParquet(null, null); + } + } diff --git a/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java new file mode 100644 index 000000000..11f120644 --- /dev/null +++ b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ + +/* + * String and File multipart body + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package com.sap.cloud.sdk.datamodel.rest.test.model; + +import java.util.Objects; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * PredictionConfig + */ +// CHECKSTYLE:OFF +public class PredictionConfig +// CHECKSTYLE:ON +{ + @JsonProperty("test") + private String test; + + @JsonAnySetter + @JsonAnyGetter + private final Map cloudSdkCustomFields = new LinkedHashMap<>(); + + /** + * Set the test of this {@link PredictionConfig} instance and return the same instance. + * + * @param test The test of this {@link PredictionConfig} + * @return The same instance of this {@link PredictionConfig} class + */ + @Nonnull public PredictionConfig test( @Nullable final String test) { + this.test = test; + return this; + } + + /** + * Get test + * @return test The test of this {@link PredictionConfig} instance. + */ + @Nonnull + public String getTest() { + return test; + } + + /** + * Set the test of this {@link PredictionConfig} instance. + * + * @param test The test of this {@link PredictionConfig} + */ + public void setTest( @Nullable final String test) { + this.test = test; + } + + /** + * Get the names of the unrecognizable properties of the {@link PredictionConfig}. + * @return The set of properties names + */ + @JsonIgnore + @Nonnull + public Set getCustomFieldNames() { + return cloudSdkCustomFields.keySet(); + } + + /** + * Get the value of an unrecognizable property of this {@link PredictionConfig} instance. + * @deprecated Use {@link #toMap()} instead. + * @param name The name of the property + * @return The value of the property + * @throws NoSuchElementException If no property with the given name could be found. + */ + @Nullable + @Deprecated + public Object getCustomField( @Nonnull final String name ) throws NoSuchElementException { + if( !cloudSdkCustomFields.containsKey(name) ) { + throw new NoSuchElementException("PredictionConfig has no field with name '" + name + "'."); + } + return cloudSdkCustomFields.get(name); + } + + /** + * Get the value of all properties of this {@link PredictionConfig} instance including unrecognized properties. + * + * @return The map of all properties + */ + @JsonIgnore + @Nonnull + public Map toMap() + { + final Map declaredFields = new LinkedHashMap<>(cloudSdkCustomFields); + if( test != null ) declaredFields.put("test", test); + return declaredFields; + } + + /** + * Set an unrecognizable property of this {@link PredictionConfig} instance. If the map previously contained a mapping + * for the key, the old value is replaced by the specified value. + * @param customFieldName The name of the property + * @param customFieldValue The value of the property + */ + @JsonIgnore + public void setCustomField( @Nonnull String customFieldName, @Nullable Object customFieldValue ) + { + cloudSdkCustomFields.put(customFieldName, customFieldValue); + } + + + @Override + public boolean equals(@Nullable final java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final PredictionConfig predictionConfig = (PredictionConfig) o; + return Objects.equals(this.cloudSdkCustomFields, predictionConfig.cloudSdkCustomFields) && + Objects.equals(this.test, predictionConfig.test); + } + + @Override + public int hashCode() { + return Objects.hash(test, cloudSdkCustomFields); + } + + @Override + @Nonnull public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("class PredictionConfig {\n"); + sb.append(" test: ").append(toIndentedString(test)).append("\n"); + cloudSdkCustomFields.forEach((k,v) -> sb.append(" ").append(k).append(": ").append(toIndentedString(v)).append("\n")); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(final java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + From 0311a534d75481b4328682cc5b424efb45799190 Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 25 Aug 2026 15:28:08 +0200 Subject: [PATCH 2/4] release notes --- release_notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_notes.md b/release_notes.md index 3bcc51e43..8a8bc3f28 100644 --- a/release_notes.md +++ b/release_notes.md @@ -20,4 +20,4 @@ ### 🐛 Fixed Issues -- +- [OpenAPI Generator] `String` properties with `contentMediaType: application/json` are not mistakenly generated as `File` anymore From 8866ae0d75cf7442080a86fafc04e55e6851a228 Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 25 Aug 2026 15:49:39 +0200 Subject: [PATCH 3/4] Renamed to MULTIPART_BINARY_STRING --- .../generator/DataModelGeneratorIntegrationTest.java | 6 +++--- .../input/sodastore.yaml | 0 .../sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java | 0 .../sdk/datamodel/rest/test/model/PredictionConfig.java | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/{double-string => multipart-binary-string}/input/sodastore.yaml (100%) rename datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/{double-string => multipart-binary-string}/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java (100%) rename datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/{double-string => multipart-binary-string}/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java (100%) diff --git a/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java b/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java index 08bf1c189..61d4a1d77 100644 --- a/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java +++ b/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java @@ -60,8 +60,8 @@ enum TestCase 6, Map.of(), Map.of()), - DOUBLE_STRING( - "double-string", + MULTIPART_BINARY_STRING( + "multipart-binary-string", "sodastore.yaml", "com.sap.cloud.sdk.datamodel.rest.test.api", "com.sap.cloud.sdk.datamodel.rest.test.model", @@ -268,7 +268,7 @@ enum TestCase } @ParameterizedTest - @EnumSource( value = TestCase.class, mode = EnumSource.Mode.EXCLUDE, names = { "FILE_HANDLING", "DOUBLE_STRING" } ) + @EnumSource( value = TestCase.class, mode = EnumSource.Mode.EXCLUDE, names = { "FILE_HANDLING", "MULTIPART_BINARY_STRING" } ) void integrationTests( final TestCase testCase, @TempDir final Path path ) throws Throwable { diff --git a/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/input/sodastore.yaml b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/multipart-binary-string/input/sodastore.yaml similarity index 100% rename from datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/input/sodastore.yaml rename to datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/multipart-binary-string/input/sodastore.yaml diff --git a/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/multipart-binary-string/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java similarity index 100% rename from datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java rename to datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/multipart-binary-string/output/com/sap/cloud/sdk/datamodel/rest/test/api/DefaultApi.java diff --git a/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java b/datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/multipart-binary-string/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java similarity index 100% rename from datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/double-string/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java rename to datamodel/openapi/openapi-generator/src/test/resources/DataModelGeneratorApacheIntegrationTest/multipart-binary-string/output/com/sap/cloud/sdk/datamodel/rest/test/model/PredictionConfig.java From b03da509494079d7c9eefbce02d8be2255775f92 Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 25 Aug 2026 15:51:15 +0200 Subject: [PATCH 4/4] formatting --- .../openapi/generator/DataModelGeneratorIntegrationTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java b/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java index 61d4a1d77..1999b7ac9 100644 --- a/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java +++ b/datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java @@ -268,7 +268,10 @@ enum TestCase } @ParameterizedTest - @EnumSource( value = TestCase.class, mode = EnumSource.Mode.EXCLUDE, names = { "FILE_HANDLING", "MULTIPART_BINARY_STRING" } ) + @EnumSource( + value = TestCase.class, + mode = EnumSource.Mode.EXCLUDE, + names = { "FILE_HANDLING", "MULTIPART_BINARY_STRING" } ) void integrationTests( final TestCase testCase, @TempDir final Path path ) throws Throwable {