diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index 1e4966697..fb0639c1a 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -53,7 +53,10 @@ final class ODataGenericConverter extends AbstractTypeConverter(Duration.class, Duration::toString, Duration::parse); private static final ODataGenericConverter BINARY = - new ODataGenericConverter<>(byte[].class, Base64.getEncoder()::encodeToString, Base64.getDecoder()::decode); + new ODataGenericConverter<>( + byte[].class, + Base64.getEncoder()::encodeToString, + ODataGenericConverter::decodeBinary); private static final ODataGenericConverter STRING = new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); @@ -73,6 +76,15 @@ final class ODataGenericConverter extends AbstractTypeConverter serializer; private final Function deserializer; + @Nonnull + private static byte[] decodeBinary( @Nonnull final String value ) + { + // Normalize URL-safe characters to standard Base64 + final String normalized = value.replace('-', '+').replace('_', '/'); + + return Base64.getDecoder().decode(normalized); + } + @Nonnull @Override public ConvertedObject toDomainNonNull( @Nonnull final JavaT object ) diff --git a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java index a3c02d65f..642b4656e 100644 --- a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java +++ b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java @@ -8,6 +8,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.util.Base64; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -158,6 +159,15 @@ public static class ReferenceObject extends VdmEntity "GeographyPoint":{"type":"Point","coordinates":[142.1,64.1]}\ }\ """; + + private static final String PAYLOAD_ODATA_REFERENCE_BASE64URL = + PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"-__v\""); + + private static final String PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET = + PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"+__v\""); + + static final String Base_64 = "+//v"; + static final String Base_64_Url = "-__v"; } @Test @@ -174,6 +184,21 @@ void testBinaryFieldParsingFromResponsePayload() assertThat(ser).isEqualTo(ReferenceObject.PAYLOAD_ODATA_REFERENCE); } + @Test + void testBinaryFieldParsingFromBase64UrlResponsePayload() + { + final ODataRequestResultGeneric result = mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE_BASE64URL); + final ReferenceObject referenceResult = result.as(ReferenceObject.class); + + Objects.requireNonNull(referenceResult); + assertThat(referenceResult.getBinaryValue()) + .isEqualTo(Base64.getUrlDecoder().decode(ReferenceObject.Base_64_Url)); + + final String ser = + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + assertThat(ser).contains("\"BinaryValue\":\"" + ReferenceObject.Base_64 + "\""); + } + @Test void testCustomFieldParsingFromResponsePayload() { @@ -186,6 +211,21 @@ void testCustomFieldParsingFromResponsePayload() assertThat(referenceResult. getCustomField("GeographyPoint")).isInstanceOf(Map.class); } + @Test + void testBinaryFieldParsingFromMixedBase64AlphabetNormalized() + { + final ODataRequestResultGeneric result = + mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET); + final ReferenceObject referenceResult = result.as(ReferenceObject.class); + + Objects.requireNonNull(referenceResult); + assertThat(referenceResult.getBinaryValue()).isEqualTo(Base64.getDecoder().decode(ReferenceObject.Base_64)); + + final String ser = + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + assertThat(ser).contains("\"BinaryValue\":\"" + ReferenceObject.Base_64 + "\""); + } + @SneakyThrows private static ODataRequestResultGeneric mockRequestResult( final String payload ) { diff --git a/release_notes.md b/release_notes.md index bc140eba2..9ef4c9e9d 100644 --- a/release_notes.md +++ b/release_notes.md @@ -14,6 +14,7 @@ ### ✨ New Functionality - [OpenAPI] Cloud SDK OpenAPI Generator now supports `apache-httpclient` library besides Spring RestTemplate through the newly introduced module `openapi-core-apache`. +- [IAS] Add `IasOptions.withTokenFormat()` to allow specifying token format ### 📈 Improvements @@ -21,4 +22,4 @@ ### 🐛 Fixed Issues -- +- [OData v4] Binary deserialization can now handle both `Base64URL` and `Base64`.