Skip to content

fix: decode base64-encoded values for all precomputed flag types#97

Merged
sameerank merged 4 commits intomainfrom
sameerank/FFL-1916-fix-inconsistent-base64-decoding
Mar 12, 2026
Merged

fix: decode base64-encoded values for all precomputed flag types#97
sameerank merged 4 commits intomainfrom
sameerank/FFL-1916-fix-inconsistent-base64-decoding

Conversation

@sameerank
Copy link
Copy Markdown
Contributor

@sameerank sameerank commented Mar 12, 2026

🎟️ Fixes FFL-1916
📜 Design Doc: N/A

Motivation and Context

When using EppoPrecomputedClient, all flags of type BOOLEAN, INTEGER, and NUMERIC always return the defaultValue instead of the actual assigned value.

The precomputed API endpoint returns all variation values as base64-encoded strings, regardless of type. For example:

  • BOOLEAN true arrives as "dHJ1ZQ==" (base64 of "true")
  • INTEGER 42 arrives as "NDI=" (base64 of "42")
  • NUMERIC 3.14 arrives as "My4xNA==" (base64 of "3.14")

However, the decodeFlag method in PrecomputedConfiguration.swift only applied base64 decoding for .string and .json variation types. For other types, the raw base64-encoded string was used as-is, causing type conversion to fail silently and return the default value.

Description

  1. Extended decodeFlag to handle all variation types:

    • STRING/JSON: Base64-decode to string (unchanged)
    • BOOLEAN: Base64-decode, then parse with Bool(decodedString.lowercased())
    • INTEGER/NUMERIC: Base64-decode, then parse with Double(decodedString)
  2. Fixed test helper to properly encode all types:

    • Updated createTestFlag in PrecomputedTestHelpers.swift to base64-encode boolean, integer, and numeric values (matching the actual wire format)
  3. Added custom decoder for PrecomputedFlag:

    • Handle missing extraLogging field gracefully (defaults to empty dictionary)

How has this been documented?

No documentation changes required - this is a bug fix that makes the SDK behave as expected.

How has this been tested?

  • Added 4 new tests in PrecomputedConfigurationWireTests.swift:
    • testBooleanFlagAssignmentWithBase64EncodedValue
    • testIntegerFlagAssignmentWithBase64EncodedValue
    • testNumericFlagAssignmentWithBase64EncodedValue
    • testAllFlagTypesWithBase64EncodedValues
  • All 208 existing tests continue to pass
  • Manually tested with a toy iOS app against real precomputed API responses
    • Used the dart-test-flag-<type> flags with defaults ("DEFAULT", false, -1, -1.0, and {}) and observed that the precomputed client returned values configured for the flags instead
      demo

The precomputed API returns all variation values as base64-encoded strings,
regardless of type. Previously, only STRING and JSON types were decoded,
causing BOOLEAN, INTEGER, and NUMERIC flags to always return defaultValue.

- Extend decodeFlag to base64-decode and parse all variation types
- Fix test helper to properly encode all types in wire format
- Add tests for boolean, integer, and numeric flag assignments
Add custom decoder to default extraLogging to empty dictionary when
the field is absent in the API response.
@sameerank sameerank marked this pull request as ready for review March 12, 2026 04:29
Comment on lines -238 to 261
if flag.variationType == .string || flag.variationType == .json {
do {
let encodedString = try flag.variationValue.getStringValue()
let decodedString = try base64DecodeOrThrow(encodedString)
do {
let encodedString = try flag.variationValue.getStringValue()
let decodedString = try base64DecodeOrThrow(encodedString)

switch flag.variationType {
case .string, .json:
decodedVariationValue = EppoValue(value: decodedString)
} catch {
print("Warning: Failed to decode variationValue, skipping flag - error: \(error.localizedDescription)")
return nil
case .boolean:
guard let boolValue = Bool(decodedString.lowercased()) else {
print("Warning: Failed to parse boolean value '\(decodedString)', skipping flag")
return nil
}
decodedVariationValue = EppoValue(value: boolValue)
case .integer, .numeric:
guard let doubleValue = Double(decodedString) else {
print("Warning: Failed to parse numeric value '\(decodedString)', skipping flag")
return nil
}
decodedVariationValue = EppoValue(value: doubleValue)
}
} else {
decodedVariationValue = flag.variationValue
} catch {
print("Warning: Failed to decode variationValue, skipping flag - error: \(error.localizedDescription)")
return nil
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this already exist for local-evaluation decoding?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it did. Refactored 3618b73

Extract duplicate variation value decoding logic into a shared function
in Obfuscation.swift. This is used by both local evaluation (FlagEvaluation)
and precomputed flags (PrecomputedConfiguration).

The shared function uses case-insensitive boolean comparison, aligning
with Android SDK behavior. This is an edge case since the server always
sends lowercase "true"/"false" values.
@sameerank sameerank merged commit 8b9f8f9 into main Mar 12, 2026
2 checks passed
@sameerank sameerank deleted the sameerank/FFL-1916-fix-inconsistent-base64-decoding branch March 12, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants