Skip to content

Commit

Permalink
Fix issues regarding serialization and error handling (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
uniumuniu authored Jan 16, 2024
1 parent 811b53f commit 9adb5af
Show file tree
Hide file tree
Showing 14 changed files with 797 additions and 103 deletions.
5 changes: 3 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
id("org.jetbrains.kotlin.jvm") version "1.8.20"
kotlin("jvm") version "1.8.0"
kotlin("plugin.serialization") version "1.8.0"

// Apply the java-library plugin for API and implementation separation.
`java-library`
Expand Down Expand Up @@ -60,7 +61,7 @@ dependencies {
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation("net.swiftzer.semver:semver:1.3.0")
implementation("com.goncalossilva:murmurhash:0.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/featurevisor/sdk/Conditions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ object Conditions {
is Plain -> conditionIsMatched(condition, context)
is And -> condition.and.all { allConditionsAreMatched(it, context) }
is Or -> condition.or.any { allConditionsAreMatched(it, context) }
is Not -> condition.not.all { allConditionsAreMatched(it, context) }.not()
is Not -> condition.not.all { allConditionsAreMatched(it, context).not() }
}
}

Expand Down
11 changes: 7 additions & 4 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont

// override from rule
if (matchedTraffic?.variation != null) {
val variation = feature.variations?.firstOrNull { it.value == matchedTraffic.variation }
val variation = feature.variations.firstOrNull { it.value == matchedTraffic.variation }
if (variation != null) {
evaluation = Evaluation(
featureKey = feature.key,
Expand Down Expand Up @@ -217,6 +217,8 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
}

fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context = emptyMap()): Evaluation {
logger?.debug("evaluate flag: $featureKey")

val evaluation: Evaluation

// sticky
Expand Down Expand Up @@ -414,6 +416,7 @@ fun FeaturevisorInstance.evaluateVariable(
context: Context = emptyMap(),
): Evaluation {

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

is BucketBy.Or -> {
type = "or"
attributeKeys = bucketBy.bucketBy.or
attributeKeys = bucketBy.bucketBy
}
}

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

bucketKey.add(AttributeValue.StringValue(featureKey))

val result = bucketKey.map {
val result = bucketKey.joinToString(separator = bucketKeySeparator) {
it.toString()
}.joinToString(separator = bucketKeySeparator)
}

configureBucketKey?.let { configureBucketKey ->
return configureBucketKey(feature, context, result)
Expand Down
10 changes: 5 additions & 5 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Feature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.featurevisor.types.Feature
import com.featurevisor.types.Force
import com.featurevisor.types.Traffic

internal fun FeaturevisorInstance.getFeatureByKey(featureKey: String): Feature? {
fun FeaturevisorInstance.getFeatureByKey(featureKey: String): Feature? {
return datafileReader.getFeature(featureKey)
}

Expand Down Expand Up @@ -69,11 +69,11 @@ internal fun FeaturevisorInstance.getMatchedTrafficAndAllocation(

var matchedAllocation: Allocation? = null
val matchedTraffic = traffic.firstOrNull { trafficItem ->
if (allGroupSegmentsAreMatched(trafficItem.segments, context, datafileReader).not()) {
false
} else {
if (allGroupSegmentsAreMatched(trafficItem.segments, context, datafileReader)) {
matchedAllocation = getMatchedAllocation(trafficItem, bucketValue)
matchedAllocation != null
true
} else {
false
}
}

Expand Down
40 changes: 32 additions & 8 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Fetch.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.featurevisor.sdk

import com.featurevisor.types.DatafileContent
import kotlinx.serialization.decodeFromString
import java.io.IOException
import okhttp3.*
import kotlinx.serialization.json.Json
Expand Down Expand Up @@ -39,21 +40,44 @@ private fun fetchDatafileContentFromUrl(
}
}

private inline fun <reified T> fetch(
const val BODY_BYTE_COUNT = 1000000L
private inline fun fetch(
request: Request,
crossinline completion: (Result<T>) -> Unit,
crossinline completion: (Result<DatafileContent>) -> Unit,
) {
val client = OkHttpClient()
val call = client.newCall(request)
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body
if (response.isSuccessful && responseBody != null) {
val json = Json { ignoreUnknownKeys = true }
val content = json.decodeFromString<T>(responseBody.string())
completion(Result.success(content))
val responseBody = response.peekBody(BODY_BYTE_COUNT)
if (response.isSuccessful) {
val json = Json {
ignoreUnknownKeys = true
}
val responseBodyString = responseBody.string()
FeaturevisorInstance.companionLogger?.debug(responseBodyString)
try {
val content = json.decodeFromString<DatafileContent>(responseBodyString)
completion(Result.success(content))
} catch(throwable: Throwable) {
completion(
Result.failure(
FeaturevisorError.UnparsableJson(
responseBody.string(),
response.message
)
)
)
}
} else {
completion(Result.failure(FeaturevisorError.UnparsableJson(responseBody?.string(), response.message)))
completion(
Result.failure(
FeaturevisorError.UnparsableJson(
responseBody.string(),
response.message
)
)
)
}
}

Expand Down
26 changes: 17 additions & 9 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ import com.featurevisor.types.Context
import com.featurevisor.types.FeatureKey
import com.featurevisor.types.VariableKey
import com.featurevisor.types.VariableValue
import com.featurevisor.types.VariableValue.*
import com.featurevisor.types.VariableValue.ArrayValue
import com.featurevisor.types.VariableValue.BooleanValue
import com.featurevisor.types.VariableValue.DoubleValue
import com.featurevisor.types.VariableValue.IntValue
import com.featurevisor.types.VariableValue.JsonValue
import com.featurevisor.types.VariableValue.ObjectValue
import com.featurevisor.types.VariableValue.StringValue
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement

internal fun FeaturevisorInstance.getVariable(
fun FeaturevisorInstance.getVariable(
featureKey: FeatureKey,
variableKey: VariableKey,
context: Context = emptyMap(),
Expand All @@ -23,47 +30,47 @@ internal fun FeaturevisorInstance.getVariable(
return evaluation.variableValue
}

internal fun FeaturevisorInstance.getVariableBoolean(
fun FeaturevisorInstance.getVariableBoolean(
featureKey: FeatureKey,
variableKey: VariableKey,
context: Context,
): Boolean? {
return (getVariable(featureKey, variableKey, context) as? BooleanValue)?.value
}

internal fun FeaturevisorInstance.getVariableString(
fun FeaturevisorInstance.getVariableString(
featureKey: FeatureKey,
variableKey: VariableKey,
context: Context,
): String? {
return (getVariable(featureKey, variableKey, context) as? StringValue)?.value
}

internal fun FeaturevisorInstance.getVariableInteger(
fun FeaturevisorInstance.getVariableInteger(
featureKey: FeatureKey,
variableKey: VariableKey,
context: Context,
): Int? {
return (getVariable(featureKey, variableKey, context) as? IntValue)?.value
}

internal fun FeaturevisorInstance.getVariableDouble(
fun FeaturevisorInstance.getVariableDouble(
featureKey: FeatureKey,
variableKey: VariableKey,
context: Context,
): Double? {
return (getVariable(featureKey, variableKey, context) as? DoubleValue)?.value
}

internal fun FeaturevisorInstance.getVariableArray(
fun FeaturevisorInstance.getVariableArray(
featureKey: FeatureKey,
variableKey: VariableKey,
context: Context,
): List<String>? {
return (getVariable(featureKey, variableKey, context) as? ArrayValue)?.values
}

internal inline fun <reified T : Any> FeaturevisorInstance.getVariableObject(
inline fun <reified T : Any> FeaturevisorInstance.getVariableObject(
featureKey: FeatureKey,
variableKey: VariableKey,
context: Context,
Expand All @@ -77,7 +84,7 @@ internal inline fun <reified T : Any> FeaturevisorInstance.getVariableObject(
}
}

internal inline fun <reified T : Any> FeaturevisorInstance.getVariableJSON(
inline fun <reified T : Any> FeaturevisorInstance.getVariableJSON(
featureKey: FeatureKey,
variableKey: VariableKey,
context: Context,
Expand All @@ -89,3 +96,4 @@ internal inline fun <reified T : Any> FeaturevisorInstance.getVariableJSON(
null
}
}

18 changes: 12 additions & 6 deletions src/main/kotlin/com/featurevisor/sdk/Instance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import com.featurevisor.types.BucketValue
import com.featurevisor.types.Context
import com.featurevisor.types.DatafileContent
import com.featurevisor.types.EventName
import com.featurevisor.types.EventName.ACTIVATION
import com.featurevisor.types.EventName.READY
import com.featurevisor.types.EventName.REFRESH
import com.featurevisor.types.EventName.UPDATE
import com.featurevisor.types.EventName.*
import com.featurevisor.types.Feature
import com.featurevisor.types.StickyFeatures
import kotlinx.coroutines.Job
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

typealias ConfigureBucketKey = (Feature, Context, BucketKey) -> BucketKey
Expand All @@ -30,6 +28,8 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
fun createInstance(options: InstanceOptions): FeaturevisorInstance {
return FeaturevisorInstance(options)
}

var companionLogger: Logger? = null
}

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

init {
with(options) {
companionLogger = logger
if (onReady != null) {
emitter.addListener(event = READY, listener = onReady)
}
Expand All @@ -77,6 +78,11 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
ACTIVATION, onActivation
)
}
if (onError != null) {
emitter.addListener(
ERROR, onError
)
}

on = emitter::addListener
off = emitter::removeListener
Expand All @@ -96,11 +102,11 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
if (result.isSuccess) {
datafileReader = DatafileReader(result.getOrThrow())
statuses.ready = true
emitter.emit(READY)
emitter.emit(READY, result.getOrThrow())
if (refreshInterval != null) startRefreshing()
} else {
logger?.error("Failed to fetch datafile: $result")
throw FetchingDataFileFailed(result.toString())
emitter.emit(ERROR)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/com/featurevisor/sdk/InstanceOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ data class InstanceOptions(
val onReady: Listener? = null,
val onRefresh: Listener? = null,
val onUpdate: Listener? = null,
val onError: Listener? = null,
val refreshInterval: Long? = null, // seconds
val stickyFeatures: StickyFeatures? = null,
) {
Expand Down
Loading

0 comments on commit 9adb5af

Please sign in to comment.