feat: add pluggable Base64Codec interface for Android compatibility#223
feat: add pluggable Base64Codec interface for Android compatibility#223typotter merged 45 commits intofeature/v4from
Conversation
- EppoConfigurationRequest: @NotNull on getters - EppoConfigurationRequestFactory: @NotNull on factory method returns - EppoConfigurationResponse: @Nullable/@NotNull on constructor, factory methods, and getters - ConfigurationParser: @NotNull on method params and returns - EppoConfigurationClient: @NotNull on get() method
… the ConfigurationRequestor
Add pluggable Base64 codec support to allow platform-specific implementations (e.g., Android SDK using android.util.Base64). - Add public Base64Codec interface with encode/decode methods - Add setBase64Codec() method for custom codec injection - Create DefaultBase64Codec inner class using java.util.Base64 - Delegate existing base64Encode/base64Decode methods to codec
Remove duplicate base64Decode implementation from FlagConfigResponseDeserializer and use the centralized version from Utils via static import.
Add tests to verify Base64 codec pluggability: - testCustomBase64Codec: verifies custom codec injection works - testBase64EncodeDecodeDefault: verifies default codec behavior - @AfterEach reset method ensures test isolation
- Add volatile keyword to base64Codec field for thread safety - Add null check in setBase64Codec() throwing IllegalArgumentException - Add resetBase64Codec() method for test cleanup - Use explicit StandardCharsets.UTF_8 in base64Decode - Add test for null codec rejection - Simplify test reset to use resetBase64Codec method
There was a problem hiding this comment.
Pull request overview
This PR introduces a pluggable Base64 codec interface to enable Android compatibility. The java.util.Base64 class is only available on Android API 26+, so this change allows platform-specific implementations (e.g., using android.util.Base64 for older Android versions) to be injected at runtime.
Changes:
- Added
Base64Codecpublic interface with encoding/decoding methods and a setter for custom implementations - Refactored existing Base64 logic into a
DefaultBase64Codecimplementation that maintains backward compatibility - Removed duplicate
base64Decodeimplementation fromFlagConfigResponseDeserializerto use centralized Utils method - Added comprehensive test coverage for custom codec injection, default behavior, and null handling
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/main/java/cloud/eppo/Utils.java | Added Base64Codec interface, setBase64Codec() setter, resetBase64Codec() for testing, and DefaultBase64Codec implementation with improved charset handling |
| eppo-sdk-common/src/main/java/cloud/eppo/ufc/dto/adapters/FlagConfigResponseDeserializer.java | Removed duplicate base64Decode implementation in favor of centralized Utils method |
| src/test/java/cloud/eppo/UtilsTest.java | Added comprehensive tests for custom codec injection, default behavior, null handling, and test isolation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
aarsilv
left a comment
There was a problem hiding this comment.
Nice! Approving with minor comments
| "zero byte output from Base64; if not running on Android hardware be sure to use" | ||
| + " RobolectricTestRunner"); |
There was a problem hiding this comment.
I wonder if we need something similar for base64Encode
There was a problem hiding this comment.
🤔 Looking a little wider at it, we're only actually using the encode method in tests. I'd prefer to keep the Codec here just in case prod code needs to encode in the future we don't have to make a breaking change to add it.
| public class UtilsTest { | ||
|
|
||
| @AfterEach | ||
| void resetCodec() { |
There was a problem hiding this comment.
any reason these are package level instead of class? (e.g., why no public keyword here?)
There was a problem hiding this comment.
package-private is enough for JUnit to find them (uses reflection/annotation to find the methods)
| assertEquals("encoded:test", Utils.base64Encode("test")); | ||
| assertTrue(encodeCalled.get()); | ||
|
|
||
| assertEquals("decoded:test", Utils.base64Decode("test")); | ||
| assertTrue(decodeCalled.get()); |
6c4391b to
54cfb3c
Compare
Motivation and Context
java.util.Base64is only available on Android API 26+ (Android 8.0 Oreo). The Android SDK needs to useandroid.util.Base64for compatibility with older devices. This change introduces a pluggable Base64 codec interface that allows platform-specific implementations.Reference: Eppo-exp/android-sdk#188
Description
Base64Codecpublic interface inUtils.javawithbase64Encode(String)andbase64Decode(String)methodssetBase64Codec(Base64Codec)static method to allow consumers to inject custom implementationsDefaultBase64Codecprivate inner class that usesjava.util.Base64(existing behavior)base64Encode()andbase64Decode()static methods to delegate to the codec instancebase64Decodeimplementation fromFlagConfigResponseDeserializer.javaHow has this been documented?
How has this been tested?
testCustomBase64Codec()to verify custom codec injection works correctlytestBase64EncodeDecodeDefault()to verify default codec behavior including null handling and round-trip encoding@AfterEachreset method to ensure test isolation./gradlew spotlessApplyand./gradlew checksuccessfully