Neur0toxine
68f2975201
All checks were successful
continuous-integration/drone/push Build is passing
72 lines
3.3 KiB
Go
72 lines
3.3 KiB
Go
package fsm
|
|
|
|
// StateID is a state identifier. Machine with string state IDs can have *a lot of* states.
|
|
type StateID string
|
|
|
|
// NilStateID is a noop state. Machine won't do anything for this transition.
|
|
const NilStateID = StateID("")
|
|
|
|
// MachineControls is a fragment of IMachine implementation. This one can Move between the states or Reset the machine.
|
|
// It may fail with an error which should be handled by the IState implementation.
|
|
type MachineControls[T any] interface {
|
|
Move(StateID, T) error
|
|
Reset()
|
|
}
|
|
|
|
// IState is a State interface. This contract enforces that any state implementation should have three methods:
|
|
// Enter, Handle and Exit. The first one is called right after the machine has moved to the state, the second one is
|
|
// called when handling some state input, the last one is called right before leaving to a next state (which happens
|
|
// if Handle has called Move on MachineControls.
|
|
type IState[T any] interface {
|
|
// ID should return state identifier.
|
|
ID() StateID
|
|
// Enter is a state enter callback. Can be used to perform some sort of input query or to move to another
|
|
// state immediately. Also, if Enter fails the machine won't move to the state and MachineControls's Move will
|
|
// return an error which was returned from Enter.
|
|
Enter(*T, MachineControls[*T]) error
|
|
// Handle is called when receiving some sort of input response. This one's signature is nearly identical to Enter,
|
|
// but it can wait for some sort of input (standard input? webhook) without locking inside the callback
|
|
// while Enter cannot do that.
|
|
Handle(*T, MachineControls[*T])
|
|
// Exit is called right before leaving the state. It can be used to modify state's payload (T) or for some other
|
|
// miscellaneous tasks.
|
|
// Note: calling Exit doesn't mean that the machine will really transition to the next state.
|
|
// The next state's Enter callback can return an error which will reset the Machine to default state & payload.
|
|
Exit(*T)
|
|
}
|
|
|
|
// ErrorState is the Machine's fatal error handler which you should implement yourself.
|
|
//
|
|
// Error state is used by Machine in case of a fatal error (for example, for invalid state ID).
|
|
// You can use ErrorState to make some kind of fatal error response like "Internal error has occurred"
|
|
// or something similar. Also, ErrorState is special because neither Enter nor Exit exists in it.
|
|
//
|
|
// Machine without ErrorState will not do anything in case of fatal errors.
|
|
type ErrorState[T any] interface {
|
|
Handle(err error, current StateID, next StateID, payload *T, machine MachineControls[*T])
|
|
}
|
|
|
|
// State is the Machine's state. This implementation doesn't do anything and only helps with the
|
|
// actual IState implementation (you don't need to write empty Enter and Exit callback if you don't use them).
|
|
type State[T any] struct {
|
|
Payload *T
|
|
}
|
|
|
|
// ID panics because you need to implement it.
|
|
func (s *State[T]) ID() StateID {
|
|
panic("implement ID() StateID method for your state")
|
|
}
|
|
|
|
// Enter here will immediately move the Machine to empty state.
|
|
func (s *State[T]) Enter(payload *T, machine MachineControls[*T]) error {
|
|
return machine.Move(NilStateID, payload)
|
|
}
|
|
|
|
// Handle here will immediately move the Machine to empty state.
|
|
func (s *State[T]) Handle(payload *T, machine MachineControls[*T]) {
|
|
_ = machine.Move(NilStateID, payload)
|
|
}
|
|
|
|
// Exit won't do anything.
|
|
func (s *State[T]) Exit(*T) {}
|