Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private fun Value.friendlyString(): String {
return when (this) {
is Value.String -> this.string
is Value.Integer -> this.integer.toString()
is Value.Long -> this.long.toString()
is Value.Double -> this.double.toString()
is Value.Boolean -> this.boolean.toString()
is Value.Instant -> this.instant.toString()
Expand Down
1 change: 1 addition & 0 deletions Provider/api/Provider.api
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public final class com/spotify/confidence/openfeature/ConfidenceFeatureProvider
public fun getDoubleEvaluation (Ljava/lang/String;DLdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getHooks ()Ljava/util/List;
public fun getIntegerEvaluation (Ljava/lang/String;ILdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getLongEvaluation (Ljava/lang/String;JLdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
public fun getObjectEvaluation (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
public fun getStringEvaluation (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,37 @@ class ConfidenceFeatureProvider private constructor(
return generateEvaluation(key, defaultValue)
}

override fun getLongEvaluation(
key: String,
defaultValue: Long,
context: EvaluationContext?
): ProviderEvaluation<Long> {
val evaluation = generateEvaluation<Any>(key, defaultValue)
return ProviderEvaluation(
value = when (val value = evaluation.value) {
is Int -> value.toLong()
is Long -> value
else -> throw OpenFeatureError.ParseError("Flag value is not an integer")
},
reason = evaluation.reason,
variant = evaluation.variant,
errorCode = evaluation.errorCode,
errorMessage = evaluation.errorMessage
)
}

override fun getObjectEvaluation(
key: String,
defaultValue: Value,
context: EvaluationContext?
): ProviderEvaluation<Value> {
val evaluation = generateEvaluation(key, defaultValue.toConfidenceValue())
val evaluation = generateEvaluation<Any>(key, defaultValue)
return ProviderEvaluation(
value = evaluation.value.toValue(),
value = when (val value = evaluation.value) {
is Value -> value
is ConfidenceValue -> value.toValue()
else -> throw OpenFeatureError.ParseError("Flag value is not an object")
},
reason = evaluation.reason,
variant = evaluation.variant,
errorCode = evaluation.errorCode,
Expand Down Expand Up @@ -207,6 +230,7 @@ internal fun Value.toConfidenceValue(): ConfidenceValue = when (this) {
is Value.Boolean -> ConfidenceValue.Boolean(this.boolean)
is Value.Double -> ConfidenceValue.Double(this.double)
is Value.Integer -> ConfidenceValue.Integer(this.integer)
is Value.Long -> this.long.toConfidenceValue()
is Value.List -> {
// if types are different, return an empty list
if (this.list.map { it.javaClass.simpleName }.groupBy { it }.size > 1) {
Expand Down Expand Up @@ -271,8 +295,7 @@ private fun OpenFeatureError.toProviderErrorEvent(): OpenFeatureProviderEvents.P
eventDetails = OpenFeatureProviderEvents.EventDetails(
message = message,
errorCode = errorCode()
),
error = this
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.spotify.confidence.openfeature
import com.spotify.confidence.ConfidenceValue
import dev.openfeature.kotlin.sdk.EvaluationContext
import dev.openfeature.kotlin.sdk.TrackingEventDetails
import dev.openfeature.kotlin.sdk.exceptions.OpenFeatureError

internal fun mergeEventContext(
sessionContext: Map<String, ConfidenceValue>,
Expand Down Expand Up @@ -37,12 +38,16 @@ internal fun TrackingEventDetails?.toTrackingData(): Map<String, ConfidenceValue
) + structure.asMap().mapValues { it.value.toConfidenceValue() }
}

private fun Number.toConfidenceValue(): ConfidenceValue = when (this) {
private const val MAX_EXACT_DOUBLE_INTEGER = 9_007_199_254_740_992L

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why not double.max?


internal fun Number.toConfidenceValue(): ConfidenceValue = when (this) {
is Int -> ConfidenceValue.Integer(this)
is Long -> if (this in Int.MIN_VALUE..Int.MAX_VALUE) {
ConfidenceValue.Integer(toInt())
} else {
} else if (this in -MAX_EXACT_DOUBLE_INTEGER..MAX_EXACT_DOUBLE_INTEGER) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

could be like int, in double.min value .. double.max value

ConfidenceValue.Double(toDouble())
} else {
throw OpenFeatureError.ParseError("Long value cannot be represented exactly by Confidence")
}
is Double -> ConfidenceValue.Double(this)
is Float -> ConfidenceValue.Double(toDouble())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.spotify.confidence.openfeature

import com.spotify.confidence.Confidence
import com.spotify.confidence.ConfidenceValue
import com.spotify.confidence.FlagResolution
import com.spotify.confidence.ResolveReason
import com.spotify.confidence.client.ResolvedFlag
import com.spotify.confidence.getEvaluation
import dev.openfeature.kotlin.sdk.Reason
import dev.openfeature.kotlin.sdk.Value
import dev.openfeature.kotlin.sdk.exceptions.ErrorCode
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Test

class ConfidenceFeatureProviderObjectTest {
@Test
fun missingFlagPreservesLongDefaults() {
val provider = createProvider(FlagResolution.EMPTY)

for (count in listOf(42L, Long.MAX_VALUE)) {
val default = Value.Structure(mapOf("count" to Value.Long(count)))

val evaluation = provider.getObjectEvaluation("missing", default, null)

assertSame(default, evaluation.value)
assertEquals(Reason.ERROR.name, evaluation.reason)
assertEquals(ErrorCode.FLAG_NOT_FOUND, evaluation.errorCode)
}
}

@Test
fun matchingFlagResolvesWithLongMaxValueInUnusedDefault() {
val provider = createProvider(
FlagResolution(
context = emptyMap(),
flags = listOf(
ResolvedFlag(
flag = "size",
variant = "variant-1",
value = mapOf("count" to ConfidenceValue.Integer(42)),
reason = ResolveReason.RESOLVE_REASON_MATCH,
shouldApply = false
)
),
resolveToken = "token"
)
)
val default = Value.Structure(mapOf("count" to Value.Long(Long.MAX_VALUE)))

val evaluation = provider.getObjectEvaluation("size", default, null)

assertEquals(Value.Structure(mapOf("count" to Value.Integer(42))), evaluation.value)
assertEquals(Reason.TARGETING_MATCH.name, evaluation.reason)
assertEquals("variant-1", evaluation.variant)
assertNull(evaluation.errorCode)
}

private fun createProvider(resolution: FlagResolution): ConfidenceFeatureProvider {
val confidence = mockk<Confidence>(relaxed = true)
every { confidence.getFlag<Any>(any(), any()) } answers {
resolution.getEvaluation(firstArg(), secondArg<Any>(), emptyMap())
}
return ConfidenceFeatureProvider.create(confidence)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package com.spotify.confidence.openfeature

import com.spotify.confidence.Confidence
import com.spotify.confidence.ConfidenceValue
import com.spotify.confidence.Evaluation
import com.spotify.confidence.ResolveReason
import dev.openfeature.kotlin.sdk.ImmutableContext
import dev.openfeature.kotlin.sdk.ImmutableStructure
import dev.openfeature.kotlin.sdk.TrackingEventDetails
import dev.openfeature.kotlin.sdk.Value
import dev.openfeature.kotlin.sdk.events.OpenFeatureProviderEvents
import dev.openfeature.kotlin.sdk.exceptions.ErrorCode
import dev.openfeature.kotlin.sdk.exceptions.OpenFeatureError
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
Expand All @@ -23,6 +26,31 @@ import org.junit.Test
import com.spotify.confidence.Result as ConfidenceResult

class ConfidenceFeatureProviderTrackTest {
@Test
fun longEvaluationWidensIntegerFlagValueAndPreservesLongDefault() {
val confidence = mockk<Confidence>(relaxed = true)
every { confidence.getFlag<Any>("size", any()) } returns
Evaluation(42, reason = ResolveReason.RESOLVE_REASON_MATCH)
every { confidence.getFlag<Any>("missing", any()) } answers
{ Evaluation(secondArg(), reason = ResolveReason.ERROR) }
val provider = ConfidenceFeatureProvider.create(confidence)

assertEquals(42L, provider.getLongEvaluation("size", 0L, null).value)
assertEquals(Long.MAX_VALUE, provider.getLongEvaluation("missing", Long.MAX_VALUE, null).value)
}

@Test
fun longEvaluationRejectsDoubleFlagValue() {
val confidence = mockk<Confidence>(relaxed = true)
every { confidence.getFlag<Any>("size", any()) } returns
Evaluation(42.0, reason = ResolveReason.RESOLVE_REASON_MATCH)
val provider = ConfidenceFeatureProvider.create(confidence)

org.junit.Assert.assertThrows(OpenFeatureError.ParseError::class.java) {
provider.getLongEvaluation("size", 0L, null)
}
}

@Test
fun shutdownStopsConfidence() {
val confidence = mockk<Confidence>(relaxed = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package com.spotify.confidence.openfeature

import com.spotify.confidence.ConfidenceValue
import dev.openfeature.kotlin.sdk.Value
import dev.openfeature.kotlin.sdk.exceptions.OpenFeatureError
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Test

class ValueMappingsTest {
Expand Down Expand Up @@ -35,6 +37,15 @@ class ValueMappingsTest {
assertEquals(42, confidenceValue.asInteger()?.integer)
}

@Test
fun openFeatureLongValueToConfidenceValue() {
assertEquals(ConfidenceValue.Integer(42), Value.Long(42).toConfidenceValue())
assertEquals(ConfidenceValue.Double(3_000_000_000.0), Value.Long(3_000_000_000).toConfidenceValue())
assertThrows(OpenFeatureError.ParseError::class.java) {
Value.Long(9_007_199_254_740_993L).toConfidenceValue()
}
}

@Test
fun openFeatureStructureValueToConfidenceValueStruct() {
val value = Value.Structure(mapOf("key" to Value.String("value")))
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ okHttp = "4.12.0"
kotlinxSerialization = "1.6.0"

# Provider
openFeatureSDK = "0.8.0"
openFeatureSDK = "0.9.0"

# Sample app
activityCompose = "1.3.1"
Expand Down
Loading