Skip to content

Commit

Permalink
Migrate instance (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
uniumuniu authored Oct 27, 2023
1 parent 9ecf32d commit 562db61
Show file tree
Hide file tree
Showing 32 changed files with 1,635 additions and 342 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ We are breaking down the various parts that we need to migrate to Swift in the s
| | SDK's `conditions.ts` ➡️ `Conditions.kt` ||
| | SDK's `datafileReader.ts` ➡️ `DatafileReader.kt` ||
| | SDK's `emitter.ts` ➡️ `Emitter.kt` ||
| | SDK's `feature.ts` ➡️ `Feature.kt` | |
| | SDK's `instance.ts` ➡️ `Instance.kt` | 🟠 |
| | SDK's `logger.ts` ➡️ `Logger.kt` | |
| | SDK's `segments.ts` ➡️ `Segments.kt` | |
| | SDK's `feature.ts` ➡️ `Instance+Feature.kt` | |
| | SDK's `instance.ts` ➡️ `Instance.kt` | |
| | SDK's `logger.ts` ➡️ `Logger.kt` | |
| | SDK's `segments.ts` ➡️ `Instance+Segments.kt` | |
| | | |
| Constructor options | `bucketKeySeparator` | |
| | `configureBucketKey` | |
Expand Down
17 changes: 2 additions & 15 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ dependencies {
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("com.squareup.okhttp3:okhttp:4.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}

// Apply a specific Java toolchain to ease working on different environments.
Expand All @@ -80,18 +82,3 @@ tasks.named<Test>("test") {
showStandardStreams = true
}
}

// TODO: Remove excludes when Instance.kt is ready
sourceSets {
main {
kotlin {
exclude("com/featurevisor/sdk/Instance.kt")
exclude("com/featurevisor/sdk/InstanceOptions.kt")
}
}
test {
kotlin {
exclude("com/featurevisor/sdk/InstanceTest.kt")
}
}
}
5 changes: 3 additions & 2 deletions src/main/kotlin/com/featurevisor/sdk/Bucket.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package com.featurevisor.sdk

import com.goncalossilva.murmurhash.MurmurHash3
import kotlin.math.floor

object Bucket {

private const val HASH_SEED = 1u
private const val MAX_HASH_VALUE = 4294967296 // 2^32

// 100% * 1000 to include three decimal places in the same integer value
private const val MAX_BUCKETED_NUMBER = 100000

fun getBucketedNumber(bucketKey: String): Int {
val hashValue = MurmurHash3(HASH_SEED).hash32x86(bucketKey.toByteArray())
val ratio = hashValue.toDouble() / MAX_HASH_VALUE

return kotlin.math.floor(ratio * MAX_BUCKETED_NUMBER).toInt()
return floor(ratio * MAX_BUCKETED_NUMBER).toInt()
}
}
62 changes: 40 additions & 22 deletions src/main/kotlin/com/featurevisor/sdk/Conditions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.featurevisor.types.Operator.STARTS_WITH
import net.swiftzer.semver.SemVer

object Conditions {

fun conditionIsMatched(condition: Plain, context: Context): Boolean {
val (attributeKey, operator, conditionValue) = condition
val attributeValue = context.getOrDefault(attributeKey, null) ?: return false
Expand All @@ -44,12 +45,36 @@ object Conditions {
NOT_CONTAINS -> attributeValue.value.contains(conditionValue.value).not()
STARTS_WITH -> attributeValue.value.startsWith(conditionValue.value)
ENDS_WITH -> attributeValue.value.endsWith(conditionValue.value)
SEMVER_EQUALS -> compareVersions(attributeValue.value, conditionValue.value) == 0
SEMVER_NOT_EQUALS -> compareVersions(attributeValue.value, conditionValue.value) != 0
SEMVER_GREATER_THAN -> compareVersions(attributeValue.value, conditionValue.value) == 1
SEMVER_GREATER_THAN_OR_EQUAL -> compareVersions(attributeValue.value, conditionValue.value) >= 0
SEMVER_LESS_THAN -> compareVersions(attributeValue.value, conditionValue.value) == -1
SEMVER_LESS_THAN_OR_EQUAL -> compareVersions(attributeValue.value, conditionValue.value) <= 0
SEMVER_EQUALS -> compareVersions(
attributeValue.value,
conditionValue.value,
) == 0

SEMVER_NOT_EQUALS -> compareVersions(
attributeValue.value,
conditionValue.value,
) != 0

SEMVER_GREATER_THAN -> compareVersions(
attributeValue.value,
conditionValue.value
) == 1

SEMVER_GREATER_THAN_OR_EQUAL -> compareVersions(
attributeValue.value,
conditionValue.value
) >= 0

SEMVER_LESS_THAN -> compareVersions(
attributeValue.value,
conditionValue.value
) == -1

SEMVER_LESS_THAN_OR_EQUAL -> compareVersions(
attributeValue.value,
conditionValue.value
) <= 0

else -> false
}
}
Expand Down Expand Up @@ -107,23 +132,16 @@ object Conditions {
}
}

fun allConditionsAreMatched(condition: Condition, context: Context): Boolean =
when (condition) {
fun allConditionsAreMatched(condition: Condition, context: Context): Boolean {
return when (condition) {
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 And -> condition.and.all { allConditionsAreMatched(it, context) }
is Or -> condition.or.any { allConditionsAreMatched(it, context) }
is Not -> condition.not.all { allConditionsAreMatched(it, context) }.not()
}
}

private fun compareVersions(actual: String, condition: String): Int =
SemVer.parse(actual).compareTo(SemVer.parse(condition))
private fun compareVersions(actual: String, condition: String): Int {
return SemVer.parse(actual).compareTo(SemVer.parse(condition))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,37 @@ import com.featurevisor.types.FeatureKey
import com.featurevisor.types.Segment
import com.featurevisor.types.SegmentKey

class DataFileReader constructor(
class DatafileReader constructor(
datafileJson: DatafileContent,
) {

private val schemaVersion: String = datafileJson.schemaVersion
private val revision: String = datafileJson.revision
private val attributes: List<Attribute> = datafileJson.attributes
private val segments: List<Segment> = datafileJson.segments
private val features: List<Feature> = datafileJson.features

fun getRevision(): String = revision
fun getRevision(): String {
return revision
}

fun getSchemaVersion(): String = schemaVersion
fun getSchemaVersion(): String {
return schemaVersion
}

fun getAllAttributes(): List<Attribute> = attributes
fun getAllAttributes(): List<Attribute> {
return attributes
}

fun getAttribute(attributeKey: AttributeKey): Attribute? =
attributes.find { attribute -> attribute.key == attributeKey }
fun getAttribute(attributeKey: AttributeKey): Attribute? {
return attributes.find { attribute -> attribute.key == attributeKey }
}

fun getSegment(segmentKey: SegmentKey): Segment? =
segments.find { segment -> segment.key == segmentKey }
fun getSegment(segmentKey: SegmentKey): Segment? {
return segments.find { segment -> segment.key == segmentKey }
}

fun getFeature(featureKey: FeatureKey): Feature? =
features.find { feature -> feature.key == featureKey }
fun getFeature(featureKey: FeatureKey): Feature? {
return features.find { feature -> feature.key == featureKey }
}
}
9 changes: 5 additions & 4 deletions src/main/kotlin/com/featurevisor/sdk/Emitter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package com.featurevisor.sdk
import com.featurevisor.types.EventName

class Emitter {
private val listeners = mutableMapOf<EventName, () -> Unit>()

fun addListener(event: EventName, listener: () -> Unit) {
private val listeners = mutableMapOf<EventName, (Array<out Any>) -> Unit>()

fun addListener(event: EventName, listener: (Array<out Any>) -> Unit) {
listeners.putIfAbsent(event, listener)
}

Expand All @@ -17,7 +18,7 @@ class Emitter {
listeners.clear()
}

fun emit(event: EventName) {
listeners.getOrDefault(event, null)?.invoke()
fun emit(event: EventName, vararg args: Any) {
listeners.getOrDefault(event, null)?.invoke(args)
}
}
22 changes: 22 additions & 0 deletions src/main/kotlin/com/featurevisor/sdk/FeaturevisorError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.featurevisor.sdk

sealed class FeaturevisorError(message: String) : Throwable(message = message) {

/// Thrown when attempting to init Featurevisor instance without passing datafile and datafileUrl.
/// At least one of them is required to init the SDK correctly
object MissingDatafileOptions : FeaturevisorError("Missing data file options")

class FetchingDataFileFailed(val result: String) : FeaturevisorError("Fetching data file failed")

/// Thrown when receiving unparseable Datafile JSON responses.
/// - Parameters:
/// - data: The data being parsed.
/// - errorMessage: The message from the error which occured during parsing.
class UnparsableJson(val data: String?, errorMessage: String) : FeaturevisorError(errorMessage)

/// Thrown when attempting to construct an invalid URL.
/// - Parameter string: The invalid URL string.
class InvalidUrl(val url: String?) : FeaturevisorError("Invalid URL")

object MissingDatafileUrlWhileRefreshing : FeaturevisorError("Missing datafile url need to refresh")
}
33 changes: 33 additions & 0 deletions src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.featurevisor.sdk

import com.featurevisor.types.AttributeValue
import com.featurevisor.types.Context
import com.featurevisor.types.EventName.ACTIVATION
import com.featurevisor.types.FeatureKey
import com.featurevisor.types.VariationValue

fun FeaturevisorInstance.activate(featureKey: FeatureKey, context: Context = emptyMap()): VariationValue? {
val evaluation = evaluateVariation(featureKey, context)
val variationValue = evaluation.variation?.value ?: evaluation.variationValue ?: return null
val finalContext = interceptContext?.invoke(context) ?: context
val captureContext = mutableMapOf<String, AttributeValue>()
val attributesForCapturing = datafileReader.getAllAttributes()
.filter { it.capture == true }

attributesForCapturing.forEach { attribute ->
finalContext[attribute.key]?.let {
captureContext[attribute.key] = it
}
}

emitter.emit(
ACTIVATION,
featureKey,
variationValue,
finalContext,
captureContext,
evaluation
)

return variationValue
}
Loading

0 comments on commit 562db61

Please sign in to comment.