Skip to content

Commit c5ba000

Browse files
authored
Make conditions work properly for every case (#8)
1 parent 366abf2 commit c5ba000

File tree

9 files changed

+712
-413
lines changed

9 files changed

+712
-413
lines changed

build.gradle.kts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ publishing {
4646
dependencies {
4747
// Use the Kotlin JUnit 5 integration.
4848
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
49-
5049
// Use the JUnit 5 integration.
5150
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2")
51+
// Uncomment when needed
52+
// testImplementation("io.mockk:mockk:1.13.8")
53+
testImplementation("io.kotest:kotest-assertions-core:5.7.2")
5254

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

@@ -57,12 +59,14 @@ dependencies {
5759

5860
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
5961
implementation("com.google.guava:guava:31.1-jre")
62+
implementation("net.swiftzer.semver:semver:1.3.0")
63+
implementation("com.goncalossilva:murmurhash:0.4.0")
6064
}
6165

6266
// Apply a specific Java toolchain to ease working on different environments.
6367
java {
6468
toolchain {
65-
languageVersion.set(JavaLanguageVersion.of(8))
69+
languageVersion.set(JavaLanguageVersion.of(11))
6670
}
6771
}
6872

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package com.featurevisor.sdk
22

3+
import com.goncalossilva.murmurhash.MurmurHash3
4+
35
object Bucket {
4-
private const val HASH_SEED = 1
6+
private const val HASH_SEED = 1u
57
private const val MAX_HASH_VALUE = 4294967296 // 2^32
68

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

1012
fun getBucketedNumber(bucketKey: String): Int {
11-
val hashValue = MurmurHash3().hash32x86(bucketKey.toByteArray())
13+
val hashValue = MurmurHash3(HASH_SEED).hash32x86(bucketKey.toByteArray())
1214
val ratio = hashValue.toDouble() / MAX_HASH_VALUE
1315

1416
return kotlin.math.floor(ratio * MAX_BUCKETED_NUMBER).toInt()
Lines changed: 103 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,129 @@
11
package com.featurevisor.sdk
22

33
import com.featurevisor.types.AttributeValue
4+
import com.featurevisor.types.Condition
5+
import com.featurevisor.types.Condition.And
6+
import com.featurevisor.types.Condition.Not
7+
import com.featurevisor.types.Condition.Or
8+
import com.featurevisor.types.Condition.Plain
49
import com.featurevisor.types.ConditionValue
510
import com.featurevisor.types.Context
6-
import com.featurevisor.types.Operator
7-
import com.featurevisor.types.PlainCondition
11+
import com.featurevisor.types.Operator.AFTER
12+
import com.featurevisor.types.Operator.BEFORE
13+
import com.featurevisor.types.Operator.CONTAINS
14+
import com.featurevisor.types.Operator.ENDS_WITH
15+
import com.featurevisor.types.Operator.EQUALS
16+
import com.featurevisor.types.Operator.GREATER_THAN
17+
import com.featurevisor.types.Operator.GREATER_THAN_OR_EQUAL
18+
import com.featurevisor.types.Operator.IN_ARRAY
19+
import com.featurevisor.types.Operator.LESS_THAN
20+
import com.featurevisor.types.Operator.LESS_THAN_OR_EQUAL
21+
import com.featurevisor.types.Operator.NOT_CONTAINS
22+
import com.featurevisor.types.Operator.NOT_EQUALS
23+
import com.featurevisor.types.Operator.NOT_IN_ARRAY
24+
import com.featurevisor.types.Operator.SEMVER_EQUALS
25+
import com.featurevisor.types.Operator.SEMVER_GREATER_THAN
26+
import com.featurevisor.types.Operator.SEMVER_GREATER_THAN_OR_EQUAL
27+
import com.featurevisor.types.Operator.SEMVER_LESS_THAN
28+
import com.featurevisor.types.Operator.SEMVER_LESS_THAN_OR_EQUAL
29+
import com.featurevisor.types.Operator.SEMVER_NOT_EQUALS
30+
import com.featurevisor.types.Operator.STARTS_WITH
31+
import net.swiftzer.semver.SemVer
832

933
object Conditions {
10-
fun conditionIsMatched(condition: PlainCondition, context: Context): Boolean {
11-
val (attribute, operator, value) = condition
34+
fun conditionIsMatched(condition: Plain, context: Context): Boolean {
35+
val (attributeKey, operator, conditionValue) = condition
36+
val attributeValue = context.getOrDefault(attributeKey, null) ?: return false
1237

13-
val contextValue = context[attribute]
14-
val conditionValue = value
38+
return when {
39+
attributeValue is AttributeValue.StringValue && conditionValue is ConditionValue.StringValue -> {
40+
when (operator) {
41+
EQUALS -> attributeValue.value == conditionValue.value
42+
NOT_EQUALS -> attributeValue.value != conditionValue.value
43+
CONTAINS -> attributeValue.value.contains(conditionValue.value)
44+
NOT_CONTAINS -> attributeValue.value.contains(conditionValue.value).not()
45+
STARTS_WITH -> attributeValue.value.startsWith(conditionValue.value)
46+
ENDS_WITH -> attributeValue.value.endsWith(conditionValue.value)
47+
SEMVER_EQUALS -> compareVersions(attributeValue.value, conditionValue.value) == 0
48+
SEMVER_NOT_EQUALS -> compareVersions(attributeValue.value, conditionValue.value) != 0
49+
SEMVER_GREATER_THAN -> compareVersions(attributeValue.value, conditionValue.value) == 1
50+
SEMVER_GREATER_THAN_OR_EQUAL -> compareVersions(attributeValue.value, conditionValue.value) >= 0
51+
SEMVER_LESS_THAN -> compareVersions(attributeValue.value, conditionValue.value) == -1
52+
SEMVER_LESS_THAN_OR_EQUAL -> compareVersions(attributeValue.value, conditionValue.value) <= 0
53+
else -> false
54+
}
55+
}
1556

16-
// string
17-
if (contextValue is AttributeValue.StringValue) {
18-
// string / string
19-
if (conditionValue is ConditionValue.StringValue) {
57+
attributeValue is AttributeValue.IntValue && conditionValue is ConditionValue.IntValue -> {
2058
when (operator) {
21-
Operator.equals -> return contextValue.value == conditionValue.value
22-
Operator.notEquals -> return contextValue.value != conditionValue.value
23-
Operator.contains -> return contextValue.value.contains(conditionValue.value)
24-
Operator.notContains ->
25-
return !contextValue.value.contains(conditionValue.value)
59+
EQUALS -> attributeValue.value == conditionValue.value
60+
NOT_EQUALS -> attributeValue.value != conditionValue.value
61+
GREATER_THAN -> attributeValue.value > conditionValue.value
62+
GREATER_THAN_OR_EQUAL -> attributeValue.value >= conditionValue.value
63+
LESS_THAN -> attributeValue.value < conditionValue.value
64+
LESS_THAN_OR_EQUAL -> attributeValue.value <= conditionValue.value
65+
else -> false
66+
}
67+
}
2668

27-
Operator.startsWith ->
28-
return contextValue.value.startsWith(conditionValue.value)
69+
attributeValue is AttributeValue.DoubleValue && conditionValue is ConditionValue.DoubleValue -> {
70+
when (operator) {
71+
EQUALS -> attributeValue.value == conditionValue.value
72+
NOT_EQUALS -> attributeValue.value != conditionValue.value
73+
GREATER_THAN -> attributeValue.value > conditionValue.value
74+
GREATER_THAN_OR_EQUAL -> attributeValue.value >= conditionValue.value
75+
LESS_THAN -> attributeValue.value < conditionValue.value
76+
LESS_THAN_OR_EQUAL -> attributeValue.value <= conditionValue.value
77+
else -> false
78+
}
79+
}
2980

30-
Operator.endsWith -> return contextValue.value.endsWith(conditionValue.value)
31-
else -> return false
81+
attributeValue is AttributeValue.BooleanValue && conditionValue is ConditionValue.BooleanValue -> {
82+
when (operator) {
83+
EQUALS -> attributeValue.value == conditionValue.value
84+
NOT_EQUALS -> attributeValue.value != conditionValue.value
85+
else -> false
3286
}
3387
}
3488

35-
// @TODO: string / array of strings for in/notIn operators
89+
attributeValue is AttributeValue.StringValue && conditionValue is ConditionValue.ArrayValue -> {
90+
when (operator) {
91+
IN_ARRAY -> attributeValue.value in conditionValue.values
92+
NOT_IN_ARRAY -> (attributeValue.value in conditionValue.values).not()
93+
else -> false
94+
}
95+
}
3696

37-
// @TODO: handle semvers
97+
attributeValue is AttributeValue.DateValue && conditionValue is ConditionValue.DateTimeValue -> {
98+
when (operator) {
99+
EQUALS -> attributeValue.value == conditionValue.value
100+
BEFORE -> attributeValue.value < conditionValue.value
101+
AFTER -> attributeValue.value > conditionValue.value
102+
else -> false
103+
}
104+
}
38105

39-
return false
106+
else -> false
40107
}
108+
}
41109

42-
// int
43-
if (contextValue is AttributeValue.IntValue && value is ConditionValue.IntValue) {
44-
// int / int
45-
when (operator) {
46-
Operator.equals -> return contextValue.value == value.value
47-
Operator.notEquals -> return contextValue.value != value.value
48-
Operator.greaterThan -> return contextValue.value > value.value
49-
Operator.greaterThanOrEquals -> return contextValue.value >= value.value
50-
Operator.lessThan -> return contextValue.value < value.value
51-
Operator.lessThanOrEquals -> return contextValue.value <= value.value
52-
else -> return false
53-
}
54-
}
110+
fun allConditionsAreMatched(condition: Condition, context: Context): Boolean =
111+
when (condition) {
112+
is Plain -> conditionIsMatched(condition, context)
55113

56-
// double
57-
if (contextValue is AttributeValue.DoubleValue && value is ConditionValue.DoubleValue) {
58-
// double / double
59-
when (operator) {
60-
Operator.equals -> return contextValue.value == value.value
61-
Operator.notEquals -> return contextValue.value != value.value
62-
Operator.greaterThan -> return contextValue.value > value.value
63-
Operator.greaterThanOrEquals -> return contextValue.value >= value.value
64-
Operator.lessThan -> return contextValue.value < value.value
65-
Operator.lessThanOrEquals -> return contextValue.value <= value.value
66-
else -> return false
114+
is And -> condition.and.all {
115+
allConditionsAreMatched(it, context)
67116
}
68-
}
69117

70-
// boolean
71-
if (contextValue is AttributeValue.BooleanValue && value is ConditionValue.BooleanValue) {
72-
// boolean / boolean
73-
when (operator) {
74-
Operator.equals -> return contextValue.value == value.value
75-
Operator.notEquals -> return contextValue.value != value.value
76-
else -> return false
118+
is Or -> condition.or.any {
119+
allConditionsAreMatched(it, context)
77120
}
78-
}
79121

80-
// @TODO: handle dates
122+
is Not -> condition.not.all {
123+
allConditionsAreMatched(it, context)
124+
}.not()
125+
}
81126

82-
return false
83-
}
127+
private fun compareVersions(actual: String, condition: String): Int =
128+
SemVer.parse(actual).compareTo(SemVer.parse(condition))
84129
}

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

Lines changed: 0 additions & 161 deletions
This file was deleted.

0 commit comments

Comments
 (0)