Skip to content

Commit 9adb5af

Browse files
authored
Fix issues regarding serialization and error handling (#13)
1 parent 811b53f commit 9adb5af

File tree

14 files changed

+797
-103
lines changed

14 files changed

+797
-103
lines changed

build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent
1010

1111
plugins {
1212
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
13-
id("org.jetbrains.kotlin.jvm") version "1.8.20"
13+
kotlin("jvm") version "1.8.0"
14+
kotlin("plugin.serialization") version "1.8.0"
1415

1516
// Apply the java-library plugin for API and implementation separation.
1617
`java-library`
@@ -60,7 +61,7 @@ dependencies {
6061
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
6162
implementation("net.swiftzer.semver:semver:1.3.0")
6263
implementation("com.goncalossilva:murmurhash:0.4.0")
63-
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
64+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
6465
implementation("com.squareup.okhttp3:okhttp:4.11.0")
6566
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
6667
}

src/main/kotlin/com/featurevisor/sdk/Conditions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ object Conditions {
137137
is Plain -> conditionIsMatched(condition, context)
138138
is And -> condition.and.all { allConditionsAreMatched(it, context) }
139139
is Or -> condition.or.any { allConditionsAreMatched(it, context) }
140-
is Not -> condition.not.all { allConditionsAreMatched(it, context) }.not()
140+
is Not -> condition.not.all { allConditionsAreMatched(it, context).not() }
141141
}
142142
}
143143

src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
169169

170170
// override from rule
171171
if (matchedTraffic?.variation != null) {
172-
val variation = feature.variations?.firstOrNull { it.value == matchedTraffic.variation }
172+
val variation = feature.variations.firstOrNull { it.value == matchedTraffic.variation }
173173
if (variation != null) {
174174
evaluation = Evaluation(
175175
featureKey = feature.key,
@@ -217,6 +217,8 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
217217
}
218218

219219
fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context = emptyMap()): Evaluation {
220+
logger?.debug("evaluate flag: $featureKey")
221+
220222
val evaluation: Evaluation
221223

222224
// sticky
@@ -414,6 +416,7 @@ fun FeaturevisorInstance.evaluateVariable(
414416
context: Context = emptyMap(),
415417
): Evaluation {
416418

419+
FeaturevisorInstance.companionLogger?.debug("evaluateVariable, featureKey: $featureKey, variableKey: $variableKey")
417420
val evaluation: Evaluation
418421
val flag = evaluateFlag(featureKey, context)
419422
if (flag.enabled == false) {
@@ -612,7 +615,7 @@ private fun FeaturevisorInstance.getBucketKey(feature: Feature, context: Context
612615

613616
is BucketBy.Or -> {
614617
type = "or"
615-
attributeKeys = bucketBy.bucketBy.or
618+
attributeKeys = bucketBy.bucketBy
616619
}
617620
}
618621

@@ -632,9 +635,9 @@ private fun FeaturevisorInstance.getBucketKey(feature: Feature, context: Context
632635

633636
bucketKey.add(AttributeValue.StringValue(featureKey))
634637

635-
val result = bucketKey.map {
638+
val result = bucketKey.joinToString(separator = bucketKeySeparator) {
636639
it.toString()
637-
}.joinToString(separator = bucketKeySeparator)
640+
}
638641

639642
configureBucketKey?.let { configureBucketKey ->
640643
return configureBucketKey(feature, context, result)

src/main/kotlin/com/featurevisor/sdk/Instance+Feature.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import com.featurevisor.types.Feature
77
import com.featurevisor.types.Force
88
import com.featurevisor.types.Traffic
99

10-
internal fun FeaturevisorInstance.getFeatureByKey(featureKey: String): Feature? {
10+
fun FeaturevisorInstance.getFeatureByKey(featureKey: String): Feature? {
1111
return datafileReader.getFeature(featureKey)
1212
}
1313

@@ -69,11 +69,11 @@ internal fun FeaturevisorInstance.getMatchedTrafficAndAllocation(
6969

7070
var matchedAllocation: Allocation? = null
7171
val matchedTraffic = traffic.firstOrNull { trafficItem ->
72-
if (allGroupSegmentsAreMatched(trafficItem.segments, context, datafileReader).not()) {
73-
false
74-
} else {
72+
if (allGroupSegmentsAreMatched(trafficItem.segments, context, datafileReader)) {
7573
matchedAllocation = getMatchedAllocation(trafficItem, bucketValue)
76-
matchedAllocation != null
74+
true
75+
} else {
76+
false
7777
}
7878
}
7979

src/main/kotlin/com/featurevisor/sdk/Instance+Fetch.kt

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.featurevisor.sdk
22

33
import com.featurevisor.types.DatafileContent
4+
import kotlinx.serialization.decodeFromString
45
import java.io.IOException
56
import okhttp3.*
67
import kotlinx.serialization.json.Json
@@ -39,21 +40,44 @@ private fun fetchDatafileContentFromUrl(
3940
}
4041
}
4142

42-
private inline fun <reified T> fetch(
43+
const val BODY_BYTE_COUNT = 1000000L
44+
private inline fun fetch(
4345
request: Request,
44-
crossinline completion: (Result<T>) -> Unit,
46+
crossinline completion: (Result<DatafileContent>) -> Unit,
4547
) {
4648
val client = OkHttpClient()
4749
val call = client.newCall(request)
4850
call.enqueue(object : Callback {
4951
override fun onResponse(call: Call, response: Response) {
50-
val responseBody = response.body
51-
if (response.isSuccessful && responseBody != null) {
52-
val json = Json { ignoreUnknownKeys = true }
53-
val content = json.decodeFromString<T>(responseBody.string())
54-
completion(Result.success(content))
52+
val responseBody = response.peekBody(BODY_BYTE_COUNT)
53+
if (response.isSuccessful) {
54+
val json = Json {
55+
ignoreUnknownKeys = true
56+
}
57+
val responseBodyString = responseBody.string()
58+
FeaturevisorInstance.companionLogger?.debug(responseBodyString)
59+
try {
60+
val content = json.decodeFromString<DatafileContent>(responseBodyString)
61+
completion(Result.success(content))
62+
} catch(throwable: Throwable) {
63+
completion(
64+
Result.failure(
65+
FeaturevisorError.UnparsableJson(
66+
responseBody.string(),
67+
response.message
68+
)
69+
)
70+
)
71+
}
5572
} else {
56-
completion(Result.failure(FeaturevisorError.UnparsableJson(responseBody?.string(), response.message)))
73+
completion(
74+
Result.failure(
75+
FeaturevisorError.UnparsableJson(
76+
responseBody.string(),
77+
response.message
78+
)
79+
)
80+
)
5781
}
5882
}
5983

src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ import com.featurevisor.types.Context
44
import com.featurevisor.types.FeatureKey
55
import com.featurevisor.types.VariableKey
66
import com.featurevisor.types.VariableValue
7-
import com.featurevisor.types.VariableValue.*
7+
import com.featurevisor.types.VariableValue.ArrayValue
8+
import com.featurevisor.types.VariableValue.BooleanValue
9+
import com.featurevisor.types.VariableValue.DoubleValue
10+
import com.featurevisor.types.VariableValue.IntValue
11+
import com.featurevisor.types.VariableValue.JsonValue
12+
import com.featurevisor.types.VariableValue.ObjectValue
13+
import com.featurevisor.types.VariableValue.StringValue
14+
import kotlinx.serialization.decodeFromString
815
import kotlinx.serialization.json.Json
916
import kotlinx.serialization.json.decodeFromJsonElement
1017
import kotlinx.serialization.json.encodeToJsonElement
1118

12-
internal fun FeaturevisorInstance.getVariable(
19+
fun FeaturevisorInstance.getVariable(
1320
featureKey: FeatureKey,
1421
variableKey: VariableKey,
1522
context: Context = emptyMap(),
@@ -23,47 +30,47 @@ internal fun FeaturevisorInstance.getVariable(
2330
return evaluation.variableValue
2431
}
2532

26-
internal fun FeaturevisorInstance.getVariableBoolean(
33+
fun FeaturevisorInstance.getVariableBoolean(
2734
featureKey: FeatureKey,
2835
variableKey: VariableKey,
2936
context: Context,
3037
): Boolean? {
3138
return (getVariable(featureKey, variableKey, context) as? BooleanValue)?.value
3239
}
3340

34-
internal fun FeaturevisorInstance.getVariableString(
41+
fun FeaturevisorInstance.getVariableString(
3542
featureKey: FeatureKey,
3643
variableKey: VariableKey,
3744
context: Context,
3845
): String? {
3946
return (getVariable(featureKey, variableKey, context) as? StringValue)?.value
4047
}
4148

42-
internal fun FeaturevisorInstance.getVariableInteger(
49+
fun FeaturevisorInstance.getVariableInteger(
4350
featureKey: FeatureKey,
4451
variableKey: VariableKey,
4552
context: Context,
4653
): Int? {
4754
return (getVariable(featureKey, variableKey, context) as? IntValue)?.value
4855
}
4956

50-
internal fun FeaturevisorInstance.getVariableDouble(
57+
fun FeaturevisorInstance.getVariableDouble(
5158
featureKey: FeatureKey,
5259
variableKey: VariableKey,
5360
context: Context,
5461
): Double? {
5562
return (getVariable(featureKey, variableKey, context) as? DoubleValue)?.value
5663
}
5764

58-
internal fun FeaturevisorInstance.getVariableArray(
65+
fun FeaturevisorInstance.getVariableArray(
5966
featureKey: FeatureKey,
6067
variableKey: VariableKey,
6168
context: Context,
6269
): List<String>? {
6370
return (getVariable(featureKey, variableKey, context) as? ArrayValue)?.values
6471
}
6572

66-
internal inline fun <reified T : Any> FeaturevisorInstance.getVariableObject(
73+
inline fun <reified T : Any> FeaturevisorInstance.getVariableObject(
6774
featureKey: FeatureKey,
6875
variableKey: VariableKey,
6976
context: Context,
@@ -77,7 +84,7 @@ internal inline fun <reified T : Any> FeaturevisorInstance.getVariableObject(
7784
}
7885
}
7986

80-
internal inline fun <reified T : Any> FeaturevisorInstance.getVariableJSON(
87+
inline fun <reified T : Any> FeaturevisorInstance.getVariableJSON(
8188
featureKey: FeatureKey,
8289
variableKey: VariableKey,
8390
context: Context,
@@ -89,3 +96,4 @@ internal inline fun <reified T : Any> FeaturevisorInstance.getVariableJSON(
8996
null
9097
}
9198
}
99+

src/main/kotlin/com/featurevisor/sdk/Instance.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ import com.featurevisor.types.BucketValue
1010
import com.featurevisor.types.Context
1111
import com.featurevisor.types.DatafileContent
1212
import com.featurevisor.types.EventName
13-
import com.featurevisor.types.EventName.ACTIVATION
14-
import com.featurevisor.types.EventName.READY
15-
import com.featurevisor.types.EventName.REFRESH
16-
import com.featurevisor.types.EventName.UPDATE
13+
import com.featurevisor.types.EventName.*
1714
import com.featurevisor.types.Feature
1815
import com.featurevisor.types.StickyFeatures
1916
import kotlinx.coroutines.Job
17+
import kotlinx.serialization.decodeFromString
2018
import kotlinx.serialization.json.Json
2119

2220
typealias ConfigureBucketKey = (Feature, Context, BucketKey) -> BucketKey
@@ -30,6 +28,8 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
3028
fun createInstance(options: InstanceOptions): FeaturevisorInstance {
3129
return FeaturevisorInstance(options)
3230
}
31+
32+
var companionLogger: Logger? = null
3333
}
3434

3535
private val on: (EventName, Listener) -> Unit
@@ -58,6 +58,7 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
5858

5959
init {
6060
with(options) {
61+
companionLogger = logger
6162
if (onReady != null) {
6263
emitter.addListener(event = READY, listener = onReady)
6364
}
@@ -77,6 +78,11 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
7778
ACTIVATION, onActivation
7879
)
7980
}
81+
if (onError != null) {
82+
emitter.addListener(
83+
ERROR, onError
84+
)
85+
}
8086

8187
on = emitter::addListener
8288
off = emitter::removeListener
@@ -96,11 +102,11 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
96102
if (result.isSuccess) {
97103
datafileReader = DatafileReader(result.getOrThrow())
98104
statuses.ready = true
99-
emitter.emit(READY)
105+
emitter.emit(READY, result.getOrThrow())
100106
if (refreshInterval != null) startRefreshing()
101107
} else {
102108
logger?.error("Failed to fetch datafile: $result")
103-
throw FetchingDataFileFailed(result.toString())
109+
emitter.emit(ERROR)
104110
}
105111
}
106112
}

src/main/kotlin/com/featurevisor/sdk/InstanceOptions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ data class InstanceOptions(
2020
val onReady: Listener? = null,
2121
val onRefresh: Listener? = null,
2222
val onUpdate: Listener? = null,
23+
val onError: Listener? = null,
2324
val refreshInterval: Long? = null, // seconds
2425
val stickyFeatures: StickyFeatures? = null,
2526
) {

0 commit comments

Comments
 (0)