fsm

repository·main·Indexed 25 days ago

https://github.com/looplab/fsm

A finite state machine implementation for Go inspired by Javascript and Python FSM libraries. It allows developers to define states, valid transitions (events), and lifecycle callbacks. Features include support for asynchronous transitions, thread-safe metadata storage, and visualization exports for Graphviz and Mermaid (FlowChart and StateDiagram).

Tokens
3K
Snippets
4
Records
24
Agent score
86%

What's inside fsm

  1. Create a basic Finite State Machine

    main

    To create a new FSM, use fsm.NewFSM. You must provide an initial state, a list of fsm.Events, and fsm.Callbacks.

    An event is defined by:

    • Name: The name of the event.
    • Src: A slice of strings representing the valid source states.
    • Dst: The destination state.

    Use fsm.Current() to retrieve the current state and fsm.Event(ctx, eventName) to trigger a state transition. The Event method requires a context.Context.

    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())
    }
  2. Use FSM with callbacks and struct fields

    main

    You can embed an *fsm.FSM instance within a custom struct to manage the state of that object. You can also define fsm.Callbacks to execute logic during state transitions.

    Callbacks are defined in the fsm.Callbacks map. A common callback key is enter_state, which takes a function with the signature func(context.Context, *fsm.Event). The *fsm.Event object provides access to the destination state via the Dst field.

    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)
        }
    }
  3. Check current state and availability

    main

    Use these methods to query the status of the FSM:

    • Current(): Returns the current state as a string.
    • Is(state string): Returns true if the FSM is currently in the specified state.
    • Can(event string): Returns true if the specified event can occur in the current state.
    • Cannot(event string): Returns the inverse of Can.
    • AvailableTransitions(): Returns a slice of event names ([]string) that are valid for the current state.
  4. Trigger state transitions with Event

    main

    Use the Event method to initiate a state transition by name. It accepts a context.Context and a variable number of arguments (args ...interface{}) which are passed to any defined callbacks.

    Possible errors returned by Event:

    • InTransitionError: A transition is already in progress.
    • InvalidEventError: The event is valid but not allowed in the current state.
    • UnknownEventError: The event name does not exist in the FSM configuration.
    • NoTransitionError: The event is valid but the destination state is the same as the current state.
    • CanceledError: The transition was canceled (e.g., via context).
    • AsyncError: The transition was initiated asynchronously (via a leave_ callback).
  5. Complete asynchronous transitions with Transition

    main
    If a leave_<STATE> callback has initiated an asynchronous transition (by calling Async on the event), you must call Transition() to complete the state change.
  6. Initialize a new FSM with NewFSM

    main

    Construct a finite state machine using NewFSM. You must provide an initial state, a slice of EventDesc defining valid transitions, and a map of Callback functions for lifecycle hooks.

    An EventDesc defines a transition where Src is a slice of valid source states and Dst is the destination state.

    Callbacks are mapped using specific naming conventions:

    • before_<EVENT>: Called before a specific event.
    • before_event: Called before any event.
    • leave_<STATE>: Called before leaving a specific state.
    • leave_state: Called before leaving any state.
    • enter_<STATE>: Called after entering a specific state.
    • enter_state: Called after entering any state.
    • after_<EVENT>: Called after a specific event.
    • after_event: Called after any event.

    Shorthand versions also work: <STATE> triggers an enter callback, and <EVENT> triggers an after callback.

  7. Visualize an FSM using Graphviz format

    main
    The Visualize function generates a string representation of a Finite State Machine (FSM) in Graphviz DOT format. This string can be used to render a visual diagram of the state machine's transitions and states. The current state of the FSM is highlighted in the output by setting its color to red.
  8. Export FSM to Mermaid diagram

    main

    Use VisualizeForMermaidWithGraphType to generate a Mermaid-compatible string representation of your Finite State Machine (FSM). You can choose between two diagram styles: FlowChart (which includes visual highlighting of the current state) or StateDiagram (standard state diagram syntax).

    Returns the Mermaid string or an error if an unsupported MermaidDiagramType is provided.

  9. Visualize an FSM with VisualizeWithType

    main

    Use VisualizeWithType to generate a string representation of your FSM's state transitions in a specific format. This is useful for generating diagrams for documentation or debugging.

    Supported VisualizeType values:

    • GRAPHVIZ: Generates Graphviz output (compatible with http://www.webgraphviz.com/).
    • MERMAID: Generates Mermaid output in stateDiagram form.
    • MermaidStateDiagram: Generates Mermaid output in stateDiagram form.
    • MermaidFlowChart: Generates Mermaid output in flowchart form.

    If an unsupported type is provided, it returns an error.

  10. Force state changes with SetState

    main

    The SetState(state string) method allows you to manually move the FSM to a specific state.

    Warning: This method does not trigger any callbacks (before, leave, enter, or after).

  11. Handle Unknown Event Errors

    main
    The UnknownEventError is returned by FSM.Event() when the provided event name has not been defined in the state machine configuration. It contains the name of the Event.