| package subscription |
|
|
| import ( |
| "context" |
| "fmt" |
|
|
| "github.com/qmuntal/stateless" |
|
|
| "github.com/openmeterio/openmeter/pkg/models" |
| ) |
|
|
| type SubscriptionStatus string |
|
|
| const ( |
| |
| SubscriptionStatusActive SubscriptionStatus = "active" |
| |
| SubscriptionStatusCanceled SubscriptionStatus = "canceled" |
| |
| SubscriptionStatusInactive SubscriptionStatus = "inactive" |
| |
| SubscriptionStatusScheduled SubscriptionStatus = "scheduled" |
| ) |
|
|
| func (s SubscriptionStatus) Values() []SubscriptionStatus { |
| return []SubscriptionStatus{ |
| SubscriptionStatusActive, |
| SubscriptionStatusCanceled, |
| SubscriptionStatusInactive, |
| SubscriptionStatusScheduled, |
| } |
| } |
|
|
| func (s SubscriptionStatus) Validate() error { |
| switch s { |
| case SubscriptionStatusActive, SubscriptionStatusCanceled, SubscriptionStatusInactive, SubscriptionStatusScheduled: |
| return nil |
| default: |
| return fmt.Errorf("invalid subscription status: %s", s) |
| } |
| } |
|
|
| type SubscriptionAction string |
|
|
| const ( |
| SubscriptionActionCreate SubscriptionAction = "create" |
| SubscriptionActionUpdate SubscriptionAction = "update" |
| SubscriptionActionCancel SubscriptionAction = "cancel" |
| SubscriptionActionContinue SubscriptionAction = "continue" |
| SubscriptionActionDelete SubscriptionAction = "delete" |
| SubscriptionActionChangeAddons SubscriptionAction = "change_addons" |
| ) |
|
|
| |
| type SubscriptionStateMachine struct { |
| sm *stateless.StateMachine |
| } |
|
|
| func (sm SubscriptionStateMachine) CanTransitionOrErr(ctx context.Context, action SubscriptionAction) error { |
| can, err := sm.sm.CanFireCtx(ctx, action) |
| |
| if err != nil { |
| return fmt.Errorf("failed to check if transition is possible: %w", err) |
| } |
|
|
| state, err := sm.sm.State(ctx) |
| if err != nil { |
| return fmt.Errorf("failed to get current state: %w", err) |
| } |
|
|
| status, ok := state.(SubscriptionStatus) |
| if !ok { |
| return fmt.Errorf("failed to cast state to SubscriptionStatus, got %T %v", state, state) |
| } |
|
|
| if err := status.Validate(); err != nil { |
| return fmt.Errorf("invalid state: %w", err) |
| } |
|
|
| if !can { |
| return models.NewGenericForbiddenError( |
| fmt.Errorf("transition %s in state %s not allowed", action, state), |
| ) |
| } |
|
|
| return nil |
| } |
|
|
| func NewStateMachine(status SubscriptionStatus) SubscriptionStateMachine { |
| sm := stateless.NewStateMachine(status) |
|
|
| sm.Configure(SubscriptionStatusInactive). |
| Permit(SubscriptionActionCreate, SubscriptionStatusActive) |
|
|
| sm.Configure(SubscriptionStatusActive). |
| PermitReentry(SubscriptionActionUpdate). |
| PermitReentry(SubscriptionActionChangeAddons). |
| Permit(SubscriptionActionCancel, SubscriptionStatusCanceled) |
|
|
| sm.Configure(SubscriptionStatusCanceled). |
| Permit(SubscriptionActionContinue, SubscriptionStatusActive) |
|
|
| sm.Configure(SubscriptionStatusScheduled). |
| Permit(SubscriptionActionDelete, nil) |
|
|
| return SubscriptionStateMachine{ |
| sm: sm, |
| } |
| } |
|
|