Skip to content

Commit 581831c

Browse files
committed
Runtime Deserialization - POC1
1 parent e703d04 commit 581831c

File tree

18 files changed

+561
-440
lines changed

18 files changed

+561
-440
lines changed

build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ dependencies {
6666
implementation("com.squareup.okhttp3:okhttp:4.11.0")
6767
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
6868
implementation("org.yaml:snakeyaml:2.2")
69-
implementation("com.google.code.gson:gson:2.10.1")
7069
}
7170

7271
// Apply a specific Java toolchain to ease working on different environments.

gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
group=com.featurevisor
22
version=0.0.1-SNAPSHOT
3+
org.gradle.daemon=true
4+
org.gradle.parallel=true

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import com.featurevisor.types.FeatureKey
88
import com.featurevisor.types.Segment
99
import com.featurevisor.types.SegmentKey
1010

11-
class DatafileReader constructor(
11+
class DatafileReader (
1212
datafileContent: DatafileContent,
1313
) {
1414

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

2121
fun getRevision(): String {
2222
return revision

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

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ fun FeaturevisorInstance.isEnabled(featureKey: FeatureKey, context: Context = em
7373
return evaluation.enabled == true
7474
}
7575

76-
@Suppress("UNREACHABLE_CODE")
7776
fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Context = emptyMap()): Evaluation {
7877
var evaluation: Evaluation
7978
try {
@@ -125,7 +124,7 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
125124
return evaluation
126125
}
127126

128-
if (feature.variations.isNullOrEmpty()) {
127+
if (feature.getVariations().isEmpty()) {
129128
// no variations
130129
evaluation = Evaluation(
131130
featureKey = featureKey,
@@ -141,7 +140,7 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
141140
// forced
142141
val force = findForceFromFeature(feature, context, datafileReader)
143142
if (force != null) {
144-
val variation = feature.variations.firstOrNull { it.value == force.variation }
143+
val variation = feature.getVariations().firstOrNull { it.value == force.variation }
145144

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

162161
val matchedTrafficAndAllocation = getMatchedTrafficAndAllocation(
163-
feature.traffic,
162+
feature.getTraffic(),
164163
finalContext,
165164
bucketValue,
166165
datafileReader,
167-
logger
168166
)
169167

170168
val matchedTraffic = matchedTrafficAndAllocation.matchedTraffic
171169

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

192190
// regular allocation
193191
if (matchedAllocation != null) {
194-
val variation = feature.variations?.firstOrNull { it.value == matchedAllocation.variation }
192+
val variation = feature.getVariations().firstOrNull { it.value == matchedAllocation.variation }
195193
if (variation != null) {
196194
evaluation = Evaluation(
197195
featureKey = feature.key,
@@ -229,7 +227,6 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont
229227
}
230228

231229

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

235232
var evaluation: Evaluation
@@ -301,8 +298,8 @@ fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context =
301298
}
302299

303300
// required
304-
if (feature.required.isNullOrEmpty().not()) {
305-
val requiredFeaturesAreEnabled = feature.required?.all { item ->
301+
if (feature.getRequired().isNullOrEmpty().not()) {
302+
val requiredFeaturesAreEnabled = feature.getRequired()?.all { item ->
306303
var requiredKey: FeatureKey? = null
307304
var requiredVariation: VariationValue? = null
308305
when (item) {
@@ -347,7 +344,7 @@ fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context =
347344
val bucketValue = getBucketValue(feature = feature, context = finalContext)
348345

349346
val matchedTraffic = getMatchedTraffic(
350-
traffic = feature.traffic,
347+
traffic = feature.getTraffic(),
351348
context = finalContext,
352349
datafileReader = datafileReader,
353350
)
@@ -440,7 +437,6 @@ fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context =
440437
}
441438
}
442439

443-
@Suppress("UNREACHABLE_CODE")
444440
fun FeaturevisorInstance.evaluateVariable(
445441
featureKey: FeatureKey,
446442
variableKey: VariableKey,
@@ -497,7 +493,7 @@ fun FeaturevisorInstance.evaluateVariable(
497493
return evaluation
498494
}
499495

500-
val variableSchema = feature.variablesSchema?.firstOrNull { variableSchema ->
496+
val variableSchema = feature.getVariablesSchema().firstOrNull { variableSchema ->
501497
variableSchema.key == variableKey
502498
}
503499

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

539535
val matchedTrafficAndAllocation = getMatchedTrafficAndAllocation(
540-
traffic = feature.traffic,
536+
traffic = feature.getTraffic(),
541537
context = finalContext,
542538
bucketValue = bucketValue,
543539
datafileReader = datafileReader,
544-
logger = logger
545540
)
546541

547542
matchedTrafficAndAllocation.matchedTraffic?.let { matchedTraffic ->
@@ -571,7 +566,7 @@ fun FeaturevisorInstance.evaluateVariable(
571566
matchedAllocation.variation
572567
}
573568

574-
val variation = feature.variations?.firstOrNull { variation ->
569+
val variation = feature.getVariations().firstOrNull { variation ->
575570
variation.value == variationValue
576571
}
577572

@@ -654,10 +649,10 @@ fun FeaturevisorInstance.evaluateVariable(
654649

655650
private fun FeaturevisorInstance.getBucketKey(feature: Feature, context: Context): BucketKey {
656651
val featureKey = feature.key
657-
var type: String
658-
var attributeKeys: List<AttributeKey>
652+
val type: String
653+
val attributeKeys: List<AttributeKey>
659654

660-
when (val bucketBy = feature.bucketBy) {
655+
when (val bucketBy = feature.getBucketBy()) {
661656
is BucketBy.Single -> {
662657
type = "plain"
663658
attributeKeys = listOf(bucketBy.bucketBy)

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal fun FeaturevisorInstance.findForceFromFeature(
2121
datafileReader: DatafileReader,
2222
): Force? {
2323

24-
return feature.force?.firstOrNull { force ->
24+
return feature.getForce().firstOrNull { force ->
2525
when {
2626
force.conditions != null -> allConditionsAreMatched(force.conditions, context)
2727
force.segments != null -> allGroupSegmentsAreMatched(
@@ -46,7 +46,7 @@ internal fun FeaturevisorInstance.getMatchedTraffic(
4646
}
4747
}
4848

49-
internal fun FeaturevisorInstance.getMatchedAllocation(
49+
internal fun getMatchedAllocation(
5050
traffic: Traffic,
5151
bucketValue: Int,
5252
): Allocation? {
@@ -68,7 +68,6 @@ internal fun FeaturevisorInstance.getMatchedTrafficAndAllocation(
6868
context: Context,
6969
bucketValue: Int,
7070
datafileReader: DatafileReader,
71-
logger: Logger?,
7271
): MatchedTrafficAndAllocation {
7372

7473
var matchedAllocation: Allocation? = null

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

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,31 @@ package com.featurevisor.sdk
22

33
import com.featurevisor.types.DatafileContent
44
import kotlinx.serialization.decodeFromString
5-
import java.io.IOException
65
import okhttp3.*
7-
import kotlinx.serialization.json.Json
86
import okhttp3.HttpUrl.Companion.toHttpUrl
9-
import java.lang.IllegalArgumentException
7+
import java.io.IOException
108

119
const val BODY_BYTE_COUNT = 1000000L
1210
val client = OkHttpClient()
1311

1412
// MARK: - Fetch datafile content
1513
@Throws(IOException::class)
16-
suspend fun FeaturevisorInstance.fetchDatafileContent(
14+
fun FeaturevisorInstance.fetchDatafileContent(
1715
url: String,
1816
handleDatafileFetch: DatafileFetchHandler? = null,
19-
completion: (Result<DatafileContent>) -> Unit,
17+
completion: (Result<Pair<DatafileContent, String>>) -> Unit,
2018
) {
2119
handleDatafileFetch?.let { handleFetch ->
22-
val result = handleFetch(url)
23-
completion(result)
20+
val result = handleFetch(url).getOrNull()!!
21+
completion(Result.success(Pair(result, "")))
2422
} ?: run {
2523
fetchDatafileContentFromUrl(url, completion)
2624
}
2725
}
2826

2927
private fun fetchDatafileContentFromUrl(
3028
url: String,
31-
completion: (Result<DatafileContent>) -> Unit,
29+
completion: (Result<Pair<DatafileContent, String>>) -> Unit,
3230
) {
3331
try {
3432
val httpUrl = url.toHttpUrl()
@@ -45,21 +43,18 @@ private fun fetchDatafileContentFromUrl(
4543

4644
private inline fun fetch(
4745
request: Request,
48-
crossinline completion: (Result<DatafileContent>) -> Unit,
46+
crossinline completion: (Result<Pair<DatafileContent, String>>) -> Unit,
4947
) {
5048
val call = client.newCall(request)
5149
call.enqueue(object : Callback {
5250
override fun onResponse(call: Call, response: Response) {
5351
val responseBody = response.peekBody(BODY_BYTE_COUNT)
5452
if (response.isSuccessful) {
55-
val json = Json {
56-
ignoreUnknownKeys = true
57-
}
5853
val responseBodyString = responseBody.string()
5954
FeaturevisorInstance.companionLogger?.debug(responseBodyString)
6055
try {
61-
val content = json.decodeFromString<DatafileContent>(responseBodyString)
62-
completion(Result.success(content))
56+
val content = JsonConfigFeatureVisor.json.decodeFromString<DatafileContent>(responseBodyString)
57+
completion(Result.success(Pair(content, responseBodyString)))
6358
} catch (throwable: Throwable) {
6459
completion(
6560
Result.failure(

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

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

3-
import com.featurevisor.sdk.FeaturevisorError.*
3+
import com.featurevisor.sdk.FeaturevisorError.MissingDatafileUrlWhileRefreshing
44
import com.featurevisor.types.EventName
5-
import kotlinx.coroutines.CoroutineScope
6-
import kotlinx.coroutines.Dispatchers
75
import kotlinx.coroutines.delay
86
import kotlinx.coroutines.isActive
97
import kotlinx.coroutines.launch
@@ -45,10 +43,10 @@ private suspend fun FeaturevisorInstance.refresh() {
4543
) { result ->
4644
result.onSuccess { datafileContent ->
4745
val currentRevision = getRevision()
48-
val newRevision = datafileContent.revision
46+
val newRevision = datafileContent.first.revision
4947
val isNotSameRevision = currentRevision != newRevision
5048

51-
datafileReader = DatafileReader(datafileContent)
49+
datafileReader = DatafileReader(datafileContent.first)
5250
logger?.info("refreshed datafile")
5351

5452
emitter.emit(EventName.REFRESH)

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

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,12 @@ package com.featurevisor.sdk
22

33
import com.featurevisor.sdk.Conditions.allConditionsAreMatched
44
import com.featurevisor.types.Context
5-
import com.featurevisor.types.FeatureKey
65
import com.featurevisor.types.GroupSegment
76
import com.featurevisor.types.GroupSegment.*
87
import com.featurevisor.types.Segment
9-
import com.featurevisor.types.VariationValue
10-
11-
internal fun FeaturevisorInstance.segmentIsMatched(
12-
featureKey: FeatureKey,
13-
context: Context,
14-
): VariationValue? {
15-
val evaluation = evaluateVariation(featureKey, context)
16-
17-
if (evaluation.variationValue != null) {
18-
return evaluation.variationValue
19-
}
20-
21-
if (evaluation.variation != null) {
22-
return evaluation.variation.value
23-
}
24-
25-
return null
26-
}
278

289
internal fun segmentIsMatched(segment: Segment, context: Context): Boolean {
29-
return allConditionsAreMatched(segment.conditions, context)
10+
return allConditionsAreMatched(segment.getCondition(), context)
3011
}
3112

3213
internal fun FeaturevisorInstance.allGroupSegmentsAreMatched(

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ inline fun <reified T : Any> FeaturevisorInstance.getVariableObject(
8585
}
8686
}
8787

88-
inline fun <reified T: Any> FeaturevisorInstance.getVariableJSON(
88+
inline fun <reified T : Any> FeaturevisorInstance.getVariableJSON(
8989
featureKey: FeatureKey,
9090
variableKey: VariableKey,
9191
context: Context,
@@ -97,4 +97,3 @@ inline fun <reified T: Any> FeaturevisorInstance.getVariableJSON(
9797
null
9898
}
9999
}
100-

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import com.featurevisor.types.*
88
import com.featurevisor.types.EventName.*
99
import kotlinx.coroutines.*
1010
import kotlinx.serialization.decodeFromString
11-
import kotlinx.serialization.json.Json
1211
import kotlin.coroutines.resume
1312

1413
typealias ConfigureBucketKey = (Feature, Context, BucketKey) -> BucketKey
@@ -111,9 +110,9 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
111110
handleDatafileFetch = handleDatafileFetch,
112111
) { result ->
113112
result.onSuccess { datafileContent ->
114-
datafileReader = DatafileReader(datafileContent)
113+
datafileReader = DatafileReader(datafileContent.first)
115114
statuses.ready = true
116-
emitter.emit(READY, datafileContent)
115+
emitter.emit(READY, datafileContent.first, datafileContent.second)
117116
if (refreshInterval != null) startRefreshing()
118117
}.onFailure { error ->
119118
logger?.error("Failed to fetch datafile: $error")
@@ -145,19 +144,19 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) {
145144
continuation.resume(this)
146145
}
147146

148-
val cb :(result:Array<out Any>) -> Unit = {
147+
val cb: (result: Array<out Any>) -> Unit = {
149148
this.emitter.removeListener(READY)
150149
continuation.resume(this)
151150
}
152151

153-
this.emitter.addListener(READY,cb)
152+
this.emitter.addListener(READY, cb)
154153
}
155154
}
156155

157156
fun setDatafile(datafileJSON: String) {
158157
val data = datafileJSON.toByteArray(Charsets.UTF_8)
159158
try {
160-
val datafileContent = Json.decodeFromString<DatafileContent>(String(data))
159+
val datafileContent = JsonConfigFeatureVisor.json.decodeFromString<DatafileContent>(String(data))
161160
datafileReader = DatafileReader(datafileContent = datafileContent)
162161
} catch (e: Exception) {
163162
logger?.error("could not parse datafile", mapOf("error" to e))

0 commit comments

Comments
 (0)