Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to fetch variation detail for json and json list values #5

Merged
merged 4 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion library/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ signing.keyId=""
signing.password=""

MODULE_PACKAGE_NAME=com.myunidays
MODULE_VERSION_NUMBER=0.1.3
MODULE_VERSION_NUMBER=0.1.4
MODULE_NAME=launchdarkly

OPEN_SOURCE_REPO=https://oss.sonatype.org/service/local/staging/deploy/maven2/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
package com.myunidays.launchdarkly

actual class EvaluationDetail<T> internal constructor(
val android: com.launchdarkly.sdk.EvaluationDetail<T>
val android: com.launchdarkly.sdk.EvaluationDetail<T>,
) : EvaluationDetailInterface<T> {

companion object {

fun <T> fromValues(
value: T?,
variationIndex: Int,
reason: com.launchdarkly.sdk.EvaluationReason?
): EvaluationDetail<T> {
val sdkDetail = createEvaluationDetail(value, variationIndex, reason)
return EvaluationDetail(sdkDetail)
}

private fun <T> createEvaluationDetail(
value: T?,
variationIndex: Int,
reason: com.launchdarkly.sdk.EvaluationReason?
): com.launchdarkly.sdk.EvaluationDetail<T> {
// Use reflection to access the private constructor
val constructor = com.launchdarkly.sdk.EvaluationDetail::class.constructors.first()
return constructor.call(value, variationIndex, reason) as com.launchdarkly.sdk.EvaluationDetail<T>
}
}

override val value: T?
get() = android.value
override val variationIndex: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,32 +83,82 @@ actual class LDClient actual constructor(
defaultValue: LDValue
): LDValue = LDValue(android.jsonValueVariation(key, defaultValue.android))

actual fun jsonValueVariationDetail(
key: String,
defaultValue: LDValue
): EvaluationDetailInterface<LDValue> = android!!.jsonValueVariationDetail(
key,
defaultValue.android
).let { evaluationDetail ->
EvaluationDetail.fromValues(
value = LDValue(evaluationDetail.value),
variationIndex = evaluationDetail.variationIndex,
reason = evaluationDetail.reason
)
}

actual fun <T> jsonValueVariation(
key: String,
deserializer: KSerializer<T>
): T? =
android.jsonValueVariation(key, com.launchdarkly.sdk.LDValue.ofNull())
.takeUnless { it.isNull }
?.let {
json.decodeFromString(
deserializer,
it.toJsonString()
)
}
this.jsonValueVariationDetail(key, deserializer).value

actual fun <T> jsonValueVariationDetail(
key: String,
deserializer: KSerializer<T>
): EvaluationDetailInterface<T?> =
android.jsonValueVariationDetail(key, com.launchdarkly.sdk.LDValue.ofNull()).let { evaluationDetail ->
evaluationDetail.value.takeUnless { it.isNull }
?.let {
json.decodeFromString(
deserializer,
it.toJsonString()
)
}.let { value ->
EvaluationDetail.fromValues(
value = value,
variationIndex = evaluationDetail.variationIndex,
reason = evaluationDetail.reason
)
}
}

actual fun <T> jsonListValueVariation(
key: String,
deserializer: KSerializer<T>
): List<T> =
android.jsonValueVariation(key, com.launchdarkly.sdk.LDValue.ofNull())
.takeUnless { it.isNull }
?.let {
json.decodeFromString(
ListSerializer(deserializer),
it.toJsonString()
)
): List<T> = this.jsonListValueVariationDetail(key, deserializer).value ?: emptyList()

// Define a generic function that returns an EvaluationDetailInterface for a list of type T
// The function takes a key and a deserializer for type T as parameters
actual fun <T> jsonListValueVariationDetail(
key: String,
deserializer: KSerializer<T>
): EvaluationDetailInterface<List<T>> =

// Call the android-specific jsonValueVariationDetail method with the provided key
// and a default null value, then process the result using 'let'
android.jsonValueVariationDetail(key, com.launchdarkly.sdk.LDValue.ofNull())
.let { jsonEvaluationDetail ->

// Extract the value from the jsonEvaluationDetail if it's not null
jsonEvaluationDetail.value.takeUnless { it.isNull }
// If the value is not null, decode it into a list of type T using the provided deserializer
?.let {
json.decodeFromString(
ListSerializer(deserializer),
it.toJsonString()
)
}.let { value ->

// Construct an EvaluationDetail object from the decoded value or an empty list if null,
// along with the variation index and reason from jsonEvaluationDetail
EvaluationDetail.fromValues(
value = value ?: emptyList(),
variationIndex = jsonEvaluationDetail.variationIndex,
reason = jsonEvaluationDetail.reason
)
}
}
?: emptyList()

actual fun identify(context: LDContext) {
android.identify(context.android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ expect class LDClient(
fun stringVariation(key: String, defaultValue: String): String
fun stringVariationDetail(key: String, defaultValue: String): EvaluationDetailInterface<String>
fun jsonValueVariation(key: String, defaultValue: LDValue): LDValue
fun jsonValueVariationDetail(key: String, defaultValue: LDValue): EvaluationDetailInterface<LDValue>
fun <T> jsonValueVariation(key: String, deserializer: KSerializer<T>): T?
fun <T> jsonValueVariationDetail(key: String, deserializer: KSerializer<T>): EvaluationDetailInterface<T?>
fun <T> jsonListValueVariation(key: String, deserializer: KSerializer<T>): List<T>
fun <T> jsonListValueVariationDetail(key: String, deserializer: KSerializer<T>): EvaluationDetailInterface<List<T>>
fun close()
fun identify(context: LDContext)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.myunidays.launchdarkly

class JsonValueEvaluationDetail<T> internal constructor(
private val ios: cocoapods.LaunchDarkly.LDJSONEvaluationDetail,
override val value: T
) :
EvaluationDetailInterface<T> {
override val variationIndex: Int
get() = ios.variationIndex().toInt()
override val reason: EvaluationReason?
get() = ios.reason()?.let { EvaluationReason(it) }
}
127 changes: 86 additions & 41 deletions library/src/iosMain/kotlin/com/myunidays/launchdarkly/LDClient.ios.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ actual class LDClient actual constructor(
?.map { it.key as String to LDValue(it.value as cocoapods.LaunchDarkly.LDValue) }
?.toMap()
?: emptyMap()

init {
cocoapods.LaunchDarkly.LDClient.startWithConfiguration(config.ios, context.ios) {
ios = cocoapods.LaunchDarkly.LDClient.get()
Expand Down Expand Up @@ -90,57 +91,101 @@ actual class LDClient actual constructor(
defaultValue: LDValue
): LDValue = LDValue(ios!!.jsonVariationForKey(key, defaultValue.ios))

actual fun jsonValueVariationDetail(
key: String,
defaultValue: LDValue
): EvaluationDetailInterface<LDValue> = ios!!.jsonVariationDetailForKey(key, defaultValue.ios).let {
JsonValueEvaluationDetail(it, LDValue(it.value()))
}

actual fun <T> jsonValueVariation(
key: String,
deserializer: KSerializer<T>
): T? =
ios?.jsonVariationForKey(key, cocoapods.LaunchDarkly.LDValue.ofNull())
.takeUnless { it?.getType() == cocoapods.LaunchDarkly.LDValueTypeNull }
?.let { remoteValue ->
json.decodeFromString(
deserializer,
JsonObject(
remoteValue.dictValue()
.mapNotNull {
runCatching {
it.key as String to it.value as cocoapods.LaunchDarkly.LDValue
}.getOrNull()
}.associate { it.first to JsonPrimitive(it.second.stringValue()) }
).toString()
)
this.jsonValueVariationDetail(key, deserializer)?.value

actual fun <T> jsonValueVariationDetail(
key: String,
deserializer: KSerializer<T>
): EvaluationDetailInterface<T?> =
ios!!.jsonVariationDetailForKey(key, cocoapods.LaunchDarkly.LDValue.ofNull())
.let { jsonEvaluationDetail ->
jsonEvaluationDetail.value().takeUnless { it?.getType() == cocoapods.LaunchDarkly.LDValueTypeNull }
?.let { remoteValue ->
json.decodeFromString(
deserializer,
JsonObject(
remoteValue.dictValue()
.mapNotNull {
runCatching {
it.key as String to it.value as cocoapods.LaunchDarkly.LDValue
}.getOrNull()
}.associate { it.first to JsonPrimitive(it.second.stringValue()) }
).toString()
)
}.let { value ->
JsonValueEvaluationDetail(jsonEvaluationDetail, value)
}
}

actual fun <T> jsonListValueVariation(
key: String,
deserializer: KSerializer<T>
): List<T> =
ios?.jsonVariationForKey(key, cocoapods.LaunchDarkly.LDValue.ofNull())
.takeUnless { it?.getType() == cocoapods.LaunchDarkly.LDValueTypeNull }
?.let { remoteValue ->
json.decodeFromString(
ListSerializer(deserializer),
JsonArray(
remoteValue.arrayValue()
.filterIsInstance<cocoapods.LaunchDarkly.LDValue>()
.map { singleRemoteValue ->
JsonObject(
singleRemoteValue.dictValue()
.mapNotNull {
runCatching {
it.key as String to
it.value as cocoapods.LaunchDarkly.LDValue
}
.getOrNull()
}
.associate {
it.first to JsonPrimitive(it.second.stringValue())
}
)
}
).toString()
)
): List<T> = this.jsonListValueVariationDetail(key, deserializer).value ?: emptyList()

// Define a generic function that returns an EvaluationDetailInterface for a list of type T
// The function takes a key and a deserializer for type T as parameters
actual fun <T> jsonListValueVariationDetail(
key: String,
deserializer: KSerializer<T>
): EvaluationDetailInterface<List<T>> =

// Call the iOS-specific jsonVariationDetailForKey method with the provided key
// and a default null value, then process the result using 'let'
ios!!.jsonVariationDetailForKey(key, cocoapods.LaunchDarkly.LDValue.ofNull())
.let { jsonEvaluationDetail ->

// Extract the value from the jsonEvaluationDetail if its type is not Null
jsonEvaluationDetail.value().takeUnless { it.getType() == cocoapods.LaunchDarkly.LDValueTypeNull }

// If the value is not null, process the remote value
?.let { remoteValue ->

// Decode the remote value into a list of type T using the provided deserializer
json.decodeFromString(
ListSerializer(deserializer),
// Convert the remote value to a JSON string
JsonArray(
// Filter the remote value array to instances of LDValue
remoteValue.arrayValue()
.filterIsInstance<cocoapods.LaunchDarkly.LDValue>()
// Map each single remote value to a JsonObject
.map { singleRemoteValue ->

JsonObject(
// Convert the dictionary of the single remote value
// to a map of string keys and JsonPrimitive values
singleRemoteValue.dictValue()
.mapNotNull {
runCatching {
it.key as String to
it.value as cocoapods.LaunchDarkly.LDValue
}
.getOrNull()
}
.associate {
it.first to JsonPrimitive(it.second.stringValue())
}
)
}
).toString()
).let { value ->
// Construct a JsonValueEvaluationDetail object from the decoded value
JsonValueEvaluationDetail(jsonEvaluationDetail, value)
}
} // Return an empty list if the value is null
?: JsonValueEvaluationDetail(jsonEvaluationDetail, emptyList())
}
?: emptyList()

actual fun identify(context: LDContext) {
ios?.identifyWithContext(context.ios)
Expand Down
Loading