stateswitch

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Dec 19, 2024 License: Apache-2.0 Imports: 4 Imported by: 2

README

Actions Status

stateswitch

Overview

A simple and clear way to create and represent state machine.

sm := stateswitch.NewStateMachine()

// Define the state machine rules (and optionally document each rule)
sm.AddTransitionRule(stateswitch.TransitionRule{
    TransitionType:   TransitionTypeSetHwInfo,
    SourceStates:     stateswitch.States{StateDiscovering, StateKnown, StateInsufficient},
    DestinationState: StateKnown,
    Condition:        th.IsSufficient,
    Transition:       th.SetHwInfo,
    PostTransition:   th.PostSetHwInfo,
    Documentation: stateswitch.TransitionRuleDoc{
        Name:        "Move to known when receiving good hardware information",
        Description: "Once we receive hardware information from a server, we can consider it known if the hardware information is sufficient",
    },
})
sm.AddTransitionRule(stateswitch.TransitionRule{
    TransitionType:   TransitionTypeSetHwInfo,
    SourceStates:     stateswitch.States{StateDiscovering, StateKnown, StateInsufficient},
    DestinationState: StateInsufficient,
    Condition:        th.IsInsufficient,
    Transition:       th.SetHwInfo,
    PostTransition:   th.PostSetHwInfo,
    Documentation: stateswitch.TransitionRuleDoc{
        Name:        "Move to insufficient when receiving bad hardware information",
        Description: "Once we receive hardware infomration from a server, we consider the server to be insufficient if the hardware is insufficient",
    },
})
sm.AddTransitionRule(stateswitch.TransitionRule{
    TransitionType:   TransitionTypeRegister,
    SourceStates:     stateswitch.States{""},
    DestinationState: StateDiscovering,
    Condition:        nil,
    Transition:       nil,
    PostTransition:   th.RegisterNew,
    Documentation: stateswitch.TransitionRuleDoc{
        Name:        "Initial registration",
        Description: "A new server which registers enters our initial discovering state",
    },
})
sm.AddTransitionRule(stateswitch.TransitionRule{
    TransitionType:   TransitionTypeRegister,
    SourceStates:     stateswitch.States{StateDiscovering, StateKnown, StateInsufficient},
    DestinationState: StateDiscovering,
    Condition:        nil,
    Transition:       nil,
    PostTransition:   th.RegisterAgain,
    Documentation: stateswitch.TransitionRuleDoc{
        Name:        "Re-registration",
        Description: "We should ignore repeated registrations from servers that are already registered",
    },
})

// Document transition types (optional)
sm.DescribeTransitionType(TransitionTypeSetHwInfo, stateswitch.TransitionTypeDoc{
    Name:        "Set hardware info",
    Description: "Triggered for every hardware information change",
})
sm.DescribeTransitionType(TransitionTypeRegister, stateswitch.TransitionTypeDoc{
    Name:        "Register",
    Description: "Triggered when a server registers",
})

// Document possible states (optional)
sm.DescribeState(StateDiscovering, stateswitch.StateDoc{
    Name:        "Discovering",
    Description: "Indicates that the server has registered but we still don't know anything about its hardware",
})
sm.DescribeState(StateKnown, stateswitch.StateDoc{
    Name:        "Discovering",
    Description: "Indicates that the server has registered but we still don't know anything about its hardware",
})
sm.DescribeState(StateInsufficient, stateswitch.StateDoc{
    Name:        "Insufficient",
    Description: "Indicates that the server has sufficient hardware",
})

Usage

First your state object need to implement the state interface:

type StateSwitch interface {
	// State return current state
	State() State
	// SetState set a new state
	SetState(state State) error
}

Then you need to create state machine

sm := stateswitch.NewStateMachine()

Add transitions with the expected behavior

sm.AddTransitionRule(stateswitch.TransitionRule{
	TransitionType:   TransitionTypeSetHwInfo,
	SourceStates:     stateswitch.States{StateDiscovering, StateKnown, StateInsufficient},
	DestinationState: StateInsufficient,
	Condition:        th.IsInsufficient,
	Transition:       th.SetHwInfo,
	PostTransition:   th.PostSetHwInfo,
	Documentation: stateswitch.TransitionRuleDoc{
		Name:        "Example transition rule",
		Description: "Example documentation for transition rule",
	},
})

TransitionRule define the behavior that will be selected for your object by transition type, source state and conditions that you define. The first transition that will satisfy those requirements will be activated. Condtion, Transition, PostTranstion and Documentation are all optional, the transition may only change the state.

Since Condtion represent boolean entity, stateswitch provides means to create a combination of these entities from basic boolean operations: Not,And, Or. For example, rule with complex condition:

sm.AddTransitionRule(stateswitch.TransitionRule{
    TransitionType:   TransitionTypeSetHwInfo,
    SourceStates:     stateswitch.States{StateDiscovering, StateKnown, StateInsufficient},
    DestinationState: StatePending,
    Condition:        And(th.IsConnected, th.HasInventory, Not(th.RoleDefined)),
    Transition:       th.SetHwInfo,
    PostTransition:   th.PostSetHwInfo,
	Documentation: stateswitch.TransitionRuleDoc{
		Name:        "Example transition rule",
		Description: "Example documentation for transition rule",
	},
})

Run transition by type, state machine will select the right one for you.

h.sm.Run(TransitionTypeSetHwInfo, &stateHost{host: host}, &TransitionArgsSetHwInfo{hwInfo: hw})

for more details and full examples take a look at the examples section.

State machine representation

Once a state-machine has been initialized, you can generate a JSON file that describes it by using the AsJSON method:

machineJSON, err := sm.AsJSON()
if err != nil {
    panic(err)
}

fmt.Println(string(machineJSON))

This results in a JSON output that looks something like this. This file can be used, for example, for generating documentation or graphs for your state machine.

You can add the above code snippet to a dedicated binary that will generate the JSON and use it in your CI/CD, or you can have you can serve the JSON as an API endpoint - up to you.

Examples

Example can be found here

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	NoConditionPassedToRunTransaction = errors.New("no condition found to run transition")
	NoMatchForTransitionType          = errors.New("no match for transition type")
)

Functions

func NewStateMachine

func NewStateMachine() *stateMachine

Create new default state machine

Types

type Condition

type Condition func(stateSwitch StateSwitch, args TransitionArgs) (bool, error)

Condition for the transition, transition will be executed only if this function return true Can be nil, in this case it's considered as return true, nil Not mandatory

func And

func And(operands ...Condition) Condition

func Not

func Not(operand Condition) Condition

func Or

func Or(operands ...Condition) Condition

type PostTransition

type PostTransition func(stateSwitch StateSwitch, args TransitionArgs) error

PostTransition will be called if condition and transition are successful. Not mandatory

type State

type State string

type StateDoc

type StateDoc struct {
	// A human readable name for the state
	Name string

	// A more verbose description of the state
	Description string
}

type StateDocJSON

type StateDocJSON struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

type StateJSON

type StateJSON struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

type StateMachine

type StateMachine interface {
	// AddTransitionRule adds a new transition rule to the state machine
	AddTransitionRule(rule TransitionRule)
	// AddTransition is a deprecated method, use AddTransitionRule instead
	AddTransition(rule TransitionRule)
	// Run transition by type
	Run(transitionType TransitionType, stateSwitch StateSwitch, args TransitionArgs) error

	StateMachineDocumentation
}

type StateMachineDocumentation

type StateMachineDocumentation interface {
	// DescribeState lets you optionally add documentation for a particular
	// [State]. This description will be included in the JSON generated by
	// AsJSON
	DescribeState(state State, stateDocumentation StateDoc)

	// DescribeTransitionType lets you optionally add documentation for a
	// particular [TransitionType]. This description will be included in the
	// JSON generated by AsJSON
	DescribeTransitionType(transitionType TransitionType, transitionTypeDocumentation TransitionTypeDoc)

	// Generates a machine-readable JSON representation of the state machine
	// states and transitions. See StateMachineJSON for the format. Such JSON
	// can be used to generate documentation or to generate a state machine
	// diagram
	AsJSON() ([]byte, error)
}

type StateMachineJSON

type StateMachineJSON struct {
	TransitionRules []TransitionRuleJSON          `json:"transition_rules"`
	States          map[string]StateJSON          `json:"states"`
	TransitionTypes map[string]TransitionTypeJSON `json:"transition_types"`
}

type StateSwitch

type StateSwitch interface {
	// State return current state
	State() State
	// SetState set a new state
	SetState(state State) error
}

StateSwitch interface used by state machine

type States

type States []State

func (States) Contain

func (s States) Contain(state State) bool

Check it states list contain specific state

type Transition

type Transition func(stateSwitch StateSwitch, args TransitionArgs) error

Transition is users business logic, should not set the state or return next state If condition return true this function will be executed Not mandatory

type TransitionArgs

type TransitionArgs interface{}

type TransitionRule

type TransitionRule struct {
	TransitionType   TransitionType
	SourceStates     States
	DestinationState State
	Condition        Condition
	Transition       Transition
	PostTransition   PostTransition

	// Documentation for the transition rule, can be left empty
	Documentation TransitionRuleDoc
}

TransitionRule is a rule that defines the required source states and conditions needed to move to a particular destination state when a particular transition type happens

func (TransitionRule) IsAllowedToRun

func (tr TransitionRule) IsAllowedToRun(stateSwitch StateSwitch, args TransitionArgs) (bool, error)

IsAllowedToRun validate if current state supported, after then check the condition, if it pass then transition is a allowed. Nil condition is automatic approval.

type TransitionRuleDoc

type TransitionRuleDoc struct {
	// A short name for the transition rule
	Name string

	// A more verbose description of the transition rule
	Description string
}

type TransitionRuleJSON

type TransitionRuleJSON struct {
	TransitionType   TransitionType `json:"transition_type"`
	SourceStates     []string       `json:"source_states"`
	DestinationState string         `json:"destination_state"`
	Name             string         `json:"name"`
	Description      string         `json:"description"`
}

type TransitionRules

type TransitionRules []TransitionRule

func (TransitionRules) Find

func (tr TransitionRules) Find(transitionType TransitionType) TransitionRules

Find search for all matching transitions by transition type

type TransitionType

type TransitionType string

TransitionType reprents an event that can cause a state transition

type TransitionTypeDoc

type TransitionTypeDoc struct {
	// A human readable name for the transition type
	Name string

	// A more verbose description of the transition type
	Description string
}

type TransitionTypeJSON

type TransitionTypeJSON struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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