Skip to content

Commit

Permalink
feat: add flag evaluation metadata to evaluation details (#111)
Browse files Browse the repository at this point in the history
Signed-off-by: Nicklas Lundin <nicklaslundin@gmail.com>
Signed-off-by: Nicklas Lundin <nicklasl@spotify.com>
  • Loading branch information
nicklasl authored Nov 18, 2024
1 parent 0421d8e commit 6e9aa29
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 15 deletions.
26 changes: 14 additions & 12 deletions android/src/main/java/dev/openfeature/sdk/FlagEvaluationDetails.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import dev.openfeature.sdk.exceptions.ErrorCode

data class FlagEvaluationDetails<T>(
val flagKey: String,
override val value: T,
override val variant: String? = null,
override val reason: String? = null,
override val errorCode: ErrorCode? = null,
override val errorMessage: String? = null
) : BaseEvaluation<T> {
val value: T,
val variant: String? = null,
val reason: String? = null,
val errorCode: ErrorCode? = null,
val errorMessage: String? = null,
val metadata: EvaluationMetadata = EvaluationMetadata.EMPTY
) {
companion object
}

Expand All @@ -18,11 +19,12 @@ fun <T> FlagEvaluationDetails.Companion.from(
flagKey: String
): FlagEvaluationDetails<T> {
return FlagEvaluationDetails(
flagKey,
providerEval.value,
providerEval.variant,
providerEval.reason,
providerEval.errorCode,
providerEval.errorMessage
flagKey = flagKey,
value = providerEval.value,
variant = providerEval.variant,
reason = providerEval.reason,
errorCode = providerEval.errorCode,
errorMessage = providerEval.errorMessage,
metadata = providerEval.metadata
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package dev.openfeature.sdk

class EvaluationMetadata internal constructor(private val values: Map<String, Any>) {

fun getString(key: String): String? = values[key] as? String

fun getBoolean(key: String): Boolean? = values[key] as? Boolean

fun getInt(key: String): Int? = values[key] as? Int

fun getDouble(key: String): Double? = values[key] as? Double

companion object {
fun builder(): Builder {
return Builder()
}

val EMPTY = EvaluationMetadata(emptyMap())
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as EvaluationMetadata

return values == other.values
}

override fun hashCode(): Int {
return values.hashCode()
}
}

class Builder {
private val values: MutableMap<String, Any> = mutableMapOf()

fun putString(key: String, value: String): Builder {
values[key] = value
return this
}

fun putInt(key: String, value: Int): Builder {
values[key] = value
return this
}

fun putDouble(key: String, value: Double): Builder {
values[key] = value
return this
}

fun putBoolean(key: String, value: Boolean): Builder {
values[key] = value
return this
}

fun build(): EvaluationMetadata {
return EvaluationMetadata(values.toMap())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ data class ProviderEvaluation<T>(
val variant: String? = null,
val reason: String? = null,
val errorCode: ErrorCode? = null,
val errorMessage: String? = null
val errorMessage: String? = null,
val metadata: EvaluationMetadata = EvaluationMetadata.EMPTY
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dev.openfeature.sdk

import org.junit.Assert
import org.junit.Test

class EvaluationMetadataTest {

private val metadata = EvaluationMetadata.builder()
.putString("key1", "value1")
.putInt("key2", 42)
.putBoolean("key3", true)
.putDouble("key4", 2.71828)
.build()

@Test
fun testAddAndGet() {
Assert.assertEquals("value1", metadata.getString("key1"))
Assert.assertEquals(42, metadata.getInt("key2"))
Assert.assertEquals(true, metadata.getBoolean("key3"))
Assert.assertEquals(2.71828, metadata.getDouble("key4"))
}

@Test
fun testGetNonExistentKey() {
Assert.assertNull(metadata.getString("key5"))
}

@Test
fun testInvalidType() {
Assert.assertNull(metadata.getString("key2"))
Assert.assertNull(metadata.getInt("key3"))
Assert.assertNull(metadata.getBoolean("key4"))
Assert.assertNull(metadata.getDouble("key1"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ class FlagEvaluationsTests {
Assert.assertEquals(booleanDetails, client.getBooleanDetails(key, false))
Assert.assertEquals(booleanDetails, client.getBooleanDetails(key, false, FlagEvaluationOptions()))

val stringDetails = FlagEvaluationDetails(key, "tset")
// in DoSomethingProvider, the string evaluation is special since it contains some metadata values
val stringDetails = FlagEvaluationDetails(key, "tset", metadata = DoSomethingProvider.evaluationMetadata)
Assert.assertEquals(stringDetails, client.getStringDetails(key, "test"))
Assert.assertEquals(stringDetails, client.getStringDetails(key, "test", FlagEvaluationOptions()))

Expand All @@ -93,6 +94,18 @@ class FlagEvaluationsTests {
Assert.assertEquals(objectDetails, client.getObjectDetails(key, Value.Structure(mapOf()), FlagEvaluationOptions()))
}

@Test
fun testMetadataFlagEvaluation() = runTest {
OpenFeatureAPI.setProvider(DoSomethingProvider())
val client = OpenFeatureAPI.getClient()
val key = "key"

val details = client.getStringDetails(key, "default")
val metadata: EvaluationMetadata = details.metadata
Assert.assertEquals("value1", metadata.getString("key1"))
Assert.assertEquals(42, metadata.getInt("key2"))
}

@Test
fun testHooksAreFired() = runTest {
OpenFeatureAPI.setProvider(NoOpProvider())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.openfeature.sdk.helpers

import dev.openfeature.sdk.EvaluationContext
import dev.openfeature.sdk.EvaluationMetadata
import dev.openfeature.sdk.FeatureProvider
import dev.openfeature.sdk.Hook
import dev.openfeature.sdk.ProviderEvaluation
Expand All @@ -19,6 +20,12 @@ class DoSomethingProvider(
override val metadata: ProviderMetadata = DoSomethingProviderMetadata(),
private var dispatcher: CoroutineDispatcher = Dispatchers.IO
) : FeatureProvider {
companion object {
val evaluationMetadata = EvaluationMetadata.builder()
.putString("key1", "value1")
.putInt("key2", 42)
.build()
}
private var eventHandler = EventHandler(dispatcher)

override fun initialize(initialContext: EvaluationContext?) {
Expand Down Expand Up @@ -51,7 +58,10 @@ class DoSomethingProvider(
defaultValue: String,
context: EvaluationContext?
): ProviderEvaluation<String> {
return ProviderEvaluation(defaultValue.reversed())
return ProviderEvaluation(
value = defaultValue.reversed(),
metadata = evaluationMetadata
)
}

override fun getIntegerEvaluation(
Expand Down

0 comments on commit 6e9aa29

Please sign in to comment.