Coverage Summary for Class: DebugConfigInspectorImpl (com.georgeci.moneysurfer.appconfig)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| DebugConfigInspectorImpl |
77.8%
(7/9)
|
81.8%
(18/22)
|
92.3%
(36/39)
|
92.8%
(271/292)
|
| DebugConfigInspectorImpl$Companion |
|
| DebugConfigInspectorImpl$override$1 |
|
| DebugConfigInspectorImpl$special$$inlined$map$1 |
0%
(0/2)
|
|
| DebugConfigInspectorImpl$special$$inlined$map$1$1 |
|
| DebugConfigInspectorImpl$special$$inlined$map$1$2 |
0%
(0/1)
|
|
| DebugConfigInspectorImpl$special$$inlined$map$1$2$1 |
|
| Total |
58.3%
(7/12)
|
81.8%
(18/22)
|
92.3%
(36/39)
|
92.8%
(271/292)
|
package com.georgeci.moneysurfer.appconfig
import com.georgeci.moneysurfer.domain.config.ConfigDebugLayerCell
import com.georgeci.moneysurfer.domain.config.ConfigDebugRow
import com.georgeci.moneysurfer.domain.config.ConfigDebugRowKind
import com.georgeci.moneysurfer.domain.config.DebugConfigInspector
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import org.koin.core.annotation.Single
/**
* Renders the whole registry for the QA panel and takes string-keyed writes back.
*
* Nothing typed crosses into `feature/settings`: rows are pre-rendered strings, and an override
* arrives as `(name, raw)` and is parsed through the codec the registry resolves for that name.
*/
@Single(binds = [DebugConfigInspector::class])
class DebugConfigInspectorImpl(
private val config: Config,
private val registry: ConfigRegistry,
private val debugSource: DebugConfigSource,
) : DebugConfigInspector {
override val isAvailable: Boolean get() = debugSource.isActive
override val degradedLayers: List<String> get() = config.degradedLayers.map { it.name }
override val rows: Flow<List<ConfigDebugRow>> = config.changes
.map { registry.keys.map { key -> key.toRow() } }
.distinctUntilChanged()
override suspend fun override(name: String, raw: String): Result<Unit> {
val key = registry.find(name)
?: return Result.failure(IllegalArgumentException("Unknown configuration key '$name'"))
if (key.codec.decode(raw) == null) {
return Result.failure(IllegalArgumentException("'$raw' is not a valid value for '$name'"))
}
debugSource.override(key, raw)
return Result.success(Unit)
}
override suspend fun clearOverride(name: String) {
registry.find(name)?.let { debugSource.clear(it) }
}
override suspend fun resetAll() = debugSource.clearAll()
private fun <T : Any> ConfigKey<T>.toRow(): ConfigDebugRow {
val resolution = config.resolve(this)
return ConfigDebugRow(
name = name,
effectiveValue = codec.encode(resolution.value),
winner = resolution.winner?.name ?: DEFAULT_WINNER,
kind = codec.valueKind.toRowKind(),
// Anything stored in the Debug layer counts, not just a value that won: an override the
// codec can no longer read does not win, and deriving this from the winner left it
// unclearable per row — "Reset all" was the only way out, taking every other override
// with it.
overridden = resolution.perLayer[ConfigLayer.Debug].let {
it != null && it != LayerValue.Absent
},
hostOwned = this in HostConfigKeys.all,
layers = ConfigLayer.entries.map { layer ->
resolution.perLayer[layer].toCell(layer, this)
},
)
}
private fun <T : Any> LayerValue<T>?.toCell(layer: ConfigLayer, key: ConfigKey<T>): ConfigDebugLayerCell =
when (this) {
null, LayerValue.Absent -> ConfigDebugLayerCell(layer.name, value = null, undecodable = false)
is LayerValue.Undecodable -> ConfigDebugLayerCell(layer.name, value = raw, undecodable = true)
is LayerValue.Present -> ConfigDebugLayerCell(
layer = layer.name,
value = key.codec.encode(value),
undecodable = false,
)
}
private fun ConfigValueKind.toRowKind(): ConfigDebugRowKind = when (this) {
ConfigValueKind.Bool -> ConfigDebugRowKind.Bool
is ConfigValueKind.Choice -> ConfigDebugRowKind.Choice(choices)
ConfigValueKind.FreeText -> ConfigDebugRowKind.FreeText
}
private companion object {
const val DEFAULT_WINNER = "default"
}
}