# Sealed Class Catalog Comprehensive list of sealed types in AmethystMultiplatform with usage patterns. ## Table of Contents - [State Management](#state-management) - [Result Types](#result-types) - [Tag Variants](#tag-variants) - [Sealed Class vs Sealed Interface](#sealed-class-vs-sealed-interface) - [Patterns](#patterns) --- ## State Management ### AccountState (Sealed Class) **File:** `commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/account/AccountManager.kt:36-46` ```kotlin sealed class AccountState { data object LoggedOut : AccountState() data class LoggedIn( val signer: NostrSigner, val pubKeyHex: String, val npub: String, val nsec: String?, val isReadOnly: Boolean ) : AccountState() } ``` **Why sealed class:** - Two distinct states with different data - `LoggedIn` holds data, `LoggedOut` doesn't - No need for generics or multiple inheritance **Usage:** ```kotlin fun handleAccountState(state: AccountState) { when (state) { is AccountState.LoggedOut -> showLogin() is AccountState.LoggedIn -> { showFeed( pubkey = state.pubKeyHex, canSign = !state.isReadOnly ) } } // Exhaustive - compiler enforces } ``` ### VerificationState (Sealed Class) **File:** `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/VerificationState.kt` ```kotlin sealed class VerificationState { data object NotStarted : VerificationState() data object Started : VerificationState() data class Failed(val reason: String) : VerificationState() data object Verified : VerificationState() } ``` **Pattern:** - State machine (NotStarted → Started → Failed/Verified) - Only `Failed` carries data (reason) - Rest are singletons (`data object`) --- ## Result Types ### SignerResult (Sealed Interface with Generics) **File:** `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/signer/SignerResult.kt:25-46` ```kotlin sealed interface SignerResult { sealed interface RequestAddressed : SignerResult { class Successful(val result: T) : RequestAddressed class Rejected : RequestAddressed class TimedOut : RequestAddressed class ReceivedButCouldNotPerform( val message: String? = null ) : RequestAddressed class ReceivedButCouldNotParseEventFromResult( val eventJson: String ) : RequestAddressed class ReceivedButCouldNotVerifyResultingEvent( val invalidEvent: Event ) : RequestAddressed } } interface IResult data class SignResult(val event: Event) : IResult data class EncryptionResult(val ciphertext: String) : IResult data class DecryptionResult(val plaintext: String) : IResult ``` **Why sealed interface:** - Generic result type `` - Nested sealed hierarchy (RequestAddressed) - Need covariance for flexible result types **Usage:** ```kotlin suspend fun signEvent(event: Event): SignerResult { return when (val result = remoteSigner.sign(event)) { is SignerResult.RequestAddressed.Successful -> result is SignerResult.RequestAddressed.Rejected -> { logger.warn("Signing rejected") result } is SignerResult.RequestAddressed.TimedOut -> { logger.error("Signing timed out") result } is SignerResult.RequestAddressed.ReceivedButCouldNotPerform -> { logger.error("Signer error: ${result.message}") result } } } ``` ### CacheResults (Sealed Class with Generics) **File:** `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/caches/CacheResults.kt` ```kotlin sealed class CacheResults { data class Found(val value: T) : CacheResults() class NotFound : CacheResults() } ``` **Pattern:** - Simple binary result (found/not found) - `Found` carries data, `NotFound` doesn't - Generic for reusability --- ## Tag Variants ### MuteTag (Sealed Class) **File:** `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip51Lists/muteList/tags/MuteTag.kt` ```kotlin sealed class MuteTag( val nameOrNull: String?, val valueOrNull: String? ) { class Event(eventId: String) : MuteTag("e", eventId) class Profile(pubkey: String) : MuteTag("p", pubkey) class Word(word: String) : MuteTag("word", word) class Thread(threadId: String) : MuteTag("thread", threadId) companion object { fun parse(tag: Array): MuteTag? { return when (tag.getOrNull(0)) { "e" -> tag.getOrNull(1)?.let { Event(it) } "p" -> tag.getOrNull(1)?.let { Profile(it) } "word" -> tag.getOrNull(1)?.let { Word(it) } "thread" -> tag.getOrNull(1)?.let { Thread(it) } else -> null } } } fun toArray(): Array { return arrayOf(nameOrNull ?: "", valueOrNull ?: "") } } ``` **Pattern:** - Common base class with shared properties - Each variant represents different tag type - Factory method `parse()` for parsing - `toArray()` for serialization ### BookmarkIdTag (Sealed Class) **File:** `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip51Lists/bookmarkList/tags/BookmarkIdTag.kt` ```kotlin sealed class BookmarkIdTag { abstract val id: String abstract val marker: String? data class Event(override val id: String, override val marker: String?) : BookmarkIdTag() data class Profile(override val id: String, override val marker: String?) : BookmarkIdTag() data class Address(override val id: String, override val marker: String?) : BookmarkIdTag() companion object { fun parse(tag: Array): BookmarkIdTag? { val marker = tag.getOrNull(3) return when (tag.getOrNull(0)) { "e" -> tag.getOrNull(1)?.let { Event(it, marker) } "p" -> tag.getOrNull(1)?.let { Profile(it, marker) } "a" -> tag.getOrNull(1)?.let { Address(it, marker) } else -> null } } } } ``` **Pattern:** - Abstract properties in sealed class - Data classes implement abstract properties - Parse factory returns sealed variant --- ## Exception Hierarchies ### SignerExceptions (Sealed Class) **File:** `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/SignerExceptions.kt` ```kotlin sealed class SignerExceptions(message: String) : Exception(message) { class UnableToSign(message: String) : SignerExceptions(message) class UnableToDecrypt(message: String) : SignerExceptions(message) class UnableToEncrypt(message: String) : SignerExceptions(message) class UnableToGetPublicKey(message: String) : SignerExceptions(message) } ``` **Pattern:** - Sealed exception hierarchy - Extends `Exception` base class - Type-safe error handling **Usage:** ```kotlin try { signer.sign(event) } catch (e: SignerExceptions) { when (e) { is SignerExceptions.UnableToSign -> logger.error("Signing failed: ${e.message}") is SignerExceptions.UnableToDecrypt -> logger.error("Decryption failed: ${e.message}") is SignerExceptions.UnableToEncrypt -> logger.error("Encryption failed: ${e.message}") is SignerExceptions.UnableToGetPublicKey -> logger.error("No public key: ${e.message}") } } ``` --- ## Sealed Class vs Sealed Interface ### When to Use Sealed Class **Examples from codebase:** 1. **AccountState** - State variants with different data 2. **VerificationState** - State machine 3. **MuteTag** - Tag variants with common base properties 4. **SignerExceptions** - Exception hierarchy **Characteristics:** - Need common constructor parameters - Single inheritance only - State variants - Exception hierarchies ### When to Use Sealed Interface **Examples from codebase:** 1. **SignerResult** - Generic result types needing variance 2. **RelayUrlNormalizer.Result** - Binary result with no shared state **Characteristics:** - Need generics with variance (`out`, `in`) - No common state needed - Multiple inheritance possible - Contract/capability representation --- ## Patterns ### Pattern: State Machine ```kotlin sealed class ConnectionState { data object Disconnected : ConnectionState() data object Connecting : ConnectionState() data class Connected(val relay: String) : ConnectionState() data class Failed(val error: String) : ConnectionState() } // Allowed transitions fun transition(from: ConnectionState, event: Event): ConnectionState { return when (from) { is ConnectionState.Disconnected -> { when (event) { is Event.Connect -> ConnectionState.Connecting else -> from } } is ConnectionState.Connecting -> { when (event) { is Event.Success -> ConnectionState.Connected(event.relay) is Event.Error -> ConnectionState.Failed(event.message) is Event.Cancel -> ConnectionState.Disconnected else -> from } } is ConnectionState.Connected -> { when (event) { is Event.Disconnect -> ConnectionState.Disconnected is Event.Error -> ConnectionState.Failed(event.message) else -> from } } is ConnectionState.Failed -> { when (event) { is Event.Retry -> ConnectionState.Connecting is Event.Cancel -> ConnectionState.Disconnected else -> from } } } } ``` ### Pattern: Result Type ```kotlin sealed interface Result { data class Success(val data: T) : Result data class Error(val exception: Exception) : Result data object Loading : Result } // Extension functions fun Result.getOrNull(): T? = when (this) { is Result.Success -> data else -> null } fun Result.getOrThrow(): T = when (this) { is Result.Success -> data is Result.Error -> throw exception is Result.Loading -> error("Still loading") } fun Result.map(transform: (T) -> R): Result = when (this) { is Result.Success -> Result.Success(transform(data)) is Result.Error -> this is Result.Loading -> Result.Loading } ``` ### Pattern: Tagged Union (Discriminated Union) ```kotlin sealed class Command { data class SendEvent(val event: Event) : Command() data class Subscribe(val filters: List) : Command() data class Unsubscribe(val subId: String) : Command() data object Close : Command() fun toJson(): String = when (this) { is SendEvent -> """["EVENT",${event.toJson()}]""" is Subscribe -> """["REQ","sub",${filters.joinToString { it.toJson() }}]""" is Unsubscribe -> """["CLOSE","$subId"]""" is Close -> """["CLOSE"]""" } } ``` ### Pattern: Nested Sealed Hierarchies ```kotlin sealed interface UiState { sealed interface Loading : UiState { data object Initial : Loading data class Refreshing(val currentData: List) : Loading } sealed interface Content : UiState { data class Success(val data: List) : Content data object Empty : Content } sealed interface Error : UiState { data class Network(val message: String) : Error data class Server(val code: Int, val message: String) : Error } } // Usage fun renderUi(state: UiState) { when (state) { is UiState.Loading.Initial -> showFullScreenLoader() is UiState.Loading.Refreshing -> showRefreshIndicator(state.currentData) is UiState.Content.Success -> showList(state.data) is UiState.Content.Empty -> showEmptyState() is UiState.Error.Network -> showNetworkError(state.message) is UiState.Error.Server -> showServerError(state.code, state.message) } } ``` --- ## All Sealed Types in Quartz **Complete list of sealed types found in codebase:** ### Commons - AccountState (class) ### Quartz - BaseZapSplitSetup (class) - MuteTag (class) - BookmarkIdTag (class) - SignerResult (interface) - VerificationState (class) - CacheResults (class) - SignerExceptions (class) - RelayUrlNormalizer.Result (interface) **Total:** 8 sealed types (7 classes, 1 interface) --- ## Decision Tree ``` Need to represent variants of a concept? YES → Use sealed type NO → Regular class/interface Variants have different data? YES → sealed class or sealed interface NO → enum (if simple constants) Need generics with variance (out/in)? YES → sealed interface NO → sealed class (simpler) Need common constructor/properties? YES → sealed class NO → sealed interface Need multiple inheritance? YES → sealed interface NO → Either works Representing state machine? → sealed class (state transitions) Representing result/error types? → sealed interface (if generic, else class) Representing tag/command variants? → sealed class (common structure) ``` --- ## References - [Sealed Classes | Kotlin Docs](https://kotlinlang.org/docs/sealed-classes.html) - [Effective Kotlin: Sealed Classes](https://kt.academy/article/ek-sealed-classes) - [Complete Guide: Sealed Classes & Interfaces 2025](https://proandroiddev.com/complete-technical-guide-sealed-classes-sealed-interfaces-enums-in-kotlin-28ffc39116df)