Skip to content

Commit

Permalink
Merge pull request #103 from matheus-corregiari/release/1.8.1
Browse files Browse the repository at this point in the history
Release 1.8.1
  • Loading branch information
matheus-corregiari authored Aug 21, 2024
2 parents acae68e + 585dae7 commit 29bf477
Show file tree
Hide file tree
Showing 9 changed files with 1,349 additions and 632 deletions.
679 changes: 455 additions & 224 deletions toolkit/event-observer/src/main/kotlin/br/com/arch/toolkit/util/_chain.kt

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import br.com.arch.toolkit.livedata.responseLiveData
import br.com.arch.toolkit.result.DataResult
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
Expand Down Expand Up @@ -37,17 +36,27 @@ fun <T, R, X> LiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: WithResponse<T, R>,
condition: suspend (T?) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T?, R?>>) -> DataResult<X>>
transform: ResponseTransform<T?, R?, X>
): ResponseLiveData<X> = responseLiveData(context = context) {
val (dispatcher, block) = transform
internalChainWith(other::invoke, condition)
.mapNotNull { (data, result) -> data?.let(::dataResultSuccess) + result }
.flowOn(dispatcher)
.mapNotNull { result -> runCatching { block(result) }.getOrElse(::dataResultError) }
.flowOn(context)
.applyTransformation(context, transform)
.collect(::emit)
}

@Experimental
fun <T, R, X> LiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: WithResponse<T, R>,
condition: suspend (T?) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T?, R?>>) -> DataResult<X>>
): ResponseLiveData<X> = chainWith(
context = context,
other = other,
condition = condition,
transform = ResponseTransform.StatusFail(transform.first, transform.second)
)

@Experimental
fun <T, R, X> LiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
Expand Down Expand Up @@ -83,17 +92,27 @@ fun <T, R, X> LiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
other: NotNullWithResponse<T, R>,
condition: suspend (T) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T, R>>) -> DataResult<X>>
transform: ResponseTransform<T, R, X>
): ResponseLiveData<X> = responseLiveData(context = context) {
val (dispatcher, block) = transform
internalChainNotNullWith(other::invoke, condition)
.mapNotNull { (data, result) -> (dataResultSuccess(data) + result).onlyWithValues() }
.flowOn(dispatcher)
.mapNotNull { result -> runCatching { block(result) }.getOrElse(::dataResultError) }
.flowOn(context)
.applyTransformation(context, transform)
.collect(::emit)
}

@Experimental
fun <T, R, X> LiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
other: NotNullWithResponse<T, R>,
condition: suspend (T) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T, R>>) -> DataResult<X>>
): ResponseLiveData<X> = chainNotNullWith(
context = context,
other = other,
condition = condition,
transform = ResponseTransform.StatusFail(transform.first, transform.second)
)

@Experimental
fun <T, R, X> LiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
Expand All @@ -112,41 +131,54 @@ fun <T, R, X> LiveData<T>.chainNotNullWith(
/* Nullable ------------------------------------------------------------------------------------- */
@FunctionalInterface
fun interface ResponseWith<T, R> {
suspend fun invoke(result: DataResult<T>?): LiveData<R>
suspend fun invoke(result: DataResult<T>): LiveData<R>
}

@Experimental
fun <T, R> ResponseLiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: ResponseWith<T, R>,
condition: suspend (DataResult<T>?) -> Boolean,
condition: suspend (DataResult<T>) -> Boolean,
): ResponseLiveData<Pair<T?, R?>> = responseLiveData(context = context) {
internalChainWith(other::invoke, condition)
.mapNotNull { (result, data) -> result + data?.let(::dataResultSuccess) }
.collect(::emit)
internalChainWith(
condition = { result -> result?.let { condition(it) } == true },
other = { result -> other.invoke(requireNotNull(result)) },
).mapNotNull { (result, data) -> result + data?.let(::dataResultSuccess) }.collect(::emit)
}

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: ResponseWith<T, R>,
condition: suspend (DataResult<T>?) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T?, R?>>) -> DataResult<X>>
condition: suspend (DataResult<T>) -> Boolean,
transform: ResponseTransform<T?, R?, X>
): ResponseLiveData<X> = responseLiveData(context = context) {
val (dispatcher, block) = transform
internalChainWith(other::invoke, condition)
.mapNotNull { (result, data) -> result + data?.let(::dataResultSuccess) }
.flowOn(dispatcher)
.mapNotNull { result -> runCatching { block(result) }.getOrElse(::dataResultError) }
.flowOn(context)
internalChainWith(
condition = { result -> result?.let { condition(it) } == true },
other = { result -> other.invoke(requireNotNull(result)) },
).mapNotNull { (result, data) -> result + data?.let(::dataResultSuccess) }
.applyTransformation(context, transform)
.collect(::emit)
}

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: ResponseWith<T, R>,
condition: suspend (DataResult<T>?) -> Boolean,
condition: suspend (DataResult<T>) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T?, R?>>) -> DataResult<X>>
): ResponseLiveData<X> = chainWith(
context = context,
other = other,
condition = condition,
transform = ResponseTransform.StatusFail(transform.first, transform.second)
)

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: ResponseWith<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: suspend (DataResult<Pair<T?, R?>>) -> DataResult<X>
): ResponseLiveData<X> = chainWith(
context = context,
Expand Down Expand Up @@ -177,17 +209,27 @@ fun <T, R, X> ResponseLiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
other: ResponseNotNullWith<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T, R>>) -> DataResult<X>>
transform: ResponseTransform<T, R, X>
): ResponseLiveData<X> = responseLiveData(context = context) {
val (dispatcher, block) = transform
internalChainNotNullWith(other::invoke, condition)
.mapNotNull { (result, data) -> (result + dataResultSuccess(data)).onlyWithValues() }
.flowOn(dispatcher)
.mapNotNull { result -> runCatching { block(result) }.getOrElse(::dataResultError) }
.flowOn(context)
.applyTransformation(context, transform)
.collect(::emit)
}

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
other: ResponseNotNullWith<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T, R>>) -> DataResult<X>>
): ResponseLiveData<X> = chainNotNullWith(
context = context,
other = other,
condition = condition,
transform = ResponseTransform.StatusFail(transform.first, transform.second)
)

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
Expand All @@ -204,38 +246,56 @@ fun <T, R, X> ResponseLiveData<T>.chainNotNullWith(

/* region Response + Response Functions ---------------------------------------------------------------- */
/* Nullable ------------------------------------------------------------------------------------- */
@FunctionalInterface
fun interface ResponseWithResponse<T, R> {
suspend fun invoke(result: DataResult<T>): ResponseLiveData<R>
}

@Experimental
fun <T, R> ResponseLiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: suspend (DataResult<T>?) -> ResponseLiveData<R>,
condition: suspend (DataResult<T>?) -> Boolean,
other: ResponseWithResponse<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
): ResponseLiveData<Pair<T?, R?>> = responseLiveData(context = context) {
internalChainWith(other, condition)
.mapNotNull { (resultA, resultB) -> resultA + resultB }
.collect(::emit)
internalChainWith(
condition = { result -> result?.let { condition(it) } == true },
other = { result -> other.invoke(requireNotNull(result)) },
).mapNotNull { (resultA, resultB) -> resultA + resultB }.collect(::emit)
}

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: suspend (DataResult<T>?) -> ResponseLiveData<R>,
condition: suspend (DataResult<T>?) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T?, R?>>) -> DataResult<X>>
other: ResponseWithResponse<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: ResponseTransform<T?, R?, X>
): ResponseLiveData<X> = responseLiveData(context = context) {
val (dispatcher, block) = transform
internalChainWith(other, condition)
.mapNotNull { (resultA, resultB) -> resultA + resultB }
.flowOn(dispatcher)
.mapNotNull { result -> runCatching { block(result) }.getOrElse(::dataResultError) }
.flowOn(context)
internalChainWith(
condition = { result -> result?.let { condition(it) } == true },
other = { result -> other.invoke(requireNotNull(result)) },
).mapNotNull { (resultA, resultB) -> resultA + resultB }
.applyTransformation(context, transform)
.collect(::emit)
}

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: suspend (DataResult<T>?) -> ResponseLiveData<R>,
condition: suspend (DataResult<T>?) -> Boolean,
other: ResponseWithResponse<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T?, R?>>) -> DataResult<X>>
): ResponseLiveData<X> = chainWith(
context = context,
other = other,
condition = condition,
transform = ResponseTransform.StatusFail(transform.first, transform.second)
)

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainWith(
context: CoroutineContext = EmptyCoroutineContext,
other: ResponseWithResponse<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: suspend (DataResult<Pair<T?, R?>>) -> DataResult<X>
): ResponseLiveData<X> = chainWith(
context = context,
Expand All @@ -248,34 +308,44 @@ fun <T, R, X> ResponseLiveData<T>.chainWith(
@Experimental
fun <T, R> ResponseLiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
other: suspend (DataResult<T>) -> ResponseLiveData<R>,
other: ResponseWithResponse<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
): ResponseLiveData<Pair<T, R>> = responseLiveData(context = context) {
internalChainNotNullWith(other, condition)
internalChainNotNullWith(other::invoke, condition)
.mapNotNull { (resultA, resultB) -> (resultA + resultB).onlyWithValues() }
.collect(::emit)
}

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
other: suspend (DataResult<T>) -> ResponseLiveData<R>,
other: ResponseWithResponse<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T, R>>) -> DataResult<X>>
transform: ResponseTransform<T, R, X>
): ResponseLiveData<X> = responseLiveData(context = context) {
val (dispatcher, block) = transform
internalChainNotNullWith(other, condition)
internalChainNotNullWith(other::invoke, condition)
.mapNotNull { (resultA, resultB) -> (resultA + resultB).onlyWithValues() }
.flowOn(dispatcher)
.mapNotNull { result -> runCatching { block(result) }.getOrElse(::dataResultError) }
.flowOn(context)
.applyTransformation(context, transform)
.collect(::emit)
}

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
other: suspend (DataResult<T>) -> ResponseLiveData<R>,
other: ResponseWithResponse<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: Pair<CoroutineDispatcher, suspend (DataResult<Pair<T, R>>) -> DataResult<X>>
): ResponseLiveData<X> = chainNotNullWith(
context = context,
other = other,
condition = condition,
transform = ResponseTransform.StatusFail(transform.first, transform.second)
)

@Experimental
fun <T, R, X> ResponseLiveData<T>.chainNotNullWith(
context: CoroutineContext = EmptyCoroutineContext,
other: ResponseWithResponse<T, R>,
condition: suspend (DataResult<T>) -> Boolean,
transform: suspend (DataResult<Pair<T, R>>) -> DataResult<X>
): ResponseLiveData<X> = chainNotNullWith(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
@file:Suppress("Filename", "unused")

package br.com.arch.toolkit.util

import br.com.arch.toolkit.annotation.Experimental
import br.com.arch.toolkit.result.DataResult
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
import kotlin.coroutines.CoroutineContext

@Experimental
internal fun <T, R, X> Flow<DataResult<Pair<T, R>>>.applyTransformation(
context: CoroutineContext,
transform: ResponseTransform<T, R, X>
) = flowOn(transform.dispatcher).mapNotNull(transform::apply).flowOn(context)

@Experimental
sealed class ResponseTransform<T, R, X> {
abstract val dispatcher: CoroutineDispatcher
abstract val failMode: Mode
abstract val func: suspend (DataResult<Pair<T, R>>) -> DataResult<X>
abstract val onErrorReturn: (suspend (Throwable) -> DataResult<X>)?

internal suspend fun apply(data: DataResult<Pair<T, R>>) =
runCatching { func(data) }.let { result ->
val finalResult = result.exceptionOrNull()?.let { error ->
onErrorReturn?.runCatching { invoke(error) }
} ?: result

when {
finalResult.isFailure -> when (failMode) {
Mode.OMIT_WHEN_FAIL -> null
Mode.ERROR_STATUS_WHEN_FAIL ->
dataResultError(finalResult.exceptionOrNull())
}

else -> finalResult.getOrNull()
}
}

@Experimental
enum class Mode {
ERROR_STATUS_WHEN_FAIL,
OMIT_WHEN_FAIL
}

@Experimental
class StatusFail<T, R, X>(
override val dispatcher: CoroutineDispatcher,
override val func: suspend (DataResult<Pair<T, R>>) -> DataResult<X>
) : ResponseTransform<T, R, X>() {
override val failMode: Mode = Mode.ERROR_STATUS_WHEN_FAIL
override val onErrorReturn: (suspend (Throwable) -> DataResult<X>)? = null
}

@Experimental
class OmitFail<T, R, X>(
override val dispatcher: CoroutineDispatcher,
override val func: suspend (DataResult<Pair<T, R>>) -> DataResult<X>
) : ResponseTransform<T, R, X>() {
override val failMode: Mode = Mode.OMIT_WHEN_FAIL
override val onErrorReturn: (suspend (Throwable) -> DataResult<X>)? = null
}

@Experimental
class Fallback<T, R, X>(
override val dispatcher: CoroutineDispatcher,
override val func: suspend (DataResult<Pair<T, R>>) -> DataResult<X>,
override val onErrorReturn: suspend (Throwable) -> DataResult<X>
) : ResponseTransform<T, R, X>() {
override val failMode: Mode = Mode.ERROR_STATUS_WHEN_FAIL
}

@Experimental
class Custom<T, R, X>(
override val dispatcher: CoroutineDispatcher,
override val failMode: Mode,
override val func: suspend (DataResult<Pair<T, R>>) -> DataResult<X>,
override val onErrorReturn: suspend (Throwable) -> DataResult<X>
) : ResponseTransform<T, R, X>()
}
Loading

0 comments on commit 29bf477

Please sign in to comment.