Merge pull request #121 from unistack-org/fsm
fsm: add state execution options
This commit is contained in:
commit
d9be99cfde
44
fsm/fsm.go
44
fsm/fsm.go
@ -14,6 +14,8 @@ var (
|
|||||||
|
|
||||||
// Options struct holding fsm options
|
// Options struct holding fsm options
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
// DryRun mode
|
||||||
|
DryRun bool
|
||||||
// Initial state
|
// Initial state
|
||||||
Initial string
|
Initial string
|
||||||
// HooksBefore func slice runs in order before state
|
// HooksBefore func slice runs in order before state
|
||||||
@ -31,29 +33,44 @@ type HookAfterFunc func(ctx context.Context, state string, args interface{})
|
|||||||
// Option func signature
|
// Option func signature
|
||||||
type Option func(*Options)
|
type Option func(*Options)
|
||||||
|
|
||||||
// StateInitial sets init state for state machine
|
// StateOptions holds state options
|
||||||
func StateInitial(initial string) Option {
|
type StateOptions struct {
|
||||||
|
DryRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateDryRun says that state executes in dry run mode
|
||||||
|
func StateDryRun(b bool) StateOption {
|
||||||
|
return func(o *StateOptions) {
|
||||||
|
o.DryRun = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateOption func signature
|
||||||
|
type StateOption func(*StateOptions)
|
||||||
|
|
||||||
|
// InitialState sets init state for state machine
|
||||||
|
func InitialState(initial string) Option {
|
||||||
return func(o *Options) {
|
return func(o *Options) {
|
||||||
o.Initial = initial
|
o.Initial = initial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateHookBefore provides hook func slice
|
// HookBefore provides hook func slice
|
||||||
func StateHookBefore(fns ...HookBeforeFunc) Option {
|
func HookBefore(fns ...HookBeforeFunc) Option {
|
||||||
return func(o *Options) {
|
return func(o *Options) {
|
||||||
o.HooksBefore = fns
|
o.HooksBefore = fns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateHookAfter provides hook func slice
|
// HookAfter provides hook func slice
|
||||||
func StateHookAfter(fns ...HookAfterFunc) Option {
|
func HookAfter(fns ...HookAfterFunc) Option {
|
||||||
return func(o *Options) {
|
return func(o *Options) {
|
||||||
o.HooksAfter = fns
|
o.HooksAfter = fns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateFunc called on state transition and return next step and error
|
// StateFunc called on state transition and return next step and error
|
||||||
type StateFunc func(ctx context.Context, args interface{}) (string, interface{}, error)
|
type StateFunc func(ctx context.Context, args interface{}, opts...StateOption) (string, interface{}, error)
|
||||||
|
|
||||||
// FSM is a finite state machine
|
// FSM is a finite state machine
|
||||||
type FSM struct {
|
type FSM struct {
|
||||||
@ -101,6 +118,8 @@ func (f *FSM) State(state string, fn StateFunc) {
|
|||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init initialize fsm and check states
|
||||||
|
|
||||||
// Start runs state machine with provided data
|
// Start runs state machine with provided data
|
||||||
func (f *FSM) Start(ctx context.Context, args interface{}, opts ...Option) (interface{}, error) {
|
func (f *FSM) Start(ctx context.Context, args interface{}, opts ...Option) (interface{}, error) {
|
||||||
var err error
|
var err error
|
||||||
@ -115,6 +134,8 @@ func (f *FSM) Start(ctx context.Context, args interface{}, opts ...Option) (inte
|
|||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sopts := []StateOption{StateDryRun(options.DryRun)}
|
||||||
|
|
||||||
cstate := options.Initial
|
cstate := options.Initial
|
||||||
states := make(map[string]StateFunc, len(f.statesMap))
|
states := make(map[string]StateFunc, len(f.statesMap))
|
||||||
for k, v := range f.statesMap {
|
for k, v := range f.statesMap {
|
||||||
@ -138,15 +159,16 @@ func (f *FSM) Start(ctx context.Context, args interface{}, opts ...Option) (inte
|
|||||||
for _, fn := range options.HooksBefore {
|
for _, fn := range options.HooksBefore {
|
||||||
fn(ctx, cstate, args)
|
fn(ctx, cstate, args)
|
||||||
}
|
}
|
||||||
nstate, args, err = fn(ctx, args)
|
nstate, args, err = fn(ctx, args, sopts...)
|
||||||
for _, fn := range options.HooksAfter {
|
for _, fn := range options.HooksAfter {
|
||||||
fn(ctx, cstate, args)
|
fn(ctx, cstate, args)
|
||||||
}
|
}
|
||||||
if err != nil {
|
switch {
|
||||||
|
case err != nil:
|
||||||
return args, err
|
return args, err
|
||||||
} else if nstate == StateEnd {
|
case nstate == StateEnd:
|
||||||
return args, nil
|
return args, nil
|
||||||
} else if nstate == "" {
|
case nstate == "":
|
||||||
for idx := range f.statesOrder {
|
for idx := range f.statesOrder {
|
||||||
if f.statesOrder[idx] == cstate && len(f.statesOrder) > idx+1 {
|
if f.statesOrder[idx] == cstate && len(f.statesOrder) > idx+1 {
|
||||||
nstate = f.statesOrder[idx+1]
|
nstate = f.statesOrder[idx+1]
|
||||||
|
@ -16,22 +16,22 @@ func TestFSMStart(t *testing.T) {
|
|||||||
pfa := func(_ context.Context, state string, _ interface{}) {
|
pfa := func(_ context.Context, state string, _ interface{}) {
|
||||||
fmt.Fprintf(buf, "after state %s\n", state)
|
fmt.Fprintf(buf, "after state %s\n", state)
|
||||||
}
|
}
|
||||||
f := New(StateInitial("1"), StateHookBefore(pfb), StateHookAfter(pfa))
|
f := New(InitialState("1"), HookBefore(pfb), HookAfter(pfa))
|
||||||
f1 := func(_ context.Context, req interface{}) (string, interface{}, error) {
|
f1 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
|
||||||
args := req.(map[string]interface{})
|
args := req.(map[string]interface{})
|
||||||
if v, ok := args["request"].(string); !ok || v == "" {
|
if v, ok := args["request"].(string); !ok || v == "" {
|
||||||
return "", nil, fmt.Errorf("empty request")
|
return "", nil, fmt.Errorf("empty request")
|
||||||
}
|
}
|
||||||
return "2", map[string]interface{}{"response": "test2"}, nil
|
return "2", map[string]interface{}{"response": "test2"}, nil
|
||||||
}
|
}
|
||||||
f2 := func(_ context.Context, req interface{}) (string, interface{}, error) {
|
f2 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
|
||||||
args := req.(map[string]interface{})
|
args := req.(map[string]interface{})
|
||||||
if v, ok := args["response"].(string); !ok || v == "" {
|
if v, ok := args["response"].(string); !ok || v == "" {
|
||||||
return "", nil, fmt.Errorf("empty response")
|
return "", nil, fmt.Errorf("empty response")
|
||||||
}
|
}
|
||||||
return "", map[string]interface{}{"response": "test"}, nil
|
return "", map[string]interface{}{"response": "test"}, nil
|
||||||
}
|
}
|
||||||
f3 := func(_ context.Context, req interface{}) (string, interface{}, error) {
|
f3 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
|
||||||
args := req.(map[string]interface{})
|
args := req.(map[string]interface{})
|
||||||
if v, ok := args["response"].(string); !ok || v == "" {
|
if v, ok := args["response"].(string); !ok || v == "" {
|
||||||
return "", nil, fmt.Errorf("empty response")
|
return "", nil, fmt.Errorf("empty response")
|
||||||
|
Loading…
Reference in New Issue
Block a user