Skip to content

Commit

Permalink
Refactor Conditions and add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
uniumuniu committed Oct 5, 2023
1 parent 1cc2d21 commit 76d55f8
Show file tree
Hide file tree
Showing 8 changed files with 542 additions and 299 deletions.
7 changes: 5 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ publishing {
dependencies {
// Use the Kotlin JUnit 5 integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")

// Use the JUnit 5 integration.
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2")
// Uncomment when needed
// testImplementation("io.mockk:mockk:1.13.8")
testImplementation("io.kotest:kotest-assertions-core:5.7.2")

testRuntimeOnly("org.junit.platform:junit-platform-launcher")

Expand All @@ -58,12 +60,13 @@ dependencies {
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation("com.google.guava:guava:31.1-jre")
implementation("net.swiftzer.semver:semver:1.3.0")
implementation("com.goncalossilva:murmurhash:0.4.0")
}

// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
languageVersion.set(JavaLanguageVersion.of(11))
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/com/featurevisor/sdk/Bucket.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.featurevisor.sdk

import com.goncalossilva.murmurhash.MurmurHash3

object Bucket {
private const val HASH_SEED = 1
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().hash32x86(bucketKey.toByteArray())
val hashValue = MurmurHash3(HASH_SEED).hash32x86(bucketKey.toByteArray())
val ratio = hashValue.toDouble() / MAX_HASH_VALUE

return kotlin.math.floor(ratio * MAX_BUCKETED_NUMBER).toInt()
Expand Down
52 changes: 25 additions & 27 deletions src/main/kotlin/com/featurevisor/sdk/Conditions.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package com.featurevisor.sdk

import com.featurevisor.types.AttributeValue
import com.featurevisor.types.AttributeValue.BooleanValue
import com.featurevisor.types.AttributeValue.DoubleValue
import com.featurevisor.types.AttributeValue.IntValue
import com.featurevisor.types.AttributeValue.StringValue
import com.featurevisor.types.Condition
import com.featurevisor.types.Condition.And
import com.featurevisor.types.Condition.Multiple
import com.featurevisor.types.Condition.Not
import com.featurevisor.types.Condition.Or
import com.featurevisor.types.Condition.Plain
import com.featurevisor.types.ConditionValue
import com.featurevisor.types.Context
import com.featurevisor.types.Operator.AFTER
import com.featurevisor.types.Operator.BEFORE
import com.featurevisor.types.Operator.CONTAINS
import com.featurevisor.types.Operator.ENDS_WITH
import com.featurevisor.types.Operator.EQUALS
Expand All @@ -30,22 +28,21 @@ import com.featurevisor.types.Operator.SEMVER_LESS_THAN
import com.featurevisor.types.Operator.SEMVER_LESS_THAN_OR_EQUAL
import com.featurevisor.types.Operator.SEMVER_NOT_EQUALS
import com.featurevisor.types.Operator.STARTS_WITH
import com.featurevisor.types.PlainCondition
import net.swiftzer.semver.SemVer

object Conditions {
fun conditionIsMatched(condition: PlainCondition, context: Context): Boolean {
fun conditionIsMatched(condition: Plain, context: Context): Boolean {
val (attributeKey, operator, conditionValue) = condition
val attributeValue = context.getOrDefault(attributeKey, null) ?: return false

return when {
attributeValue is StringValue && conditionValue is StringValue -> {
attributeValue is AttributeValue.StringValue && conditionValue is ConditionValue.StringValue -> {
when (operator) {
EQUALS -> attributeValue.value == conditionValue.value
NOT_EQUALS -> attributeValue.value != conditionValue.value
CONTAINS -> attributeValue.value.contains(conditionValue.value)
NOT_CONTAINS ->
!attributeValue.value.contains(conditionValue.value)
attributeValue.value.contains(conditionValue.value).not()

STARTS_WITH ->
attributeValue.value.startsWith(conditionValue.value)
Expand All @@ -59,11 +56,9 @@ object Conditions {
SEMVER_LESS_THAN_OR_EQUAL -> compareVersions(attributeValue.value, conditionValue.value) <= 0
else -> false
}

// @TODO: handle semvers
}

attributeValue is IntValue && conditionValue is IntValue -> {
attributeValue is AttributeValue.IntValue && conditionValue is ConditionValue.IntValue -> {
when (operator) {
EQUALS -> attributeValue.value == conditionValue.value
NOT_EQUALS -> attributeValue.value != conditionValue.value
Expand All @@ -75,7 +70,7 @@ object Conditions {
}
}

attributeValue is DoubleValue && conditionValue is DoubleValue -> {
attributeValue is AttributeValue.DoubleValue && conditionValue is ConditionValue.DoubleValue -> {
when (operator) {
EQUALS -> attributeValue.value == conditionValue.value
NOT_EQUALS -> attributeValue.value != conditionValue.value
Expand All @@ -87,46 +82,49 @@ object Conditions {
}
}

attributeValue is BooleanValue && conditionValue is BooleanValue -> {
attributeValue is AttributeValue.BooleanValue && conditionValue is ConditionValue.BooleanValue -> {
when (operator) {
EQUALS -> attributeValue.value == conditionValue.value
NOT_EQUALS -> attributeValue.value != conditionValue.value
else -> false
}
}

attributeValue is StringValue && conditionValue is AttributeValue.ArrayValue -> {
attributeValue is AttributeValue.StringValue && conditionValue is ConditionValue.ArrayValue -> {
when (operator) {
IN_ARRAY -> attributeValue.value in conditionValue.values
NOT_IN_ARRAY -> (attributeValue.value in conditionValue.values).not()
else -> false
}
}

// @TODO: handle dates
attributeValue is AttributeValue.DateValue && conditionValue is ConditionValue.DateTimeValue -> {
when (operator) {
EQUALS -> attributeValue.value == conditionValue.value
BEFORE -> attributeValue.value < conditionValue.value
AFTER -> attributeValue.value > conditionValue.value
else -> false
}
}

else -> false
}
}

fun allConditionsAreMatched(condition: Condition, context: Context): Boolean {
return when (condition) {
is Plain -> conditionIsMatched(condition.condition, context)

is Multiple -> condition.conditions.all {
allConditionsAreMatched(condition, context)
}
is Plain -> conditionIsMatched(condition, context)

is And -> condition.condition.and.all {
allConditionsAreMatched(condition, context)
is And -> condition.and.all {
allConditionsAreMatched(it, context)
}

is Or -> condition.condition.or.any {
allConditionsAreMatched(condition, context)
is Or -> condition.or.any {
allConditionsAreMatched(it, context)
}

is Not -> condition.condition.not.all {
allConditionsAreMatched(condition, context)
is Not -> condition.not.all {
allConditionsAreMatched(it, context)
}.not()
}
}
Expand Down
161 changes: 0 additions & 161 deletions src/main/kotlin/com/featurevisor/sdk/MurmurHash3.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package com.featurevisor.types

import java.time.LocalDate

typealias AttributeKey = String

data class Attribute(
val key: AttributeKey,
val type: String,
val archived: Boolean?,
val capture: Boolean?,
)

sealed class AttributeValue {
data class StringValue(val value: String) : AttributeValue()
data class IntValue(val value: Int) : AttributeValue()
data class DoubleValue(val value: Double) : AttributeValue()
data class BooleanValue(val value: Boolean) : AttributeValue()
data class ArrayValue(val values: List<String>) : AttributeValue()

// @TODO: implement Date
data class DateValue(val value: LocalDate) : AttributeValue()
object NullValue : AttributeValue()
}
25 changes: 25 additions & 0 deletions src/main/kotlin/com/featurevisor/types/Condition.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.featurevisor.types

import java.time.LocalDate

sealed class Condition {
data class Plain(
val attributeKey: AttributeKey,
val operator: Operator,
val value: ConditionValue,
) : Condition()

data class And(val and: List<Condition>) : Condition()
data class Or(val or: List<Condition>) : Condition()
data class Not(val not: List<Condition>) : Condition()
}

sealed class ConditionValue {
data class StringValue(val value: String) : ConditionValue()
data class IntValue(val value: Int) : ConditionValue()
data class DoubleValue(val value: Double) : ConditionValue()
data class BooleanValue(val value: Boolean) : ConditionValue()
data class ArrayValue(val values: List<String>) : ConditionValue()
data class DateTimeValue(val value: LocalDate) : ConditionValue()
object NullValue : ConditionValue()
}
Loading

0 comments on commit 76d55f8

Please sign in to comment.