fsm

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2024 License: Apache-2.0 Imports: 7 Imported by: 615

README

PkgGoDev Bulid Status Coverage Status Go Report Card

FSM for Go

FSM is a finite state machine for Go.

It is heavily based on two FSM implementations:

For API docs and examples see http://godoc.org/github.com/looplab/fsm

Basic Example

From examples/simple.go:

package main

import (
    "context"
    "fmt"

    "github.com/looplab/fsm"
)

func main() {
    fsm := fsm.NewFSM(
        "closed",
        fsm.Events{
            {Name: "open", Src: []string{"closed"}, Dst: "open"},
            {Name: "close", Src: []string{"open"}, Dst: "closed"},
        },
        fsm.Callbacks{},
    )

    fmt.Println(fsm.Current())

    err := fsm.Event(context.Background(), "open")
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(fsm.Current())

    err = fsm.Event(context.Background(), "close")
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(fsm.Current())
}

Usage as a struct field

From examples/struct.go:

package main

import (
    "context"
    "fmt"

    "github.com/looplab/fsm"
)

type Door struct {
    To  string
    FSM *fsm.FSM
}

func NewDoor(to string) *Door {
    d := &Door{
        To: to,
    }

    d.FSM = fsm.NewFSM(
        "closed",
        fsm.Events{
            {Name: "open", Src: []string{"closed"}, Dst: "open"},
            {Name: "close", Src: []string{"open"}, Dst: "closed"},
        },
        fsm.Callbacks{
            "enter_state": func(_ context.Context, e *fsm.Event) { d.enterState(e) },
        },
    )

    return d
}

func (d *Door) enterState(e *fsm.Event) {
    fmt.Printf("The door to %s is %s\n", d.To, e.Dst)
}

func main() {
    door := NewDoor("heaven")

    err := door.FSM.Event(context.Background(), "open")
    if err != nil {
        fmt.Println(err)
    }

    err = door.FSM.Event(context.Background(), "close")
    if err != nil {
        fmt.Println(err)
    }
}

License

FSM is licensed under Apache License 2.0

http://www.apache.org/licenses/LICENSE-2.0

Documentation

Overview

Package fsm implements a finite state machine.

It is heavily based on two FSM implementations:

Javascript Finite State Machine https://github.com/jakesgordon/javascript-state-machine

Fysom for Python https://github.com/oxplot/fysom (forked at https://github.com/mriehl/fysom)

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Visualize

func Visualize(fsm *FSM) string

Visualize outputs a visualization of a FSM in Graphviz format.

func VisualizeForMermaidWithGraphType added in v0.2.0

func VisualizeForMermaidWithGraphType(fsm *FSM, graphType MermaidDiagramType) (string, error)

VisualizeForMermaidWithGraphType outputs a visualization of a FSM in Mermaid format as specified by the graphType.

func VisualizeWithType added in v0.2.0

func VisualizeWithType(fsm *FSM, visualizeType VisualizeType) (string, error)

VisualizeWithType outputs a visualization of a FSM in the desired format. If the type is not given it defaults to GRAPHVIZ

Types

type AsyncError

type AsyncError struct {
	Err error

	Ctx              context.Context
	CancelTransition func()
}

AsyncError is returned by FSM.Event() when a callback have initiated an asynchronous state transition.

func (AsyncError) Error

func (e AsyncError) Error() string

type Callback

type Callback func(context.Context, *Event)

Callback is a function type that callbacks should use. Event is the current event info as the callback happens.

type Callbacks

type Callbacks map[string]Callback

Callbacks is a shorthand for defining the callbacks in NewFSM.

type CanceledError

type CanceledError struct {
	Err error
}

CanceledError is returned by FSM.Event() when a callback have canceled a transition.

func (CanceledError) Error

func (e CanceledError) Error() string

type Event

type Event struct {
	// FSM is an reference to the current FSM.
	FSM *FSM

	// Event is the event name.
	Event string

	// Src is the state before the transition.
	Src string

	// Dst is the state after the transition.
	Dst string

	// Err is an optional error that can be returned from a callback.
	Err error

	// Args is an optional list of arguments passed to the callback.
	Args []interface{}
	// contains filtered or unexported fields
}

Event is the info that get passed as a reference in the callbacks.

func (*Event) Async

func (e *Event) Async()

Async can be called in leave_<STATE> to do an asynchronous state transition.

The current state transition will be on hold in the old state until a final call to Transition is made. This will complete the transition and possibly call the other callbacks.

func (*Event) Cancel

func (e *Event) Cancel(err ...error)

Cancel can be called in before_<EVENT> or leave_<STATE> to cancel the current transition before it happens. It takes an optional error, which will overwrite e.Err if set before.

type EventDesc

type EventDesc struct {
	// Name is the event name used when calling for a transition.
	Name string

	// Src is a slice of source states that the FSM must be in to perform a
	// state transition.
	Src []string

	// Dst is the destination state that the FSM will be in if the transition
	// succeeds.
	Dst string
}

EventDesc represents an event when initializing the FSM.

The event can have one or more source states that is valid for performing the transition. If the FSM is in one of the source states it will end up in the specified destination state, calling all defined callbacks as it goes.

type Events

type Events []EventDesc

Events is a shorthand for defining the transition map in NewFSM.

type FSM

type FSM struct {
	// contains filtered or unexported fields
}

FSM is the state machine that holds the current state.

It has to be created with NewFSM to function properly.

func NewFSM

func NewFSM(initial string, events []EventDesc, callbacks map[string]Callback) *FSM

NewFSM constructs a FSM from events and callbacks.

The events and transitions are specified as a slice of Event structs specified as Events. Each Event is mapped to one or more internal transitions from Event.Src to Event.Dst.

Callbacks are added as a map specified as Callbacks where the key is parsed as the callback event as follows, and called in the same order:

1. before_<EVENT> - called before event named <EVENT>

2. before_event - called before all events

3. leave_<OLD_STATE> - called before leaving <OLD_STATE>

4. leave_state - called before leaving all states

5. enter_<NEW_STATE> - called after entering <NEW_STATE>

6. enter_state - called after entering all states

7. after_<EVENT> - called after event named <EVENT>

8. after_event - called after all events

There are also two short form versions for the most commonly used callbacks. They are simply the name of the event or state:

1. <NEW_STATE> - called after entering <NEW_STATE>

2. <EVENT> - called after event named <EVENT>

If both a shorthand version and a full version is specified it is undefined which version of the callback will end up in the internal map. This is due to the pseudo random nature of Go maps. No checking for multiple keys is currently performed.

Example
fsm := NewFSM(
	"green",
	Events{
		{Name: "warn", Src: []string{"green"}, Dst: "yellow"},
		{Name: "panic", Src: []string{"yellow"}, Dst: "red"},
		{Name: "panic", Src: []string{"green"}, Dst: "red"},
		{Name: "calm", Src: []string{"red"}, Dst: "yellow"},
		{Name: "clear", Src: []string{"yellow"}, Dst: "green"},
	},
	Callbacks{
		"before_warn": func(_ context.Context, e *Event) {
			fmt.Println("before_warn")
		},
		"before_event": func(_ context.Context, e *Event) {
			fmt.Println("before_event")
		},
		"leave_green": func(_ context.Context, e *Event) {
			fmt.Println("leave_green")
		},
		"leave_state": func(_ context.Context, e *Event) {
			fmt.Println("leave_state")
		},
		"enter_yellow": func(_ context.Context, e *Event) {
			fmt.Println("enter_yellow")
		},
		"enter_state": func(_ context.Context, e *Event) {
			fmt.Println("enter_state")
		},
		"after_warn": func(_ context.Context, e *Event) {
			fmt.Println("after_warn")
		},
		"after_event": func(_ context.Context, e *Event) {
			fmt.Println("after_event")
		},
	},
)
fmt.Println(fsm.Current())
err := fsm.Event(context.Background(), "warn")
if err != nil {
	fmt.Println(err)
}
fmt.Println(fsm.Current())
Output:

green
before_warn
before_event
leave_green
leave_state
enter_yellow
enter_state
after_warn
after_event
yellow

func (*FSM) AvailableTransitions

func (f *FSM) AvailableTransitions() []string

AvailableTransitions returns a list of transitions available in the current state.

Example
fsm := NewFSM(
	"closed",
	Events{
		{Name: "open", Src: []string{"closed"}, Dst: "open"},
		{Name: "close", Src: []string{"open"}, Dst: "closed"},
		{Name: "kick", Src: []string{"closed"}, Dst: "broken"},
	},
	Callbacks{},
)
// sort the results ordering is consistent for the output checker
transitions := fsm.AvailableTransitions()
sort.Strings(transitions)
fmt.Println(transitions)
Output:

[kick open]

func (*FSM) Can

func (f *FSM) Can(event string) bool

Can returns true if event can occur in the current state.

Example
fsm := NewFSM(
	"closed",
	Events{
		{Name: "open", Src: []string{"closed"}, Dst: "open"},
		{Name: "close", Src: []string{"open"}, Dst: "closed"},
	},
	Callbacks{},
)
fmt.Println(fsm.Can("open"))
fmt.Println(fsm.Can("close"))
Output:

true
false

func (*FSM) Cannot

func (f *FSM) Cannot(event string) bool

Cannot returns true if event can not occur in the current state. It is a convenience method to help code read nicely.

Example
fsm := NewFSM(
	"closed",
	Events{
		{Name: "open", Src: []string{"closed"}, Dst: "open"},
		{Name: "close", Src: []string{"open"}, Dst: "closed"},
	},
	Callbacks{},
)
fmt.Println(fsm.Cannot("open"))
fmt.Println(fsm.Cannot("close"))
Output:

false
true

func (*FSM) Current

func (f *FSM) Current() string

Current returns the current state of the FSM.

Example
fsm := NewFSM(
	"closed",
	Events{
		{Name: "open", Src: []string{"closed"}, Dst: "open"},
		{Name: "close", Src: []string{"open"}, Dst: "closed"},
	},
	Callbacks{},
)
fmt.Println(fsm.Current())
Output:

closed

func (*FSM) DeleteMetadata added in v1.0.0

func (f *FSM) DeleteMetadata(key string)

DeleteMetadata deletes the dataValue in metadata by key

func (*FSM) Event

func (f *FSM) Event(ctx context.Context, event string, args ...interface{}) error

Event initiates a state transition with the named event.

The call takes a variable number of arguments that will be passed to the callback, if defined.

It will return nil if the state change is ok or one of these errors:

- event X inappropriate because previous transition did not complete

- event X inappropriate in current state Y

- event X does not exist

- internal error on state transition

The last error should never occur in this situation and is a sign of an internal bug.

Example
fsm := NewFSM(
	"closed",
	Events{
		{Name: "open", Src: []string{"closed"}, Dst: "open"},
		{Name: "close", Src: []string{"open"}, Dst: "closed"},
	},
	Callbacks{},
)
fmt.Println(fsm.Current())
err := fsm.Event(context.Background(), "open")
if err != nil {
	fmt.Println(err)
}
fmt.Println(fsm.Current())
err = fsm.Event(context.Background(), "close")
if err != nil {
	fmt.Println(err)
}
fmt.Println(fsm.Current())
Output:

closed
open
closed

func (*FSM) Is

func (f *FSM) Is(state string) bool

Is returns true if state is the current state.

Example
fsm := NewFSM(
	"closed",
	Events{
		{Name: "open", Src: []string{"closed"}, Dst: "open"},
		{Name: "close", Src: []string{"open"}, Dst: "closed"},
	},
	Callbacks{},
)
fmt.Println(fsm.Is("closed"))
fmt.Println(fsm.Is("open"))
Output:

true
false

func (*FSM) Metadata added in v0.3.0

func (f *FSM) Metadata(key string) (interface{}, bool)

Metadata returns the value stored in metadata

func (*FSM) SetMetadata added in v0.3.0

func (f *FSM) SetMetadata(key string, dataValue interface{})

SetMetadata stores the dataValue in metadata indexing it with key

func (*FSM) SetState

func (f *FSM) SetState(state string)

SetState allows the user to move to the given state from current state. The call does not trigger any callbacks, if defined.

func (*FSM) Transition

func (f *FSM) Transition() error

Transition wraps transitioner.transition.

Example
fsm := NewFSM(
	"closed",
	Events{
		{Name: "open", Src: []string{"closed"}, Dst: "open"},
		{Name: "close", Src: []string{"open"}, Dst: "closed"},
	},
	Callbacks{
		"leave_closed": func(_ context.Context, e *Event) {
			e.Async()
		},
	},
)
err := fsm.Event(context.Background(), "open")
if e, ok := err.(AsyncError); !ok && e.Err != nil {
	fmt.Println(err)
}
fmt.Println(fsm.Current())
err = fsm.Transition()
if err != nil {
	fmt.Println(err)
}
fmt.Println(fsm.Current())
Output:

closed
open

type InTransitionError

type InTransitionError struct {
	Event string
}

InTransitionError is returned by FSM.Event() when an asynchronous transition is already in progress.

func (InTransitionError) Error

func (e InTransitionError) Error() string

type InternalError

type InternalError struct{}

InternalError is returned by FSM.Event() and should never occur. It is a probably because of a bug.

func (InternalError) Error

func (e InternalError) Error() string

type InvalidEventError

type InvalidEventError struct {
	Event string
	State string
}

InvalidEventError is returned by FSM.Event() when the event cannot be called in the current state.

func (InvalidEventError) Error

func (e InvalidEventError) Error() string

type MermaidDiagramType added in v0.2.0

type MermaidDiagramType string

MermaidDiagramType the type of the mermaid diagram type

const (
	// FlowChart the diagram type for output in flowchart style (https://mermaid-js.github.io/mermaid/#/flowchart) (including current state)
	FlowChart MermaidDiagramType = "flowChart"
	// StateDiagram the diagram type for output in stateDiagram style (https://mermaid-js.github.io/mermaid/#/stateDiagram)
	StateDiagram MermaidDiagramType = "stateDiagram"
)

type NoTransitionError

type NoTransitionError struct {
	Err error
}

NoTransitionError is returned by FSM.Event() when no transition have happened, for example if the source and destination states are the same.

func (NoTransitionError) Error

func (e NoTransitionError) Error() string

type NotInTransitionError

type NotInTransitionError struct{}

NotInTransitionError is returned by FSM.Transition() when an asynchronous transition is not in progress.

func (NotInTransitionError) Error

func (e NotInTransitionError) Error() string

type UnknownEventError

type UnknownEventError struct {
	Event string
}

UnknownEventError is returned by FSM.Event() when the event is not defined.

func (UnknownEventError) Error

func (e UnknownEventError) Error() string

type VisualizeType added in v0.2.0

type VisualizeType string

VisualizeType the type of the visualization

const (
	// GRAPHVIZ the type for graphviz output (http://www.webgraphviz.com/)
	GRAPHVIZ VisualizeType = "graphviz"
	// MERMAID the type for mermaid output (https://mermaid-js.github.io/mermaid/#/stateDiagram) in the stateDiagram form
	MERMAID VisualizeType = "mermaid"
	// MermaidStateDiagram the type for mermaid output (https://mermaid-js.github.io/mermaid/#/stateDiagram) in the stateDiagram form
	MermaidStateDiagram VisualizeType = "mermaid-state-diagram"
	// MermaidFlowChart the type for mermaid output (https://mermaid-js.github.io/mermaid/#/flowchart) in the flow chart form
	MermaidFlowChart VisualizeType = "mermaid-flow-chart"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL