diff --git a/core/src/main/kotlin/com/ams/fat12ex/core/Fat12Result.kt b/core/src/main/kotlin/com/ams/fat12ex/core/Fat12Result.kt index c70d2da..b8e1447 100644 --- a/core/src/main/kotlin/com/ams/fat12ex/core/Fat12Result.kt +++ b/core/src/main/kotlin/com/ams/fat12ex/core/Fat12Result.kt @@ -12,8 +12,32 @@ package com.ams.fat12ex.core sealed class Fat12Result { data class Ok(val value: T) : Fat12Result() data class NameConflict(val name: String) : Fat12Result() - object DiskFull : Fat12Result() + object DiskFull : Fat12Result() { + // Stable, human-readable rendering (an `object` otherwise inherits the + // Foo@hash default) so getOrThrow()'s message reads "...not Ok: DiskFull". + override fun toString(): String = "DiskFull" + } data class TooLarge(val actualBytes: Long) : Fat12Result() data class NotFound(val path: String) : Fat12Result() data class InvalidName(val name: String, val reason: String) : Fat12Result() } + +/** True when this result is [Fat12Result.Ok]. */ +val Fat12Result<*>.isOk: Boolean get() = this is Fat12Result.Ok + +/** True when this result is any non-[Fat12Result.Ok] outcome. */ +val Fat12Result<*>.isError: Boolean get() = this !is Fat12Result.Ok + +/** The success value, or `null` for any non-[Fat12Result.Ok] outcome. */ +fun Fat12Result.getOrNull(): T? = (this as? Fat12Result.Ok)?.value + +/** + * The success value, or throw [IllegalStateException] describing the non-[Fat12Result.Ok] + * outcome. Use only when a non-Ok result is a programming error at the call site; prefer + * [getOrNull] or an exhaustive `when` for recoverable handling. + */ +fun Fat12Result.getOrThrow(): T = + when (this) { + is Fat12Result.Ok -> value + else -> error("Fat12Result was not Ok: $this") + } diff --git a/core/src/test/kotlin/com/ams/fat12ex/core/Fat12ResultAccessorsTest.kt b/core/src/test/kotlin/com/ams/fat12ex/core/Fat12ResultAccessorsTest.kt new file mode 100644 index 0000000..acfd971 --- /dev/null +++ b/core/src/test/kotlin/com/ams/fat12ex/core/Fat12ResultAccessorsTest.kt @@ -0,0 +1,66 @@ +package com.ams.fat12ex.core + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +/** + * [Fat12Result] convenience-accessor tests — [isOk] / [isError] / [getOrNull] / + * [getOrThrow]. + * + * Proves the accessors branch on success without an exhaustive `when` or an + * `is Fat12Result.Ok` cast, for both the [Fat12Result.Ok] case and a couple of + * representative non-Ok variants ([Fat12Result.NotFound], [Fat12Result.DiskFull]). + */ +class Fat12ResultAccessorsTest { + + @Test + fun ok_isOk_getOrNull_getOrThrow() { + val result: Fat12Result = Fat12Result.Ok("payload") + assertTrue(result.isOk) + assertFalse(result.isError) + assertEquals("payload", result.getOrNull()) + assertEquals("payload", result.getOrThrow()) + } + + @Test + fun notFound_isNotOk_getOrNull_null_getOrThrow_throws() { + val result: Fat12Result = Fat12Result.NotFound("/MISSING.TXT") + assertFalse(result.isOk) + assertTrue(result.isError) + assertNull(result.getOrNull()) + val ex = assertThrows { result.getOrThrow() } + assertTrue( + ex.message?.contains("NotFound(path=/MISSING.TXT)") == true, + "message must describe the non-Ok outcome, was: ${ex.message}", + ) + } + + @Test + fun diskFull_isNotOk_getOrNull_null_getOrThrow_throws() { + val result: Fat12Result = Fat12Result.DiskFull + assertFalse(result.isOk) + assertTrue(result.isError) + assertNull(result.getOrNull()) + val ex = assertThrows { result.getOrThrow() } + // DiskFull is a singleton object; its message must be the stable "DiskFull", + // not the inherited Foo@hash default. + assertTrue( + ex.message?.contains("DiskFull") == true && ex.message?.contains("@") != true, + "message must read 'DiskFull', was: ${ex.message}", + ) + } + + @Test + fun ok_withNullableValue_getOrNull_distinguishesNullValueFromError() { + // An Ok wrapping a null value still reports isOk; getOrNull returns null here + // too, but getOrThrow returns the (null) value rather than throwing. + val result: Fat12Result = Fat12Result.Ok(null) + assertTrue(result.isOk) + assertNull(result.getOrNull()) + assertNull(result.getOrThrow()) + } +}