From 31957e58c71f3545ad542d0c75dd3abaa7022665 Mon Sep 17 00:00:00 2001 From: "quim.muntal.diaz" Date: Tue, 7 Sep 2021 15:34:59 +0200 Subject: [PATCH 1/5] first pass on generics --- config.go | 72 +++++++++---------- example_test.go | 2 +- go.mod | 8 ++- graph.go | 32 ++++----- statemachine.go | 188 +++++++++++++++++++++++++----------------------- states.go | 82 ++++++++++----------- states_test.go | 56 +++++++-------- triggers.go | 54 +++++++------- 8 files changed, 253 insertions(+), 241 deletions(-) diff --git a/config.go b/config.go index c237334..1f3e7da 100644 --- a/config.go +++ b/config.go @@ -7,14 +7,14 @@ import ( type transitionKey struct{} -func withTransition(ctx context.Context, transition Transition) context.Context { +func withTransition[S State, T Trigger](ctx context.Context, transition Transition[S, T]) context.Context { return context.WithValue(ctx, transitionKey{}, transition) } // GetTransition returns the transition from the context. // If there is no transition the returned value is empty. -func GetTransition(ctx context.Context) Transition { - return ctx.Value(transitionKey{}).(Transition) +func GetTransition[S State, T Trigger](ctx context.Context) Transition[S, T] { + return ctx.Value(transitionKey{}).(Transition[S, T]) } // ActionFunc describes a generic action function. @@ -25,29 +25,29 @@ type ActionFunc = func(ctx context.Context, args ...interface{}) error type GuardFunc = func(ctx context.Context, args ...interface{}) bool // DestinationSelectorFunc defines a functions that is called to select a dynamic destination. -type DestinationSelectorFunc = func(ctx context.Context, args ...interface{}) (State, error) +type DestinationSelectorFunc[S State] func(ctx context.Context, args ...interface{}) (S, error) // StateConfiguration is the configuration for a single state value. -type StateConfiguration struct { - sm *StateMachine - sr *stateRepresentation - lookup func(State) *stateRepresentation +type StateConfiguration[S State, T Trigger] struct { + sm *StateMachine[S, T] + sr *stateRepresentation[S, T] + lookup func(S) *stateRepresentation[S, T] } // State is configured with this configuration. -func (sc *StateConfiguration) State() State { +func (sc *StateConfiguration[S, T]) State() S { return sc.sr.State } // Machine that is configured with this configuration. -func (sc *StateConfiguration) Machine() *StateMachine { +func (sc *StateConfiguration[S, T]) Machine() *StateMachine[S, T] { return sc.sm } // InitialTransition adds internal transition to this state. // When entering the current state the state machine will look for an initial transition, // and enter the target state. -func (sc *StateConfiguration) InitialTransition(targetState State) *StateConfiguration { +func (sc *StateConfiguration[S, T]) InitialTransition(targetState S) *StateConfiguration[S, T] { if sc.sr.HasInitialState { panic(fmt.Sprintf("stateless: This state has already been configured with an initial transition (%v).", sc.sr.InitialTransitionTarget)) } @@ -59,12 +59,12 @@ func (sc *StateConfiguration) InitialTransition(targetState State) *StateConfigu } // Permit accept the specified trigger and transition to the destination state if the guard conditions are met (if any). -func (sc *StateConfiguration) Permit(trigger Trigger, destinationState State, guards ...GuardFunc) *StateConfiguration { +func (sc *StateConfiguration[S, T]) Permit(trigger T, destinationState S, guards ...GuardFunc) *StateConfiguration[S, T] { if destinationState == sc.sr.State { panic("stateless: Permit() require that the destination state is not equal to the source state. To accept a trigger without changing state, use either Ignore() or PermitReentry().") } - sc.sr.AddTriggerBehaviour(&transitioningTriggerBehaviour{ - baseTriggerBehaviour: baseTriggerBehaviour{Trigger: trigger, Guard: newtransitionGuard(guards...)}, + sc.sr.AddTriggerBehaviour(&transitioningTriggerBehaviour[S, T]{ + baseTriggerBehaviour: baseTriggerBehaviour[T]{Trigger: trigger, Guard: newtransitionGuard(guards...)}, Destination: destinationState, }) return sc @@ -72,9 +72,9 @@ func (sc *StateConfiguration) Permit(trigger Trigger, destinationState State, gu // InternalTransition add an internal transition to the state machine. // An internal action does not cause the Exit and Entry actions to be triggered, and does not change the state of the state machine. -func (sc *StateConfiguration) InternalTransition(trigger Trigger, action ActionFunc, guards ...GuardFunc) *StateConfiguration { - sc.sr.AddTriggerBehaviour(&internalTriggerBehaviour{ - baseTriggerBehaviour: baseTriggerBehaviour{Trigger: trigger, Guard: newtransitionGuard(guards...)}, +func (sc *StateConfiguration[S, T]) InternalTransition(trigger T, action ActionFunc, guards ...GuardFunc) *StateConfiguration[S, T] { + sc.sr.AddTriggerBehaviour(&internalTriggerBehaviour[S, T]{ + baseTriggerBehaviour: baseTriggerBehaviour[T]{Trigger: trigger, Guard: newtransitionGuard(guards...)}, Action: action, }) return sc @@ -84,37 +84,37 @@ func (sc *StateConfiguration) InternalTransition(trigger Trigger, action ActionF // Reentry behaves as though the configured state transitions to an identical sibling state. // Applies to the current state only. Will not re-execute superstate actions, or // cause actions to execute transitioning between super- and sub-states. -func (sc *StateConfiguration) PermitReentry(trigger Trigger, guards ...GuardFunc) *StateConfiguration { - sc.sr.AddTriggerBehaviour(&reentryTriggerBehaviour{ - baseTriggerBehaviour: baseTriggerBehaviour{Trigger: trigger, Guard: newtransitionGuard(guards...)}, +func (sc *StateConfiguration[S, T]) PermitReentry(trigger T, guards ...GuardFunc) *StateConfiguration[S, T] { + sc.sr.AddTriggerBehaviour(&reentryTriggerBehaviour[S, T]{ + baseTriggerBehaviour: baseTriggerBehaviour[T]{Trigger: trigger, Guard: newtransitionGuard(guards...)}, Destination: sc.sr.State, }) return sc } // Ignore the specified trigger when in the configured state, if the guards return true. -func (sc *StateConfiguration) Ignore(trigger Trigger, guards ...GuardFunc) *StateConfiguration { - sc.sr.AddTriggerBehaviour(&ignoredTriggerBehaviour{ - baseTriggerBehaviour: baseTriggerBehaviour{Trigger: trigger, Guard: newtransitionGuard(guards...)}, +func (sc *StateConfiguration[S, T]) Ignore(trigger T, guards ...GuardFunc) *StateConfiguration[S, T] { + sc.sr.AddTriggerBehaviour(&ignoredTriggerBehaviour[T]{ + baseTriggerBehaviour: baseTriggerBehaviour[T]{Trigger: trigger, Guard: newtransitionGuard(guards...)}, }) return sc } // PermitDynamic accept the specified trigger and transition to the destination state, calculated dynamically by the supplied function. -func (sc *StateConfiguration) PermitDynamic(trigger Trigger, selector DestinationSelectorFunc, guards ...GuardFunc) *StateConfiguration { +func (sc *StateConfiguration[S, T]) PermitDynamic(trigger T, selector DestinationSelectorFunc[S], guards ...GuardFunc) *StateConfiguration[S, T] { guardDescriptors := make([]invocationInfo, len(guards)) for i, guard := range guards { guardDescriptors[i] = newinvocationInfo(guard) } - sc.sr.AddTriggerBehaviour(&dynamicTriggerBehaviour{ - baseTriggerBehaviour: baseTriggerBehaviour{Trigger: trigger, Guard: newtransitionGuard(guards...)}, + sc.sr.AddTriggerBehaviour(&dynamicTriggerBehaviour[S, T]{ + baseTriggerBehaviour: baseTriggerBehaviour[T]{Trigger: trigger, Guard: newtransitionGuard(guards...)}, Destination: selector, }) return sc } // OnActive specify an action that will execute when activating the configured state. -func (sc *StateConfiguration) OnActive(action func(context.Context) error) *StateConfiguration { +func (sc *StateConfiguration[S, T]) OnActive(action func(context.Context) error) *StateConfiguration[S, T] { sc.sr.ActivateActions = append(sc.sr.ActivateActions, actionBehaviourSteady{ Action: action, Description: newinvocationInfo(action), @@ -123,7 +123,7 @@ func (sc *StateConfiguration) OnActive(action func(context.Context) error) *Stat } // OnDeactivate specify an action that will execute when deactivating the configured state. -func (sc *StateConfiguration) OnDeactivate(action func(context.Context) error) *StateConfiguration { +func (sc *StateConfiguration[S, T]) OnDeactivate(action func(context.Context) error) *StateConfiguration[S, T] { sc.sr.DeactivateActions = append(sc.sr.DeactivateActions, actionBehaviourSteady{ Action: action, Description: newinvocationInfo(action), @@ -132,8 +132,8 @@ func (sc *StateConfiguration) OnDeactivate(action func(context.Context) error) * } // OnEntry specify an action that will execute when transitioning into the configured state. -func (sc *StateConfiguration) OnEntry(action ActionFunc) *StateConfiguration { - sc.sr.EntryActions = append(sc.sr.EntryActions, actionBehaviour{ +func (sc *StateConfiguration[S, T]) OnEntry(action ActionFunc) *StateConfiguration[S, T] { + sc.sr.EntryActions = append(sc.sr.EntryActions, actionBehaviour[S, T]{ Action: action, Description: newinvocationInfo(action), }) @@ -141,8 +141,8 @@ func (sc *StateConfiguration) OnEntry(action ActionFunc) *StateConfiguration { } // OnEntryFrom Specify an action that will execute when transitioning into the configured state from a specific trigger. -func (sc *StateConfiguration) OnEntryFrom(trigger Trigger, action ActionFunc) *StateConfiguration { - sc.sr.EntryActions = append(sc.sr.EntryActions, actionBehaviour{ +func (sc *StateConfiguration[S, T]) OnEntryFrom(trigger T, action ActionFunc) *StateConfiguration[S, T] { + sc.sr.EntryActions = append(sc.sr.EntryActions, actionBehaviour[S, T]{ Action: action, Description: newinvocationInfo(action), Trigger: &trigger, @@ -151,8 +151,8 @@ func (sc *StateConfiguration) OnEntryFrom(trigger Trigger, action ActionFunc) *S } // OnExit specify an action that will execute when transitioning from the configured state. -func (sc *StateConfiguration) OnExit(action ActionFunc) *StateConfiguration { - sc.sr.ExitActions = append(sc.sr.ExitActions, actionBehaviour{ +func (sc *StateConfiguration[S, T]) OnExit(action ActionFunc) *StateConfiguration[S, T] { + sc.sr.ExitActions = append(sc.sr.ExitActions, actionBehaviour[S, T]{ Action: action, Description: newinvocationInfo(action), }) @@ -165,7 +165,7 @@ func (sc *StateConfiguration) OnExit(action ActionFunc) *StateConfiguration { // entry actions for the superstate are executed. // Likewise when leaving from the substate to outside the supserstate, // exit actions for the superstate will execute. -func (sc *StateConfiguration) SubstateOf(superstate State) *StateConfiguration { +func (sc *StateConfiguration[S, T]) SubstateOf(superstate S) *StateConfiguration[S, T] { state := sc.sr.State // Check for accidental identical cyclic configuration if state == superstate { @@ -174,7 +174,7 @@ func (sc *StateConfiguration) SubstateOf(superstate State) *StateConfiguration { // Check for accidental identical nested cyclic configuration var empty struct{} - supersets := map[State]struct{}{state: empty} + supersets := map[S]struct{}{state: empty} // Build list of super states and check for activeSc := sc.lookup(superstate) diff --git a/example_test.go b/example_test.go index b5e8e5f..c8a7f5c 100644 --- a/example_test.go +++ b/example_test.go @@ -29,7 +29,7 @@ const ( ) func Example() { - phoneCall := stateless.NewStateMachine(stateOffHook) + phoneCall := stateless.NewStateMachine[string, string](stateOffHook) phoneCall.SetTriggerParameters(triggerSetVolume, reflect.TypeOf(0)) phoneCall.SetTriggerParameters(triggerCallDialed, reflect.TypeOf("")) diff --git a/go.mod b/go.mod index c8c52d8..5a6b824 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,11 @@ module github.com/qmuntal/stateless -go 1.13 +go 1.18 require github.com/stretchr/testify v1.4.0 + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect +) diff --git a/graph.go b/graph.go index aa7b8a8..3f70819 100644 --- a/graph.go +++ b/graph.go @@ -8,10 +8,10 @@ import ( "unicode" ) -type graph struct { +type graph[S State, T Trigger] struct { } -func (g *graph) FormatStateMachine(sm *StateMachine) string { +func (g *graph[S, T]) FormatStateMachine(sm *StateMachine[S, T]) string { var sb strings.Builder sb.WriteString("digraph {\n\tcompound=true;\n\tnode [shape=Mrecord];\n\trankdir=\"LR\";\n\n") @@ -42,7 +42,7 @@ func (g *graph) FormatStateMachine(sm *StateMachine) string { return sb.String() } -func (g *graph) formatActions(sr *stateRepresentation) string { +func (g *graph[S, T]) formatActions(sr *stateRepresentation[S, T]) string { es := make([]string, 0, len(sr.EntryActions)+len(sr.ExitActions)+len(sr.ActivateActions)+len(sr.DeactivateActions)) for _, act := range sr.ActivateActions { es = append(es, fmt.Sprintf("activated / %s", esc(act.Description.String()))) @@ -61,7 +61,7 @@ func (g *graph) formatActions(sr *stateRepresentation) string { return strings.Join(es, `\n`) } -func (g *graph) formatOneState(sr *stateRepresentation, level int) string { +func (g *graph[S, T]) formatOneState(sr *stateRepresentation[S, T], level int) string { var indent string for i := 0; i < level; i++ { indent += "\t" @@ -96,7 +96,7 @@ func (g *graph) formatOneState(sr *stateRepresentation, level int) string { return sb.String() } -func (g *graph) getEntryActions(ab []actionBehaviour, t Trigger) []string { +func (g *graph[S, T]) getEntryActions(ab []actionBehaviour[S, T], t T) []string { var actions []string for _, ea := range ab { if ea.Trigger == nil || *ea.Trigger == t { @@ -106,7 +106,7 @@ func (g *graph) getEntryActions(ab []actionBehaviour, t Trigger) []string { return actions } -func getLeafState(sm *StateMachine, sr *stateRepresentation) *stateRepresentation { +func getLeafState[S State, T Trigger](sm *StateMachine[S, T], sr *stateRepresentation[S, T]) *stateRepresentation[S, T] { if sr.HasInitialState { s, ok := sm.stateConfig[sr.InitialTransitionTarget] if !ok { @@ -125,27 +125,27 @@ func getLeafState(sm *StateMachine, sr *stateRepresentation) *stateRepresentatio return sr } -func (g *graph) resolveTransition(sm *StateMachine, sr *stateRepresentation) (*stateRepresentation, string) { +func (g *graph[S, T]) resolveTransition(sm *StateMachine[S, T], sr *stateRepresentation[S, T]) (*stateRepresentation[S, T], string) { if anyLeaf := getLeafState(sm, sr); anyLeaf != nil && sr != anyLeaf { return anyLeaf, fmt.Sprintf("cluster_%s", str(sr.State)) } return sr, "" } -func (g *graph) formatAllStateTransitions(sm *StateMachine, sr *stateRepresentation) string { +func (g *graph[S, T]) formatAllStateTransitions(sm *StateMachine[S, T], sr *stateRepresentation[S, T]) string { var sb strings.Builder for _, triggers := range sr.TriggerBehaviours { for _, trigger := range triggers { switch t := trigger.(type) { - case *ignoredTriggerBehaviour: + case *ignoredTriggerBehaviour[T]: sb.WriteString(g.formatOneTransition(sm, sr.State, sr.State, t.Trigger, "", "", nil, t.Guard)) - case *reentryTriggerBehaviour: + case *reentryTriggerBehaviour[S, T]: actions := g.getEntryActions(sr.EntryActions, t.Trigger) sb.WriteString(g.formatOneTransition(sm, sr.State, t.Destination, t.Trigger, "", "", actions, t.Guard)) - case *internalTriggerBehaviour: + case *internalTriggerBehaviour[S, T]: actions := g.getEntryActions(sr.EntryActions, t.Trigger) sb.WriteString(g.formatOneTransition(sm, sr.State, sr.State, t.Trigger, "", "", actions, t.Guard)) - case *transitioningTriggerBehaviour: + case *transitioningTriggerBehaviour[S, T]: src := sm.stateConfig[sr.State] if src == nil { return "" @@ -158,14 +158,14 @@ func (g *graph) formatAllStateTransitions(sm *StateMachine, sr *stateRepresentat dest, lhead = g.resolveTransition(sm, dest) actions = g.getEntryActions(dest.EntryActions, t.Trigger) } - var destState State + var destState S if dest == nil { destState = t.Destination } else { destState = dest.State } sb.WriteString(g.formatOneTransition(sm, src.State, destState, t.Trigger, ltail, lhead, actions, t.Guard)) - case *dynamicTriggerBehaviour: + case *dynamicTriggerBehaviour[S, T]: // TODO: not supported yet } } @@ -173,7 +173,7 @@ func (g *graph) formatAllStateTransitions(sm *StateMachine, sr *stateRepresentat return sb.String() } -func (g *graph) formatOneTransition(sm *StateMachine, source, destination State, trigger Trigger, ltail, lhead string, actions []string, guards transitionGuard) string { +func (g *graph[S, T]) formatOneTransition(sm *StateMachine[S, T], source, destination S, trigger T, ltail, lhead string, actions []string, guards transitionGuard) string { var sb strings.Builder sb.WriteString(str(trigger)) if len(actions) > 0 { @@ -189,7 +189,7 @@ func (g *graph) formatOneTransition(sm *StateMachine, source, destination State, return g.formatOneLine(str(source), str(destination), sb.String(), lhead, ltail) } -func (g *graph) formatOneLine(fromNodeName, toNodeName, label, lhead, ltail string) string { +func (g *graph[S, T]) formatOneLine(fromNodeName, toNodeName, label, lhead, ltail string) string { var sb strings.Builder sb.WriteString(fmt.Sprintf("\t%s -> %s [label=\"%s\"", fromNodeName, toNodeName, label)) if lhead != "" { diff --git a/statemachine.go b/statemachine.go index 40574e7..ac3e8b4 100644 --- a/statemachine.go +++ b/statemachine.go @@ -10,10 +10,14 @@ import ( ) // State is used to to represent the possible machine states. -type State = interface{} +type State interface { + comparable +} // Trigger is used to represent the triggers that cause state transitions. -type Trigger = interface{} +type Trigger interface { + comparable +} // FiringMode enumerate the different modes used when Fire-ing a trigger. type FiringMode uint8 @@ -27,34 +31,34 @@ const ( ) // Transition describes a state transition. -type Transition struct { - Source State - Destination State - Trigger Trigger +type Transition[S State, T Trigger] struct { + Source S + Destination S + Trigger T isInitial bool } // IsReentry returns true if the transition is a re-entry, // i.e. the identity transition. -func (t *Transition) IsReentry() bool { +func (t *Transition[S, T]) IsReentry() bool { return t.Source == t.Destination } -type TransitionFunc = func(context.Context, Transition) +type TransitionFunc[S State, T Trigger] func(context.Context, Transition[S, T]) // UnhandledTriggerActionFunc defines a function that will be called when a trigger is not handled. -type UnhandledTriggerActionFunc = func(ctx context.Context, state State, trigger Trigger, unmetGuards []string) error +type UnhandledTriggerActionFunc[S State, T Trigger] func(ctx context.Context, state S, trigger T, unmetGuards []string) error // DefaultUnhandledTriggerAction is the default unhandled trigger action. -func DefaultUnhandledTriggerAction(_ context.Context, state State, trigger Trigger, unmetGuards []string) error { +func DefaultUnhandledTriggerAction[S State, T Trigger](_ context.Context, state S, trigger T, unmetGuards []string) error { if len(unmetGuards) != 0 { return fmt.Errorf("stateless: Trigger '%v' is valid for transition from state '%v' but a guard conditions are not met. Guard descriptions: '%v", trigger, state, unmetGuards) } return fmt.Errorf("stateless: No valid leaving transitions are permitted from state '%v' for trigger '%v', consider ignoring the trigger", state, trigger) } -func callEvents(events []TransitionFunc, ctx context.Context, transition Transition) { +func callEvents[S State, T Trigger](events []TransitionFunc[S, T], ctx context.Context, transition Transition[S, T]) { for _, e := range events { e(ctx, transition) } @@ -63,47 +67,49 @@ func callEvents(events []TransitionFunc, ctx context.Context, transition Transit // A StateMachine is an abstract machine that can be in exactly one of a finite number of states at any given time. // It is safe to use the StateMachine concurrently, but non of the callbacks (state manipulation, actions, events, ...) are guarded, // so it is up to the client to protect them against race conditions. -type StateMachine struct { +type StateMachine[S State, T Trigger] struct { // ops is accessed atomically so we put it at the beginning of the struct to achieve 64 bit alignment ops uint64 - stateConfig map[State]*stateRepresentation - triggerConfig map[Trigger]triggerWithParameters - stateAccessor func(context.Context) (State, error) - stateMutator func(context.Context, State) error - unhandledTriggerAction UnhandledTriggerActionFunc - onTransitioningEvents []TransitionFunc - onTransitionedEvents []TransitionFunc + stateConfig map[S]*stateRepresentation[S, T] + triggerConfig map[T]triggerWithParameters[T] + stateAccessor func(context.Context) (S, error) + stateMutator func(context.Context, S) error + unhandledTriggerAction UnhandledTriggerActionFunc[S, T] + onTransitioningEvents []TransitionFunc[S, T] + onTransitionedEvents []TransitionFunc[S, T] eventQueue list.List firingMode FiringMode firingMutex sync.Mutex } -func newStateMachine() *StateMachine { - return &StateMachine{ - stateConfig: make(map[State]*stateRepresentation), - triggerConfig: make(map[Trigger]triggerWithParameters), - unhandledTriggerAction: UnhandledTriggerActionFunc(DefaultUnhandledTriggerAction), +func newStateMachine[S State, T Trigger]() *StateMachine[S, T] { + return &StateMachine[S, T]{ + stateConfig: make(map[S]*stateRepresentation[S, T]), + triggerConfig: make(map[T]triggerWithParameters[T]), + unhandledTriggerAction: UnhandledTriggerActionFunc[S, T](DefaultUnhandledTriggerAction[S, T]), } } // NewStateMachine returns a queued state machine. -func NewStateMachine(initialState State) *StateMachine { - return NewStateMachineWithMode(initialState, FiringQueued) +func NewStateMachine[S State, T Trigger](initialState S) *StateMachine[S, T] { + return NewStateMachineWithMode[S, T](initialState, FiringQueued) +} + +type stateReference[S State] struct { + State S } // NewStateMachineWithMode returns a state machine with the desired firing mode -func NewStateMachineWithMode(initialState State, firingMode FiringMode) *StateMachine { +func NewStateMachineWithMode[S State, T Trigger](initialState S, firingMode FiringMode) *StateMachine[S, T] { var stateMutex sync.Mutex - sm := newStateMachine() - reference := &struct { - State State - }{State: initialState} - sm.stateAccessor = func(_ context.Context) (State, error) { + sm := newStateMachine[S, T]() + reference := stateReference[S]{State: initialState} + sm.stateAccessor = func(_ context.Context) (S, error) { stateMutex.Lock() defer stateMutex.Unlock() return reference.State, nil } - sm.stateMutator = func(_ context.Context, state State) error { + sm.stateMutator = func(_ context.Context, state S) error { stateMutex.Lock() defer stateMutex.Unlock() reference.State = state @@ -114,8 +120,8 @@ func NewStateMachineWithMode(initialState State, firingMode FiringMode) *StateMa } // NewStateMachineWithExternalStorage returns a state machine with external state storage. -func NewStateMachineWithExternalStorage(stateAccessor func(context.Context) (State, error), stateMutator func(context.Context, State) error, firingMode FiringMode) *StateMachine { - sm := newStateMachine() +func NewStateMachineWithExternalStorage[S State, T Trigger](stateAccessor func(context.Context) (S, error), stateMutator func(context.Context, S) error, firingMode FiringMode) *StateMachine[S, T] { + sm := newStateMachine[S, T]() sm.stateAccessor = stateAccessor sm.stateMutator = stateMutator sm.firingMode = firingMode @@ -124,12 +130,12 @@ func NewStateMachineWithExternalStorage(stateAccessor func(context.Context) (Sta // ToGraph returns the DOT representation of the state machine. // It is not guaranteed that the returned string will be the same in different executions. -func (sm *StateMachine) ToGraph() string { - return new(graph).FormatStateMachine(sm) +func (sm *StateMachine[S, T]) ToGraph() string { + return new(graph[S, T]).FormatStateMachine(sm) } // State returns the current state. -func (sm *StateMachine) State(ctx context.Context) (State, error) { +func (sm *StateMachine[S, T]) State(ctx context.Context) (S, error) { return sm.stateAccessor(ctx) } @@ -137,7 +143,7 @@ func (sm *StateMachine) State(ctx context.Context) (State, error) { // It is safe to use this method when used together with NewStateMachine // or when using NewStateMachineWithExternalStorage with an state accessor that // does not return an error. -func (sm *StateMachine) MustState() State { +func (sm *StateMachine[S, T]) MustState() S { st, err := sm.State(context.Background()) if err != nil { panic(err) @@ -146,12 +152,12 @@ func (sm *StateMachine) MustState() State { } // PermittedTriggers see PermittedTriggersCtx. -func (sm *StateMachine) PermittedTriggers(args ...interface{}) ([]Trigger, error) { +func (sm *StateMachine[S, T]) PermittedTriggers(args ...interface{}) ([]T, error) { return sm.PermittedTriggersCtx(context.Background(), args...) } // PermittedTriggersCtx returns the currently-permissible trigger values. -func (sm *StateMachine) PermittedTriggersCtx(ctx context.Context, args ...interface{}) ([]Trigger, error) { +func (sm *StateMachine[S, T]) PermittedTriggersCtx(ctx context.Context, args ...interface{}) ([]T, error) { sr, err := sm.currentState(ctx) if err != nil { return nil, err @@ -160,14 +166,14 @@ func (sm *StateMachine) PermittedTriggersCtx(ctx context.Context, args ...interf } // Activate see ActivateCtx. -func (sm *StateMachine) Activate() error { +func (sm *StateMachine[S, T]) Activate() error { return sm.ActivateCtx(context.Background()) } // ActivateCtx activates current state. Actions associated with activating the current state will be invoked. // The activation is idempotent and subsequent activation of the same current state // will not lead to re-execution of activation callbacks. -func (sm *StateMachine) ActivateCtx(ctx context.Context) error { +func (sm *StateMachine[S, T]) ActivateCtx(ctx context.Context) error { sr, err := sm.currentState(ctx) if err != nil { return err @@ -176,14 +182,14 @@ func (sm *StateMachine) ActivateCtx(ctx context.Context) error { } // Deactivate see DeactivateCtx. -func (sm *StateMachine) Deactivate() error { +func (sm *StateMachine[S, T]) Deactivate() error { return sm.DeactivateCtx(context.Background()) } // DeactivateCtx deactivates current state. Actions associated with deactivating the current state will be invoked. // The deactivation is idempotent and subsequent deactivation of the same current state // will not lead to re-execution of deactivation callbacks. -func (sm *StateMachine) DeactivateCtx(ctx context.Context) error { +func (sm *StateMachine[S, T]) DeactivateCtx(ctx context.Context) error { sr, err := sm.currentState(ctx) if err != nil { return err @@ -192,13 +198,13 @@ func (sm *StateMachine) DeactivateCtx(ctx context.Context) error { } // IsInState see IsInStateCtx. -func (sm *StateMachine) IsInState(state State) (bool, error) { +func (sm *StateMachine[S, T]) IsInState(state S) (bool, error) { return sm.IsInStateCtx(context.Background(), state) } // IsInStateCtx determine if the state machine is in the supplied state. // Returns true if the current state is equal to, or a substate of, the supplied state. -func (sm *StateMachine) IsInStateCtx(ctx context.Context, state State) (bool, error) { +func (sm *StateMachine[S, T]) IsInStateCtx(ctx context.Context, state S) (bool, error) { sr, err := sm.currentState(ctx) if err != nil { return false, err @@ -207,12 +213,12 @@ func (sm *StateMachine) IsInStateCtx(ctx context.Context, state State) (bool, er } // CanFire see CanFireCtx. -func (sm *StateMachine) CanFire(trigger Trigger, args ...interface{}) (bool, error) { +func (sm *StateMachine[S, T]) CanFire(trigger T, args ...interface{}) (bool, error) { return sm.CanFireCtx(context.Background(), trigger, args...) } // CanFireCtx returns true if the trigger can be fired in the current state. -func (sm *StateMachine) CanFireCtx(ctx context.Context, trigger Trigger, args ...interface{}) (bool, error) { +func (sm *StateMachine[S, T]) CanFireCtx(ctx context.Context, trigger T, args ...interface{}) (bool, error) { sr, err := sm.currentState(ctx) if err != nil { return false, err @@ -221,8 +227,8 @@ func (sm *StateMachine) CanFireCtx(ctx context.Context, trigger Trigger, args .. } // SetTriggerParameters specify the arguments that must be supplied when a specific trigger is fired. -func (sm *StateMachine) SetTriggerParameters(trigger Trigger, argumentTypes ...reflect.Type) { - config := triggerWithParameters{Trigger: trigger, ArgumentTypes: argumentTypes} +func (sm *StateMachine[S, T]) SetTriggerParameters(trigger T, argumentTypes ...reflect.Type) { + config := triggerWithParameters[T]{Trigger: trigger, ArgumentTypes: argumentTypes} if _, ok := sm.triggerConfig[config.Trigger]; ok { panic(fmt.Sprintf("stateless: Parameters for the trigger '%v' have already been configured.", trigger)) } @@ -230,7 +236,7 @@ func (sm *StateMachine) SetTriggerParameters(trigger Trigger, argumentTypes ...r } // Fire see FireCtx -func (sm *StateMachine) Fire(trigger Trigger, args ...interface{}) error { +func (sm *StateMachine[S, T]) Fire(trigger T, args ...interface{}) error { return sm.FireCtx(context.Background(), trigger, args...) } @@ -247,41 +253,41 @@ func (sm *StateMachine) Fire(trigger Trigger, args ...interface{}) error { // // The context is passed down to all actions and callbacks called within the scope of this method. // There is no context error checking, although it may be implemented in future releases. -func (sm *StateMachine) FireCtx(ctx context.Context, trigger Trigger, args ...interface{}) error { +func (sm *StateMachine[S, T]) FireCtx(ctx context.Context, trigger T, args ...interface{}) error { return sm.internalFire(ctx, trigger, args...) } // OnTransitioned registers a callback that will be invoked every time the state machine // successfully finishes a transitions from one state into another. -func (sm *StateMachine) OnTransitioned(fn ...TransitionFunc) { +func (sm *StateMachine[S, T]) OnTransitioned(fn ...TransitionFunc[S, T]) { sm.onTransitionedEvents = append(sm.onTransitionedEvents, fn...) } // OnTransitioning registers a callback that will be invoked every time the state machine // starts a transitions from one state into another. -func (sm *StateMachine) OnTransitioning(fn ...TransitionFunc) { +func (sm *StateMachine[S, T]) OnTransitioning(fn ...TransitionFunc[S, T]) { sm.onTransitioningEvents = append(sm.onTransitioningEvents, fn...) } // OnUnhandledTrigger override the default behaviour of returning an error when an unhandled trigger. -func (sm *StateMachine) OnUnhandledTrigger(fn UnhandledTriggerActionFunc) { +func (sm *StateMachine[S, T]) OnUnhandledTrigger(fn UnhandledTriggerActionFunc[S, T]) { sm.unhandledTriggerAction = fn } // Configure begin configuration of the entry/exit actions and allowed transitions // when the state machine is in a particular state. -func (sm *StateMachine) Configure(state State) *StateConfiguration { - return &StateConfiguration{sm: sm, sr: sm.stateRepresentation(state), lookup: sm.stateRepresentation} +func (sm *StateMachine[S, T]) Configure(state S) *StateConfiguration[S, T] { + return &StateConfiguration[S, T]{sm: sm, sr: sm.stateRepresentation(state), lookup: sm.stateRepresentation} } // Firing returns true when the state machine is processing a trigger. -func (sm *StateMachine) Firing() bool { +func (sm *StateMachine[S, T]) Firing() bool { return atomic.LoadUint64(&sm.ops) != 0 } // String returns a human-readable representation of the state machine. // It is not guaranteed that the order of the PermittedTriggers is the same in consecutive executions. -func (sm *StateMachine) String() string { +func (sm *StateMachine[S, T]) String() string { state, err := sm.State(context.Background()) if err != nil { return "" @@ -292,12 +298,12 @@ func (sm *StateMachine) String() string { return fmt.Sprintf("StateMachine {{ State = %v, PermittedTriggers = %v }}", state, triggers) } -func (sm *StateMachine) setState(ctx context.Context, state State) error { +func (sm *StateMachine[S, T]) setState(ctx context.Context, state S) error { return sm.stateMutator(ctx, state) } -func (sm *StateMachine) currentState(ctx context.Context) (sr *stateRepresentation, err error) { - var state State +func (sm *StateMachine[S, T]) currentState(ctx context.Context) (sr *stateRepresentation[S, T], err error) { + var state S state, err = sm.State(ctx) if err == nil { sr = sm.stateRepresentation(state) @@ -305,16 +311,16 @@ func (sm *StateMachine) currentState(ctx context.Context) (sr *stateRepresentati return } -func (sm *StateMachine) stateRepresentation(state State) (sr *stateRepresentation) { +func (sm *StateMachine[S, T]) stateRepresentation(state S) (sr *stateRepresentation[S, T]) { var ok bool if sr, ok = sm.stateConfig[state]; !ok { - sr = newstateRepresentation(state) + sr = newstateRepresentation[S, T](state) sm.stateConfig[state] = sr } return } -func (sm *StateMachine) internalFire(ctx context.Context, trigger Trigger, args ...interface{}) error { +func (sm *StateMachine[S, T]) internalFire(ctx context.Context, trigger T, args ...interface{}) error { switch sm.firingMode { case FiringImmediate: return sm.internalFireOne(ctx, trigger, args...) @@ -325,15 +331,15 @@ func (sm *StateMachine) internalFire(ctx context.Context, trigger Trigger, args } } -type queuedTrigger struct { +type queuedTrigger[T Trigger] struct { Context context.Context - Trigger Trigger + Trigger T Args []interface{} } -func (sm *StateMachine) internalFireQueued(ctx context.Context, trigger Trigger, args ...interface{}) error { +func (sm *StateMachine[S, T]) internalFireQueued(ctx context.Context, trigger T, args ...interface{}) error { sm.firingMutex.Lock() - sm.eventQueue.PushBack(queuedTrigger{Context: ctx, Trigger: trigger, Args: args}) + sm.eventQueue.PushBack(queuedTrigger[T]{Context: ctx, Trigger: trigger, Args: args}) sm.firingMutex.Unlock() if sm.Firing() { return nil @@ -346,7 +352,7 @@ func (sm *StateMachine) internalFireQueued(ctx context.Context, trigger Trigger, sm.firingMutex.Unlock() break } - et := sm.eventQueue.Remove(e).(queuedTrigger) + et := sm.eventQueue.Remove(e).(queuedTrigger[T]) sm.firingMutex.Unlock() if err := sm.internalFireOne(et.Context, et.Trigger, et.Args...); err != nil { return err @@ -355,11 +361,11 @@ func (sm *StateMachine) internalFireQueued(ctx context.Context, trigger Trigger, return nil } -func (sm *StateMachine) internalFireOne(ctx context.Context, trigger Trigger, args ...interface{}) (err error) { +func (sm *StateMachine[S, T]) internalFireOne(ctx context.Context, trigger T, args ...interface{}) (err error) { atomic.AddUint64(&sm.ops, 1) defer atomic.AddUint64(&sm.ops, ^uint64(0)) var ( - config triggerWithParameters + config triggerWithParameters[T] ok bool ) if config, ok = sm.triggerConfig[trigger]; ok { @@ -370,45 +376,45 @@ func (sm *StateMachine) internalFireOne(ctx context.Context, trigger Trigger, ar return } representativeState := sm.stateRepresentation(source) - var result triggerBehaviourResult + var result triggerBehaviourResult[T] if result, ok = representativeState.FindHandler(ctx, trigger, args...); !ok { return sm.unhandledTriggerAction(ctx, representativeState.State, trigger, result.UnmetGuardConditions) } switch t := result.Handler.(type) { - case *ignoredTriggerBehaviour: + case *ignoredTriggerBehaviour[T]: // ignored - case *reentryTriggerBehaviour: - transition := Transition{Source: source, Destination: t.Destination, Trigger: trigger} + case *reentryTriggerBehaviour[S, T]: + transition := Transition[S, T]{Source: source, Destination: t.Destination, Trigger: trigger} err = sm.handleReentryTrigger(ctx, representativeState, transition, args...) - case *dynamicTriggerBehaviour: + case *dynamicTriggerBehaviour[S, T]: destination, ok := t.ResultsInTransitionFrom(ctx, source, args...) if !ok { err = fmt.Errorf("stateless: Dynamic handler for trigger %v in state %v has failed", trigger, source) } else { - transition := Transition{Source: source, Destination: destination, Trigger: trigger} + transition := Transition[S, T]{Source: source, Destination: destination, Trigger: trigger} err = sm.handleTransitioningTrigger(ctx, representativeState, transition, args...) } - case *transitioningTriggerBehaviour: - transition := Transition{Source: source, Destination: t.Destination, Trigger: trigger} + case *transitioningTriggerBehaviour[S, T]: + transition := Transition[S, T]{Source: source, Destination: t.Destination, Trigger: trigger} err = sm.handleTransitioningTrigger(ctx, representativeState, transition, args...) - case *internalTriggerBehaviour: - var sr *stateRepresentation + case *internalTriggerBehaviour[S, T]: + var sr *stateRepresentation[S, T] sr, err = sm.currentState(ctx) if err == nil { - transition := Transition{Source: source, Destination: source, Trigger: trigger} + transition := Transition[S, T]{Source: source, Destination: source, Trigger: trigger} err = sr.InternalAction(ctx, transition, args...) } } return } -func (sm *StateMachine) handleReentryTrigger(ctx context.Context, sr *stateRepresentation, transition Transition, args ...interface{}) error { +func (sm *StateMachine[S, T]) handleReentryTrigger(ctx context.Context, sr *stateRepresentation[S, T], transition Transition[S, T], args ...interface{}) error { if err := sr.Exit(ctx, transition, args...); err != nil { return err } newSr := sm.stateRepresentation(transition.Destination) if !transition.IsReentry() { - transition = Transition{Source: transition.Destination, Destination: transition.Destination, Trigger: transition.Trigger} + transition = Transition[S, T]{Source: transition.Destination, Destination: transition.Destination, Trigger: transition.Trigger} if err := newSr.Exit(ctx, transition, args...); err != nil { return err } @@ -425,7 +431,7 @@ func (sm *StateMachine) handleReentryTrigger(ctx context.Context, sr *stateRepre return nil } -func (sm *StateMachine) handleTransitioningTrigger(ctx context.Context, sr *stateRepresentation, transition Transition, args ...interface{}) error { +func (sm *StateMachine[S, T]) handleTransitioningTrigger(ctx context.Context, sr *stateRepresentation[S, T], transition Transition[S, T], args ...interface{}) error { if err := sr.Exit(ctx, transition, args...); err != nil { return err } @@ -444,11 +450,11 @@ func (sm *StateMachine) handleTransitioningTrigger(ctx context.Context, sr *stat return err } } - callEvents(sm.onTransitionedEvents, ctx, Transition{transition.Source, rep.State, transition.Trigger, false}) + callEvents(sm.onTransitionedEvents, ctx, Transition[S, T]{transition.Source, rep.State, transition.Trigger, false}) return nil } -func (sm *StateMachine) enterState(ctx context.Context, sr *stateRepresentation, transition Transition, args ...interface{}) (*stateRepresentation, error) { +func (sm *StateMachine[S, T]) enterState(ctx context.Context, sr *stateRepresentation[S, T], transition Transition[S, T], args ...interface{}) (*stateRepresentation[S, T], error) { // Enter the new state err := sr.Enter(ctx, transition, args...) if err != nil { @@ -468,9 +474,9 @@ func (sm *StateMachine) enterState(ctx context.Context, sr *stateRepresentation, if !isValidForInitialState { panic(fmt.Sprintf("stateless: The target (%v) for the initial transition is not a substate.", sr.InitialTransitionTarget)) } - initialTranslation := Transition{Source: transition.Source, Destination: sr.InitialTransitionTarget, Trigger: transition.Trigger, isInitial: true} + initialTranslation := Transition[S, T]{Source: transition.Source, Destination: sr.InitialTransitionTarget, Trigger: transition.Trigger, isInitial: true} sr = sm.stateRepresentation(sr.InitialTransitionTarget) - callEvents(sm.onTransitioningEvents, ctx, Transition{transition.Destination, initialTranslation.Destination, transition.Trigger, false}) + callEvents(sm.onTransitioningEvents, ctx, Transition[S, T]{transition.Destination, initialTranslation.Destination, transition.Trigger, false}) sr, err = sm.enterState(ctx, sr, initialTranslation, args...) } return sr, err diff --git a/states.go b/states.go index 6ba3d8f..7b535b9 100644 --- a/states.go +++ b/states.go @@ -5,13 +5,13 @@ import ( "fmt" ) -type actionBehaviour struct { +type actionBehaviour[S State, T Trigger] struct { Action ActionFunc Description invocationInfo - Trigger *Trigger + Trigger *T } -func (a actionBehaviour) Execute(ctx context.Context, transition Transition, args ...interface{}) (err error) { +func (a actionBehaviour[S, T]) Execute(ctx context.Context, transition Transition[S, T], args ...interface{}) (err error) { if a.Trigger == nil || *a.Trigger == transition.Trigger { ctx = withTransition(ctx, transition) err = a.Action(ctx, args...) @@ -28,41 +28,41 @@ func (a actionBehaviourSteady) Execute(ctx context.Context) error { return a.Action(ctx) } -type stateRepresentation struct { - State State - InitialTransitionTarget State - Superstate *stateRepresentation - EntryActions []actionBehaviour - ExitActions []actionBehaviour +type stateRepresentation[S State, T Trigger] struct { + State S + InitialTransitionTarget S + Superstate *stateRepresentation[S, T] + EntryActions []actionBehaviour[S, T] + ExitActions []actionBehaviour[S, T] ActivateActions []actionBehaviourSteady DeactivateActions []actionBehaviourSteady - Substates []*stateRepresentation - TriggerBehaviours map[Trigger][]triggerBehaviour + Substates []*stateRepresentation[S, T] + TriggerBehaviours map[T][]triggerBehaviour[T] HasInitialState bool } -func newstateRepresentation(state State) *stateRepresentation { - return &stateRepresentation{ +func newstateRepresentation[S State, T Trigger](state S) *stateRepresentation[S, T] { + return &stateRepresentation[S, T]{ State: state, - TriggerBehaviours: make(map[Trigger][]triggerBehaviour), + TriggerBehaviours: make(map[T][]triggerBehaviour[T]), } } -func (sr *stateRepresentation) SetInitialTransition(state State) { +func (sr *stateRepresentation[S, T]) SetInitialTransition(state S) { sr.InitialTransitionTarget = state sr.HasInitialState = true } -func (sr *stateRepresentation) state() State { +func (sr *stateRepresentation[S, T]) state() S { return sr.State } -func (sr *stateRepresentation) CanHandle(ctx context.Context, trigger Trigger, args ...interface{}) (ok bool) { +func (sr *stateRepresentation[S, T]) CanHandle(ctx context.Context, trigger T, args ...interface{}) (ok bool) { _, ok = sr.FindHandler(ctx, trigger, args...) return } -func (sr *stateRepresentation) FindHandler(ctx context.Context, trigger Trigger, args ...interface{}) (handler triggerBehaviourResult, ok bool) { +func (sr *stateRepresentation[S, T]) FindHandler(ctx context.Context, trigger T, args ...interface{}) (handler triggerBehaviourResult[T], ok bool) { handler, ok = sr.findHandler(ctx, trigger, args...) if ok || sr.Superstate == nil { return @@ -71,22 +71,22 @@ func (sr *stateRepresentation) FindHandler(ctx context.Context, trigger Trigger, return } -func (sr *stateRepresentation) findHandler(ctx context.Context, trigger Trigger, args ...interface{}) (result triggerBehaviourResult, ok bool) { +func (sr *stateRepresentation[S, T]) findHandler(ctx context.Context, trigger T, args ...interface{}) (result triggerBehaviourResult[T], ok bool) { var ( - possibleBehaviours []triggerBehaviour + possibleBehaviours []triggerBehaviour[T] ) if possibleBehaviours, ok = sr.TriggerBehaviours[trigger]; !ok { return } - allResults := make([]triggerBehaviourResult, 0, len(possibleBehaviours)) + allResults := make([]triggerBehaviourResult[T], 0, len(possibleBehaviours)) for _, behaviour := range possibleBehaviours { - allResults = append(allResults, triggerBehaviourResult{ + allResults = append(allResults, triggerBehaviourResult[T]{ Handler: behaviour, UnmetGuardConditions: behaviour.UnmetGuardConditions(ctx, args...), }) } - metResults := make([]triggerBehaviourResult, 0, len(allResults)) - unmetResults := make([]triggerBehaviourResult, 0, len(allResults)) + metResults := make([]triggerBehaviourResult[T], 0, len(allResults)) + unmetResults := make([]triggerBehaviourResult[T], 0, len(allResults)) for _, result := range allResults { if len(result.UnmetGuardConditions) == 0 { metResults = append(metResults, result) @@ -105,7 +105,7 @@ func (sr *stateRepresentation) findHandler(ctx context.Context, trigger Trigger, return } -func (sr *stateRepresentation) Activate(ctx context.Context) error { +func (sr *stateRepresentation[S, T]) Activate(ctx context.Context) error { if sr.Superstate != nil { if err := sr.Superstate.Activate(ctx); err != nil { return err @@ -114,7 +114,7 @@ func (sr *stateRepresentation) Activate(ctx context.Context) error { return sr.executeActivationActions(ctx) } -func (sr *stateRepresentation) Deactivate(ctx context.Context) error { +func (sr *stateRepresentation[S, T]) Deactivate(ctx context.Context) error { if err := sr.executeDeactivationActions(ctx); err != nil { return err } @@ -124,7 +124,7 @@ func (sr *stateRepresentation) Deactivate(ctx context.Context) error { return nil } -func (sr *stateRepresentation) Enter(ctx context.Context, transition Transition, args ...interface{}) error { +func (sr *stateRepresentation[S, T]) Enter(ctx context.Context, transition Transition[S, T], args ...interface{}) error { if transition.IsReentry() { return sr.executeEntryActions(ctx, transition, args...) } @@ -139,7 +139,7 @@ func (sr *stateRepresentation) Enter(ctx context.Context, transition Transition, return sr.executeEntryActions(ctx, transition, args...) } -func (sr *stateRepresentation) Exit(ctx context.Context, transition Transition, args ...interface{}) (err error) { +func (sr *stateRepresentation[S, T]) Exit(ctx context.Context, transition Transition[S, T], args ...interface{}) (err error) { isReentry := transition.IsReentry() if !isReentry && sr.IncludeState(transition.Destination) { return @@ -162,13 +162,13 @@ func (sr *stateRepresentation) Exit(ctx context.Context, transition Transition, return } -func (sr *stateRepresentation) InternalAction(ctx context.Context, transition Transition, args ...interface{}) error { - var internalTransition *internalTriggerBehaviour - var stateRep *stateRepresentation = sr +func (sr *stateRepresentation[S, T]) InternalAction(ctx context.Context, transition Transition[S, T], args ...interface{}) error { + var internalTransition *internalTriggerBehaviour[S, T] + var stateRep *stateRepresentation[S, T] = sr for stateRep != nil { if result, ok := stateRep.findHandler(ctx, transition.Trigger, args...); ok { switch t := result.Handler.(type) { - case *internalTriggerBehaviour: + case *internalTriggerBehaviour[S, T]: internalTransition = t } break @@ -181,7 +181,7 @@ func (sr *stateRepresentation) InternalAction(ctx context.Context, transition Tr return internalTransition.Execute(ctx, transition, args...) } -func (sr *stateRepresentation) IncludeState(state State) bool { +func (sr *stateRepresentation[S, T]) IncludeState(state S) bool { if state == sr.State { return true } @@ -193,7 +193,7 @@ func (sr *stateRepresentation) IncludeState(state State) bool { return false } -func (sr *stateRepresentation) IsIncludedInState(state State) bool { +func (sr *stateRepresentation[S, T]) IsIncludedInState(state S) bool { if state == sr.State { return true } @@ -203,13 +203,13 @@ func (sr *stateRepresentation) IsIncludedInState(state State) bool { return false } -func (sr *stateRepresentation) AddTriggerBehaviour(tb triggerBehaviour) { +func (sr *stateRepresentation[S, T]) AddTriggerBehaviour(tb triggerBehaviour[T]) { trigger := tb.GetTrigger() sr.TriggerBehaviours[trigger] = append(sr.TriggerBehaviours[trigger], tb) } -func (sr *stateRepresentation) PermittedTriggers(ctx context.Context, args ...interface{}) (triggers []Trigger) { +func (sr *stateRepresentation[S, T]) PermittedTriggers(ctx context.Context, args ...interface{}) (triggers []T) { for key, value := range sr.TriggerBehaviours { for _, tb := range value { if len(tb.UnmetGuardConditions(ctx, args...)) == 0 { @@ -221,7 +221,7 @@ func (sr *stateRepresentation) PermittedTriggers(ctx context.Context, args ...in if sr.Superstate != nil { triggers = append(triggers, sr.Superstate.PermittedTriggers(ctx, args...)...) // remove duplicated - seen := make(map[Trigger]struct{}, len(triggers)) + seen := make(map[T]struct{}, len(triggers)) j := 0 for _, v := range triggers { if _, ok := seen[v]; ok { @@ -236,7 +236,7 @@ func (sr *stateRepresentation) PermittedTriggers(ctx context.Context, args ...in return } -func (sr *stateRepresentation) executeActivationActions(ctx context.Context) error { +func (sr *stateRepresentation[S, T]) executeActivationActions(ctx context.Context) error { for _, a := range sr.ActivateActions { if err := a.Execute(ctx); err != nil { return err @@ -245,7 +245,7 @@ func (sr *stateRepresentation) executeActivationActions(ctx context.Context) err return nil } -func (sr *stateRepresentation) executeDeactivationActions(ctx context.Context) error { +func (sr *stateRepresentation[S, T]) executeDeactivationActions(ctx context.Context) error { for _, a := range sr.DeactivateActions { if err := a.Execute(ctx); err != nil { return err @@ -254,7 +254,7 @@ func (sr *stateRepresentation) executeDeactivationActions(ctx context.Context) e return nil } -func (sr *stateRepresentation) executeEntryActions(ctx context.Context, transition Transition, args ...interface{}) error { +func (sr *stateRepresentation[S, T]) executeEntryActions(ctx context.Context, transition Transition[S, T], args ...interface{}) error { for _, a := range sr.EntryActions { if err := a.Execute(ctx, transition, args...); err != nil { return err @@ -263,7 +263,7 @@ func (sr *stateRepresentation) executeEntryActions(ctx context.Context, transiti return nil } -func (sr *stateRepresentation) executeExitActions(ctx context.Context, transition Transition, args ...interface{}) error { +func (sr *stateRepresentation[S, T]) executeExitActions(ctx context.Context, transition Transition[S, T], args ...interface{}) error { for _, a := range sr.ExitActions { if err := a.Execute(ctx, transition, args...); err != nil { return err diff --git a/states_test.go b/states_test.go index e338795..1f901e8 100644 --- a/states_test.go +++ b/states_test.go @@ -8,64 +8,64 @@ import ( "github.com/stretchr/testify/assert" ) -func createSuperSubstatePair() (*stateRepresentation, *stateRepresentation) { - super := newstateRepresentation(stateA) - sub := newstateRepresentation(stateB) +func createSuperSubstatePair() (*stateRepresentation[string, string], *stateRepresentation[string, string]) { + super := newstateRepresentation[string, string](stateA) + sub := newstateRepresentation[string, string](stateB) super.Substates = append(super.Substates, sub) sub.Superstate = super return super, sub } func Test_stateRepresentation_Includes_SameState(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) assert.True(t, sr.IncludeState(stateB)) } func Test_stateRepresentation_Includes_Substate(t *testing.T) { - sr := newstateRepresentation(stateB) - sr.Substates = append(sr.Substates, newstateRepresentation(stateC)) + sr := newstateRepresentation[string, string](stateB) + sr.Substates = append(sr.Substates, newstateRepresentation[string, string](stateC)) assert.True(t, sr.IncludeState(stateC)) } func Test_stateRepresentation_Includes_UnrelatedState(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) assert.False(t, sr.IncludeState(stateC)) } func Test_stateRepresentation_Includes_Superstate(t *testing.T) { - sr := newstateRepresentation(stateB) - sr.Superstate = newstateRepresentation(stateC) + sr := newstateRepresentation[string, string](stateB) + sr.Superstate = newstateRepresentation[string, string](stateC) assert.False(t, sr.IncludeState(stateC)) } func Test_stateRepresentation_IsIncludedInState_SameState(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) assert.True(t, sr.IsIncludedInState(stateB)) } func Test_stateRepresentation_IsIncludedInState_Substate(t *testing.T) { - sr := newstateRepresentation(stateB) - sr.Substates = append(sr.Substates, newstateRepresentation(stateC)) + sr := newstateRepresentation[string, string](stateB) + sr.Substates = append(sr.Substates, newstateRepresentation[string, string](stateC)) assert.False(t, sr.IsIncludedInState(stateC)) } func Test_stateRepresentation_IsIncludedInState_UnrelatedState(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) assert.False(t, sr.IsIncludedInState(stateC)) } func Test_stateRepresentation_IsIncludedInState_Superstate(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) assert.False(t, sr.IsIncludedInState(stateC)) } func Test_stateRepresentation_CanHandle_TransitionExists_TriggerCannotBeFired(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) assert.False(t, sr.CanHandle(context.Background(), triggerX)) } func Test_stateRepresentation_CanHandle_TransitionDoesNotExist_TriggerCanBeFired(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) sr.AddTriggerBehaviour(&ignoredTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{Trigger: triggerX}}) assert.True(t, sr.CanHandle(context.Background(), triggerX)) } @@ -77,7 +77,7 @@ func Test_stateRepresentation_CanHandle_TransitionExistsInSupersate_TriggerCanBe } func Test_stateRepresentation_CanHandle_TransitionUnmetGuardConditions_TriggerCannotBeFired(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) sr.AddTriggerBehaviour(&transitioningTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{ Trigger: triggerX, Guard: newtransitionGuard(func(_ context.Context, _ ...interface{}) bool { @@ -90,7 +90,7 @@ func Test_stateRepresentation_CanHandle_TransitionUnmetGuardConditions_TriggerCa } func Test_stateRepresentation_CanHandle_TransitionGuardConditionsMet_TriggerCanBeFired(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) sr.AddTriggerBehaviour(&transitioningTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{ Trigger: triggerX, Guard: newtransitionGuard(func(_ context.Context, _ ...interface{}) bool { @@ -140,7 +140,7 @@ func Test_stateRepresentation_FindHandler_TransitionExistSuperstateMetGuardCondi } func Test_stateRepresentation_Enter_EnteringActionsExecuted(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} var actualTransition Transition sr.EntryActions = append(sr.EntryActions, actionBehaviour{ @@ -155,7 +155,7 @@ func Test_stateRepresentation_Enter_EnteringActionsExecuted(t *testing.T) { } func Test_stateRepresentation_Enter_EnteringActionsExecuted_Error(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} var actualTransition Transition sr.EntryActions = append(sr.EntryActions, actionBehaviour{ @@ -169,7 +169,7 @@ func Test_stateRepresentation_Enter_EnteringActionsExecuted_Error(t *testing.T) } func Test_stateRepresentation_Enter_LeavingActionsNotExecuted(t *testing.T) { - sr := newstateRepresentation(stateA) + sr := newstateRepresentation[string, string](stateA) transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} var actualTransition Transition sr.ExitActions = append(sr.ExitActions, actionBehaviour{ @@ -226,7 +226,7 @@ func Test_stateRepresentation_Enter_Substate_SuperEntryActionsExecuted(t *testin func Test_stateRepresentation_Enter_ActionsExecuteInOrder(t *testing.T) { var actual []int - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) sr.EntryActions = append(sr.EntryActions, actionBehaviour{ Action: func(_ context.Context, _ ...interface{}) error { actual = append(actual, 0) @@ -269,7 +269,7 @@ func Test_stateRepresentation_Enter_Substate_SuperstateEntryActionsExecuteBefore } func Test_stateRepresentation_Exit_EnteringActionsNotExecuted(t *testing.T) { - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} var actualTransition Transition sr.EntryActions = append(sr.EntryActions, actionBehaviour{ @@ -283,7 +283,7 @@ func Test_stateRepresentation_Exit_EnteringActionsNotExecuted(t *testing.T) { } func Test_stateRepresentation_Exit_LeavingActionsExecuted(t *testing.T) { - sr := newstateRepresentation(stateA) + sr := newstateRepresentation[string, string](stateA) transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} var actualTransition Transition sr.ExitActions = append(sr.ExitActions, actionBehaviour{ @@ -298,7 +298,7 @@ func Test_stateRepresentation_Exit_LeavingActionsExecuted(t *testing.T) { } func Test_stateRepresentation_Exit_LeavingActionsExecuted_Error(t *testing.T) { - sr := newstateRepresentation(stateA) + sr := newstateRepresentation[string, string](stateA) transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} var actualTransition Transition sr.ExitActions = append(sr.ExitActions, actionBehaviour{ @@ -327,9 +327,9 @@ func Test_stateRepresentation_Exit_FromSubToSuperstate_SubstateExitActionsExecut func Test_stateRepresentation_Exit_FromSubToOther_SuperstateExitActionsExecuted(t *testing.T) { super, sub := createSuperSubstatePair() - supersuper := newstateRepresentation(stateC) + supersuper := newstateRepresentation[string, string](stateC) super.Superstate = supersuper - supersuper.Superstate = newstateRepresentation(stateD) + supersuper.Superstate = newstateRepresentation[string, string](stateD) executed := false super.ExitActions = append(super.ExitActions, actionBehaviour{ Action: func(_ context.Context, _ ...interface{}) error { @@ -372,7 +372,7 @@ func Test_stateRepresentation_Exit_Substate_SuperExitActionsExecuted(t *testing. func Test_stateRepresentation_Exit_ActionsExecuteInOrder(t *testing.T) { var actual []int - sr := newstateRepresentation(stateB) + sr := newstateRepresentation[string, string](stateB) sr.ExitActions = append(sr.ExitActions, actionBehaviour{ Action: func(_ context.Context, _ ...interface{}) error { actual = append(actual, 0) diff --git a/triggers.go b/triggers.go index 666851b..082502f 100644 --- a/triggers.go +++ b/triggers.go @@ -71,49 +71,49 @@ func (t transitionGuard) UnmetGuardConditions(ctx context.Context, args ...inter return unmet } -type triggerBehaviour interface { +type triggerBehaviour[T Trigger] interface { GuardConditionMet(context.Context, ...interface{}) bool UnmetGuardConditions(context.Context, ...interface{}) []string - GetTrigger() Trigger + GetTrigger() T } -type baseTriggerBehaviour struct { +type baseTriggerBehaviour[T Trigger] struct { Guard transitionGuard - Trigger Trigger + Trigger T } -func (t *baseTriggerBehaviour) GetTrigger() Trigger { +func (t *baseTriggerBehaviour[T]) GetTrigger() T { return t.Trigger } -func (t *baseTriggerBehaviour) GuardConditionMet(ctx context.Context, args ...interface{}) bool { +func (t *baseTriggerBehaviour[T]) GuardConditionMet(ctx context.Context, args ...interface{}) bool { return t.Guard.GuardConditionMet(ctx, args...) } -func (t *baseTriggerBehaviour) UnmetGuardConditions(ctx context.Context, args ...interface{}) []string { +func (t *baseTriggerBehaviour[T]) UnmetGuardConditions(ctx context.Context, args ...interface{}) []string { return t.Guard.UnmetGuardConditions(ctx, args...) } -type ignoredTriggerBehaviour struct { - baseTriggerBehaviour +type ignoredTriggerBehaviour[T Trigger] struct { + baseTriggerBehaviour[T] } -type reentryTriggerBehaviour struct { - baseTriggerBehaviour - Destination State +type reentryTriggerBehaviour[S State, T Trigger] struct { + baseTriggerBehaviour[T] + Destination S } -type transitioningTriggerBehaviour struct { - baseTriggerBehaviour - Destination State +type transitioningTriggerBehaviour[S State, T Trigger] struct { + baseTriggerBehaviour[T] + Destination S } -type dynamicTriggerBehaviour struct { - baseTriggerBehaviour - Destination func(context.Context, ...interface{}) (State, error) +type dynamicTriggerBehaviour[S State, T Trigger] struct { + baseTriggerBehaviour[T] + Destination func(context.Context, ...interface{}) (S, error) } -func (t *dynamicTriggerBehaviour) ResultsInTransitionFrom(ctx context.Context, _ State, args ...interface{}) (st State, ok bool) { +func (t *dynamicTriggerBehaviour[S, T]) ResultsInTransitionFrom(ctx context.Context, _ S, args ...interface{}) (st S, ok bool) { var err error st, err = t.Destination(ctx, args...) if err == nil { @@ -122,28 +122,28 @@ func (t *dynamicTriggerBehaviour) ResultsInTransitionFrom(ctx context.Context, _ return } -type internalTriggerBehaviour struct { - baseTriggerBehaviour +type internalTriggerBehaviour[S State, T Trigger] struct { + baseTriggerBehaviour[T] Action ActionFunc } -func (t *internalTriggerBehaviour) Execute(ctx context.Context, transition Transition, args ...interface{}) error { +func (t *internalTriggerBehaviour[S, T]) Execute(ctx context.Context, transition Transition[S, T], args ...interface{}) error { ctx = withTransition(ctx, transition) return t.Action(ctx, args...) } -type triggerBehaviourResult struct { - Handler triggerBehaviour +type triggerBehaviourResult[T Trigger] struct { + Handler triggerBehaviour[T] UnmetGuardConditions []string } // triggerWithParameters associates configured parameters with an underlying trigger value. -type triggerWithParameters struct { - Trigger Trigger +type triggerWithParameters[T Trigger] struct { + Trigger T ArgumentTypes []reflect.Type } -func (t triggerWithParameters) validateParameters(args ...interface{}) { +func (t triggerWithParameters[T]) validateParameters(args ...interface{}) { if len(args) != len(t.ArgumentTypes) { panic(fmt.Sprintf("stateless: Too many parameters have been supplied. Expecting '%d' but got '%d'.", len(t.ArgumentTypes), len(args))) } From 176461998bb60c4968c6516a595472522f2be228 Mon Sep 17 00:00:00 2001 From: "quim.muntal.diaz" Date: Mon, 13 Sep 2021 08:33:41 +0200 Subject: [PATCH 2/5] add type params to generic structs --- statemachine_test.go | 248 +++++++++++++++++++++---------------------- states_test.go | 100 ++++++++--------- 2 files changed, 174 insertions(+), 174 deletions(-) diff --git a/statemachine_test.go b/statemachine_test.go index 44e2259..f16f9a4 100644 --- a/statemachine_test.go +++ b/statemachine_test.go @@ -25,11 +25,11 @@ const ( func TestTransition_IsReentry(t *testing.T) { tests := []struct { name string - t *Transition + t *Transition[string, string] want bool }{ - {"TransitionIsNotChange", &Transition{"1", "1", "0", false}, true}, - {"TransitionIsChange", &Transition{"1", "2", "0", false}, false}, + {"TransitionIsNotChange", &Transition[string, string]{"1", "1", "0", false}, true}, + {"TransitionIsChange", &Transition[string, string]{"1", "2", "0", false}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -41,15 +41,15 @@ func TestTransition_IsReentry(t *testing.T) { } func TestStateMachine_NewStateMachine(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) assert.Equal(t, stateA, sm.MustState()) } func TestStateMachine_NewStateMachineWithExternalStorage(t *testing.T) { - var state State = stateB - sm := NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { + state := stateB + sm := NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { return state, nil - }, func(_ context.Context, s State) error { + }, func(_ context.Context, s string) error { state = s return nil }, FiringImmediate) @@ -62,7 +62,7 @@ func TestStateMachine_NewStateMachineWithExternalStorage(t *testing.T) { } func TestStateMachine_Configure_SubstateIsIncludedInCurrentState(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB).SubstateOf(stateC) ok, _ := sm.IsInState(stateC) @@ -71,7 +71,7 @@ func TestStateMachine_Configure_SubstateIsIncludedInCurrentState(t *testing.T) { } func TestStateMachine_Configure_InSubstate_TriggerIgnoredInSuperstate_RemainsInSubstate(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB).SubstateOf(stateC) sm.Configure(stateC).Ignore(triggerX) sm.Fire(triggerX) @@ -80,7 +80,7 @@ func TestStateMachine_Configure_InSubstate_TriggerIgnoredInSuperstate_RemainsInS } func TestStateMachine_CanFire(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB).Permit(triggerX, stateA) okX, _ := sm.CanFire(triggerX) okY, _ := sm.CanFire(triggerY) @@ -89,9 +89,9 @@ func TestStateMachine_CanFire(t *testing.T) { } func TestStateMachine_CanFire_StatusError(t *testing.T) { - sm := NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { - return nil, errors.New("status error") - }, func(_ context.Context, s State) error { return nil }, FiringImmediate) + sm := NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { + return "", errors.New("status error") + }, func(_ context.Context, s string) error { return nil }, FiringImmediate) sm.Configure(stateB).Permit(triggerX, stateA) @@ -101,9 +101,9 @@ func TestStateMachine_CanFire_StatusError(t *testing.T) { } func TestStateMachine_IsInState_StatusError(t *testing.T) { - sm := NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { - return nil, errors.New("status error") - }, func(_ context.Context, s State) error { return nil }, FiringImmediate) + sm := NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { + return "", errors.New("status error") + }, func(_ context.Context, s string) error { return nil }, FiringImmediate) ok, err := sm.IsInState(stateA) assert.False(t, ok) @@ -111,41 +111,41 @@ func TestStateMachine_IsInState_StatusError(t *testing.T) { } func TestStateMachine_Activate_StatusError(t *testing.T) { - sm := NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { - return nil, errors.New("status error") - }, func(_ context.Context, s State) error { return nil }, FiringImmediate) + sm := NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { + return "", errors.New("status error") + }, func(_ context.Context, s string) error { return nil }, FiringImmediate) assert.EqualError(t, sm.Activate(), "status error") assert.EqualError(t, sm.Deactivate(), "status error") } func TestStateMachine_PermittedTriggers_StatusError(t *testing.T) { - sm := NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { - return nil, errors.New("status error") - }, func(_ context.Context, s State) error { return nil }, FiringImmediate) + sm := NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { + return "", errors.New("status error") + }, func(_ context.Context, s string) error { return nil }, FiringImmediate) _, err := sm.PermittedTriggers() assert.EqualError(t, err, "status error") } func TestStateMachine_MustState_StatusError(t *testing.T) { - sm := NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { - return nil, errors.New("") - }, func(_ context.Context, s State) error { return nil }, FiringImmediate) + sm := NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { + return "", errors.New("") + }, func(_ context.Context, s string) error { return nil }, FiringImmediate) assert.Panics(t, func() { sm.MustState() }) } func TestStateMachine_Fire_StatusError(t *testing.T) { - sm := NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { - return nil, errors.New("status error") - }, func(_ context.Context, s State) error { return nil }, FiringImmediate) + sm := NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { + return "", errors.New("status error") + }, func(_ context.Context, s string) error { return nil }, FiringImmediate) assert.EqualError(t, sm.Fire(triggerX), "status error") } func TestStateMachine_Configure_PermittedTriggersIncludeSuperstatePermittedTriggers(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateA).Permit(triggerZ, stateB) sm.Configure(stateB).SubstateOf(stateC).Permit(triggerX, stateA) sm.Configure(stateC).Permit(triggerY, stateA) @@ -158,7 +158,7 @@ func TestStateMachine_Configure_PermittedTriggersIncludeSuperstatePermittedTrigg } func TestStateMachine_PermittedTriggers_PermittedTriggersAreDistinctValues(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB).SubstateOf(stateC).Permit(triggerX, stateA) sm.Configure(stateC).Permit(triggerX, stateB) @@ -169,7 +169,7 @@ func TestStateMachine_PermittedTriggers_PermittedTriggersAreDistinctValues(t *te } func TestStateMachine_PermittedTriggers_AcceptedTriggersRespectGuards(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB).Permit(triggerX, stateA, func(_ context.Context, _ ...interface{}) bool { return false }) @@ -180,7 +180,7 @@ func TestStateMachine_PermittedTriggers_AcceptedTriggersRespectGuards(t *testing } func TestStateMachine_PermittedTriggers_AcceptedTriggersRespectMultipleGuards(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB).Permit(triggerX, stateA, func(_ context.Context, _ ...interface{}) bool { return true }, func(_ context.Context, _ ...interface{}) bool { @@ -193,7 +193,7 @@ func TestStateMachine_PermittedTriggers_AcceptedTriggersRespectMultipleGuards(t } func TestStateMachine_Fire_DiscriminatedByGuard_ChoosesPermitedTransition(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB). Permit(triggerX, stateA, func(_ context.Context, _ ...interface{}) bool { return false @@ -208,9 +208,9 @@ func TestStateMachine_Fire_DiscriminatedByGuard_ChoosesPermitedTransition(t *tes } func TestStateMachine_Fire_SaveError(t *testing.T) { - sm := NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { + sm := NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { return stateB, nil - }, func(_ context.Context, s State) error { return errors.New("status error") }, FiringImmediate) + }, func(_ context.Context, s string) error { return errors.New("status error") }, FiringImmediate) sm.Configure(stateB). Permit(triggerX, stateA) @@ -221,7 +221,7 @@ func TestStateMachine_Fire_SaveError(t *testing.T) { func TestStateMachine_Fire_TriggerIsIgnored_ActionsNotExecuted(t *testing.T) { fired := false - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB). OnEntry(func(_ context.Context, _ ...interface{}) error { fired = true @@ -236,7 +236,7 @@ func TestStateMachine_Fire_TriggerIsIgnored_ActionsNotExecuted(t *testing.T) { func TestStateMachine_Fire_SelfTransitionPermited_ActionsFire(t *testing.T) { fired := false - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB). OnEntry(func(_ context.Context, _ ...interface{}) error { fired = true @@ -250,7 +250,7 @@ func TestStateMachine_Fire_SelfTransitionPermited_ActionsFire(t *testing.T) { } func TestStateMachine_Fire_ImplicitReentryIsDisallowed(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) assert.Panics(t, func() { sm.Configure(stateB). Permit(triggerX, stateB) @@ -258,12 +258,12 @@ func TestStateMachine_Fire_ImplicitReentryIsDisallowed(t *testing.T) { } func TestStateMachine_Fire_ErrorForInvalidTransition(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) assert.Error(t, sm.Fire(triggerX)) } func TestStateMachine_Fire_ErrorForInvalidTransitionMentionsGuardDescriptionIfPresent(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA).Permit(triggerX, stateB, func(_ context.Context, _ ...interface{}) bool { return false }) @@ -271,7 +271,7 @@ func TestStateMachine_Fire_ErrorForInvalidTransitionMentionsGuardDescriptionIfPr } func TestStateMachine_Fire_ParametersSuppliedToFireArePassedToEntryAction(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.SetTriggerParameters(triggerX, reflect.TypeOf(""), reflect.TypeOf(0)) sm.Configure(stateB).Permit(triggerX, stateC) @@ -292,12 +292,12 @@ func TestStateMachine_Fire_ParametersSuppliedToFireArePassedToEntryAction(t *tes } func TestStateMachine_OnUnhandledTrigger_TheProvidedHandlerIsCalledWithStateAndTrigger(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) var ( - unhandledState State - unhandledTrigger Trigger + unhandledState string + unhandledTrigger string ) - sm.OnUnhandledTrigger(func(_ context.Context, state State, trigger Trigger, unmetGuards []string) error { + sm.OnUnhandledTrigger(func(_ context.Context, state string, trigger string, unmetGuards []string) error { unhandledState = state unhandledTrigger = trigger return nil @@ -310,7 +310,7 @@ func TestStateMachine_OnUnhandledTrigger_TheProvidedHandlerIsCalledWithStateAndT } func TestStateMachine_SetTriggerParameters_TriggerParametersAreImmutableOnceSet(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.SetTriggerParameters(triggerX, reflect.TypeOf(""), reflect.TypeOf(0)) @@ -318,7 +318,7 @@ func TestStateMachine_SetTriggerParameters_TriggerParametersAreImmutableOnceSet( } func TestStateMachine_SetTriggerParameters_Invalid(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.SetTriggerParameters(triggerX, reflect.TypeOf(""), reflect.TypeOf(0)) sm.Configure(stateB).Permit(triggerX, stateA) @@ -329,11 +329,11 @@ func TestStateMachine_SetTriggerParameters_Invalid(t *testing.T) { } func TestStateMachine_OnTransitioning_EventFires(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB).Permit(triggerX, stateA) - var transition Transition - sm.OnTransitioning(func(_ context.Context, tr Transition) { + var transition Transition[string, string] + sm.OnTransitioning(func(_ context.Context, tr Transition[string, string]) { transition = tr }) sm.Fire(triggerX) @@ -345,11 +345,11 @@ func TestStateMachine_OnTransitioning_EventFires(t *testing.T) { } func TestStateMachine_OnTransitioned_EventFires(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.Configure(stateB).Permit(triggerX, stateA) - var transition Transition - sm.OnTransitioned(func(_ context.Context, tr Transition) { + var transition Transition[string, string] + sm.OnTransitioned(func(_ context.Context, tr Transition[string, string]) { transition = tr }) sm.Fire(triggerX) @@ -361,7 +361,7 @@ func TestStateMachine_OnTransitioned_EventFires(t *testing.T) { } func TestStateMachine_OnTransitioned_EventFiresBeforeTheOnEntryEvent(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) expectedOrdering := []string{"OnExit", "OnTransitioning", "OnEntry", "OnTransitioned"} var actualOrdering []string @@ -370,17 +370,17 @@ func TestStateMachine_OnTransitioned_EventFiresBeforeTheOnEntryEvent(t *testing. return nil }).Machine() - var transition Transition + var transition Transition[string, string] sm.Configure(stateA).OnEntry(func(ctx context.Context, args ...interface{}) error { actualOrdering = append(actualOrdering, "OnEntry") - transition = GetTransition(ctx) + transition = GetTransition[string, string](ctx) return nil }) - sm.OnTransitioning(func(_ context.Context, tr Transition) { + sm.OnTransitioning(func(_ context.Context, tr Transition[string, string]) { actualOrdering = append(actualOrdering, "OnTransitioning") }) - sm.OnTransitioned(func(_ context.Context, tr Transition) { + sm.OnTransitioned(func(_ context.Context, tr Transition[string, string]) { actualOrdering = append(actualOrdering, "OnTransitioned") }) @@ -393,25 +393,25 @@ func TestStateMachine_OnTransitioned_EventFiresBeforeTheOnEntryEvent(t *testing. } func TestStateMachine_SubstateOf_DirectCyclicConfigurationDetected(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) assert.Panics(t, func() { sm.Configure(stateA).SubstateOf(stateA) }) } func TestStateMachine_SubstateOf_NestedCyclicConfigurationDetected(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateB).SubstateOf(stateA) assert.Panics(t, func() { sm.Configure(stateA).SubstateOf(stateB) }) } func TestStateMachine_SubstateOf_NestedTwoLevelsCyclicConfigurationDetected(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateB).SubstateOf(stateA) sm.Configure(stateC).SubstateOf(stateB) assert.Panics(t, func() { sm.Configure(stateA).SubstateOf(stateC) }) } func TestStateMachine_SubstateOf_DelayedNestedCyclicConfigurationDetected(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateB).SubstateOf(stateA) sm.Configure(stateC) sm.Configure(stateA).SubstateOf(stateC) @@ -419,7 +419,7 @@ func TestStateMachine_SubstateOf_DelayedNestedCyclicConfigurationDetected(t *tes } func TestStateMachine_Fire_IgnoreVsPermitReentry(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var calls int sm.Configure(stateA). OnEntry(func(_ context.Context, _ ...interface{}) error { @@ -436,7 +436,7 @@ func TestStateMachine_Fire_IgnoreVsPermitReentry(t *testing.T) { } func TestStateMachine_Fire_IgnoreVsPermitReentryFrom(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var calls int sm.Configure(stateA). OnEntryFrom(triggerX, func(_ context.Context, _ ...interface{}) error { @@ -457,7 +457,7 @@ func TestStateMachine_Fire_IgnoreVsPermitReentryFrom(t *testing.T) { } func TestStateMachine_Fire_IfSelfTransitionPermited_ActionsFire_InSubstate(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var onEntryStateBfired, onExitStateBfired, onExitStateAfired bool sm.Configure(stateB). OnEntry(func(_ context.Context, _ ...interface{}) error { @@ -486,7 +486,7 @@ func TestStateMachine_Fire_IfSelfTransitionPermited_ActionsFire_InSubstate(t *te } func TestStateMachine_Fire_TransitionWhenParameterizedGuardTrue(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.SetTriggerParameters(triggerX, reflect.TypeOf(0)) sm.Configure(stateA). Permit(triggerX, stateB, func(_ context.Context, args ...interface{}) bool { @@ -499,7 +499,7 @@ func TestStateMachine_Fire_TransitionWhenParameterizedGuardTrue(t *testing.T) { } func TestStateMachine_Fire_ErrorWhenParameterizedGuardFalse(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.SetTriggerParameters(triggerX, reflect.TypeOf(0)) sm.Configure(stateA). Permit(triggerX, stateB, func(_ context.Context, args ...interface{}) bool { @@ -512,7 +512,7 @@ func TestStateMachine_Fire_ErrorWhenParameterizedGuardFalse(t *testing.T) { } func TestStateMachine_Fire_TransitionWhenBothParameterizedGuardClausesTrue(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.SetTriggerParameters(triggerX, reflect.TypeOf(0)) sm.Configure(stateA). Permit(triggerX, stateB, func(_ context.Context, args ...interface{}) bool { @@ -527,7 +527,7 @@ func TestStateMachine_Fire_TransitionWhenBothParameterizedGuardClausesTrue(t *te } func TestStateMachine_Fire_TransitionWhenGuardReturnsTrueOnTriggerWithMultipleParameters(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.SetTriggerParameters(triggerX, reflect.TypeOf(""), reflect.TypeOf(0)) sm.Configure(stateA). Permit(triggerX, stateB, func(_ context.Context, args ...interface{}) bool { @@ -540,16 +540,16 @@ func TestStateMachine_Fire_TransitionWhenGuardReturnsTrueOnTriggerWithMultiplePa } func TestStateMachine_Fire_TransitionWhenPermitDyanmicIfHasMultipleExclusiveGuards(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.SetTriggerParameters(triggerX, reflect.TypeOf(0)) sm.Configure(stateA). - PermitDynamic(triggerX, func(_ context.Context, args ...interface{}) (State, error) { + PermitDynamic(triggerX, func(_ context.Context, args ...interface{}) (string, error) { if args[0].(int) == 3 { return stateB, nil } return stateC, nil }, func(_ context.Context, args ...interface{}) bool { return args[0].(int) == 3 || args[0].(int) == 5 }). - PermitDynamic(triggerX, func(_ context.Context, args ...interface{}) (State, error) { + PermitDynamic(triggerX, func(_ context.Context, args ...interface{}) (string, error) { if args[0].(int) == 2 { return stateC, nil } @@ -562,10 +562,10 @@ func TestStateMachine_Fire_TransitionWhenPermitDyanmicIfHasMultipleExclusiveGuar } func TestStateMachine_Fire_PermitDyanmic_Error(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). - PermitDynamic(triggerX, func(_ context.Context, _ ...interface{}) (State, error) { - return nil, errors.New("") + PermitDynamic(triggerX, func(_ context.Context, _ ...interface{}) (string, error) { + return "", errors.New("") }) assert.Error(t, sm.Fire(triggerX), "") @@ -573,16 +573,16 @@ func TestStateMachine_Fire_PermitDyanmic_Error(t *testing.T) { } func TestStateMachine_Fire_PanicsWhenPermitDyanmicIfHasMultipleNonExclusiveGuards(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.SetTriggerParameters(triggerX, reflect.TypeOf(0)) sm.Configure(stateA). - PermitDynamic(triggerX, func(_ context.Context, args ...interface{}) (State, error) { + PermitDynamic(triggerX, func(_ context.Context, args ...interface{}) (string, error) { if args[0].(int) == 4 { return stateB, nil } return stateC, nil }, func(_ context.Context, args ...interface{}) bool { return args[0].(int)%2 == 0 }). - PermitDynamic(triggerX, func(_ context.Context, args ...interface{}) (State, error) { + PermitDynamic(triggerX, func(_ context.Context, args ...interface{}) (string, error) { if args[0].(int) == 2 { return stateC, nil } @@ -593,7 +593,7 @@ func TestStateMachine_Fire_PanicsWhenPermitDyanmicIfHasMultipleNonExclusiveGuard } func TestStateMachine_Fire_TransitionWhenPermitIfHasMultipleExclusiveGuardsWithSuperStateTrue(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.SetTriggerParameters(triggerX, reflect.TypeOf(0)) sm.Configure(stateA). Permit(triggerX, stateD, func(_ context.Context, args ...interface{}) bool { @@ -612,7 +612,7 @@ func TestStateMachine_Fire_TransitionWhenPermitIfHasMultipleExclusiveGuardsWithS } func TestStateMachine_Fire_TransitionWhenPermitIfHasMultipleExclusiveGuardsWithSuperStateFalse(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) sm.SetTriggerParameters(triggerX, reflect.TypeOf(0)) sm.Configure(stateA). Permit(triggerX, stateD, func(_ context.Context, args ...interface{}) bool { @@ -631,7 +631,7 @@ func TestStateMachine_Fire_TransitionWhenPermitIfHasMultipleExclusiveGuardsWithS } func TestStateMachine_Fire_TransitionToSuperstateDoesNotExitSuperstate(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) var superExit, superEntry, subExit bool sm.Configure(stateA). OnEntry(func(_ context.Context, _ ...interface{}) error { @@ -659,7 +659,7 @@ func TestStateMachine_Fire_TransitionToSuperstateDoesNotExitSuperstate(t *testin } func TestStateMachine_Fire_OnExitFiresOnlyOnceReentrySubstate(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var exitB, exitA, entryB, entryA int sm.Configure(stateA). SubstateOf(stateB). @@ -692,7 +692,7 @@ func TestStateMachine_Fire_OnExitFiresOnlyOnceReentrySubstate(t *testing.T) { } func TestStateMachine_Activate(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) expectedOrdering := []string{"ActivatedC", "ActivatedA"} var actualOrdering []string @@ -711,10 +711,10 @@ func TestStateMachine_Activate(t *testing.T) { }) // should not be called for activation - sm.OnTransitioning(func(_ context.Context, _ Transition) { + sm.OnTransitioning(func(_ context.Context, _ Transition[string, string]) { actualOrdering = append(actualOrdering, "OnTransitioning") }) - sm.OnTransitioned(func(_ context.Context, _ Transition) { + sm.OnTransitioned(func(_ context.Context, _ Transition[string, string]) { actualOrdering = append(actualOrdering, "OnTransitioned") }) @@ -724,7 +724,7 @@ func TestStateMachine_Activate(t *testing.T) { } func TestStateMachine_Activate_Error(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var actualOrdering []string @@ -745,7 +745,7 @@ func TestStateMachine_Activate_Error(t *testing.T) { } func TestStateMachine_Activate_Idempotent(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var actualOrdering []string @@ -768,7 +768,7 @@ func TestStateMachine_Activate_Idempotent(t *testing.T) { } func TestStateMachine_Deactivate(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) expectedOrdering := []string{"DeactivatedA", "DeactivatedC"} var actualOrdering []string @@ -787,10 +787,10 @@ func TestStateMachine_Deactivate(t *testing.T) { }) // should not be called for activation - sm.OnTransitioning(func(_ context.Context, _ Transition) { + sm.OnTransitioning(func(_ context.Context, _ Transition[string, string]) { actualOrdering = append(actualOrdering, "OnTransitioning") }) - sm.OnTransitioned(func(_ context.Context, _ Transition) { + sm.OnTransitioned(func(_ context.Context, _ Transition[string, string]) { actualOrdering = append(actualOrdering, "OnTransitioned") }) @@ -801,7 +801,7 @@ func TestStateMachine_Deactivate(t *testing.T) { } func TestStateMachine_Deactivate_NoActivated(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var actualOrdering []string @@ -824,7 +824,7 @@ func TestStateMachine_Deactivate_NoActivated(t *testing.T) { } func TestStateMachine_Deactivate_Error(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var actualOrdering []string @@ -846,7 +846,7 @@ func TestStateMachine_Deactivate_Error(t *testing.T) { } func TestStateMachine_Deactivate_Idempotent(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var actualOrdering []string @@ -872,7 +872,7 @@ func TestStateMachine_Deactivate_Idempotent(t *testing.T) { } func TestStateMachine_Activate_Transitioning(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) var actualOrdering []string expectedOrdering := []string{"ActivatedA", "ExitedA", "OnTransitioning", "EnteredB", "OnTransitioned", @@ -916,10 +916,10 @@ func TestStateMachine_Activate_Transitioning(t *testing.T) { }). Permit(triggerY, stateA) - sm.OnTransitioning(func(_ context.Context, _ Transition) { + sm.OnTransitioning(func(_ context.Context, _ Transition[string, string]) { actualOrdering = append(actualOrdering, "OnTransitioning") }) - sm.OnTransitioned(func(_ context.Context, _ Transition) { + sm.OnTransitioned(func(_ context.Context, _ Transition[string, string]) { actualOrdering = append(actualOrdering, "OnTransitioned") }) @@ -931,7 +931,7 @@ func TestStateMachine_Activate_Transitioning(t *testing.T) { } func TestStateMachine_Fire_ImmediateEntryAProcessedBeforeEnterB(t *testing.T) { - sm := NewStateMachineWithMode(stateA, FiringImmediate) + sm := NewStateMachineWithMode[string, string](stateA, FiringImmediate) var actualOrdering []string expectedOrdering := []string{"ExitA", "ExitB", "EnterA", "EnterB"} @@ -965,7 +965,7 @@ func TestStateMachine_Fire_ImmediateEntryAProcessedBeforeEnterB(t *testing.T) { } func TestStateMachine_Fire_QueuedEntryAProcessedBeforeEnterB(t *testing.T) { - sm := NewStateMachineWithMode(stateA, FiringQueued) + sm := NewStateMachineWithMode[string, string](stateA, FiringQueued) var actualOrdering []string expectedOrdering := []string{"ExitA", "EnterB", "ExitB", "EnterA"} @@ -999,7 +999,7 @@ func TestStateMachine_Fire_QueuedEntryAProcessedBeforeEnterB(t *testing.T) { } func TestStateMachine_Fire_QueuedEntryAsyncFire(t *testing.T) { - sm := NewStateMachineWithMode(stateA, FiringQueued) + sm := NewStateMachineWithMode[string, string](stateA, FiringQueued) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1016,7 +1016,7 @@ func TestStateMachine_Fire_QueuedEntryAsyncFire(t *testing.T) { } func TestStateMachine_Fire_Race(t *testing.T) { - sm := NewStateMachineWithMode(stateA, FiringImmediate) + sm := NewStateMachineWithMode[string, string](stateA, FiringImmediate) var actualOrdering []string var mu sync.Mutex @@ -1066,7 +1066,7 @@ func TestStateMachine_Fire_Race(t *testing.T) { } func TestStateMachine_Fire_Queued_ErrorExit(t *testing.T) { - sm := NewStateMachineWithMode(stateA, FiringQueued) + sm := NewStateMachineWithMode[string, string](stateA, FiringQueued) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1087,7 +1087,7 @@ func TestStateMachine_Fire_Queued_ErrorExit(t *testing.T) { } func TestStateMachine_Fire_Queued_ErrorEnter(t *testing.T) { - sm := NewStateMachineWithMode(stateA, FiringQueued) + sm := NewStateMachineWithMode[string, string](stateA, FiringQueued) sm.Configure(stateA). OnEntry(func(_ context.Context, _ ...interface{}) error { @@ -1108,7 +1108,7 @@ func TestStateMachine_Fire_Queued_ErrorEnter(t *testing.T) { } func TestStateMachine_InternalTransition_StayInSameStateOneState(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateB). InternalTransition(triggerX, func(_ context.Context, _ ...interface{}) error { return nil @@ -1119,7 +1119,7 @@ func TestStateMachine_InternalTransition_StayInSameStateOneState(t *testing.T) { } func TestStateMachine_InternalTransition_HandledOnlyOnceInSuper(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) handledIn := stateC sm.Configure(stateA). InternalTransition(triggerX, func(_ context.Context, _ ...interface{}) error { @@ -1139,7 +1139,7 @@ func TestStateMachine_InternalTransition_HandledOnlyOnceInSuper(t *testing.T) { } func TestStateMachine_InternalTransition_HandledOnlyOnceInSub(t *testing.T) { - sm := NewStateMachine(stateB) + sm := NewStateMachine[string, string](stateB) handledIn := stateC sm.Configure(stateA). InternalTransition(triggerX, func(_ context.Context, _ ...interface{}) error { @@ -1159,7 +1159,7 @@ func TestStateMachine_InternalTransition_HandledOnlyOnceInSub(t *testing.T) { } func TestStateMachine_InitialTransition_EntersSubState(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1175,7 +1175,7 @@ func TestStateMachine_InitialTransition_EntersSubState(t *testing.T) { } func TestStateMachine_InitialTransition_EntersSubStateofSubstate(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1198,7 +1198,7 @@ func TestStateMachine_InitialTransition_Ordering(t *testing.T) { var actualOrdering []string expectedOrdering := []string{"ExitA", "OnTransitioningAB", "EnterB", "OnTransitioningBC", "EnterC", "OnTransitionedAC"} - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). Permit(triggerX, stateB). @@ -1221,10 +1221,10 @@ func TestStateMachine_InitialTransition_Ordering(t *testing.T) { return nil }) - sm.OnTransitioning(func(_ context.Context, tr Transition) { + sm.OnTransitioning(func(_ context.Context, tr Transition[string, string]) { actualOrdering = append(actualOrdering, fmt.Sprintf("OnTransitioning%v%v", tr.Source, tr.Destination)) }) - sm.OnTransitioned(func(_ context.Context, tr Transition) { + sm.OnTransitioned(func(_ context.Context, tr Transition[string, string]) { actualOrdering = append(actualOrdering, fmt.Sprintf("OnTransitioned%v%v", tr.Source, tr.Destination)) }) @@ -1236,7 +1236,7 @@ func TestStateMachine_InitialTransition_Ordering(t *testing.T) { } func TestStateMachine_InitialTransition_DoesNotEnterSubStateofSubstate(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1254,7 +1254,7 @@ func TestStateMachine_InitialTransition_DoesNotEnterSubStateofSubstate(t *testin } func TestStateMachine_InitialTransition_DoNotAllowTransitionToSelf(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) assert.Panics(t, func() { sm.Configure(stateA). InitialTransition(stateA) @@ -1262,7 +1262,7 @@ func TestStateMachine_InitialTransition_DoNotAllowTransitionToSelf(t *testing.T) } func TestStateMachine_InitialTransition_WithMultipleSubStates(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA).Permit(triggerX, stateB) sm.Configure(stateB).InitialTransition(stateC) sm.Configure(stateC).SubstateOf(stateB) @@ -1271,7 +1271,7 @@ func TestStateMachine_InitialTransition_WithMultipleSubStates(t *testing.T) { } func TestStateMachine_InitialTransition_DoNotAllowTransitionToAnotherSuperstate(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1283,7 +1283,7 @@ func TestStateMachine_InitialTransition_DoNotAllowTransitionToAnotherSuperstate( } func TestStateMachine_InitialTransition_DoNotAllowMoreThanOneInitialTransition(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1297,14 +1297,14 @@ func TestStateMachine_InitialTransition_DoNotAllowMoreThanOneInitialTransition(t func TestStateMachine_String(t *testing.T) { tests := []struct { name string - sm *StateMachine + sm *StateMachine[string, string] want string }{ - {"noTriggers", NewStateMachine(stateA), "StateMachine {{ State = A, PermittedTriggers = [] }}"}, - {"error state", NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) { - return nil, errors.New("status error") - }, func(_ context.Context, s State) error { return nil }, FiringImmediate), ""}, - {"triggers", NewStateMachine(stateB).Configure(stateB).Permit(triggerX, stateA).Machine(), + {"noTriggers", NewStateMachine[string, string](stateA), "StateMachine {{ State = A, PermittedTriggers = [] }}"}, + {"error state", NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { + return "", errors.New("status error") + }, func(_ context.Context, s string) error { return nil }, FiringImmediate), ""}, + {"triggers", NewStateMachine[string, string](stateB).Configure(stateB).Permit(triggerX, stateA).Machine(), "StateMachine {{ State = B, PermittedTriggers = [X] }}"}, } for _, tt := range tests { @@ -1317,7 +1317,7 @@ func TestStateMachine_String(t *testing.T) { } func TestStateMachine_Firing_Queued(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1333,7 +1333,7 @@ func TestStateMachine_Firing_Queued(t *testing.T) { } func TestStateMachine_Firing_Immediate(t *testing.T) { - sm := NewStateMachineWithMode(stateA, FiringImmediate) + sm := NewStateMachineWithMode[string, string](stateA, FiringImmediate) sm.Configure(stateA). Permit(triggerX, stateB) @@ -1349,7 +1349,7 @@ func TestStateMachine_Firing_Immediate(t *testing.T) { } func TestStateMachine_Firing_Concurrent(t *testing.T) { - sm := NewStateMachine(stateA) + sm := NewStateMachine[string, string](stateA) sm.Configure(stateA). PermitReentry(triggerX). diff --git a/states_test.go b/states_test.go index 1f901e8..f19b54b 100644 --- a/states_test.go +++ b/states_test.go @@ -66,19 +66,19 @@ func Test_stateRepresentation_CanHandle_TransitionExists_TriggerCannotBeFired(t func Test_stateRepresentation_CanHandle_TransitionDoesNotExist_TriggerCanBeFired(t *testing.T) { sr := newstateRepresentation[string, string](stateB) - sr.AddTriggerBehaviour(&ignoredTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{Trigger: triggerX}}) + sr.AddTriggerBehaviour(&ignoredTriggerBehaviour[string]{baseTriggerBehaviour: baseTriggerBehaviour[string]{Trigger: triggerX}}) assert.True(t, sr.CanHandle(context.Background(), triggerX)) } func Test_stateRepresentation_CanHandle_TransitionExistsInSupersate_TriggerCanBeFired(t *testing.T) { super, sub := createSuperSubstatePair() - super.AddTriggerBehaviour(&ignoredTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{Trigger: triggerX}}) + super.AddTriggerBehaviour(&ignoredTriggerBehaviour[string]{baseTriggerBehaviour: baseTriggerBehaviour[string]{Trigger: triggerX}}) assert.True(t, sub.CanHandle(context.Background(), triggerX)) } func Test_stateRepresentation_CanHandle_TransitionUnmetGuardConditions_TriggerCannotBeFired(t *testing.T) { sr := newstateRepresentation[string, string](stateB) - sr.AddTriggerBehaviour(&transitioningTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{ + sr.AddTriggerBehaviour(&transitioningTriggerBehaviour[string, string]{baseTriggerBehaviour: baseTriggerBehaviour[string]{ Trigger: triggerX, Guard: newtransitionGuard(func(_ context.Context, _ ...interface{}) bool { return true @@ -91,7 +91,7 @@ func Test_stateRepresentation_CanHandle_TransitionUnmetGuardConditions_TriggerCa func Test_stateRepresentation_CanHandle_TransitionGuardConditionsMet_TriggerCanBeFired(t *testing.T) { sr := newstateRepresentation[string, string](stateB) - sr.AddTriggerBehaviour(&transitioningTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{ + sr.AddTriggerBehaviour(&transitioningTriggerBehaviour[string, string]{baseTriggerBehaviour: baseTriggerBehaviour[string]{ Trigger: triggerX, Guard: newtransitionGuard(func(_ context.Context, _ ...interface{}) bool { return true @@ -104,7 +104,7 @@ func Test_stateRepresentation_CanHandle_TransitionGuardConditionsMet_TriggerCanB func Test_stateRepresentation_FindHandler_TransitionExistAndSuperstateUnmetGuardConditions_FireNotPossible(t *testing.T) { super, sub := createSuperSubstatePair() - super.AddTriggerBehaviour(&transitioningTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{ + super.AddTriggerBehaviour(&transitioningTriggerBehaviour[string, string]{baseTriggerBehaviour: baseTriggerBehaviour[string]{ Trigger: triggerX, Guard: newtransitionGuard(func(_ context.Context, _ ...interface{}) bool { return true @@ -122,7 +122,7 @@ func Test_stateRepresentation_FindHandler_TransitionExistAndSuperstateUnmetGuard func Test_stateRepresentation_FindHandler_TransitionExistSuperstateMetGuardConditions_CanBeFired(t *testing.T) { super, sub := createSuperSubstatePair() - super.AddTriggerBehaviour(&transitioningTriggerBehaviour{baseTriggerBehaviour: baseTriggerBehaviour{ + super.AddTriggerBehaviour(&transitioningTriggerBehaviour[string, string]{baseTriggerBehaviour: baseTriggerBehaviour[string]{ Trigger: triggerX, Guard: newtransitionGuard(func(_ context.Context, _ ...interface{}) bool { return true @@ -141,9 +141,9 @@ func Test_stateRepresentation_FindHandler_TransitionExistSuperstateMetGuardCondi func Test_stateRepresentation_Enter_EnteringActionsExecuted(t *testing.T) { sr := newstateRepresentation[string, string](stateB) - transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} - var actualTransition Transition - sr.EntryActions = append(sr.EntryActions, actionBehaviour{ + transition := Transition[string, string]{Source: stateA, Destination: stateB, Trigger: triggerX} + var actualTransition Transition[string, string] + sr.EntryActions = append(sr.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { actualTransition = transition return nil @@ -156,9 +156,9 @@ func Test_stateRepresentation_Enter_EnteringActionsExecuted(t *testing.T) { func Test_stateRepresentation_Enter_EnteringActionsExecuted_Error(t *testing.T) { sr := newstateRepresentation[string, string](stateB) - transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} - var actualTransition Transition - sr.EntryActions = append(sr.EntryActions, actionBehaviour{ + transition := Transition[string, string]{Source: stateA, Destination: stateB, Trigger: triggerX} + var actualTransition Transition[string, string] + sr.EntryActions = append(sr.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { return errors.New("") }, @@ -170,9 +170,9 @@ func Test_stateRepresentation_Enter_EnteringActionsExecuted_Error(t *testing.T) func Test_stateRepresentation_Enter_LeavingActionsNotExecuted(t *testing.T) { sr := newstateRepresentation[string, string](stateA) - transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} - var actualTransition Transition - sr.ExitActions = append(sr.ExitActions, actionBehaviour{ + transition := Transition[string, string]{Source: stateA, Destination: stateB, Trigger: triggerX} + var actualTransition Transition[string, string] + sr.ExitActions = append(sr.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { actualTransition = transition return nil @@ -185,13 +185,13 @@ func Test_stateRepresentation_Enter_LeavingActionsNotExecuted(t *testing.T) { func Test_stateRepresentation_Enter_FromSubToSuperstate_SubstateEntryActionsExecuted(t *testing.T) { super, sub := createSuperSubstatePair() executed := false - sub.EntryActions = append(sub.EntryActions, actionBehaviour{ + sub.EntryActions = append(sub.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { executed = true return nil }, }) - transition := Transition{Source: super.State, Destination: sub.State, Trigger: triggerX} + transition := Transition[string, string]{Source: super.State, Destination: sub.State, Trigger: triggerX} sub.Enter(context.Background(), transition) assert.True(t, executed) } @@ -199,13 +199,13 @@ func Test_stateRepresentation_Enter_FromSubToSuperstate_SubstateEntryActionsExec func Test_stateRepresentation_Enter_SuperFromSubstate_SuperEntryActionsNotExecuted(t *testing.T) { super, sub := createSuperSubstatePair() executed := false - super.EntryActions = append(super.EntryActions, actionBehaviour{ + super.EntryActions = append(super.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { executed = true return nil }, }) - transition := Transition{Source: super.State, Destination: sub.State, Trigger: triggerX} + transition := Transition[string, string]{Source: super.State, Destination: sub.State, Trigger: triggerX} sub.Enter(context.Background(), transition) assert.False(t, executed) } @@ -213,13 +213,13 @@ func Test_stateRepresentation_Enter_SuperFromSubstate_SuperEntryActionsNotExecut func Test_stateRepresentation_Enter_Substate_SuperEntryActionsExecuted(t *testing.T) { super, sub := createSuperSubstatePair() executed := false - super.EntryActions = append(super.EntryActions, actionBehaviour{ + super.EntryActions = append(super.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { executed = true return nil }, }) - transition := Transition{Source: stateC, Destination: sub.State, Trigger: triggerX} + transition := Transition[string, string]{Source: stateC, Destination: sub.State, Trigger: triggerX} sub.Enter(context.Background(), transition) assert.True(t, executed) } @@ -227,19 +227,19 @@ func Test_stateRepresentation_Enter_Substate_SuperEntryActionsExecuted(t *testin func Test_stateRepresentation_Enter_ActionsExecuteInOrder(t *testing.T) { var actual []int sr := newstateRepresentation[string, string](stateB) - sr.EntryActions = append(sr.EntryActions, actionBehaviour{ + sr.EntryActions = append(sr.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { actual = append(actual, 0) return nil }, }) - sr.EntryActions = append(sr.EntryActions, actionBehaviour{ + sr.EntryActions = append(sr.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { actual = append(actual, 1) return nil }, }) - transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} + transition := Transition[string, string]{Source: stateA, Destination: stateB, Trigger: triggerX} sr.Enter(context.Background(), transition) assert.Equal(t, 2, len(actual)) assert.Equal(t, 0, actual[0]) @@ -249,30 +249,30 @@ func Test_stateRepresentation_Enter_ActionsExecuteInOrder(t *testing.T) { func Test_stateRepresentation_Enter_Substate_SuperstateEntryActionsExecuteBeforeSubstate(t *testing.T) { super, sub := createSuperSubstatePair() var order, subOrder, superOrder int - super.EntryActions = append(super.EntryActions, actionBehaviour{ + super.EntryActions = append(super.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { order += 1 superOrder = order return nil }, }) - sub.EntryActions = append(sub.EntryActions, actionBehaviour{ + sub.EntryActions = append(sub.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { order += 1 subOrder = order return nil }, }) - transition := Transition{Source: stateC, Destination: sub.State, Trigger: triggerX} + transition := Transition[string, string]{Source: stateC, Destination: sub.State, Trigger: triggerX} sub.Enter(context.Background(), transition) assert.True(t, superOrder < subOrder) } func Test_stateRepresentation_Exit_EnteringActionsNotExecuted(t *testing.T) { sr := newstateRepresentation[string, string](stateB) - transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} - var actualTransition Transition - sr.EntryActions = append(sr.EntryActions, actionBehaviour{ + transition := Transition[string, string]{Source: stateA, Destination: stateB, Trigger: triggerX} + var actualTransition Transition[string, string] + sr.EntryActions = append(sr.EntryActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { actualTransition = transition return nil @@ -284,9 +284,9 @@ func Test_stateRepresentation_Exit_EnteringActionsNotExecuted(t *testing.T) { func Test_stateRepresentation_Exit_LeavingActionsExecuted(t *testing.T) { sr := newstateRepresentation[string, string](stateA) - transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} - var actualTransition Transition - sr.ExitActions = append(sr.ExitActions, actionBehaviour{ + transition := Transition[string, string]{Source: stateA, Destination: stateB, Trigger: triggerX} + var actualTransition Transition[string, string] + sr.ExitActions = append(sr.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { actualTransition = transition return nil @@ -299,9 +299,9 @@ func Test_stateRepresentation_Exit_LeavingActionsExecuted(t *testing.T) { func Test_stateRepresentation_Exit_LeavingActionsExecuted_Error(t *testing.T) { sr := newstateRepresentation[string, string](stateA) - transition := Transition{Source: stateA, Destination: stateB, Trigger: triggerX} - var actualTransition Transition - sr.ExitActions = append(sr.ExitActions, actionBehaviour{ + transition := Transition[string, string]{Source: stateA, Destination: stateB, Trigger: triggerX} + var actualTransition Transition[string, string] + sr.ExitActions = append(sr.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { return errors.New("") }, @@ -314,13 +314,13 @@ func Test_stateRepresentation_Exit_LeavingActionsExecuted_Error(t *testing.T) { func Test_stateRepresentation_Exit_FromSubToSuperstate_SubstateExitActionsExecuted(t *testing.T) { super, sub := createSuperSubstatePair() executed := false - sub.ExitActions = append(sub.ExitActions, actionBehaviour{ + sub.ExitActions = append(sub.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { executed = true return nil }, }) - transition := Transition{Source: sub.State, Destination: super.State, Trigger: triggerX} + transition := Transition[string, string]{Source: sub.State, Destination: super.State, Trigger: triggerX} sub.Exit(context.Background(), transition) assert.True(t, executed) } @@ -331,13 +331,13 @@ func Test_stateRepresentation_Exit_FromSubToOther_SuperstateExitActionsExecuted( super.Superstate = supersuper supersuper.Superstate = newstateRepresentation[string, string](stateD) executed := false - super.ExitActions = append(super.ExitActions, actionBehaviour{ + super.ExitActions = append(super.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { executed = true return nil }, }) - transition := Transition{Source: sub.State, Destination: stateD, Trigger: triggerX} + transition := Transition[string, string]{Source: sub.State, Destination: stateD, Trigger: triggerX} sub.Exit(context.Background(), transition) assert.True(t, executed) } @@ -345,13 +345,13 @@ func Test_stateRepresentation_Exit_FromSubToOther_SuperstateExitActionsExecuted( func Test_stateRepresentation_Exit_FromSuperToSubstate_SuperExitActionsNotExecuted(t *testing.T) { super, sub := createSuperSubstatePair() executed := false - super.ExitActions = append(super.ExitActions, actionBehaviour{ + super.ExitActions = append(super.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { executed = true return nil }, }) - transition := Transition{Source: super.State, Destination: sub.State, Trigger: triggerX} + transition := Transition[string, string]{Source: super.State, Destination: sub.State, Trigger: triggerX} sub.Exit(context.Background(), transition) assert.False(t, executed) } @@ -359,13 +359,13 @@ func Test_stateRepresentation_Exit_FromSuperToSubstate_SuperExitActionsNotExecut func Test_stateRepresentation_Exit_Substate_SuperExitActionsExecuted(t *testing.T) { super, sub := createSuperSubstatePair() executed := false - super.ExitActions = append(super.ExitActions, actionBehaviour{ + super.ExitActions = append(super.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { executed = true return nil }, }) - transition := Transition{Source: sub.State, Destination: stateC, Trigger: triggerX} + transition := Transition[string, string]{Source: sub.State, Destination: stateC, Trigger: triggerX} sub.Exit(context.Background(), transition) assert.True(t, executed) } @@ -373,19 +373,19 @@ func Test_stateRepresentation_Exit_Substate_SuperExitActionsExecuted(t *testing. func Test_stateRepresentation_Exit_ActionsExecuteInOrder(t *testing.T) { var actual []int sr := newstateRepresentation[string, string](stateB) - sr.ExitActions = append(sr.ExitActions, actionBehaviour{ + sr.ExitActions = append(sr.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { actual = append(actual, 0) return nil }, }) - sr.ExitActions = append(sr.ExitActions, actionBehaviour{ + sr.ExitActions = append(sr.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { actual = append(actual, 1) return nil }, }) - transition := Transition{Source: stateB, Destination: stateC, Trigger: triggerX} + transition := Transition[string, string]{Source: stateB, Destination: stateC, Trigger: triggerX} sr.Exit(context.Background(), transition) assert.Equal(t, 2, len(actual)) assert.Equal(t, 0, actual[0]) @@ -395,21 +395,21 @@ func Test_stateRepresentation_Exit_ActionsExecuteInOrder(t *testing.T) { func Test_stateRepresentation_Exit_Substate_SubstateEntryActionsExecuteBeforeSuperstate(t *testing.T) { super, sub := createSuperSubstatePair() var order, subOrder, superOrder int - super.ExitActions = append(super.ExitActions, actionBehaviour{ + super.ExitActions = append(super.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { order += 1 superOrder = order return nil }, }) - sub.ExitActions = append(sub.ExitActions, actionBehaviour{ + sub.ExitActions = append(sub.ExitActions, actionBehaviour[string, string]{ Action: func(_ context.Context, _ ...interface{}) error { order += 1 subOrder = order return nil }, }) - transition := Transition{Source: sub.State, Destination: stateC, Trigger: triggerX} + transition := Transition[string, string]{Source: sub.State, Destination: stateC, Trigger: triggerX} sub.Exit(context.Background(), transition) assert.True(t, subOrder < superOrder) } From cc8c873b6aa6102e0c9f62f3a16de4eb0f105857 Mon Sep 17 00:00:00 2001 From: "quim.muntal.diaz" Date: Mon, 13 Sep 2021 08:52:40 +0200 Subject: [PATCH 3/5] fix readme --- README.md | 30 +++++++++++++++--------------- states.go | 1 - 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e8c70c6..2a6ca06 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ # Stateless -**Create *state machines* and lightweight *state machine-based workflows* directly in Go code:** +**Create _state machines_ and lightweight _state machine-based workflows_ directly in Go code:** ```go -phoneCall := stateless.NewStateMachine(stateOffHook) +phoneCall := stateless.NewStateMachine[string, string](stateOffHook) phoneCall.Configure(stateOffHook).Permit(triggerCallDialed, stateRinging) @@ -50,19 +50,19 @@ The state machine implemented in this library is based on the theory of [UML sta Most standard state machine constructs are supported: -* Support for states and triggers of any comparable type (int, strings, boolean, structs, etc.) -* Hierarchical states -* Entry/exit events for states -* Guard clauses to support conditional transitions -* Introspection +- Support for states and triggers of any comparable type (int, strings, boolean, structs, etc.) +- Hierarchical states +- Entry/exit events for states +- Guard clauses to support conditional transitions +- Introspection Some useful extensions are also provided: -* Ability to store state externally (for example, in a property tracked by an ORM) -* Parameterised triggers -* Reentrant states -* Thread-safe -* Export to DOT graph +- Ability to store state externally (for example, in a property tracked by an ORM) +- Parameterised triggers +- Reentrant states +- Thread-safe +- Export to DOT graph ### Hierarchical States @@ -102,9 +102,9 @@ sm.Configure(State.C) Stateless is designed to be embedded in various application models. For example, some ORMs place requirements upon where mapped data may be stored, and UI frameworks often require state to be stored in special "bindable" properties. To this end, the `StateMachine` constructor can accept function arguments that will be used to read and write the state values: ```go -machine := stateless.NewStateMachineWithExternalStorage(func(_ context.Context) (stateless.State, error) { +machine := stateless.NewStateMachineWithExternalStorage[string, string](func(_ context.Context) (string, error) { return myState.Value, nil -}, func(_ context.Context, state stateless.State) error { +}, func(_ context.Context, state string) error { myState.Value = state return nil }, stateless.FiringQueued) @@ -208,7 +208,7 @@ This can then be rendered by tools that support the DOT graph language, such as This is the complete Phone Call graph as builded in `example_test.go`. -![Phone Call graph](assets/phone-graph.png?raw=true "Phone Call complete DOT") +![Phone Call graph](assets/phone-graph.png?raw=true 'Phone Call complete DOT') ## Project Goals diff --git a/states.go b/states.go index 7b535b9..d944179 100644 --- a/states.go +++ b/states.go @@ -206,7 +206,6 @@ func (sr *stateRepresentation[S, T]) IsIncludedInState(state S) bool { func (sr *stateRepresentation[S, T]) AddTriggerBehaviour(tb triggerBehaviour[T]) { trigger := tb.GetTrigger() sr.TriggerBehaviours[trigger] = append(sr.TriggerBehaviours[trigger], tb) - } func (sr *stateRepresentation[S, T]) PermittedTriggers(ctx context.Context, args ...interface{}) (triggers []T) { From 56a4c6a7471ac8db55d96945676df4aab22a323c Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 18 Mar 2022 12:32:21 +0100 Subject: [PATCH 4/5] remove unused type params --- config.go | 2 +- statemachine.go | 40 ++++++++++++++++++++-------------------- states.go | 26 +++++++++++++------------- triggers.go | 8 ++++---- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/config.go b/config.go index 1f3e7da..1b257f9 100644 --- a/config.go +++ b/config.go @@ -35,7 +35,7 @@ type StateConfiguration[S State, T Trigger] struct { } // State is configured with this configuration. -func (sc *StateConfiguration[S, T]) State() S { +func (sc *StateConfiguration[S, _]) State() S { return sc.sr.State } diff --git a/statemachine.go b/statemachine.go index ac3e8b4..09d4a9b 100644 --- a/statemachine.go +++ b/statemachine.go @@ -41,7 +41,7 @@ type Transition[S State, T Trigger] struct { // IsReentry returns true if the transition is a re-entry, // i.e. the identity transition. -func (t *Transition[S, T]) IsReentry() bool { +func (t *Transition[_, _]) IsReentry() bool { return t.Source == t.Destination } @@ -135,7 +135,7 @@ func (sm *StateMachine[S, T]) ToGraph() string { } // State returns the current state. -func (sm *StateMachine[S, T]) State(ctx context.Context) (S, error) { +func (sm *StateMachine[S, _]) State(ctx context.Context) (S, error) { return sm.stateAccessor(ctx) } @@ -143,7 +143,7 @@ func (sm *StateMachine[S, T]) State(ctx context.Context) (S, error) { // It is safe to use this method when used together with NewStateMachine // or when using NewStateMachineWithExternalStorage with an state accessor that // does not return an error. -func (sm *StateMachine[S, T]) MustState() S { +func (sm *StateMachine[S, _]) MustState() S { st, err := sm.State(context.Background()) if err != nil { panic(err) @@ -152,12 +152,12 @@ func (sm *StateMachine[S, T]) MustState() S { } // PermittedTriggers see PermittedTriggersCtx. -func (sm *StateMachine[S, T]) PermittedTriggers(args ...interface{}) ([]T, error) { +func (sm *StateMachine[_, T]) PermittedTriggers(args ...interface{}) ([]T, error) { return sm.PermittedTriggersCtx(context.Background(), args...) } // PermittedTriggersCtx returns the currently-permissible trigger values. -func (sm *StateMachine[S, T]) PermittedTriggersCtx(ctx context.Context, args ...interface{}) ([]T, error) { +func (sm *StateMachine[_, T]) PermittedTriggersCtx(ctx context.Context, args ...interface{}) ([]T, error) { sr, err := sm.currentState(ctx) if err != nil { return nil, err @@ -166,14 +166,14 @@ func (sm *StateMachine[S, T]) PermittedTriggersCtx(ctx context.Context, args ... } // Activate see ActivateCtx. -func (sm *StateMachine[S, T]) Activate() error { +func (sm *StateMachine[_, _]) Activate() error { return sm.ActivateCtx(context.Background()) } // ActivateCtx activates current state. Actions associated with activating the current state will be invoked. // The activation is idempotent and subsequent activation of the same current state // will not lead to re-execution of activation callbacks. -func (sm *StateMachine[S, T]) ActivateCtx(ctx context.Context) error { +func (sm *StateMachine[_, _]) ActivateCtx(ctx context.Context) error { sr, err := sm.currentState(ctx) if err != nil { return err @@ -182,14 +182,14 @@ func (sm *StateMachine[S, T]) ActivateCtx(ctx context.Context) error { } // Deactivate see DeactivateCtx. -func (sm *StateMachine[S, T]) Deactivate() error { +func (sm *StateMachine[_, _]) Deactivate() error { return sm.DeactivateCtx(context.Background()) } // DeactivateCtx deactivates current state. Actions associated with deactivating the current state will be invoked. // The deactivation is idempotent and subsequent deactivation of the same current state // will not lead to re-execution of deactivation callbacks. -func (sm *StateMachine[S, T]) DeactivateCtx(ctx context.Context) error { +func (sm *StateMachine[_, _]) DeactivateCtx(ctx context.Context) error { sr, err := sm.currentState(ctx) if err != nil { return err @@ -198,13 +198,13 @@ func (sm *StateMachine[S, T]) DeactivateCtx(ctx context.Context) error { } // IsInState see IsInStateCtx. -func (sm *StateMachine[S, T]) IsInState(state S) (bool, error) { +func (sm *StateMachine[S, _]) IsInState(state S) (bool, error) { return sm.IsInStateCtx(context.Background(), state) } // IsInStateCtx determine if the state machine is in the supplied state. // Returns true if the current state is equal to, or a substate of, the supplied state. -func (sm *StateMachine[S, T]) IsInStateCtx(ctx context.Context, state S) (bool, error) { +func (sm *StateMachine[S, _]) IsInStateCtx(ctx context.Context, state S) (bool, error) { sr, err := sm.currentState(ctx) if err != nil { return false, err @@ -213,12 +213,12 @@ func (sm *StateMachine[S, T]) IsInStateCtx(ctx context.Context, state S) (bool, } // CanFire see CanFireCtx. -func (sm *StateMachine[S, T]) CanFire(trigger T, args ...interface{}) (bool, error) { +func (sm *StateMachine[_, T]) CanFire(trigger T, args ...interface{}) (bool, error) { return sm.CanFireCtx(context.Background(), trigger, args...) } // CanFireCtx returns true if the trigger can be fired in the current state. -func (sm *StateMachine[S, T]) CanFireCtx(ctx context.Context, trigger T, args ...interface{}) (bool, error) { +func (sm *StateMachine[_, T]) CanFireCtx(ctx context.Context, trigger T, args ...interface{}) (bool, error) { sr, err := sm.currentState(ctx) if err != nil { return false, err @@ -227,7 +227,7 @@ func (sm *StateMachine[S, T]) CanFireCtx(ctx context.Context, trigger T, args .. } // SetTriggerParameters specify the arguments that must be supplied when a specific trigger is fired. -func (sm *StateMachine[S, T]) SetTriggerParameters(trigger T, argumentTypes ...reflect.Type) { +func (sm *StateMachine[_, T]) SetTriggerParameters(trigger T, argumentTypes ...reflect.Type) { config := triggerWithParameters[T]{Trigger: trigger, ArgumentTypes: argumentTypes} if _, ok := sm.triggerConfig[config.Trigger]; ok { panic(fmt.Sprintf("stateless: Parameters for the trigger '%v' have already been configured.", trigger)) @@ -236,7 +236,7 @@ func (sm *StateMachine[S, T]) SetTriggerParameters(trigger T, argumentTypes ...r } // Fire see FireCtx -func (sm *StateMachine[S, T]) Fire(trigger T, args ...interface{}) error { +func (sm *StateMachine[_, T]) Fire(trigger T, args ...interface{}) error { return sm.FireCtx(context.Background(), trigger, args...) } @@ -253,7 +253,7 @@ func (sm *StateMachine[S, T]) Fire(trigger T, args ...interface{}) error { // // The context is passed down to all actions and callbacks called within the scope of this method. // There is no context error checking, although it may be implemented in future releases. -func (sm *StateMachine[S, T]) FireCtx(ctx context.Context, trigger T, args ...interface{}) error { +func (sm *StateMachine[_, T]) FireCtx(ctx context.Context, trigger T, args ...interface{}) error { return sm.internalFire(ctx, trigger, args...) } @@ -287,7 +287,7 @@ func (sm *StateMachine[S, T]) Firing() bool { // String returns a human-readable representation of the state machine. // It is not guaranteed that the order of the PermittedTriggers is the same in consecutive executions. -func (sm *StateMachine[S, T]) String() string { +func (sm *StateMachine[_, _]) String() string { state, err := sm.State(context.Background()) if err != nil { return "" @@ -298,7 +298,7 @@ func (sm *StateMachine[S, T]) String() string { return fmt.Sprintf("StateMachine {{ State = %v, PermittedTriggers = %v }}", state, triggers) } -func (sm *StateMachine[S, T]) setState(ctx context.Context, state S) error { +func (sm *StateMachine[S, _]) setState(ctx context.Context, state S) error { return sm.stateMutator(ctx, state) } @@ -320,7 +320,7 @@ func (sm *StateMachine[S, T]) stateRepresentation(state S) (sr *stateRepresentat return } -func (sm *StateMachine[S, T]) internalFire(ctx context.Context, trigger T, args ...interface{}) error { +func (sm *StateMachine[_, T]) internalFire(ctx context.Context, trigger T, args ...interface{}) error { switch sm.firingMode { case FiringImmediate: return sm.internalFireOne(ctx, trigger, args...) @@ -337,7 +337,7 @@ type queuedTrigger[T Trigger] struct { Args []interface{} } -func (sm *StateMachine[S, T]) internalFireQueued(ctx context.Context, trigger T, args ...interface{}) error { +func (sm *StateMachine[_, T]) internalFireQueued(ctx context.Context, trigger T, args ...interface{}) error { sm.firingMutex.Lock() sm.eventQueue.PushBack(queuedTrigger[T]{Context: ctx, Trigger: trigger, Args: args}) sm.firingMutex.Unlock() diff --git a/states.go b/states.go index d944179..60561f6 100644 --- a/states.go +++ b/states.go @@ -48,21 +48,21 @@ func newstateRepresentation[S State, T Trigger](state S) *stateRepresentation[S, } } -func (sr *stateRepresentation[S, T]) SetInitialTransition(state S) { +func (sr *stateRepresentation[S, _]) SetInitialTransition(state S) { sr.InitialTransitionTarget = state sr.HasInitialState = true } -func (sr *stateRepresentation[S, T]) state() S { +func (sr *stateRepresentation[S, _]) state() S { return sr.State } -func (sr *stateRepresentation[S, T]) CanHandle(ctx context.Context, trigger T, args ...interface{}) (ok bool) { +func (sr *stateRepresentation[_, T]) CanHandle(ctx context.Context, trigger T, args ...interface{}) (ok bool) { _, ok = sr.FindHandler(ctx, trigger, args...) return } -func (sr *stateRepresentation[S, T]) FindHandler(ctx context.Context, trigger T, args ...interface{}) (handler triggerBehaviourResult[T], ok bool) { +func (sr *stateRepresentation[_, T]) FindHandler(ctx context.Context, trigger T, args ...interface{}) (handler triggerBehaviourResult[T], ok bool) { handler, ok = sr.findHandler(ctx, trigger, args...) if ok || sr.Superstate == nil { return @@ -71,7 +71,7 @@ func (sr *stateRepresentation[S, T]) FindHandler(ctx context.Context, trigger T, return } -func (sr *stateRepresentation[S, T]) findHandler(ctx context.Context, trigger T, args ...interface{}) (result triggerBehaviourResult[T], ok bool) { +func (sr *stateRepresentation[_, T]) findHandler(ctx context.Context, trigger T, args ...interface{}) (result triggerBehaviourResult[T], ok bool) { var ( possibleBehaviours []triggerBehaviour[T] ) @@ -105,7 +105,7 @@ func (sr *stateRepresentation[S, T]) findHandler(ctx context.Context, trigger T, return } -func (sr *stateRepresentation[S, T]) Activate(ctx context.Context) error { +func (sr *stateRepresentation[_, _]) Activate(ctx context.Context) error { if sr.Superstate != nil { if err := sr.Superstate.Activate(ctx); err != nil { return err @@ -114,7 +114,7 @@ func (sr *stateRepresentation[S, T]) Activate(ctx context.Context) error { return sr.executeActivationActions(ctx) } -func (sr *stateRepresentation[S, T]) Deactivate(ctx context.Context) error { +func (sr *stateRepresentation[_, _]) Deactivate(ctx context.Context) error { if err := sr.executeDeactivationActions(ctx); err != nil { return err } @@ -181,7 +181,7 @@ func (sr *stateRepresentation[S, T]) InternalAction(ctx context.Context, transit return internalTransition.Execute(ctx, transition, args...) } -func (sr *stateRepresentation[S, T]) IncludeState(state S) bool { +func (sr *stateRepresentation[S, _]) IncludeState(state S) bool { if state == sr.State { return true } @@ -193,7 +193,7 @@ func (sr *stateRepresentation[S, T]) IncludeState(state S) bool { return false } -func (sr *stateRepresentation[S, T]) IsIncludedInState(state S) bool { +func (sr *stateRepresentation[S, _]) IsIncludedInState(state S) bool { if state == sr.State { return true } @@ -203,12 +203,12 @@ func (sr *stateRepresentation[S, T]) IsIncludedInState(state S) bool { return false } -func (sr *stateRepresentation[S, T]) AddTriggerBehaviour(tb triggerBehaviour[T]) { +func (sr *stateRepresentation[_, T]) AddTriggerBehaviour(tb triggerBehaviour[T]) { trigger := tb.GetTrigger() sr.TriggerBehaviours[trigger] = append(sr.TriggerBehaviours[trigger], tb) } -func (sr *stateRepresentation[S, T]) PermittedTriggers(ctx context.Context, args ...interface{}) (triggers []T) { +func (sr *stateRepresentation[_, T]) PermittedTriggers(ctx context.Context, args ...interface{}) (triggers []T) { for key, value := range sr.TriggerBehaviours { for _, tb := range value { if len(tb.UnmetGuardConditions(ctx, args...)) == 0 { @@ -235,7 +235,7 @@ func (sr *stateRepresentation[S, T]) PermittedTriggers(ctx context.Context, args return } -func (sr *stateRepresentation[S, T]) executeActivationActions(ctx context.Context) error { +func (sr *stateRepresentation[_, _]) executeActivationActions(ctx context.Context) error { for _, a := range sr.ActivateActions { if err := a.Execute(ctx); err != nil { return err @@ -244,7 +244,7 @@ func (sr *stateRepresentation[S, T]) executeActivationActions(ctx context.Contex return nil } -func (sr *stateRepresentation[S, T]) executeDeactivationActions(ctx context.Context) error { +func (sr *stateRepresentation[_, _]) executeDeactivationActions(ctx context.Context) error { for _, a := range sr.DeactivateActions { if err := a.Execute(ctx); err != nil { return err diff --git a/triggers.go b/triggers.go index 082502f..e0b6f2e 100644 --- a/triggers.go +++ b/triggers.go @@ -86,11 +86,11 @@ func (t *baseTriggerBehaviour[T]) GetTrigger() T { return t.Trigger } -func (t *baseTriggerBehaviour[T]) GuardConditionMet(ctx context.Context, args ...interface{}) bool { +func (t *baseTriggerBehaviour[_]) GuardConditionMet(ctx context.Context, args ...interface{}) bool { return t.Guard.GuardConditionMet(ctx, args...) } -func (t *baseTriggerBehaviour[T]) UnmetGuardConditions(ctx context.Context, args ...interface{}) []string { +func (t *baseTriggerBehaviour[_]) UnmetGuardConditions(ctx context.Context, args ...interface{}) []string { return t.Guard.UnmetGuardConditions(ctx, args...) } @@ -113,7 +113,7 @@ type dynamicTriggerBehaviour[S State, T Trigger] struct { Destination func(context.Context, ...interface{}) (S, error) } -func (t *dynamicTriggerBehaviour[S, T]) ResultsInTransitionFrom(ctx context.Context, _ S, args ...interface{}) (st S, ok bool) { +func (t *dynamicTriggerBehaviour[S, _]) ResultsInTransitionFrom(ctx context.Context, _ S, args ...interface{}) (st S, ok bool) { var err error st, err = t.Destination(ctx, args...) if err == nil { @@ -143,7 +143,7 @@ type triggerWithParameters[T Trigger] struct { ArgumentTypes []reflect.Type } -func (t triggerWithParameters[T]) validateParameters(args ...interface{}) { +func (t triggerWithParameters[_]) validateParameters(args ...interface{}) { if len(args) != len(t.ArgumentTypes) { panic(fmt.Sprintf("stateless: Too many parameters have been supplied. Expecting '%d' but got '%d'.", len(t.ArgumentTypes), len(args))) } From e4142ebe3cd5bb94880a9f0b0b1ffde5a1e41778 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 18 Mar 2022 12:33:00 +0100 Subject: [PATCH 5/5] tets on 1.18 --- .github/workflows/test.yml | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0ba4ae1..88e7e24 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,32 +1,32 @@ -on: [push, pull_request] -name: Test -jobs: - test: - strategy: - matrix: - go-version: [1.13.x, 1.14.x, 1.15.x] - platform: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.platform }} - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v2 - - name: Test - run: go test -race -covermode atomic -coverprofile profile.cov ./... - - name: Send coverage - uses: shogo82148/actions-goveralls@v1 - with: - path-to-profile: profile.cov - flag-name: Go-${{ matrix.go-version }} - parallel: true - finish: - needs: test - runs-on: ubuntu-latest - steps: - - uses: shogo82148/actions-goveralls@v1 - with: - parallel-finished: true - +on: [push, pull_request] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.18.x] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Test + run: go test -race -covermode atomic -coverprofile profile.cov ./... + - name: Send coverage + uses: shogo82148/actions-goveralls@v1 + with: + path-to-profile: profile.cov + flag-name: Go-${{ matrix.go-version }} + parallel: true + finish: + needs: test + runs-on: ubuntu-latest + steps: + - uses: shogo82148/actions-goveralls@v1 + with: + parallel-finished: true +