refactor(errors): унифицировал доменные ошибки и добавил failed-статус задач

This commit is contained in:
2026-05-18 14:52:33 +03:00
parent a1226a8803
commit f3f99aed5a
38 changed files with 498 additions and 112 deletions

View File

@@ -0,0 +1,55 @@
package com.github.nullptroma.wallenc.domain.errors
/**
* Единая иерархия сбоев Wallenc. Ожидаемые бизнес-отказы (CanEncryptResult и т.п.)
* остаются отдельными sealed-типами; сюда попадают только операционные ошибки.
*/
sealed class WallencException(
message: String? = null,
cause: Throwable? = null,
) : Exception(message, cause) {
sealed class Feature : WallencException() {
data object StorageNotFound : Feature()
data object NeedsDecryptedView : Feature()
data object SecretNotFound : Feature()
data object StorageNotWritable : Feature()
}
sealed class Storage(cause: Throwable? = null) : WallencException(cause = cause) {
data object NotAvailable : Storage()
data object FileNotFound : Storage()
data object IncorrectKey : Storage()
data class IoFailed(override val cause: Throwable) : Storage(cause)
data object EncInfoMissing : Storage()
data object NotEncrypted : Storage()
data object NotWritable : Storage()
data object NotAFile : Storage()
data object NotADirectory : Storage()
data object PathIsFile : Storage()
data object CannotWriteOverDirectory : Storage()
data object DeleteRootForbidden : Storage()
data object UnexpectedState : Storage()
}
/** Ошибки аутентификации (OAuth, токен), без привязки к провайдеру. */
sealed class Auth : WallencException() {
data object Failed : Auth()
data object TokenMissing : Auth()
}
/** Сетевые и удалённые операции (HTTP, блокировки, таймауты). */
sealed class Network(cause: Throwable? = null) : WallencException(cause = cause) {
data class HttpFailed(
val operation: String,
val statusCode: Int,
override val cause: Throwable? = null,
) : Network(cause)
data class IoFailed(override val cause: Throwable) : Network(cause)
data object ResourceLocked : Network()
data object OperationFailed : Network()
data object OperationTimedOut : Network()
}
data class Unknown(override val cause: Throwable?) : WallencException(cause = cause)
}

View File

@@ -0,0 +1,13 @@
package com.github.nullptroma.wallenc.domain.errors
import java.io.FileNotFoundException
import java.io.IOException
fun Throwable.toWallencException(): WallencException = when (this) {
is WallencException -> this
is FileNotFoundException -> WallencException.Storage.FileNotFound
is IOException -> WallencException.Network.IoFailed(this)
else -> WallencException.Unknown(this)
}
fun Throwable.rethrowAsWallencException(): Nothing = throw toWallencException()

View File

@@ -1,5 +1,7 @@
package com.github.nullptroma.wallenc.domain.tasks
import com.github.nullptroma.wallenc.domain.errors.WallencException
interface TaskContext {
val taskId: TaskId
@@ -8,4 +10,6 @@ interface TaskContext {
suspend fun reportProgress(progress: TaskProgress) = reportProgress(progress.fraction, progress.label)
fun log(level: TaskLogLevel, message: String)
fun fail(error: WallencException): Nothing
}

View File

@@ -1,9 +1,11 @@
package com.github.nullptroma.wallenc.domain.tasks
import com.github.nullptroma.wallenc.domain.errors.WallencException
sealed class TaskRunState {
data object Queued : TaskRunState()
data class Running(val progress: TaskProgress?) : TaskRunState()
data object Completed : TaskRunState()
data object Cancelled : TaskRunState()
data class Failed(val message: String) : TaskRunState()
data class Failed(val error: WallencException) : TaskRunState()
}

View File

@@ -0,0 +1,39 @@
package com.github.nullptroma.wallenc.domain.errors
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.FileNotFoundException
import java.io.IOException
class WallencExceptionMappingTest {
@Test
fun preservesWallencException() {
val original = WallencException.Feature.StorageNotFound
assertSame(original, original.toWallencException())
}
@Test
fun mapsFileNotFoundException() {
val mapped = FileNotFoundException("missing").toWallencException()
assertEquals(WallencException.Storage.FileNotFound, mapped)
}
@Test
fun mapsIOExceptionToIoFailed() {
val cause = IOException("disk")
val mapped = cause.toWallencException()
assertTrue(mapped is WallencException.Network.IoFailed)
assertSame(cause, (mapped as WallencException.Network.IoFailed).cause)
}
@Test
fun mapsGenericExceptionToUnknown() {
val cause = Exception("boom")
val mapped = cause.toWallencException()
assertTrue(mapped is WallencException.Unknown)
assertSame(cause, (mapped as WallencException.Unknown).cause)
}
}