feat(core:domain): typed Result / Error / DataError core (REDI-80)
- Error marker interface; Result<D, E: Error> (Success/Error) + EmptyResult typealias. - Inline chainable helpers: map / onSuccess / onFailure / asEmptyResult. - DataError sealed interface with full Network + Local case sets. - Pure Kotlin, zero Android imports.
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
package com.example.architecture.core.domain
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Errors raised by the data layer. [Network] for remote calls, [Local] for on-device storage.
|
||||||
|
* A repository that merges multiple sources can expose the [DataError] supertype.
|
||||||
|
*/
|
||||||
|
sealed interface DataError : Error {
|
||||||
|
enum class Network : DataError {
|
||||||
|
BAD_REQUEST,
|
||||||
|
REQUEST_TIMEOUT,
|
||||||
|
UNAUTHORIZED,
|
||||||
|
FORBIDDEN,
|
||||||
|
NOT_FOUND,
|
||||||
|
CONFLICT,
|
||||||
|
TOO_MANY_REQUESTS,
|
||||||
|
NO_INTERNET,
|
||||||
|
PAYLOAD_TOO_LARGE,
|
||||||
|
SERVER_ERROR,
|
||||||
|
SERVICE_UNAVAILABLE,
|
||||||
|
SERIALIZATION,
|
||||||
|
UNKNOWN,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Local : DataError {
|
||||||
|
DISK_FULL,
|
||||||
|
NOT_FOUND,
|
||||||
|
UNKNOWN,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.example.architecture.core.domain
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker for every typed error in the app. Each layer/feature defines its own [Error]
|
||||||
|
* implementations (e.g. [DataError], or a feature validation enum) and pairs them with [Result].
|
||||||
|
*/
|
||||||
|
interface Error
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.example.architecture.core.domain
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed result usable across every layer (data, domain, presentation, validation). Carries either
|
||||||
|
* success [data] or a typed [Error]. Prefer this over throwing for expected failures.
|
||||||
|
*/
|
||||||
|
sealed interface Result<out D, out E : Error> {
|
||||||
|
data class Success<out D>(val data: D) : Result<D, Nothing>
|
||||||
|
|
||||||
|
// The bound is fully qualified because inside this scope `Error` would resolve to this class.
|
||||||
|
data class Error<out E : com.example.architecture.core.domain.Error>(
|
||||||
|
val error: E,
|
||||||
|
) : Result<Nothing, E>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A [Result] that carries no success payload — for operations that either succeed or fail. */
|
||||||
|
typealias EmptyResult<E> = Result<Unit, E>
|
||||||
|
|
||||||
|
inline fun <T, E : Error, R> Result<T, E>.map(map: (T) -> R): Result<R, E> {
|
||||||
|
return when (this) {
|
||||||
|
is Result.Error -> Result.Error(error)
|
||||||
|
is Result.Success -> Result.Success(map(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T, E : Error> Result<T, E>.onSuccess(action: (T) -> Unit): Result<T, E> {
|
||||||
|
return when (this) {
|
||||||
|
is Result.Error -> this
|
||||||
|
is Result.Success -> {
|
||||||
|
action(data)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T, E : Error> Result<T, E>.onFailure(action: (E) -> Unit): Result<T, E> {
|
||||||
|
return when (this) {
|
||||||
|
is Result.Error -> {
|
||||||
|
action(error)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
is Result.Success -> this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, E : Error> Result<T, E>.asEmptyResult(): EmptyResult<E> = map { }
|
||||||
Reference in New Issue
Block a user