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