Skip to content

Commit 7116f6a

Browse files
Remove hard casts of Any in Evaluator (#165)
1 parent 1f2c269 commit 7116f6a

File tree

6 files changed

+107
-76
lines changed

6 files changed

+107
-76
lines changed

src/main/kotlin/com/statsig/sdk/DataTypes.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ internal data class APIRule(
4444

4545
internal data class APICondition(
4646
@SerializedName("type") val type: String,
47-
@SerializedName("targetValue") val targetValue: Any,
48-
@SerializedName("operator") val operator: String,
49-
@SerializedName("field") val field: String,
50-
@SerializedName("additionalValues") val additionalValues: Map<String, Any>,
47+
@SerializedName("targetValue") val targetValue: Any?,
48+
@SerializedName("operator") val operator: String?,
49+
@SerializedName("field") val field: String?,
50+
@SerializedName("additionalValues") val additionalValues: Map<String, Any>?,
5151
@SerializedName("idType") val idType: String,
5252
)
5353

src/main/kotlin/com/statsig/sdk/EvaluationDetails.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ internal class EvaluationDetails(
55
var initTime: Long,
66
var reason: EvaluationReason,
77
) {
8-
var serverTime: Long = Utils().getTimeInMillis()
8+
var serverTime: Long = Utils.getTimeInMillis()
99

1010
fun toMap(): Map<String, String> {
1111
return mapOf(

src/main/kotlin/com/statsig/sdk/Evaluator.kt

Lines changed: 89 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ internal class Evaluator(
7777
}
7878

7979
fun getVariants(configName: String): Map<String, Map<String, Any>> {
80-
var variants: MutableMap<String, Map<String, Any>> = HashMap()
80+
val variants: MutableMap<String, Map<String, Any>> = HashMap()
8181
val config = specStore.getConfig(configName) ?: return variants
8282

8383
var previousAllocation = 0.0
84-
for (r: APIRule in config!!.rules) {
84+
for (r: APIRule in config.rules) {
8585
val value = r.returnValue.toString()
8686
val cond = r.conditions[0]
8787
var percent = 0.0
@@ -163,8 +163,18 @@ internal class Evaluator(
163163
return this.evaluateConfig(user, specStore.getConfig(dynamicConfigName))
164164
}
165165

166-
fun getClientInitializeResponse(user: StatsigUser, hash: HashAlgo = HashAlgo.SHA256, clientSDKKey: String? = null): Map<String, Any> {
167-
return ClientInitializeFormatter(this.specStore, this::evaluateConfig, user, hash, clientSDKKey).getFormattedResponse()
166+
fun getClientInitializeResponse(
167+
user: StatsigUser,
168+
hash: HashAlgo = HashAlgo.SHA256,
169+
clientSDKKey: String? = null
170+
): Map<String, Any> {
171+
return ClientInitializeFormatter(
172+
this.specStore,
173+
this::evaluateConfig,
174+
user,
175+
hash,
176+
clientSDKKey
177+
).getFormattedResponse()
168178
}
169179

170180
fun getLayer(user: StatsigUser, layerName: String): ConfigEvaluation {
@@ -293,7 +303,7 @@ internal class Evaluator(
293303
undelegatedSecondaryExposures.addAll(secondaryExposures)
294304
secondaryExposures.addAll(delegatedResult.secondaryExposures)
295305

296-
var evaluation = ConfigEvaluation(
306+
val evaluation = ConfigEvaluation(
297307
fetchFromServer = delegatedResult.fetchFromServer,
298308
booleanValue = delegatedResult.booleanValue,
299309
jsonValue = delegatedResult.jsonValue,
@@ -333,7 +343,7 @@ internal class Evaluator(
333343
)
334344
}
335345

336-
private fun conditionFromString(input: String?): ConfigCondition? {
346+
private fun conditionFromString(input: String?): ConfigCondition {
337347
return when (input) {
338348
"PUBLIC", "public" -> ConfigCondition.PUBLIC
339349
"FAIL_GATE", "fail_gate" -> ConfigCondition.FAIL_GATE
@@ -352,23 +362,25 @@ internal class Evaluator(
352362
private fun evaluateCondition(user: StatsigUser, condition: APICondition): ConfigEvaluation {
353363
try {
354364
var value: Any?
355-
val conditionEnum: ConfigCondition?
356-
conditionEnum = try {
365+
val field: String = Utils.toStringOrEmpty(condition.field)
366+
val conditionEnum: ConfigCondition? = try {
357367
conditionFromString(condition.type)
358368
} catch (e: java.lang.IllegalArgumentException) {
359369
errorBoundary.logException("evaluateCondition:condition", e)
360370
println("[Statsig]: An exception was caught: $e")
361371
null
362372
}
373+
363374
when (conditionEnum) {
364375
ConfigCondition.PUBLIC ->
365376
return ConfigEvaluation(fetchFromServer = false, booleanValue = true)
366377

367378
ConfigCondition.FAIL_GATE, ConfigCondition.PASS_GATE -> {
368-
val result = this.checkGate(user, condition.targetValue as String)
379+
val name = Utils.toStringOrEmpty(condition.targetValue)
380+
val result = this.checkGate(user, name)
369381
val newExposure =
370382
mapOf(
371-
"gate" to condition.targetValue,
383+
"gate" to name,
372384
"gateValue" to result.booleanValue.toString(),
373385
"ruleID" to result.ruleID,
374386
)
@@ -390,38 +402,38 @@ internal class Evaluator(
390402
}
391403

392404
ConfigCondition.IP_BASED -> {
393-
value = getFromUser(user, condition.field)
405+
value = getFromUser(user, field)
394406
if (value == null) {
395407
val ipString = getFromUser(user, "ip")?.toString()
396-
if (ipString == null) {
397-
return ConfigEvaluation(fetchFromServer = false, booleanValue = false)
408+
value = if (ipString == null) {
409+
null
398410
} else {
399-
value = CountryLookup.lookupIPString(ipString)
411+
CountryLookup.lookupIPString(ipString)
400412
}
401413
}
402414
}
403415

404416
ConfigCondition.UA_BASED -> {
405-
value = getFromUser(user, condition.field)
417+
value = getFromUser(user, field)
406418
if (value == null && !condition.field.equals("browser_version")) {
407-
value = getFromUserAgent(user, condition.field)
419+
value = getFromUserAgent(user, field)
408420
}
409421
}
410422

411423
ConfigCondition.USER_FIELD -> {
412-
value = getFromUser(user, condition.field)
424+
value = getFromUser(user, field)
413425
}
414426

415427
ConfigCondition.CURRENT_TIME -> {
416428
value = System.currentTimeMillis().toString()
417429
}
418430

419431
ConfigCondition.ENVIRONMENT_FIELD -> {
420-
value = getFromEnvironment(user, condition.field)
432+
value = getFromEnvironment(user, field)
421433
}
422434

423435
ConfigCondition.USER_BUCKET -> {
424-
val salt = getValueAsString(condition.additionalValues["salt"])
436+
val salt = getValueAsString(condition.additionalValues?.let { it["salt"] })
425437
val unitID = getUnitID(user, condition.idType) ?: ""
426438
value = computeUserHash("$salt.$unitID").mod(1000UL)
427439
}
@@ -487,7 +499,7 @@ internal class Evaluator(
487499
"version_gt" -> {
488500
return ConfigEvaluation(
489501
false,
490-
versionCompareHelper(value, condition.targetValue as String) { v1: String, v2: String ->
502+
versionCompareHelper(value, condition.targetValue) { v1: String, v2: String ->
491503
versionCompare(v1, v2) > 0
492504
},
493505
)
@@ -496,7 +508,7 @@ internal class Evaluator(
496508
"version_gte" -> {
497509
return ConfigEvaluation(
498510
false,
499-
versionCompareHelper(value, condition.targetValue as String) { v1: String, v2: String ->
511+
versionCompareHelper(value, condition.targetValue) { v1: String, v2: String ->
500512
versionCompare(v1, v2) >= 0
501513
},
502514
)
@@ -505,7 +517,7 @@ internal class Evaluator(
505517
"version_lt" -> {
506518
return ConfigEvaluation(
507519
false,
508-
versionCompareHelper(value, condition.targetValue as String) { v1: String, v2: String ->
520+
versionCompareHelper(value, condition.targetValue) { v1: String, v2: String ->
509521
versionCompare(v1, v2) < 0
510522
},
511523
)
@@ -514,7 +526,7 @@ internal class Evaluator(
514526
"version_lte" -> {
515527
return ConfigEvaluation(
516528
false,
517-
versionCompareHelper(value, condition.targetValue as String) { v1: String, v2: String ->
529+
versionCompareHelper(value, condition.targetValue) { v1: String, v2: String ->
518530
versionCompare(v1, v2) <= 0
519531
},
520532
)
@@ -523,7 +535,7 @@ internal class Evaluator(
523535
"version_eq" -> {
524536
return ConfigEvaluation(
525537
false,
526-
versionCompareHelper(value, condition.targetValue as String) { v1: String, v2: String ->
538+
versionCompareHelper(value, condition.targetValue) { v1: String, v2: String ->
527539
versionCompare(v1, v2) == 0
528540
},
529541
)
@@ -532,7 +544,7 @@ internal class Evaluator(
532544
"version_neq" -> {
533545
return ConfigEvaluation(
534546
false,
535-
versionCompareHelper(value, condition.targetValue as String) { v1: String, v2: String ->
547+
versionCompareHelper(value, condition.targetValue) { v1: String, v2: String ->
536548
versionCompare(v1, v2) != 0
537549
},
538550
)
@@ -611,15 +623,22 @@ internal class Evaluator(
611623
}
612624

613625
"str_matches" -> {
626+
val targetValue = getValueAsString(condition.targetValue)
627+
?: return ConfigEvaluation(
628+
fetchFromServer = false,
629+
booleanValue = false,
630+
)
631+
614632
val strValue =
615633
getValueAsString(value)
616634
?: return ConfigEvaluation(
617635
fetchFromServer = false,
618636
booleanValue = false,
619637
)
638+
620639
return ConfigEvaluation(
621640
fetchFromServer = false,
622-
booleanValue = Regex(condition.targetValue as String).containsMatchIn(strValue),
641+
booleanValue = Regex(targetValue).containsMatchIn(strValue),
623642
)
624643
}
625644

@@ -667,7 +686,7 @@ internal class Evaluator(
667686
}
668687

669688
"in_segment_list", "not_in_segment_list" -> {
670-
val idList = specStore.getIDList(condition.targetValue as String)
689+
val idList = specStore.getIDList(Utils.toStringOrEmpty(condition.targetValue))
671690
val stringValue = getValueAsString(value)
672691
if (idList != null && stringValue != null) {
673692
val bytes =
@@ -702,14 +721,20 @@ internal class Evaluator(
702721
target: Any?,
703722
compare: (value: String, target: String) -> Boolean,
704723
): Boolean {
705-
var strValue = getValueAsString(value) ?: return false
706-
var iterable =
707-
if (target is Iterable<*>) {
708-
target
709-
} else if (target is Array<*>) {
710-
target.asIterable()
711-
} else {
712-
return false
724+
val strValue = getValueAsString(value) ?: return false
725+
val iterable =
726+
when (target) {
727+
is Iterable<*> -> {
728+
target
729+
}
730+
731+
is Array<*> -> {
732+
target.asIterable()
733+
}
734+
735+
else -> {
736+
return false
737+
}
713738
}
714739

715740
for (match in iterable) {
@@ -780,7 +805,7 @@ internal class Evaluator(
780805
return null
781806
}
782807
return try {
783-
var epoch: Long = getEpoch(input) ?: return parseISOTimestamp(input)
808+
val epoch: Long = getEpoch(input) ?: return parseISOTimestamp(input)
784809
val instant = Instant.ofEpochMilli(epoch)
785810
Date.from(instant)
786811
} catch (e: Exception) {
@@ -789,8 +814,8 @@ internal class Evaluator(
789814
}
790815

791816
private fun versionCompare(v1: String, v2: String): Int {
792-
var parts1 = v1.split(".")
793-
var parts2 = v2.split(".")
817+
val parts1 = v1.split(".")
818+
val parts2 = v2.split(".")
794819

795820
var i = 0
796821
while (i < parts1.size.coerceAtLeast(parts2.size)) {
@@ -859,31 +884,43 @@ internal class Evaluator(
859884
if (input == null) {
860885
return null
861886
}
887+
862888
if (input is String) {
863889
return input.toDoubleOrNull()
864890
}
865-
if (input is Number) {
891+
892+
if (input is ULong) {
866893
return input.toDouble()
867894
}
868-
if (input is ULong) {
895+
896+
if (input is Double) {
897+
return input
898+
}
899+
900+
if (input is Number) {
869901
return input.toDouble()
870902
}
871-
return input as? Double
903+
904+
return null
872905
}
873906

874907
private fun contains(targets: Any?, value: Any?, ignoreCase: Boolean): Boolean {
875908
if (targets == null || value == null) {
876909
return false
877910
}
878-
var iterable: Iterable<*>
879-
if (targets is Iterable<*>) {
880-
iterable = targets
881-
} else if (targets is Array<*>) {
882-
iterable = targets.asIterable()
883-
} else {
884-
return false
885-
}
911+
val iterable: Iterable<*> = when (targets) {
912+
is Iterable<*> -> {
913+
targets
914+
}
915+
916+
is Array<*> -> {
917+
targets.asIterable()
918+
}
886919

920+
else -> {
921+
return false
922+
}
923+
}
887924
for (option in iterable) {
888925
if ((option is String) && (value is String) && option.equals(value, ignoreCase)) {
889926
return true
@@ -892,7 +929,6 @@ internal class Evaluator(
892929
return true
893930
}
894931
}
895-
896932
return false
897933
}
898934

@@ -931,21 +967,9 @@ internal class Evaluator(
931967
private fun browserVersionFromUserAgent(userAgent: String): String {
932968
val agent = uaParser.parseUserAgent(userAgent)
933969
return arrayOf(
934-
if (agent.major.isNullOrBlank()) {
935-
"0"
936-
} else {
937-
agent.major
938-
},
939-
if (agent.minor.isNullOrBlank()) {
940-
"0"
941-
} else {
942-
agent.minor
943-
},
944-
if (agent.patch.isNullOrBlank()) {
945-
"0"
946-
} else {
947-
agent.patch
948-
},
970+
if (agent.major.isNullOrBlank()) "0" else agent.major,
971+
if (agent.minor.isNullOrBlank()) "0" else agent.minor,
972+
if (agent.patch.isNullOrBlank()) "0" else agent.patch,
949973
).joinToString(".")
950974
}
951975

src/main/kotlin/com/statsig/sdk/StatsigEvent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ internal data class StatsigEvent(
1010
@SerializedName("user") var user: StatsigUser? = null,
1111
@SerializedName("statsigMetadata") val statsigMetadata: Map<String, String>? = null,
1212
@SerializedName("secondaryExposures") val secondaryExposures: ArrayList<Map<String, String>>? = arrayListOf(),
13-
@SerializedName("time") val time: Long? = Utils().getTimeInMillis(),
13+
@SerializedName("time") val time: Long? = Utils.getTimeInMillis(),
1414
) {
1515
init {
1616
// We need to use a special copy of the user object that strips out private attributes for logging purposes

0 commit comments

Comments
 (0)