An open API service indexing awesome lists of open source software.

https://github.com/bykof/stateful

Finite state machine for Go
https://github.com/bykof/stateful

go golang graph state-machine transitions

Last synced: 6 months ago
JSON representation

Finite state machine for Go

Awesome Lists containing this project

README

          

Welcome to stateful 👋




Travis CI


License: MIT




Twitter: michaelbykovski



> Create easy state machines with your existing code

# Table of Contents
1. [Documentation](#documentation)
2. [Usage](#usage)
3. [Draw graph](#draw-graph)
4. [Wildcards](#wildcards)
5. [Examples](#examples)
6. [Credits](#credits)

## Documentation

You can find the documentation here: [https://pkg.go.dev/github.com/bykof/stateful?tab=doc](https://pkg.go.dev/github.com/bykof/stateful?tab=doc)

## Usage

It is very easy to use stateful.
Just create a struct and implement the `stateful` interface
```go
import "github.com/bykof/stateful"

type (
MyMachine struct {
state stateful.State
amount int
}

AmountParams struct {
Amount int
}
)

func NewMyMachine() MyMachine {
return MyMachine{
state: A,
amount: 0,
}
}

// State implement interface stateful
func (mm MyMachine) State() stateful.State {
return mm.state
}

// SetState implement interface stateful
func (mm *MyMachine) SetState(state stateful.State) error {
mm.state = state
return nil
}
```

Declare some proper states:
```go
const (
A = stateful.DefaultState("A")
B = stateful.DefaultState("B")
)
```

Then add some transitions to the machine:
```go
// Declare a transition of you machine and return the new state of the machine.
func (mm *MyMachine) FromAToB(transitionArguments stateful.TransitionArguments) (stateful.State, error) {
amountParams, ok := transitionArguments.(AmountParams)
if !ok {
return nil, errors.New("could not parse AmountParams")
}

mm.amount += amountParams.Amount
return B, nil
}

func (mm *MyMachine) FromBToA(transitionArguments stateful.TransitionArguments) (stateful.State, error) {
amountParams, ok := transitionArguments.(AmountParams)
if !ok {
return nil, errors.New("could not parse AmountParams")
}

mm.amount -= amountParams.Amount
return A, nil
}

// The state machine will check, if you transfer to a proper and defined state in the machine. See below.
func (mm *MyMachine) FromAToNotExistingC(_ stateful.TransitionArguments) (stateful.State, error) {
return stateful.DefaultState("C")
}
```

And now initialize the machine:
```go
myMachine := NewMyMachine()
stateMachine := &stateful.StateMachine{
StatefulObject: &myMachine,
}

stateMachine.AddTransition(
// The transition function
myMachine.FromAToB,
// SourceStates
stateful.States{A},
// DestinationStates
stateful.States{B},
)

stateMachine.AddTransition(
myMachine.FromBToA,
stateful.States{B},
stateful.States{A},
)
```

Everything is done! Now run the machine:
```go
_ := stateMachine.Run(
// The transition function
myMachine.FromAToB,
// The transition params which will be passed to the transition function
stateful.TransitionArguments(AmountParams{Amount: 1}),
)

_ = stateMachine.Run(
myMachine.FromBToA,
stateful.TransitionArguments(AmountParams{Amount: 1}),
)

err := stateMachine.Run(
myMachine.FromBToA,
stateful.TransitionArguments(AmountParams{Amount: 1}),
)

// We cannot run the transition "FromBToA" from current state "A"...
if err != nil {
log.Fatal(err) // will print: you cannot run FromAToB from state A
}

// We cannot transfer the machine with current transition to returned state "C"
err = stateMachine.Run(
myMachine.FromAToNotExistingC,
stateful.TransitionArguments(nil),
)

if err != nil {
log.Fatal(err) // will print: you cannot transfer to state C
}
```

That's it!

## Draw graph

You can draw a graph of your state machine in `dot` format of graphviz.

Just pass in your created statemachine into the StateMachineGraph.

```go
import "github.com/bykof/stateful/src/statefulGraph"
stateMachineGraph := statefulGraph.StateMachineGraph{StateMachine: *stateMachine}
_ = stateMachineGraph.DrawGraph()
```

This will print following to the console:
```
digraph {
A->B[ label="FromAToB" ];
B->A[ label="FromBToA" ];
A;
B;

}
```

which is actually this graph:

![MyMachine Transition Graph](https://github.com/bykof/stateful/raw/master/docs/resources/myMachine.png)

## Wildcards

You can also address wildcards as SourceStates or DestinationStates

```
stateMachine.AddTransition(
myMachine.FromBToAllStates,
stateful.States{B},
stateful.States{stateful.AllStates},
)
```

This will give you the opportunity to jump e.g. B to AllStates.

*Keep in mind that `AllStates` creates a lot of complexity and maybe a missbehavior.
So use it only if you are knowing what you are doing*

## Examples

Have a look at the examples: [examples](https://github.com/bykof/stateful/tree/master/examples)

## Credits

Thank you [calhoun](https://www.calhoun.io/) for the sweet gopher image!

## Run tests

```sh
go test ./...
```

## Author

👤 **Michael Bykovski**

* Twitter: [@michaelbykovski](https://twitter.com/michaelbykovski)
* Github: [@bykof](https://github.com/bykof)

## Show your support

Give a ⭐️ if this project helped you!

## 📝 License

Copyright © 2019 [Michael Bykovski](https://github.com/bykof).

This project is [MIT](https://opensource.org/licenses/MIT) licensed.