-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rudimentary iOS LaunchDarkly support (#44)
- Loading branch information
1 parent
f44c971
commit ad48eda
Showing
5 changed files
with
192 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
...c/iosMain/kotlin/io/github/kevincianfarini/monarch/launchdarkly/LaunchDarklyClientShim.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package io.github.kevincianfarini.monarch.launchdarkly | ||
|
||
/** | ||
* A temporary, experimental shim to allow iOS consumers of Monarch to wire their own LDClient | ||
* as a data store using [LaunchDarklyClientShim.asFeatureFlagDataStore]. This interface will be | ||
* removed in future versions of this library when future, first-party support of LaunchDarkly | ||
* is available. | ||
*/ | ||
public interface LaunchDarklyClientShim { | ||
|
||
public fun boolVariation(forKey: String, default: Boolean): Boolean | ||
|
||
public fun intVariation(forKey: String, default: Int): Int | ||
|
||
public fun doubleVariation(forKey: String, default: Double): Double | ||
|
||
public fun stringVariation(forKey: String, default: String): String | ||
|
||
public fun jsonStringVariation(forKey: String, default: String?): String? | ||
|
||
public fun observe(key: String, owner: Any, handler: () -> Unit) | ||
|
||
public fun stopObserving(owner: Any) | ||
} |
87 changes: 87 additions & 0 deletions
87
...kotlin/io/github/kevincianfarini/monarch/launchdarkly/LaunchDarklyFeatureFlagDataStore.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package io.github.kevincianfarini.monarch.launchdarkly | ||
|
||
import io.github.kevincianfarini.monarch.ObservableFeatureFlagDataStore | ||
import kotlinx.cinterop.ExperimentalForeignApi | ||
import kotlinx.cinterop.pin | ||
import kotlinx.coroutines.channels.awaitClose | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.callbackFlow | ||
import kotlinx.coroutines.flow.conflate | ||
|
||
public fun LaunchDarklyClientShim.asFeatureFlagDataStore(): ObservableFeatureFlagDataStore { | ||
return LaunchDarklyFeatureFlagDataStore(this) | ||
} | ||
|
||
private class LaunchDarklyFeatureFlagDataStore( | ||
private val shim: LaunchDarklyClientShim | ||
) : ObservableFeatureFlagDataStore { | ||
|
||
override fun getBoolean(key: String, default: Boolean): Boolean { | ||
return shim.getValue(key, default) | ||
} | ||
|
||
override fun getString(key: String, default: String): String { | ||
return shim.getValue(key, default) | ||
} | ||
|
||
override fun getDouble(key: String, default: Double): Double { | ||
return shim.getValue(key, default) | ||
} | ||
|
||
override fun getLong(key: String, default: Long): Long { | ||
return shim.getValue(key, default) | ||
} | ||
|
||
override fun getByteArray(key: String, default: ByteArray): ByteArray { | ||
throw NotImplementedError("LaunchDarkly does not support ByteArray flags.") | ||
} | ||
|
||
override fun observeString(key: String, default: String): Flow<String> { | ||
return shim.observeValue(key, default) | ||
} | ||
|
||
override fun observeBoolean(key: String, default: Boolean): Flow<Boolean> { | ||
return shim.observeValue(key, default) | ||
} | ||
|
||
override fun observeDouble(key: String, default: Double): Flow<Double> { | ||
return shim.observeValue(key, default) | ||
} | ||
|
||
override fun observeLong(key: String, default: Long): Flow<Long> { | ||
return shim.observeValue(key, default) | ||
} | ||
|
||
override fun observeByteArray(key: String, default: ByteArray): Flow<ByteArray> { | ||
throw NotImplementedError("LaunchDarkly does not support ByteArray flags.") | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalForeignApi::class) | ||
private inline fun <reified T : Any> LaunchDarklyClientShim.observeValue(key: String, default: T): Flow<T> { | ||
return callbackFlow { | ||
trySend(getValue<T>(key, default)).getOrThrow() | ||
val owner = Any().pin() | ||
observe(key, owner.get()) { trySend(getValue<T>(key, default)).getOrThrow() } | ||
awaitClose { | ||
stopObserving(owner.get()) | ||
owner.unpin() | ||
} | ||
}.conflate() | ||
} | ||
|
||
private inline fun <reified T : Any> LaunchDarklyClientShim.getValue(key: String, default: T): T { | ||
return when (val clazz = T::class) { | ||
Boolean::class -> boolVariation(key, default as Boolean) as T | ||
String::class -> { | ||
val jsonString = jsonStringVariation(key, null) | ||
when (jsonString) { | ||
null -> stringVariation(key, default as String) | ||
else -> jsonString | ||
} as T | ||
} | ||
Double::class -> doubleVariation(key, default as Double) as T | ||
Long::class -> intVariation(key, (default as Long).toInt()).toLong() as T | ||
else -> throw IllegalArgumentException("Illegal type for getValue: $clazz") | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
...o/github/kevincianfarini/monarch/launchdarkly/LaunchDarklyFeatureFlagDataStoreTest.ios.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package io.github.kevincianfarini.monarch.launchdarkly | ||
|
||
import io.github.kevincianfarini.monarch.ObservableFeatureFlagDataStore | ||
import kotlinx.serialization.SerializationStrategy | ||
import kotlinx.serialization.json.Json | ||
|
||
actual fun sut(): Pair<ObservableFeatureFlagDataStore, MutableLDClientInterface> { | ||
val client = FakeLDShim() | ||
return Pair(client.asFeatureFlagDataStore(), client) | ||
} | ||
|
||
private class FakeLDShim : LaunchDarklyClientShim, MutableLDClientInterface { | ||
|
||
private val flagValues = mutableMapOf<String, Any>() | ||
private val listeners = mutableSetOf<FlagListener>() | ||
|
||
override fun setVariation(flagKey: String, value: Boolean) { | ||
flagValues[flagKey] = value | ||
listeners.filter { it.key == flagKey }.forEach { it.handler() } | ||
} | ||
|
||
override fun setVariation(flagKey: String, value: String) { | ||
flagValues[flagKey] = value | ||
listeners.filter { it.key == flagKey }.forEach { it.handler() } | ||
} | ||
|
||
override fun setVariation(flagKey: String, value: Double) { | ||
flagValues[flagKey] = value | ||
listeners.filter { it.key == flagKey }.forEach { it.handler() } | ||
} | ||
|
||
override fun setVariation(flagKey: String, value: Int) { | ||
flagValues[flagKey] = value | ||
listeners.filter { it.key == flagKey }.forEach { it.handler() } | ||
} | ||
|
||
override fun <T> setVariation(flagKey: String, value: T, serialzer: SerializationStrategy<T>) { | ||
flagValues[flagKey] = JsonValue(Json.Default.encodeToString(serialzer, value)) | ||
listeners.filter { it.key == flagKey }.forEach { it.handler() } | ||
} | ||
|
||
override fun boolVariation(forKey: String, default: Boolean): Boolean { | ||
return (flagValues[forKey] as? Boolean) ?: default | ||
} | ||
|
||
override fun intVariation(forKey: String, default: Int): Int { | ||
return (flagValues[forKey] as? Int) ?: default | ||
} | ||
|
||
override fun doubleVariation(forKey: String, default: Double): Double { | ||
return (flagValues[forKey] as? Double) ?: default | ||
} | ||
|
||
override fun stringVariation(forKey: String, default: String): String { | ||
return (flagValues[forKey] as? String) ?: default | ||
} | ||
|
||
override fun jsonStringVariation(forKey: String, default: String?): String? { | ||
return (flagValues[forKey] as? JsonValue)?.jsonString ?: default | ||
} | ||
|
||
override fun observe(key: String, owner: Any, handler: () -> Unit) { | ||
listeners.add(FlagListener(key, owner, handler)) | ||
} | ||
|
||
override fun stopObserving(owner: Any) { | ||
listeners.removeAll { it.owner === owner } | ||
} | ||
} | ||
|
||
private data class FlagListener( | ||
val key: String, | ||
val owner: Any, | ||
val handler: () -> Unit, | ||
) | ||
|
||
private class JsonValue(val jsonString: String) |