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

Runtime deserialisation #53

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.yaml:snakeyaml:2.2")
implementation("com.google.code.gson:gson:2.10.1")
}

// Apply a specific Java toolchain to ease working on different environments.
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
group=com.featurevisor
version=0.0.1-SNAPSHOT
org.gradle.daemon=true
org.gradle.parallel=true
8 changes: 4 additions & 4 deletions src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import com.featurevisor.types.FeatureKey
import com.featurevisor.types.Segment
import com.featurevisor.types.SegmentKey

class DatafileReader constructor(
class DatafileReader (
datafileContent: DatafileContent,
) {

private val schemaVersion: String = datafileContent.schemaVersion
private val revision: String = datafileContent.revision
private val attributes: Map<AttributeKey, Attribute> = datafileContent.attributes.associateBy { it.key }
private val segments: Map<SegmentKey, Segment> = datafileContent.segments.associateBy { it.key }
private val features: Map<FeatureKey, Feature> = datafileContent.features.associateBy { it.key }
private val attributes: Map<AttributeKey, Attribute> = datafileContent.getAttributes().associateBy { it.key }
private val segments: Map<SegmentKey, Segment> = datafileContent.getSegment().associateBy { it.key }
private val features: Map<FeatureKey, Feature> = datafileContent.getFeature().associateBy { it.key }

fun getRevision(): String {
return revision
Expand Down
33 changes: 14 additions & 19 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ fun FeaturevisorInstance.isEnabled(featureKey: FeatureKey, context: Context = em
return evaluation.enabled == true
}

@Suppress("UNREACHABLE_CODE")
fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Context = emptyMap()): Evaluation {
var evaluation: Evaluation
try {
Expand Down Expand Up @@ -125,7 +124,7 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
return evaluation
}

if (feature.variations.isNullOrEmpty()) {
if (feature.getVariations().isEmpty()) {
// no variations
evaluation = Evaluation(
featureKey = featureKey,
Expand All @@ -141,7 +140,7 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
// forced
val force = findForceFromFeature(feature, context, datafileReader)
if (force != null) {
val variation = feature.variations.firstOrNull { it.value == force.variation }
val variation = feature.getVariations().firstOrNull { it.value == force.variation }

if (variation != null) {
evaluation = Evaluation(
Expand All @@ -160,18 +159,17 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
val bucketValue = getBucketValue(feature, finalContext)

val matchedTrafficAndAllocation = getMatchedTrafficAndAllocation(
feature.traffic,
feature.getTraffic(),
finalContext,
bucketValue,
datafileReader,
logger
)

val matchedTraffic = matchedTrafficAndAllocation.matchedTraffic

// override from rule
if (matchedTraffic?.variation != null) {
val variation = feature.variations.firstOrNull { it.value == matchedTraffic.variation }
val variation = feature.getVariations().firstOrNull { it.value == matchedTraffic.variation }
if (variation != null) {
evaluation = Evaluation(
featureKey = feature.key,
Expand All @@ -191,7 +189,7 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont

// regular allocation
if (matchedAllocation != null) {
val variation = feature.variations?.firstOrNull { it.value == matchedAllocation.variation }
val variation = feature.getVariations().firstOrNull { it.value == matchedAllocation.variation }
if (variation != null) {
evaluation = Evaluation(
featureKey = feature.key,
Expand Down Expand Up @@ -229,7 +227,6 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
}


@Suppress("UNREACHABLE_CODE")
fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context = emptyMap()): Evaluation {

var evaluation: Evaluation
Expand Down Expand Up @@ -301,8 +298,8 @@ fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context =
}

// required
if (feature.required.isNullOrEmpty().not()) {
val requiredFeaturesAreEnabled = feature.required?.all { item ->
if (feature.getRequired().isNullOrEmpty().not()) {
val requiredFeaturesAreEnabled = feature.getRequired()?.all { item ->
var requiredKey: FeatureKey? = null
var requiredVariation: VariationValue? = null
when (item) {
Expand Down Expand Up @@ -347,7 +344,7 @@ fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context =
val bucketValue = getBucketValue(feature = feature, context = finalContext)

val matchedTraffic = getMatchedTraffic(
traffic = feature.traffic,
traffic = feature.getTraffic(),
context = finalContext,
datafileReader = datafileReader,
)
Expand Down Expand Up @@ -440,7 +437,6 @@ fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context =
}
}

@Suppress("UNREACHABLE_CODE")
fun FeaturevisorInstance.evaluateVariable(
featureKey: FeatureKey,
variableKey: VariableKey,
Expand Down Expand Up @@ -497,7 +493,7 @@ fun FeaturevisorInstance.evaluateVariable(
return evaluation
}

val variableSchema = feature.variablesSchema?.firstOrNull { variableSchema ->
val variableSchema = feature.getVariablesSchema().firstOrNull { variableSchema ->
variableSchema.key == variableKey
}

Expand Down Expand Up @@ -537,11 +533,10 @@ fun FeaturevisorInstance.evaluateVariable(
val bucketValue = getBucketValue(feature, finalContext)

val matchedTrafficAndAllocation = getMatchedTrafficAndAllocation(
traffic = feature.traffic,
traffic = feature.getTraffic(),
context = finalContext,
bucketValue = bucketValue,
datafileReader = datafileReader,
logger = logger
)

matchedTrafficAndAllocation.matchedTraffic?.let { matchedTraffic ->
Expand Down Expand Up @@ -571,7 +566,7 @@ fun FeaturevisorInstance.evaluateVariable(
matchedAllocation.variation
}

val variation = feature.variations?.firstOrNull { variation ->
val variation = feature.getVariations().firstOrNull { variation ->
variation.value == variationValue
}

Expand Down Expand Up @@ -654,10 +649,10 @@ fun FeaturevisorInstance.evaluateVariable(

private fun FeaturevisorInstance.getBucketKey(feature: Feature, context: Context): BucketKey {
val featureKey = feature.key
var type: String
var attributeKeys: List<AttributeKey>
val type: String
val attributeKeys: List<AttributeKey>

when (val bucketBy = feature.bucketBy) {
when (val bucketBy = feature.getBucketBy()) {
is BucketBy.Single -> {
type = "plain"
attributeKeys = listOf(bucketBy.bucketBy)
Expand Down
5 changes: 2 additions & 3 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Feature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal fun FeaturevisorInstance.findForceFromFeature(
datafileReader: DatafileReader,
): Force? {

return feature.force?.firstOrNull { force ->
return feature.getForce().firstOrNull { force ->
when {
force.conditions != null -> allConditionsAreMatched(force.conditions, context)
force.segments != null -> allGroupSegmentsAreMatched(
Expand All @@ -46,7 +46,7 @@ internal fun FeaturevisorInstance.getMatchedTraffic(
}
}

internal fun FeaturevisorInstance.getMatchedAllocation(
internal fun getMatchedAllocation(
traffic: Traffic,
bucketValue: Int,
): Allocation? {
Expand All @@ -68,7 +68,6 @@ internal fun FeaturevisorInstance.getMatchedTrafficAndAllocation(
context: Context,
bucketValue: Int,
datafileReader: DatafileReader,
logger: Logger?,
): MatchedTrafficAndAllocation {

var matchedAllocation: Allocation? = null
Expand Down
23 changes: 9 additions & 14 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Fetch.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,31 @@ package com.featurevisor.sdk

import com.featurevisor.types.DatafileContent
import kotlinx.serialization.decodeFromString
import java.io.IOException
import okhttp3.*
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.lang.IllegalArgumentException
import java.io.IOException

const val BODY_BYTE_COUNT = 1000000L
val client = OkHttpClient()

// MARK: - Fetch datafile content
@Throws(IOException::class)
suspend fun FeaturevisorInstance.fetchDatafileContent(
fun FeaturevisorInstance.fetchDatafileContent(
url: String,
handleDatafileFetch: DatafileFetchHandler? = null,
completion: (Result<DatafileContent>) -> Unit,
completion: (Result<Pair<DatafileContent, String>>) -> Unit,
) {
handleDatafileFetch?.let { handleFetch ->
val result = handleFetch(url)
completion(result)
val result = handleFetch(url).getOrNull()!!
completion(Result.success(Pair(result, "")))
} ?: run {
fetchDatafileContentFromUrl(url, completion)
}
}

private fun fetchDatafileContentFromUrl(
url: String,
completion: (Result<DatafileContent>) -> Unit,
completion: (Result<Pair<DatafileContent, String>>) -> Unit,
) {
try {
val httpUrl = url.toHttpUrl()
Expand All @@ -45,21 +43,18 @@ private fun fetchDatafileContentFromUrl(

private inline fun fetch(
request: Request,
crossinline completion: (Result<DatafileContent>) -> Unit,
crossinline completion: (Result<Pair<DatafileContent, String>>) -> Unit,
) {
val call = client.newCall(request)
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
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))
val content = JsonConfigFeatureVisor.json.decodeFromString<DatafileContent>(responseBodyString)
completion(Result.success(Pair(content, responseBodyString)))
} catch (throwable: Throwable) {
completion(
Result.failure(
Expand Down
8 changes: 3 additions & 5 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Refresh.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.featurevisor.sdk

import com.featurevisor.sdk.FeaturevisorError.*
import com.featurevisor.sdk.FeaturevisorError.MissingDatafileUrlWhileRefreshing
import com.featurevisor.types.EventName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -45,10 +43,10 @@ private suspend fun FeaturevisorInstance.refresh() {
) { result ->
result.onSuccess { datafileContent ->
val currentRevision = getRevision()
val newRevision = datafileContent.revision
val newRevision = datafileContent.first.revision
val isNotSameRevision = currentRevision != newRevision

datafileReader = DatafileReader(datafileContent)
datafileReader = DatafileReader(datafileContent.first)
logger?.info("refreshed datafile")

emitter.emit(EventName.REFRESH)
Expand Down
21 changes: 1 addition & 20 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Segments.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,12 @@ package com.featurevisor.sdk

import com.featurevisor.sdk.Conditions.allConditionsAreMatched
import com.featurevisor.types.Context
import com.featurevisor.types.FeatureKey
import com.featurevisor.types.GroupSegment
import com.featurevisor.types.GroupSegment.*
import com.featurevisor.types.Segment
import com.featurevisor.types.VariationValue

internal fun FeaturevisorInstance.segmentIsMatched(
featureKey: FeatureKey,
context: Context,
): VariationValue? {
val evaluation = evaluateVariation(featureKey, context)

if (evaluation.variationValue != null) {
return evaluation.variationValue
}

if (evaluation.variation != null) {
return evaluation.variation.value
}

return null
}

internal fun segmentIsMatched(segment: Segment, context: Context): Boolean {
return allConditionsAreMatched(segment.conditions, context)
return allConditionsAreMatched(segment.getCondition(), context)
}

internal fun FeaturevisorInstance.allGroupSegmentsAreMatched(
Expand Down
3 changes: 1 addition & 2 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ inline fun <reified T : Any> FeaturevisorInstance.getVariableObject(
}
}

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

11 changes: 5 additions & 6 deletions src/main/kotlin/com/featurevisor/sdk/Instance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.featurevisor.types.*
import com.featurevisor.types.EventName.*
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlin.coroutines.resume

typealias ConfigureBucketKey = (Feature, Context, BucketKey) -> BucketKey
Expand Down Expand Up @@ -111,9 +110,9 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
handleDatafileFetch = handleDatafileFetch,
) { result ->
result.onSuccess { datafileContent ->
datafileReader = DatafileReader(datafileContent)
datafileReader = DatafileReader(datafileContent.first)
statuses.ready = true
emitter.emit(READY, datafileContent)
emitter.emit(READY, datafileContent.first, datafileContent.second)
if (refreshInterval != null) startRefreshing()
}.onFailure { error ->
logger?.error("Failed to fetch datafile: $error")
Expand Down Expand Up @@ -145,19 +144,19 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
continuation.resume(this)
}

val cb :(result:Array<out Any>) -> Unit = {
val cb: (result: Array<out Any>) -> Unit = {
this.emitter.removeListener(READY)
continuation.resume(this)
}

this.emitter.addListener(READY,cb)
this.emitter.addListener(READY, cb)
}
}

fun setDatafile(datafileJSON: String) {
val data = datafileJSON.toByteArray(Charsets.UTF_8)
try {
val datafileContent = Json.decodeFromString<DatafileContent>(String(data))
val datafileContent = JsonConfigFeatureVisor.json.decodeFromString<DatafileContent>(String(data))
datafileReader = DatafileReader(datafileContent = datafileContent)
} catch (e: Exception) {
logger?.error("could not parse datafile", mapOf("error" to e))
Expand Down
Loading
Loading