Personer is a small bot written in Kotlin, which is started by systemd
timer (thanks nixos
) and performs a read-update cycle over our project cards (custom JIRA issues). It features modern Kotlin’s Flow framework.
@FlowPreview
fun projectCards(fields: Set<String>) =
suspend (0, 1).total.let { total ->
search(0, total, env[PERSONER_PAGE_SIZE]).asFlow()
rangeUntil.concurrentFlatMap { start -> search(start, env[PERSONER_PAGE_SIZE], fields).issues }
.toList()
}
Which allows concurrently flatMap
-ing a flow (fancy name of the lazy stream) with retry abilities on errors.
@FlowPreview
fun <T, R> Flow<T>.concurrentFlatMap(transform: suspend (T) -> Iterable<R>) =
{ value ->
flatMapMerge { emitAll(transform(value).asFlow()) }
flow }.retryOnTimeouts()
fun <T> Flow<T>.retryOnTimeouts() =
this.flowOn(Dispatchers.IO)
.retry { cause -> generateSequence(cause, Throwable::cause).any { it is SocketTimeoutException } }
Tons of boilerplate code above that – is just the consequence of having to inject a custom parser inside the library, which doesn’t allow that natively.
The logic itself is straightforward:
enum class Region(vararg val names: String) {
("United States"),
USA("Ireland", "United Kingdom"),
UK("Belarus", "Russian Federation", "Kazakhstan", "Ukraine"),
CIS(
EU"Austria", "Belgium", "Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany",
"Gibraltar", "Greece", "Hungary", "Italy", "Latvia", "Luxembourg", "Montenegro", "Netherlands", "Norway",
"Poland", "Portugal", "Slovak Republic", "Spain", "Sweden", "Switzerland"
),
{
None override fun id(): String = "-1"
};
open fun id(): String = possibleValues.first { it.value == name }.id.toString()
companion object {
fun fromRegion(region: String): Region = values().firstOrNull { it.names.contains(region) } ?: None
}
}
However in the most recent version countries are not hardcoded anymore, but loaded from environment variables with help of konfig.
val PERSONER_COUNTRIES_CONFIG by
(
listType(
listType,
stringType":".toRegex()
).wrappedAs { mapping -> mapping.last().split(",").map { Pair(it, Region.valueOf(mapping.first())) } },
";".toRegex()).wrappedAs { it.flatten().toMap() }