Skip to content

Commit

Permalink
Add support for double feature flags (#2029)
Browse files Browse the repository at this point in the history
While here, make some minor cleanups + reorganization for boolean
feature flags

Co-authored-by: Ricardo Mariano <rmariano@squareup.com>
Co-authored-by: Chris Ryan <74329397+chris-ryan-square@users.noreply.github.com>
  • Loading branch information
3 people committed Jul 15, 2021
1 parent c412dca commit 37b4fcc
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 35 deletions.
Expand Up @@ -32,6 +32,11 @@ class FakeFeatureFlags constructor(
"Boolean flag $feature must be overridden with override() before use; the default value of false has been DEPRECATED"
)

override fun getDouble(feature: Feature, key: String, attributes: Attributes): Double =
get(feature, key, attributes) as? Double ?: throw IllegalArgumentException(
"Double flag $feature must be overridden with override() before use; the default value of false has been DEPRECATED"
)

override fun getInt(feature: Feature, key: String, attributes: Attributes): Int =
get(feature, key, attributes) as? Int ?: throw IllegalArgumentException(
"Int flag $feature must be overridden with override() before use"
Expand Down Expand Up @@ -71,6 +76,7 @@ class FakeFeatureFlags constructor(
}

override fun getBoolean(feature: Feature) = getBoolean(feature, KEY)
override fun getDouble(feature: Feature) = getDouble(feature, KEY)
override fun getInt(feature: Feature) = getInt(feature, KEY)
override fun getString(feature: Feature) = getString(feature, KEY)
override fun <T : Enum<T>> getEnum(feature: Feature, clazz: Class<T>): T = getEnum(
Expand Down Expand Up @@ -135,6 +141,14 @@ class FakeFeatureFlags constructor(
tracker: (Boolean) -> Unit
) = trackAny(feature, key, attributes, executor, tracker)

override fun trackDouble(
feature: Feature,
key: String,
attributes: Attributes,
executor: Executor,
tracker: (Double) -> Unit
) = trackAny(feature, key, attributes, executor, tracker)

override fun trackInt(
feature: Feature,
key: String,
Expand Down Expand Up @@ -175,6 +189,12 @@ class FakeFeatureFlags constructor(
tracker: (Boolean) -> Unit
) = trackBoolean(feature, KEY, executor, tracker)

override fun trackDouble(
feature: Feature,
executor: Executor,
tracker: (Double) -> Unit
) = trackDouble(feature, KEY, executor, tracker)

override fun trackInt(
feature: Feature,
executor: Executor,
Expand Down Expand Up @@ -206,6 +226,11 @@ class FakeFeatureFlags constructor(
value: Boolean
) = override<Boolean>(feature, value)

fun override(
feature: Feature,
value: Double
) = override<Double>(feature, value)

fun override(
feature: Feature,
value: Int
Expand Down Expand Up @@ -251,6 +276,14 @@ class FakeFeatureFlags constructor(
attributes: Attributes = defaultAttributes
) = overrideKey<Boolean>(feature, key, value, attributes)

@JvmOverloads
fun overrideKey(
feature: Feature,
key: String,
value: Double,
attributes: Attributes = defaultAttributes
) = overrideKey<Double>(feature, key, value, attributes)

@JvmOverloads
fun overrideKey(
feature: Feature,
Expand Down
Expand Up @@ -28,6 +28,70 @@ internal class FakeFeatureFlagsTest {
subject = FakeFeatureFlags { moshi }
}

@Test
fun getBoolean() {
// Default throws.
assertThrows<RuntimeException> { subject.getBoolean(FEATURE, TOKEN) }

// Can be overridden
subject.override(FEATURE, true)
subject.override(OTHER_FEATURE, false)
assertThat(subject.getBoolean(FEATURE, TOKEN)).isEqualTo(true)
assertThat(subject.getBoolean(OTHER_FEATURE, TOKEN)).isEqualTo(false)

// Can override with specific keys
subject.overrideKey(FEATURE, "joker", false)
assertThat(subject.getBoolean(FEATURE, TOKEN)).isEqualTo(true)
assertThat(subject.getBoolean(FEATURE, "joker")).isEqualTo(false)

// Can override with specific keys and attributes
val attributes = Attributes(mapOf("type" to "bad"))
subject.overrideKey(FEATURE, "joker", false, attributes)
assertThat(subject.getBoolean(FEATURE, TOKEN)).isEqualTo(true)
assertThat(subject.getBoolean(FEATURE, "joker")).isEqualTo(false)
assertThat(subject.getBoolean(FEATURE, "joker", attributes)).isEqualTo(false)
// Provides the key level override when there is no match on attributes
assertThat(
subject.getBoolean(
FEATURE,
"joker",
Attributes(mapOf("don't" to "exist"))
)
).isEqualTo(false)
}

@Test
fun getDouble() {
// Default throws.
assertThrows<RuntimeException> { subject.getDouble(FEATURE, TOKEN) }

// Can be overridden
subject.override(FEATURE, 1.0)
subject.override(OTHER_FEATURE, 2.0)
assertThat(subject.getDouble(FEATURE, TOKEN)).isEqualTo(1.0)
assertThat(subject.getDouble(OTHER_FEATURE, TOKEN)).isEqualTo(2.0)

// Can override with specific keys
subject.overrideKey(FEATURE, "joker", 3.0)
assertThat(subject.getDouble(FEATURE, TOKEN)).isEqualTo(1.0)
assertThat(subject.getDouble(FEATURE, "joker")).isEqualTo(3.0)

// Can override with specific keys and attributes
val attributes = Attributes(mapOf("type" to "bad"))
subject.overrideKey(FEATURE, "joker", 4.0, attributes)
assertThat(subject.getDouble(FEATURE, TOKEN)).isEqualTo(1.0)
assertThat(subject.getDouble(FEATURE, "joker")).isEqualTo(3.0)
assertThat(subject.getDouble(FEATURE, "joker", attributes)).isEqualTo(4.0)
// Provides the key level override when there is no match on attributes
assertThat(
subject.getDouble(
FEATURE,
"joker",
Attributes(mapOf("don't" to "exist"))
)
).isEqualTo(3.0)
}

@Test
fun getInt() {
// Default throws.
Expand Down Expand Up @@ -153,38 +217,6 @@ internal class FakeFeatureFlagsTest {
.isEqualTo(JsonFeature("test-key-class"))
}

@Test
fun getBoolean() {
// Default throws.
assertThrows<RuntimeException> { subject.getBoolean(FEATURE, TOKEN) }

// Can be overridden
subject.override(FEATURE, true)
subject.override(OTHER_FEATURE, false)
assertThat(subject.getBoolean(FEATURE, TOKEN)).isEqualTo(true)
assertThat(subject.getBoolean(OTHER_FEATURE, TOKEN)).isEqualTo(false)

// Can override with specific keys
subject.overrideKey(FEATURE, "joker", false)
assertThat(subject.getBoolean(FEATURE, TOKEN)).isEqualTo(true)
assertThat(subject.getBoolean(FEATURE, "joker")).isEqualTo(false)

// Can override with specific keys and attributes
val attributes = Attributes(mapOf("type" to "bad"))
subject.overrideKey(FEATURE, "joker", false, attributes)
assertThat(subject.getBoolean(FEATURE, TOKEN)).isEqualTo(true)
assertThat(subject.getBoolean(FEATURE, "joker")).isEqualTo(false)
assertThat(subject.getBoolean(FEATURE, "joker", attributes)).isEqualTo(false)
// Provides the key level override when there is no match on attributes
assertThat(
subject.getBoolean(
FEATURE,
"joker",
Attributes(mapOf("don't" to "exist"))
)
).isEqualTo(false)
}

@Test
fun getString() {
// Default returns false and not throw as the other variants.
Expand Down
15 changes: 14 additions & 1 deletion wisp/wisp-feature/src/main/kotlin/wisp/feature/DynamicConfig.kt
Expand Up @@ -8,10 +8,15 @@ import java.util.concurrent.Executor
*/
interface DynamicConfig {
/**
* Returns the value of an boolean dynamic flag.
* Returns the value of a boolean dynamic flag.
*/
fun getBoolean(feature: Feature): Boolean

/**
* Returns the value of a double dynamic flag.
*/
fun getDouble(feature: Feature): Double

/**
* Returns the value of an integer dynamic flag.
*/
Expand Down Expand Up @@ -40,6 +45,14 @@ interface DynamicConfig {
*/
fun trackBoolean(feature: Feature, executor: Executor, tracker: (Boolean) -> Unit): TrackerReference

/**
* Registers a double dynamic config tracker which will be invoked whenever the double
* dynamic config changes value.
*
* Returns a tracker reference which can be used to un-register the tracker.
*/
fun trackDouble(feature: Feature, executor: Executor, tracker: (Double) -> Unit): TrackerReference

/**
* Registers a integer dynamic config tracker which will be invoked whenever the integer
* dynamic config changes value.
Expand Down
38 changes: 36 additions & 2 deletions wisp/wisp-feature/src/main/kotlin/wisp/feature/FeatureFlags.kt
Expand Up @@ -8,7 +8,7 @@ import java.util.concurrent.Executor
interface FeatureFlags {

/**
* Calculates the value of an boolean feature flag for the given key and attributes.
* Calculates the value of a boolean feature flag for the given key and attributes.
* @see [getEnum] for param details
*/
fun getBoolean(
Expand All @@ -17,6 +17,16 @@ interface FeatureFlags {
attributes: Attributes = Attributes()
): Boolean

/**
* Calculates the value of a double feature flag for the given key and attributes.
* @see [getEnum] for param details
*/
fun getDouble(
feature: Feature,
key: String,
attributes: Attributes = Attributes()
): Double

/**
* Calculates the value of an integer feature flag for the given key and attributes.
* @see [getEnum] for param details
Expand Down Expand Up @@ -68,7 +78,7 @@ interface FeatureFlags {
): T

/**
* Registers a tracker for the value of an boolean feature flag for the given key and attributes.
* Registers a tracker for the value of a boolean feature flag for the given key and attributes.
* @see [trackEnum] for param details
*/
fun trackBoolean(
Expand All @@ -79,6 +89,18 @@ interface FeatureFlags {
tracker: (Boolean) -> Unit
): TrackerReference

/**
* Registers a tracker for the value of a double feature flag for the given key and attributes.
* @see [trackEnum] for param details
*/
fun trackDouble(
feature: Feature,
key: String,
attributes: Attributes = Attributes(),
executor: Executor,
tracker: (Double) -> Unit
): TrackerReference

/**
* Registers a tracker for the value of an integer feature flag for the given key and attributes.
* @see [trackEnum] for param details
Expand Down Expand Up @@ -145,6 +167,11 @@ interface FeatureFlags {
key: String
) = getBoolean(feature, key, Attributes())

fun getDouble(
feature: Feature,
key: String
) = getDouble(feature, key, Attributes())

fun getInt(
feature: Feature,
key: String
Expand Down Expand Up @@ -174,6 +201,13 @@ interface FeatureFlags {
tracker: (Boolean) -> Unit
) = trackBoolean(feature, key, Attributes(), executor, tracker)

fun trackDouble(
feature: Feature,
key: String,
executor: Executor,
tracker: (Double) -> Unit
) = trackDouble(feature, key, Attributes(), executor, tracker)

fun trackInt(
feature: Feature,
key: String,
Expand Down

0 comments on commit 37b4fcc

Please sign in to comment.