mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
82 lines
2.0 KiB
Go
82 lines
2.0 KiB
Go
package domain
|
|
|
|
import "fmt"
|
|
|
|
var (
|
|
ErrRevisionGap = fmt.Errorf("revision gap requires resync")
|
|
ErrSyncConflict = fmt.Errorf("conflicting revisioned event")
|
|
)
|
|
|
|
type SyncEvent struct {
|
|
Kind ResourceKind
|
|
ResourceID string
|
|
Revision uint64
|
|
State State
|
|
}
|
|
|
|
type ReplicaResource struct {
|
|
Kind ResourceKind
|
|
ResourceID string
|
|
State State
|
|
Revision uint64
|
|
NeedsResync bool
|
|
}
|
|
|
|
func NewReplicaResource(kind ResourceKind, resourceID string, state State) (*ReplicaResource, error) {
|
|
if resourceID == "" || !validStateForKind(kind, state) {
|
|
return nil, fmt.Errorf("invalid replica resource")
|
|
}
|
|
return &ReplicaResource{Kind: kind, ResourceID: resourceID, State: state}, nil
|
|
}
|
|
|
|
// ApplyEvent makes duplicate/out-of-order delivery converge. A gap is not
|
|
// guessed through; callers must fetch the authoritative REST snapshot and use
|
|
// ReplaceSnapshot before resuming the event stream.
|
|
func (r *ReplicaResource) ApplyEvent(event SyncEvent) error {
|
|
if event.Kind != r.Kind || event.ResourceID != r.ResourceID {
|
|
return ErrSyncConflict
|
|
}
|
|
if r.NeedsResync {
|
|
return ErrRevisionGap
|
|
}
|
|
if event.Revision <= r.Revision {
|
|
if event.Revision == r.Revision && event.State != r.State {
|
|
return ErrSyncConflict
|
|
}
|
|
return nil
|
|
}
|
|
if event.Revision != r.Revision+1 {
|
|
r.NeedsResync = true
|
|
return ErrRevisionGap
|
|
}
|
|
if !legalTransition(r.Kind, r.State, event.State) {
|
|
return ErrSyncConflict
|
|
}
|
|
r.State, r.Revision = event.State, event.Revision
|
|
return nil
|
|
}
|
|
|
|
func (r *ReplicaResource) ReplaceSnapshot(revision uint64, state State) error {
|
|
if revision < r.Revision || !validStateForKind(r.Kind, state) {
|
|
return ErrSyncConflict
|
|
}
|
|
r.State, r.Revision, r.NeedsResync = state, revision, false
|
|
return nil
|
|
}
|
|
|
|
func validStateForKind(kind ResourceKind, state State) bool {
|
|
switch kind {
|
|
case ResourceQueueTicket:
|
|
_, ok := queueTransitions[state]
|
|
return ok || state == Queued
|
|
case ResourceProposal:
|
|
_, ok := proposalTransitions[state]
|
|
return ok || state == Open
|
|
case ResourceMatch:
|
|
_, ok := matchTransitions[state]
|
|
return ok
|
|
default:
|
|
return false
|
|
}
|
|
}
|