Add domain states and event runtime (T-002)
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user