dnowak
05/09/2023, 6:07 PMimport arrow.core.raise.Effect
import arrow.core.raise.Raise
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Tag
import io.micrometer.core.instrument.Timer
sealed interface Outcome<out Error, out Value> {
data class Value<out Value>(val value: Value) : Outcome<Nothing, Value>
data class Error<out Error>(val error: Error) : Outcome<Error, Nothing>
data class Throwable(val throwable: kotlin.Throwable) : Outcome<Nothing, Nothing>
}
class TimedRaise<Error>(private val delegate: Raise<Error>) : Raise<Error> by delegate {
var error: Error? = null
override fun raise(r: Error): Nothing {
error = r
delegate.raise(r)
}
}
typealias GenerateTags<E, V> = (Outcome<E, V>) -> List<Tag>
class TimedEffect<E, A>(
private val meterRegistry: MeterRegistry,
private val metricName: String,
private val tags: List<Tag>,
private val generateTags: GenerateTags<E, A>,
private val delegate: Effect<E, A>,
) : Effect<E, A> by delegate {
override suspend fun invoke(raise: Raise<E>): A {
val timedRaise = TimedRaise(raise)
val sample = Timer.start(meterRegistry)
val result = runCatching { delegate.invoke(timedRaise) }
val outcome = outcome(result, timedRaise.error)
val outcomeTags = generateTags(outcome)
val timer = meterRegistry.timer(metricName, tags + outcomeTags)
sample.stop(timer)
result.fold(
{ value -> return value },
{ e -> throw e }
)
}
private fun outcome(result: Result<A>, error: E?): Outcome<E, A> = when {
error != null -> Outcome.Error(error)
else -> result.fold({ Outcome.Value(it) }, { Outcome.Throwable(it) })
}
}
fun <E, A> Effect<E, A>.timed(
meterRegistry: MeterRegistry,
metricName: String,
tags: List<Tag> = emptyList(),
generateTags: GenerateTags<E, A> = { emptyList() }
): Effect<E, A> = TimedEffect(meterRegistry, metricName, tags, generateTags, this)
Is it correct? Will it cover all possible scenarios - result, error, exception?
Will it be possible to write a generic proxy with multiple context receivers?simon.vergauwen
05/11/2023, 6:30 AMimport arrow.core.raise.Effect
import arrow.core.raise.Raise
import arrow.core.raise.effect
import arrow.core.raise.recover
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Tag
import io.micrometer.core.instrument.Timer
import kotlin.experimental.ExperimentalTypeInference
sealed interface Outcome<out Error, out Value> {
data class Value<out Value>(val value: Value) : Outcome<Nothing, Value>
data class Error<out Error>(val error: Error) : Outcome<Error, Nothing>
data class Throwable(val throwable: Throwable) : Outcome<Nothing, Nothing>
}
typealias GenerateTags<E, V> = (Outcome<E, V>) -> List<Tag>
fun <E, A> Effect<E, A>.timed(
meterRegistry: MeterRegistry,
metricName: String,
tags: List<Tag> = emptyList(),
generateTags: GenerateTags<E, A> = { emptyList() }
): Effect<E, A> = effect {
timed(meterRegistry, metricName, tags, generateTags) {
bind()
}
}
@OptIn(ExperimentalTypeInference::class)
inline fun <E, A> outcome(@BuilderInference block: Raise<E>.() -> A): Outcome<E, A> =
recover( // can also be arrow.core.raise.fold
{ Outcome.Value(block(this)) },
{ e: E -> Outcome.Error(e) }
) { t: Throwable -> Outcome.Throwable(t) }
// context(Raise<E>, MeterRegistry)
@OptIn(ExperimentalTypeInference::class)
inline fun <E, A> Raise<E>.timed(
meterRegistry: MeterRegistry,
metricName: String,
tags: List<Tag> = emptyList(),
generateTags: GenerateTags<E, A> = { emptyList() },
@BuilderInference block: Raise<E>.() -> A
): A {
val sample = Timer.start(meterRegistry)
val outcome = outcome { block(this) }
val outcomeTags = generateTags(outcome)
val timer = meterRegistry.timer(metricName, tags + outcomeTags)
sample.stop(timer)
return when (outcome) {
is Outcome.Error -> raise(outcome.error)
is Outcome.Throwable -> throw outcome.throwable
is Outcome.Value -> outcome.value
}
}
// With context receivers
context(Raise<E>)
fun <E, A> Outcome<E, A>.bind(): A =
when (this) {
is Outcome.Error -> raise(error)
is Outcome.Throwable -> throw throwable
is Outcome.Value -> value
}
Hope that helps you out! Let me know if anything is unclearTower Guidev2
05/11/2023, 8:27 AMsimon.vergauwen
05/11/2023, 8:29 AM