Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
15
core/featureflag/build.gradle.kts
Normal file
15
core/featureflag/build.gradle.kts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.kmp)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "net.thunderbird.core.featureflag"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.androidx.annotation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package net.thunderbird.core.featureflag
|
||||
|
||||
data class FeatureFlag(
|
||||
val key: FeatureFlagKey,
|
||||
val enabled: Boolean = false,
|
||||
)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package net.thunderbird.core.featureflag
|
||||
|
||||
fun interface FeatureFlagFactory {
|
||||
fun createFeatureCatalog(): List<FeatureFlag>
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package net.thunderbird.core.featureflag
|
||||
|
||||
@JvmInline
|
||||
value class FeatureFlagKey(val key: String) {
|
||||
companion object Keys {
|
||||
val DisplayInAppNotifications = "display_in_app_notifications".toFeatureFlagKey()
|
||||
val UseNotificationSenderForSystemNotifications =
|
||||
"use_notification_sender_for_system_notifications".toFeatureFlagKey()
|
||||
}
|
||||
}
|
||||
|
||||
fun String.toFeatureFlagKey(): FeatureFlagKey = FeatureFlagKey(this)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package net.thunderbird.core.featureflag
|
||||
|
||||
fun interface FeatureFlagProvider {
|
||||
fun provide(key: FeatureFlagKey): FeatureFlagResult
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package net.thunderbird.core.featureflag
|
||||
|
||||
sealed interface FeatureFlagResult {
|
||||
data object Enabled : FeatureFlagResult
|
||||
data object Disabled : FeatureFlagResult
|
||||
data object Unavailable : FeatureFlagResult
|
||||
|
||||
fun <T> whenEnabledOrNot(
|
||||
onEnabled: () -> T,
|
||||
onDisabledOrUnavailable: () -> T,
|
||||
): T = when (this) {
|
||||
is Enabled -> onEnabled()
|
||||
is Disabled, Unavailable -> onDisabledOrUnavailable()
|
||||
}
|
||||
|
||||
fun onEnabled(action: () -> Unit): FeatureFlagResult {
|
||||
if (this is Enabled) {
|
||||
action()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun onDisabled(action: () -> Unit): FeatureFlagResult {
|
||||
if (this is Disabled) {
|
||||
action()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun onUnavailable(action: () -> Unit): FeatureFlagResult {
|
||||
if (this is Unavailable) {
|
||||
action()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun onDisabledOrUnavailable(action: () -> Unit): FeatureFlagResult {
|
||||
if (this is Disabled || this is Unavailable) {
|
||||
action()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package net.thunderbird.core.featureflag
|
||||
|
||||
class InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory: FeatureFlagFactory,
|
||||
) : FeatureFlagProvider {
|
||||
|
||||
private val features: Map<FeatureFlagKey, FeatureFlag> =
|
||||
featureFlagFactory.createFeatureCatalog().associateBy { it.key }
|
||||
|
||||
override fun provide(key: FeatureFlagKey): FeatureFlagResult {
|
||||
return when (features[key]?.enabled) {
|
||||
null -> FeatureFlagResult.Unavailable
|
||||
true -> FeatureFlagResult.Enabled
|
||||
false -> FeatureFlagResult.Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
@file:JvmName("FeatureFlagProviderCompat")
|
||||
|
||||
package net.thunderbird.core.featureflag.compat
|
||||
|
||||
import androidx.annotation.Discouraged
|
||||
import net.thunderbird.core.featureflag.FeatureFlagKey
|
||||
import net.thunderbird.core.featureflag.FeatureFlagProvider
|
||||
import net.thunderbird.core.featureflag.FeatureFlagResult
|
||||
import net.thunderbird.core.featureflag.toFeatureFlagKey
|
||||
|
||||
/**
|
||||
* Provides a feature flag result based on a string key, primarily for Java compatibility.
|
||||
*
|
||||
* This function acts as a bridge for Java code to access the Kotlin-idiomatic `provide`
|
||||
* function that expects a [FeatureFlagKey], as value classes are not compatible with Java
|
||||
* code.
|
||||
*
|
||||
* **Note:** This function is discouraged for use in Kotlin code. Prefer using the
|
||||
* [FeatureFlagProvider.provide(key: FeatureFlagKey)][FeatureFlagProvider.provide]
|
||||
* function directly in Kotlin.
|
||||
*
|
||||
* @receiver The [FeatureFlagProvider] instance to query.
|
||||
* @param key The string representation of the feature flag key.
|
||||
* @return The [FeatureFlagResult] corresponding to the given key.
|
||||
*/
|
||||
@Discouraged(message = "This function should be only used within Java files.")
|
||||
fun FeatureFlagProvider.provide(key: String): FeatureFlagResult {
|
||||
return provide(key.toFeatureFlagKey())
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package net.thunderbird.core.featureflag
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Test
|
||||
|
||||
class FeatureFlagResultTest {
|
||||
|
||||
@Test
|
||||
fun `should only call onEnabled when enabled`() {
|
||||
val testSubject = FeatureFlagResult.Enabled
|
||||
|
||||
var resultEnabled = ""
|
||||
var resultDisabled = ""
|
||||
var resultUnavailable = ""
|
||||
|
||||
testSubject.onEnabled {
|
||||
resultEnabled = "enabled"
|
||||
}.onDisabled {
|
||||
resultDisabled = "disabled"
|
||||
}.onUnavailable {
|
||||
resultUnavailable = "unavailable"
|
||||
}
|
||||
|
||||
assertThat(resultEnabled).isEqualTo("enabled")
|
||||
assertThat(resultDisabled).isEqualTo("")
|
||||
assertThat(resultUnavailable).isEqualTo("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should only call onDisabled when disabled`() {
|
||||
val testSubject = FeatureFlagResult.Disabled
|
||||
|
||||
var resultEnabled = ""
|
||||
var resultDisabled = ""
|
||||
var resultUnavailable = ""
|
||||
|
||||
testSubject.onEnabled {
|
||||
resultEnabled = "enabled"
|
||||
}.onDisabled {
|
||||
resultDisabled = "disabled"
|
||||
}.onUnavailable {
|
||||
resultUnavailable = "unavailable"
|
||||
}
|
||||
|
||||
assertThat(resultEnabled).isEqualTo("")
|
||||
assertThat(resultDisabled).isEqualTo("disabled")
|
||||
assertThat(resultUnavailable).isEqualTo("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should only call onUnavailable when unavailable`() {
|
||||
val testSubject = FeatureFlagResult.Unavailable
|
||||
|
||||
var resultEnabled = ""
|
||||
var resultDisabled = ""
|
||||
var resultUnavailable = ""
|
||||
|
||||
testSubject.onEnabled {
|
||||
resultEnabled = "enabled"
|
||||
}.onDisabled {
|
||||
resultDisabled = "disabled"
|
||||
}.onUnavailable {
|
||||
resultUnavailable = "unavailable"
|
||||
}
|
||||
|
||||
assertThat(resultEnabled).isEqualTo("")
|
||||
assertThat(resultDisabled).isEqualTo("")
|
||||
assertThat(resultUnavailable).isEqualTo("unavailable")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should call onDisabledOrUnavailable when disabled`() {
|
||||
val testSubject = FeatureFlagResult.Disabled
|
||||
|
||||
var resultEnabled = ""
|
||||
var resultDisabled = ""
|
||||
var resultUnavailable = ""
|
||||
var resultDisabledOrUnavailable = ""
|
||||
|
||||
testSubject.onEnabled {
|
||||
resultEnabled = "enabled"
|
||||
}.onDisabled {
|
||||
resultDisabled = "disabled"
|
||||
}.onUnavailable {
|
||||
resultUnavailable = "unavailable"
|
||||
}.onDisabledOrUnavailable {
|
||||
resultDisabledOrUnavailable = "disabled or unavailable"
|
||||
}
|
||||
|
||||
assertThat(resultEnabled).isEqualTo("")
|
||||
assertThat(resultDisabled).isEqualTo("disabled")
|
||||
assertThat(resultUnavailable).isEqualTo("")
|
||||
assertThat(resultDisabledOrUnavailable).isEqualTo("disabled or unavailable")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should call onDisabledOrUnavailable when unavailable`() {
|
||||
val testSubject = FeatureFlagResult.Unavailable
|
||||
|
||||
var resultEnabled = ""
|
||||
var resultDisabled = ""
|
||||
var resultUnavailable = ""
|
||||
var resultDisabledOrUnavailable = ""
|
||||
|
||||
testSubject.onEnabled {
|
||||
resultEnabled = "enabled"
|
||||
}.onDisabled {
|
||||
resultDisabled = "disabled"
|
||||
}.onUnavailable {
|
||||
resultUnavailable = "unavailable"
|
||||
}.onDisabledOrUnavailable {
|
||||
resultDisabledOrUnavailable = "disabled or unavailable"
|
||||
}
|
||||
|
||||
assertThat(resultEnabled).isEqualTo("")
|
||||
assertThat(resultDisabled).isEqualTo("")
|
||||
assertThat(resultUnavailable).isEqualTo("unavailable")
|
||||
assertThat(resultDisabledOrUnavailable).isEqualTo("disabled or unavailable")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `whenEnabledOrNot should return correct value based on state`() {
|
||||
val enabledResult = FeatureFlagResult.Enabled.whenEnabledOrNot(
|
||||
onEnabled = { "Feature is ON" },
|
||||
onDisabledOrUnavailable = { "Feature is OFF" },
|
||||
)
|
||||
assertThat(enabledResult).isEqualTo("Feature is ON")
|
||||
|
||||
val disabledResult = FeatureFlagResult.Disabled.whenEnabledOrNot(
|
||||
onEnabled = { "Feature is ON" },
|
||||
onDisabledOrUnavailable = { "Feature is OFF" },
|
||||
)
|
||||
assertThat(disabledResult).isEqualTo("Feature is OFF")
|
||||
|
||||
val unavailableResult = FeatureFlagResult.Unavailable.whenEnabledOrNot(
|
||||
onEnabled = { "Feature is ON" },
|
||||
onDisabledOrUnavailable = { "Feature is OFF" },
|
||||
)
|
||||
assertThat(unavailableResult).isEqualTo("Feature is OFF")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package net.thunderbird.core.featureflag
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isInstanceOf
|
||||
import org.junit.Test
|
||||
|
||||
class InMemoryFeatureFlagProviderTest {
|
||||
|
||||
@Test
|
||||
fun `should return FeatureFlagResult#Enabled when feature is enabled`() {
|
||||
val feature1Key = FeatureFlagKey("feature1")
|
||||
val featureFlagProvider = InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory = {
|
||||
listOf(
|
||||
FeatureFlag(key = feature1Key, enabled = true),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val result = featureFlagProvider.provide(feature1Key)
|
||||
|
||||
assertThat(result).isInstanceOf<FeatureFlagResult.Enabled>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return FeatureFlagResult#Disabled when feature is disabled`() {
|
||||
val feature1Key = FeatureFlagKey("feature1")
|
||||
val featureFlagProvider = InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory = {
|
||||
listOf(
|
||||
FeatureFlag(key = feature1Key, enabled = false),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val result = featureFlagProvider.provide(feature1Key)
|
||||
|
||||
assertThat(result).isInstanceOf<FeatureFlagResult.Disabled>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return FeatureFlagResult#Unavailable when feature is not found`() {
|
||||
val feature1Key = FeatureFlagKey("feature1")
|
||||
val feature2Key = FeatureFlagKey("feature2")
|
||||
val featureFlagProvider = InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory = {
|
||||
listOf(
|
||||
FeatureFlag(key = feature1Key, enabled = false),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val result = featureFlagProvider.provide(feature2Key)
|
||||
|
||||
assertThat(result).isInstanceOf<FeatureFlagResult.Unavailable>()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue