Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions pkg/api/settlement.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,9 @@ func (a *API) handleConfirmIssue(w http.ResponseWriter, r *http.Request) {
return
}
settled, err := a.Store.Mutate(idea.ID, true, func(i *store.Idea) error {
if !store.CanTransition(i.Status, store.StatusSettled) {
return &store.ValidationError{Msg: "cannot settle an idea in status " + i.Status}
if err := i.TransitionTo(store.StatusSettled, "settle"); err != nil {
return err
}
i.Status = store.StatusSettled
i.IssueURL = in.IssueURL
return nil
})
Expand Down
28 changes: 9 additions & 19 deletions pkg/api/wave2.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,8 @@ func (a *API) handleOffer(w http.ResponseWriter, r *http.Request) {
if o := i.OfferTo(rp.RepoID); o != nil && o.Status != store.OfferDeclined {
return &store.ValidationError{Msg: "already offered to this repo"}
}
if i.Status != store.StatusOffered {
if !store.CanTransition(i.Status, store.StatusOffered) {
return &store.ValidationError{Msg: "cannot offer an idea in status " + i.Status}
}
i.Status = store.StatusOffered
if err := i.TransitionTo(store.StatusOffered, "offer"); err != nil {
return err
}
if o := i.OfferTo(rp.RepoID); o != nil {
o.Status = store.OfferPending
Expand Down Expand Up @@ -218,11 +215,8 @@ func (a *API) offerExternal(w http.ResponseWriter, idea *store.Idea, repoID stri
if o := i.OfferTo(repoID); o != nil && o.Status != store.OfferDeclined {
return &store.ValidationError{Msg: "already offered to this repo"}
}
if i.Status != store.StatusOffered {
if !store.CanTransition(i.Status, store.StatusOffered) {
return &store.ValidationError{Msg: "cannot offer an idea in status " + i.Status}
}
i.Status = store.StatusOffered
if err := i.TransitionTo(store.StatusOffered, "offer"); err != nil {
return err
}
if o := i.OfferTo(repoID); o != nil {
o.Status = store.OfferPending
Expand Down Expand Up @@ -405,9 +399,7 @@ func (a *API) handleDecide(w http.ResponseWriter, r *http.Request) {
return nil // other repos are still considering it
}
}
if store.CanTransition(i.Status, store.StatusDeclined) {
i.Status = store.StatusDeclined
}
i.TryTransition(store.StatusDeclined)
return nil
})
if err != nil {
Expand Down Expand Up @@ -437,15 +429,14 @@ func (a *API) accept(w http.ResponseWriter, r *http.Request, idea *store.Idea, r
return
}
updated, err := a.Store.Mutate(idea.ID, true, func(i *store.Idea) error {
if !store.CanTransition(i.Status, store.StatusAccepted) {
return &store.ValidationError{Msg: "cannot accept an idea in status " + i.Status}
if err := i.TransitionTo(store.StatusAccepted, "accept"); err != nil {
return err
}
if o := i.OfferTo(rp.RepoID); o != nil {
now := timeNow()
o.Status = store.OfferAccepted
o.DecidedAt = &now
}
i.Status = store.StatusAccepted
i.TargetRepo = rp.RepoID
return nil
})
Expand Down Expand Up @@ -489,10 +480,9 @@ func (a *API) legacySettle(w http.ResponseWriter, r *http.Request, updated *stor
return
}
settled, err := a.Store.Mutate(updated.ID, true, func(i *store.Idea) error {
if !store.CanTransition(i.Status, store.StatusSettled) {
return &store.ValidationError{Msg: "cannot settle an idea in status " + i.Status}
if err := i.TransitionTo(store.StatusSettled, "settle"); err != nil {
return err
}
i.Status = store.StatusSettled
i.IssueURL = issueURL
return nil
})
Expand Down
28 changes: 28 additions & 0 deletions pkg/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,34 @@ func (i *Idea) HasPassed(repoID string) bool {
return false
}

// TransitionTo advances the idea to status to, enforcing the lifecycle
// state machine. action names the attempted move (e.g. "offer", "accept",
// "settle") for the returned error message. A no-op (to == i.Status) always
// succeeds. Callers that previously duplicated the CanTransition-then-set
// pattern inline should call this instead so the invariant lives in one
// place.
func (i *Idea) TransitionTo(to, action string) error {
if i.Status == to {
return nil
}
if !CanTransition(i.Status, to) {
return &ValidationError{Msg: "cannot " + action + " an idea in status " + i.Status}
}
i.Status = to
return nil
}

// TryTransition sets the idea's status to to if the lifecycle state machine
// allows it, reporting whether the transition happened. Used where a
// disallowed transition is a silent no-op rather than a hard error.
func (i *Idea) TryTransition(to string) bool {
if !CanTransition(i.Status, to) {
return false
}
i.Status = to
return true
}

// ErrNotFound is returned when an idea does not exist.
var ErrNotFound = errors.New("store: idea not found")

Expand Down
Loading