Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues regarding serialization and error handling #13

Merged
merged 27 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4a164ba
!wip
uniumuniu Oct 18, 2023
a1a9ecb
test commit
uniumuniu Oct 18, 2023
5b78435
Revert "test commit"
uniumuniu Oct 18, 2023
ea136a6
Finish Instance+Feature.kt and Instance+Segments.kt
uniumuniu Oct 18, 2023
2f8de11
Introduce Instance exstension functions
uniumuniu Oct 20, 2023
cc069fd
Update README.md
uniumuniu Oct 20, 2023
90d3f12
Apply codestyle
uniumuniu Oct 25, 2023
4e36ebd
Fix unit tests
uniumuniu Oct 25, 2023
6ddf82c
Add custom serializers
uniumuniu Nov 7, 2023
7cd8733
Fix deserializaing of VariableValue
uniumuniu Nov 9, 2023
be9dbcf
Merge remote-tracking branch 'origin/main' into migrate-instance
uniumuniu Nov 9, 2023
8cd4e71
types to separate package
uniumuniu Nov 9, 2023
974f79d
fix package path
uniumuniu Nov 9, 2023
9cb2cf1
clean-up
uniumuniu Nov 15, 2023
009f515
fix issues with variables
uniumuniu Nov 27, 2023
c671d5f
Remove some logs
uniumuniu Nov 29, 2023
08b5041
Fix operator serializing
uniumuniu Nov 29, 2023
9755161
Fix conditions
uniumuniu Dec 5, 2023
ec27537
Fix conditions deserialization
uniumuniu Dec 6, 2023
82641f1
Fix issues with json variables
uniumuniu Dec 8, 2023
586b962
Fix integer variable serialization
uniumuniu Dec 27, 2023
cb08f53
Fix operator
uniumuniu Dec 28, 2023
e336dfc
Fix serialization issues
uniumuniu Dec 28, 2023
2229f2d
Fix error handling
uniumuniu Dec 28, 2023
c576116
Add unit tests for serializers
uniumuniu Jan 14, 2024
c890002
Revert removing SegmentKey alias
uniumuniu Jan 14, 2024
f653af3
Remove unnecessary comments
uniumuniu Jan 14, 2024
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
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
Loading