package domain import ( "errors" "fmt" ) // AppStatus describes the user-visible lifecycle state of one catalog app. type AppStatus string const ( StatusNotInstalled AppStatus = "not_installed" StatusQueued AppStatus = "queued" StatusDownloading AppStatus = "downloading" StatusVerifying AppStatus = "verifying" StatusExtracting AppStatus = "extracting" StatusInstalling AppStatus = "installing" StatusInstalled AppStatus = "installed" StatusUpdateAvailable AppStatus = "update_available" StatusRunning AppStatus = "running" StatusFailed AppStatus = "failed" StatusRollbackPending AppStatus = "rollback_pending" StatusIncompatible AppStatus = "incompatible" ) var ( // ErrInvalidStatus indicates that a transition contains an unknown status. ErrInvalidStatus = errors.New("invalid app status") // ErrInvalidTransition indicates that two valid states cannot transition directly. ErrInvalidTransition = errors.New("invalid app status transition") ) var validStatuses = map[AppStatus]struct{}{ StatusNotInstalled: {}, StatusQueued: {}, StatusDownloading: {}, StatusVerifying: {}, StatusExtracting: {}, StatusInstalling: {}, StatusInstalled: {}, StatusUpdateAvailable: {}, StatusRunning: {}, StatusFailed: {}, StatusRollbackPending: {}, StatusIncompatible: {}, } type statusTransition struct { from AppStatus to AppStatus } var allowedTransitions = map[statusTransition]struct{}{ {StatusNotInstalled, StatusQueued}: {}, {StatusNotInstalled, StatusIncompatible}: {}, {StatusQueued, StatusDownloading}: {}, {StatusQueued, StatusNotInstalled}: {}, {StatusQueued, StatusInstalled}: {}, {StatusQueued, StatusUpdateAvailable}: {}, {StatusQueued, StatusFailed}: {}, {StatusDownloading, StatusQueued}: {}, {StatusDownloading, StatusVerifying}: {}, {StatusDownloading, StatusNotInstalled}: {}, {StatusDownloading, StatusInstalled}: {}, {StatusDownloading, StatusUpdateAvailable}: {}, {StatusDownloading, StatusFailed}: {}, {StatusVerifying, StatusExtracting}: {}, {StatusVerifying, StatusFailed}: {}, {StatusExtracting, StatusInstalling}: {}, {StatusExtracting, StatusFailed}: {}, {StatusInstalling, StatusInstalled}: {}, {StatusInstalling, StatusRollbackPending}: {}, {StatusInstalling, StatusFailed}: {}, {StatusInstalled, StatusUpdateAvailable}: {}, {StatusInstalled, StatusRunning}: {}, {StatusInstalled, StatusQueued}: {}, {StatusInstalled, StatusIncompatible}: {}, {StatusUpdateAvailable, StatusQueued}: {}, {StatusUpdateAvailable, StatusRunning}: {}, {StatusUpdateAvailable, StatusInstalled}: {}, {StatusUpdateAvailable, StatusIncompatible}: {}, {StatusRunning, StatusInstalled}: {}, {StatusRunning, StatusUpdateAvailable}: {}, {StatusFailed, StatusQueued}: {}, {StatusFailed, StatusNotInstalled}: {}, {StatusFailed, StatusInstalled}: {}, {StatusFailed, StatusUpdateAvailable}: {}, {StatusFailed, StatusRollbackPending}: {}, {StatusRollbackPending, StatusInstalled}: {}, {StatusRollbackPending, StatusFailed}: {}, {StatusIncompatible, StatusNotInstalled}: {}, {StatusIncompatible, StatusInstalled}: {}, {StatusIncompatible, StatusUpdateAvailable}: {}, } // Valid reports whether status is one of the documented domain states. func (status AppStatus) Valid() bool { _, ok := validStatuses[status] return ok } // CanTransition reports whether from can move directly to to. func CanTransition(from, to AppStatus) bool { return ValidateTransition(from, to) == nil } // ValidateTransition verifies a direct state change. func ValidateTransition(from, to AppStatus) error { if !from.Valid() { return fmt.Errorf("%w: %q", ErrInvalidStatus, from) } if !to.Valid() { return fmt.Errorf("%w: %q", ErrInvalidStatus, to) } if _, ok := allowedTransitions[statusTransition{from: from, to: to}]; !ok { return &TransitionError{From: from, To: to} } return nil } // TransitionError describes a rejected direct state change. type TransitionError struct { From AppStatus To AppStatus } func (err *TransitionError) Error() string { return fmt.Sprintf("%s: %s -> %s", ErrInvalidTransition, err.From, err.To) } // Unwrap allows callers to use errors.Is with ErrInvalidTransition. func (err *TransitionError) Unwrap() error { return ErrInvalidTransition }