fsm

package
v0.10.3 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

README

FSM

fsm is a small finite state machine package for polymorphic state objects. Its core model is: the same business object delegates a behavior to its current state, and different concrete states implement that behavior differently.

For example, an unpaid order and a paid order can both expose Pay(), but UnpaidOrderState.Pay() performs the payment while PaidOrderState.Pay() returns an error because the order is already paid.

In the order example below, an unpaid order cannot cancel or refund. A paid order can be canceled, which means a full refund and a terminal canceled state. A paid order can also receive partial refunds; if balance remains, it becomes a partially refunded order. A partially refunded order can be refunded repeatedly, can be canceled for the remaining balance, and cannot be paid again.

Responsibilities

The FSM package owns:

  • transition definitions: from + action -> to
  • transition guards as edge metadata through Condition
  • exposing candidate transition edges through RuntimeStateMachine.Transitions
  • constructing the target state through a registered StateBuilder
  • the standard transition flow through Transit
  • checking that transitions and builders are wired consistently
  • freezing configured machines into a read-only runtime surface

The consumer owns:

  • the business object that implements StateContext
  • the current state field on that object
  • the state behavior interface, such as Pay, Refund, and Cancel
  • a base/default state implementation whose behavior methods return errors
  • concrete state implementations that accept, reject, or vary each behavior
  • accepting the next state through SetState, including type checks, recording events, or incrementing versions
  • concurrency control around the business object

Setup Pattern

Define stable labels and actions:

const (
	StateUnpaid            fsm.StateLabel = "order.unpaid"
	StatePaid              fsm.StateLabel = "order.paid"
	StatePartialRefunded   fsm.StateLabel = "order.partial_refunded"
	StateCanceled          fsm.StateLabel = "order.canceled"

	ActionPay    fsm.Action = "pay"
	ActionRefund fsm.Action = "refund"
	ActionCancel fsm.Action = "cancel"
)

Define a state behavior interface and concrete states:

type OrderState interface {
	fsm.State
	Pay(amount int, method string) (string, error)
	Refund(amount int) error
	Cancel() error
}

type BaseOrderState struct {
	*fsm.SimpleState
}

func (state *BaseOrderState) Pay(int, string) (string, error) {
	return fmt.Errorf("%s cannot pay", state.Label())
}

func (state *BaseOrderState) Refund(int) error {
	return fmt.Errorf("%s cannot refund", state.Label())
}

func (state *BaseOrderState) Cancel() error {
	return fmt.Errorf("%s cannot cancel", state.Label())
}

type UnpaidOrderState struct {
	BaseOrderState
}

func (state *UnpaidOrderState) Pay(amount int, method string) (string, error) {
	order := state.Context().(*Order)
	order.paidAmount = amount
	order.paymentMethod = method
	return fmt.Sprintf("receipt:%s:%d", method, amount), nil
}

type PaidOrderState struct {
	BaseOrderState
}

func (state *PaidOrderState) Pay(int, string) (string, error) {
	return "", errors.New("order has already been paid")
}

func (state *PaidOrderState) Refund(amount int) error {
	order := state.Context().(*Order)
	if amount > order.remainingAmount() {
		return errors.New("refund amount exceeds remaining amount")
	}
	order.refundedAmount += amount
	return nil
}

func (state *PaidOrderState) Cancel() error {
	order := state.Context().(*Order)
	order.refundedAmount = order.paidAmount
	order.canceled = true
	return nil
}

type PartialRefundedOrderState struct {
	BaseOrderState
}

func (state *PartialRefundedOrderState) Pay(int, string) (string, error) {
	return "", errors.New("partially refunded order cannot be paid again")
}

func (state *PartialRefundedOrderState) Refund(amount int) error {
	order := state.Context().(*Order)
	if amount > order.remainingAmount() {
		return errors.New("refund amount exceeds remaining amount")
	}
	order.refundedAmount += amount
	return nil
}

func (state *PartialRefundedOrderState) Cancel() error {
	order := state.Context().(*Order)
	order.refundedAmount = order.paidAmount
	order.canceled = true
	return nil
}

Register every state builder used by transitions, then add transitions. The registered builder should return the concrete state implementation for that label:

sm := fsm.NewStateMachine("order")
if err := sm.RegisterStateBuilder(StateUnpaid, func() fsm.State {
	return &UnpaidOrderState{
		BaseOrderState: BaseOrderState{
			SimpleState: fsm.NewSimpleState(StateUnpaid),
		},
	}
}); err != nil {
	return err
}
if err := sm.RegisterStateBuilder(StatePaid, func() fsm.State {
	return &PaidOrderState{
		BaseOrderState: BaseOrderState{
			SimpleState: fsm.NewSimpleState(StatePaid),
		},
	}
}); err != nil {
	return err
}
if err := sm.RegisterStateBuilder(StatePartialRefunded, NewPartialRefundedOrderState); err != nil {
	return err
}
if err := sm.RegisterStateBuilder(StateCanceled, NewCanceledOrderState); err != nil {
	return err
}
if err := sm.AddTransition(StateUnpaid, StatePaid, ActionPay, nil); err != nil {
	return err
}
if err := sm.AddTransition(StatePaid, StatePartialRefunded, ActionRefund, func(sc fsm.StateContext) bool {
	return sc.(*Order).HasRemainingRefundAmount()
}); err != nil {
	return err
}
if err := sm.AddTransition(StatePaid, StateCanceled, ActionRefund, func(sc fsm.StateContext) bool {
	return sc.(*Order).IsFullyRefunded()
}); err != nil {
	return err
}
if err := sm.AddTransition(StatePaid, StateCanceled, ActionCancel, nil); err != nil {
	return err
}
if err := sm.AddTransition(StatePartialRefunded, StatePartialRefunded, ActionRefund, func(sc fsm.StateContext) bool {
	return sc.(*Order).HasRemainingRefundAmount()
}); err != nil {
	return err
}
if err := sm.AddTransition(StatePartialRefunded, StateCanceled, ActionRefund, func(sc fsm.StateContext) bool {
	return sc.(*Order).IsFullyRefunded()
}); err != nil {
	return err
}
if err := sm.AddTransition(StatePartialRefunded, StateCanceled, ActionCancel, nil); err != nil {
	return err
}

if err := sm.Check(); err != nil {
	return err
}
if err := fsm.RegisterStateMachine(sm); err != nil {
	return err
}

Call Check during startup or in tests. It catches missing state builders, registered builders that are not referenced by any transition, and builders that return nil or a state with the wrong label. RegisterStateMachine also calls Check and freezes build-time machines before storing them, so machines returned by GetStateMachine and MustGetStateMachine expose the read-only RuntimeStateMachine surface. If you do not use the registry, call Freeze after setup and pass the returned RuntimeStateMachine to runtime code.

Runtime Pattern

The business object implements StateContext:

type Order struct {
	stateMachineName string
	state            OrderState
	paidAmount     int
	paymentMethod  string
	refundedAmount int
	canceled       bool
}

func (order *Order) remainingAmount() int {
	return order.paidAmount - order.refundedAmount
}

func (order *Order) HasRemainingRefundAmount() bool {
	return order.remainingAmount() > 0
}

func (order *Order) IsFullyRefunded() bool {
	return order.remainingAmount() == 0
}

func NewOrder() *Order {
	initial := NewUnpaidOrderState().(OrderState)
	order := &Order{
		stateMachineName: "order",
		state:            initial,
	}
	initial.SetContext(order)
	return order
}

func (order *Order) CurrentState() fsm.State {
	return order.state
}

func (order *Order) transition(action fsm.Action) error {
	return fsm.Transit(order, fsm.MustGetStateMachine(order.stateMachineName), action)
}

func (order *Order) SetState(next fsm.State) error {
	state, ok := next.(OrderState)
	if !ok {
		return errors.New("next state is not an order state")
	}
	order.state = state
	return nil
}

A business operation should usually follow this order:

func (order *Order) Pay(amount int, method string) (string, error) {
	receipt, err := order.CurrentState().(OrderState).Pay(amount, method)
	if err != nil {
		return "", err
	}
	if err := order.transition(ActionPay); err != nil {
		return "", err
	}
	return receipt, nil
}

func (order *Order) Refund(amount int) error {
	if err := order.CurrentState().(OrderState).Refund(amount); err != nil {
		return err
	}
	return order.transition(ActionRefund)
}

func (order *Order) Cancel() error {
	if err := order.CurrentState().(OrderState).Cancel(); err != nil {
		return err
	}
	return order.transition(ActionCancel)
}

Do not put the business side effect in StateMachine, and do not use HasTransition as the primary permission check before calling the state method. The concrete state method decides whether the behavior is allowed. For an already-paid order, PaidOrderState.Pay() should return the business error. After the state behavior succeeds, the private transition helper asks fsm.Transit to perform the standard state-change flow. Transit reads candidate edges from RuntimeStateMachine.Transitions, evaluates each edge's Condition, builds the matched state with BuildState, calls next.SetContext(order), and delegates assignment to order.SetState.

Keep FSM plumbing out of the public business method signature. Callers should call order.Pay(), not order.Pay(sm).

Use fsm.Transit for the transition loop instead of reimplementing it on every business object. Keep business-specific acceptance rules in SetState, where the aggregate can type-check the next state and record any domain events or version changes.

A useful pattern is to embed a base state that implements every behavior method with a default error. Each concrete state overrides only the behaviors it supports or wants to reject with a more specific error. That keeps state-specific behavior on the state type itself instead of accumulating if/else or switch checks on the business object.

Conditions

Condition is a transition guard: it decides whether a specific transition edge from one state to another state is allowed. It belongs in the transition table, but is evaluated by fsm.Transit, not by StateMachine.

sm.AddTransition(StatePaid, StatePartialRefunded, ActionRefund, func(sc fsm.StateContext) bool {
	order := sc.(*Order)
	return order.HasRemainingRefundAmount()
})
sm.AddTransition(StatePaid, StateCanceled, ActionRefund, func(sc fsm.StateContext) bool {
	order := sc.(*Order)
	return order.IsFullyRefunded()
})

Prefer calling business methods on the aggregate instead of reading or recomputing its fields in transition registration code.

The call chain is explicit: order.Refund(amount) first delegates to CurrentState().(OrderState).Refund(amount). If that method succeeds, it calls the private order.transition(ActionRefund) helper. That helper calls fsm.Transit(order, sm, ActionRefund). Transit reads candidates from RuntimeStateMachine.Transitions, evaluates each registered Condition with the order as StateContext, builds the matched state with BuildState, sets the next state's context, and calls order.SetState.

Important details:

  • nil condition means unconditional.
  • Transitions for the same from + action are checked in the order they were added.
  • The first matching transition wins.
  • If no condition matches, Transit leaves the current state unchanged.
  • Put unconditional transitions after conditional transitions when both share the same from + action.

Common Agent Mistakes

  • Treating the transition table as the behavior model. The behavior model is the current state's method implementation.
  • Putting state-specific behavior behind if/else or switch in the business object instead of overriding methods on concrete states.
  • Forgetting to give the base state default error implementations for behavior methods that are unavailable in most states.
  • Calling HasTransition before the state method and accidentally bypassing errors from states like PaidOrderState.Pay().
  • Calling the transition helper before executing the current state's business method.
  • Updating the business object's state without going through fsm.Transit.
  • Forgetting to call SetContext on the state returned by BuildState inside Transit.
  • Treating Condition as the only business validation instead of a transition guard for one from -> to edge.
  • Registering a transition target without a matching StateBuilder.
  • Registering only fsm.NewSimpleState for states that need behavior methods.
  • Adding an unconditional transition before a conditional transition for the same from + action.

See example_test.go for an executable usage example.

Documentation

Overview

Package fsm provides small finite state machine primitives for polymorphic state objects.

The central model is: the same business object delegates a behavior to its current State, and different concrete states implement that behavior differently. For example, UnpaidOrderState.Pay may perform the payment, while PaidOrderState.Pay returns an error because the order is already paid.

The package owns transition definitions and target state construction after a behavior has succeeded. The consumer owns the business object, its current concrete state, state-specific behavior methods, and the side effects that happen during a transition.

A common consumer pattern is to define a base state whose behavior methods return default errors, then embed it in concrete states and override only the methods supported by that state. That keeps state-specific behavior in concrete state types instead of adding if/else or switch checks to the business object.

The usual setup flow is:

  • create a StateMachine with NewStateMachine
  • register every state builder used by transitions
  • add transitions with AddTransition
  • call Check during application startup or tests
  • call Freeze, or register the machine with RegisterStateMachine, before using it at runtime

The usual runtime flow is:

  • the business object implements StateContext
  • each business method type-asserts CurrentState to the business state interface and executes behavior with natural parameters and return values
  • if behavior succeeds, call Transit with the business object, a RuntimeStateMachine, and action, usually through a private helper on the business object
  • Transit gets candidate edges from the RuntimeStateMachine, evaluates their conditions, builds the matching state, sets its context, and delegates state replacement to StateContext.SetState

Keep state-machine lookup and transition plumbing out of public business method signatures. Prefer methods such as Order.Pay() that delegate to the current state and then transition after the behavior succeeds.

StateContext.SetState is intentionally implemented by the business object so it can type-check the next state and keep any domain side effects local.

Do not use HasTransition as the primary business permission check before calling the current state's behavior method. That bypasses state polymorphism: an invalid behavior should normally be rejected by the concrete state method itself.

A Condition is a transition guard evaluated by Transit to decide whether a specific from-state to to-state edge is allowed. A nil condition is unconditional. If transitions exist for the current state and action but no condition matches, Transit leaves the current state unchanged.

StateMachine protects its transition and builder tables with a mutex and can be frozen into the read-only RuntimeStateMachine surface, but it does not synchronize the consumer's StateContext. The consumer is responsible for any concurrency control around its business object.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterStateMachine

func RegisterStateMachine(sm RuntimeStateMachine) error

func Transit added in v0.10.0

func Transit(context StateContext, sm RuntimeStateMachine, action Action) error

Transit executes the standard transition flow for a StateContext.

Types

type Action

type Action string

Action define the action type.

type Condition added in v0.6.2

type Condition func(StateContext) bool

Condition guards whether a specific transition edge is allowed.

type RuntimeStateMachine added in v0.10.1

type RuntimeStateMachine interface {
	Name() string                                // get the state machine name
	HasTransition(StateLabel, Action) bool       // check if has transition from a state by action
	Transitions(StateLabel, Action) []Transition // get transition edges from a state by action
	BuildState(StateLabel) (State, error)        // build a state by label
	Check() error                                // check the completeness of States and Transitions
}

RuntimeStateMachine is the read-only state machine surface used at runtime.

func GetStateMachine

func GetStateMachine(name string) (RuntimeStateMachine, bool)

func MustGetStateMachine

func MustGetStateMachine(name string) RuntimeStateMachine

type SimpleState

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

SimpleState is a reusable State implementation for concrete state structs that only need context and label storage.

func NewSimpleState

func NewSimpleState(label StateLabel) *SimpleState

func (*SimpleState) Context

func (s *SimpleState) Context() StateContext

func (*SimpleState) Label

func (s *SimpleState) Label() StateLabel

func (*SimpleState) SetContext

func (s *SimpleState) SetContext(context StateContext)

type State

type State interface {
	SetContext(StateContext) // set the state context
	Context() StateContext   // return the state context
	Label() StateLabel       // get the state label
}

State define the state interface.

type StateBuilder

type StateBuilder func() State

StateBuilder define the state builder interface.

type StateBuilderError added in v0.10.0

type StateBuilderError struct {
	State  StateLabel
	Actual StateLabel
}

StateBuilderError reports a missing, nil, or label-mismatched state builder.

func NewStateBuilderError added in v0.10.0

func NewStateBuilderError(state StateLabel) *StateBuilderError

func NewStateBuilderMismatchError added in v0.10.1

func NewStateBuilderMismatchError(expected, actual StateLabel) *StateBuilderError

func (*StateBuilderError) Error added in v0.10.0

func (sbe *StateBuilderError) Error() string

type StateContext

type StateContext interface {
	CurrentState() State       // get the current state
	SetState(next State) error // replace the current state
}

StateContext define the state context interface.

type StateLabel

type StateLabel string

StateLabel define the state type.

type StateMachine

type StateMachine interface {
	RuntimeStateMachine
	AddTransition(StateLabel, StateLabel, Action, Condition) error // add a transition
	RegisterStateBuilder(StateLabel, StateBuilder) error           // register a state
	Freeze() (RuntimeStateMachine, error)                          // validate and make the machine read-only
}

StateMachine is the build-time state machine surface.

Example (OrderPayment)
package main

import (
	"errors"
	"fmt"

	"github.com/go-jimu/components/fsm"
)

const (
	exampleStateMachineName                    = "order"
	exampleStateUnpaid          fsm.StateLabel = "order.unpaid"
	exampleStatePaid            fsm.StateLabel = "order.paid"
	exampleStatePartialRefunded fsm.StateLabel = "order.partial_refunded"
	exampleStateCanceled        fsm.StateLabel = "order.canceled"
	exampleActionPay            fsm.Action     = "pay"
	exampleActionRefund         fsm.Action     = "refund"
	exampleActionCancel         fsm.Action     = "cancel"
)

type exampleOrder struct {
	stateMachineName string
	state            exampleOrderState
	paidAmount       int
	paymentMethod    string
	refundedAmount   int
	canceled         bool
}

func (order *exampleOrder) remainingAmount() int {
	return order.paidAmount - order.refundedAmount
}

func (order *exampleOrder) hasRemainingRefundAmount() bool {
	return order.remainingAmount() > 0
}

func (order *exampleOrder) isFullyRefunded() bool {
	return order.remainingAmount() == 0
}

type exampleOrderState interface {
	fsm.State
	Pay(amount int, method string) (string, error)
	Refund(amount int) error
	Cancel() error
}

type exampleBaseOrderState struct {
	*fsm.SimpleState
}

type exampleUnpaidOrderState struct {
	exampleBaseOrderState
}

type examplePaidOrderState struct {
	exampleBaseOrderState
}

type examplePartialRefundedOrderState struct {
	exampleBaseOrderState
}

type exampleCanceledOrderState struct {
	exampleBaseOrderState
}

func newExampleOrder() *exampleOrder {
	initial := newExampleUnpaidOrderState().(exampleOrderState)
	order := &exampleOrder{
		stateMachineName: exampleStateMachineName,
		state:            initial,
	}
	initial.SetContext(order)
	return order
}

func (order *exampleOrder) CurrentState() fsm.State {
	return order.state
}

func (order *exampleOrder) transition(action fsm.Action) error {
	return fsm.Transit(order, fsm.MustGetStateMachine(order.stateMachineName), action)
}

func (order *exampleOrder) SetState(next fsm.State) error {
	state, ok := next.(exampleOrderState)
	if !ok {
		return errors.New("next state is not an order state")
	}
	order.state = state
	return nil
}

func (order *exampleOrder) Pay(amount int, method string) (string, error) {
	receipt, err := order.CurrentState().(exampleOrderState).Pay(amount, method)
	if err != nil {
		return "", err
	}
	if err := order.transition(exampleActionPay); err != nil {
		return "", err
	}
	return receipt, nil
}

func (order *exampleOrder) Refund(amount int) error {
	if err := order.CurrentState().(exampleOrderState).Refund(amount); err != nil {
		return err
	}
	return order.transition(exampleActionRefund)
}

func (order *exampleOrder) Cancel() error {
	if err := order.CurrentState().(exampleOrderState).Cancel(); err != nil {
		return err
	}
	return order.transition(exampleActionCancel)
}

func (state *exampleBaseOrderState) Pay(int, string) (string, error) {
	return "", fmt.Errorf("%s cannot pay", state.Label())
}

func (state *exampleBaseOrderState) Refund(int) error {
	return fmt.Errorf("%s cannot refund", state.Label())
}

func (state *exampleBaseOrderState) Cancel() error {
	return fmt.Errorf("%s cannot cancel", state.Label())
}

func (state *exampleUnpaidOrderState) Pay(amount int, method string) (string, error) {
	if amount <= 0 {
		return "", errors.New("payment amount must be positive")
	}
	order := state.Context().(*exampleOrder)
	order.paidAmount = amount
	order.paymentMethod = method
	return fmt.Sprintf("receipt:%s:%d", method, amount), nil
}

func (state *examplePaidOrderState) Pay(int, string) (string, error) {
	return "", errors.New("order has already been paid")
}

func (state *examplePaidOrderState) Refund(amount int) error {
	if amount <= 0 {
		return errors.New("refund amount must be positive")
	}
	order := state.Context().(*exampleOrder)
	if amount > order.remainingAmount() {
		return errors.New("refund amount exceeds remaining amount")
	}
	order.refundedAmount += amount
	return nil
}

func (state *examplePaidOrderState) Cancel() error {
	order := state.Context().(*exampleOrder)
	order.refundedAmount = order.paidAmount
	order.canceled = true
	return nil
}

func (state *examplePartialRefundedOrderState) Pay(int, string) (string, error) {
	return "", errors.New("partially refunded order cannot be paid again")
}

func (state *examplePartialRefundedOrderState) Refund(amount int) error {
	if amount <= 0 {
		return errors.New("refund amount must be positive")
	}
	order := state.Context().(*exampleOrder)
	if amount > order.remainingAmount() {
		return errors.New("refund amount exceeds remaining amount")
	}
	order.refundedAmount += amount
	return nil
}

func (state *examplePartialRefundedOrderState) Cancel() error {
	order := state.Context().(*exampleOrder)
	order.refundedAmount = order.paidAmount
	order.canceled = true
	return nil
}

func newExampleUnpaidOrderState() fsm.State {
	return &exampleUnpaidOrderState{
		exampleBaseOrderState: exampleBaseOrderState{
			SimpleState: fsm.NewSimpleState(exampleStateUnpaid),
		},
	}
}

func newExamplePaidOrderState() fsm.State {
	return &examplePaidOrderState{
		exampleBaseOrderState: exampleBaseOrderState{
			SimpleState: fsm.NewSimpleState(exampleStatePaid),
		},
	}
}

func newExamplePartialRefundedOrderState() fsm.State {
	return &examplePartialRefundedOrderState{
		exampleBaseOrderState: exampleBaseOrderState{
			SimpleState: fsm.NewSimpleState(exampleStatePartialRefunded),
		},
	}
}

func newExampleCanceledOrderState() fsm.State {
	return &exampleCanceledOrderState{
		exampleBaseOrderState: exampleBaseOrderState{
			SimpleState: fsm.NewSimpleState(exampleStateCanceled),
		},
	}
}

func newExampleOrderStateMachine() fsm.StateMachine {
	sm := fsm.NewStateMachine(exampleStateMachineName)
	sm.RegisterStateBuilder(exampleStateUnpaid, newExampleUnpaidOrderState)
	sm.RegisterStateBuilder(exampleStatePaid, newExamplePaidOrderState)
	sm.RegisterStateBuilder(exampleStatePartialRefunded, newExamplePartialRefundedOrderState)
	sm.RegisterStateBuilder(exampleStateCanceled, newExampleCanceledOrderState)
	sm.AddTransition(exampleStateUnpaid, exampleStatePaid, exampleActionPay, nil)
	sm.AddTransition(exampleStatePaid, exampleStatePartialRefunded, exampleActionRefund, func(sc fsm.StateContext) bool {
		return sc.(*exampleOrder).hasRemainingRefundAmount()
	})
	sm.AddTransition(exampleStatePaid, exampleStateCanceled, exampleActionRefund, func(sc fsm.StateContext) bool {
		return sc.(*exampleOrder).isFullyRefunded()
	})
	sm.AddTransition(exampleStatePaid, exampleStateCanceled, exampleActionCancel, nil)
	sm.AddTransition(exampleStatePartialRefunded, exampleStatePartialRefunded, exampleActionRefund, func(sc fsm.StateContext) bool {
		return sc.(*exampleOrder).hasRemainingRefundAmount()
	})
	sm.AddTransition(exampleStatePartialRefunded, exampleStateCanceled, exampleActionRefund, func(sc fsm.StateContext) bool {
		return sc.(*exampleOrder).isFullyRefunded()
	})
	sm.AddTransition(exampleStatePartialRefunded, exampleStateCanceled, exampleActionCancel, nil)
	return sm
}

func main() {
	sm := newExampleOrderStateMachine()
	if err := sm.Check(); err != nil {
		fmt.Println(err)
		return
	}
	fsm.RegisterStateMachine(sm)

	paidOrder := newExampleOrder()
	receipt, _ := paidOrder.Pay(42, "card")
	fmt.Println(receipt, paidOrder.CurrentState().Label())
	_ = paidOrder.Refund(12)
	fmt.Println(paidOrder.CurrentState().Label(), paidOrder.refundedAmount, paidOrder.remainingAmount())
	_ = paidOrder.Refund(10)
	fmt.Println(paidOrder.CurrentState().Label(), paidOrder.refundedAmount, paidOrder.remainingAmount())
	fmt.Println(paidOrder.Pay(5, "card"))
	_ = paidOrder.Cancel()
	fmt.Println(paidOrder.CurrentState().Label(), paidOrder.refundedAmount, paidOrder.canceled)

	unpaidOrder := newExampleOrder()
	fmt.Println(unpaidOrder.Cancel())
	_, _ = unpaidOrder.Pay(10, "cash")
	_ = unpaidOrder.Cancel()
	fmt.Println(unpaidOrder.CurrentState().Label(), unpaidOrder.refundedAmount, unpaidOrder.canceled)
}
Output:
receipt:card:42 order.paid
order.partial_refunded 12 30
order.partial_refunded 22 20
 partially refunded order cannot be paid again
order.canceled 42 true
order.unpaid cannot cancel
order.canceled 10 true

func NewStateMachine

func NewStateMachine(name string) StateMachine

type StateMachineCheckError added in v0.10.1

type StateMachineCheckError struct {
	MissingStateBuilders      []StateLabel
	UnreferencedStateBuilders []StateLabel
	InvalidStateBuilders      []StateBuilderError
}

StateMachineCheckError reports state-machine wiring problems found by Check.

func (*StateMachineCheckError) Error added in v0.10.1

func (err *StateMachineCheckError) Error() string

type StateMachineDefinitionError added in v0.10.1

type StateMachineDefinitionError struct {
	Message string
}

StateMachineDefinitionError reports invalid state-machine setup input.

func (*StateMachineDefinitionError) Error added in v0.10.1

func (err *StateMachineDefinitionError) Error() string

type Transition added in v0.10.0

type Transition struct {
	To        StateLabel
	Condition Condition
}

Transition defines a transition edge.

type TransitionError

type TransitionError struct {
	Action  Action
	Current StateLabel
}

TransitionError reports a missing transition for the current state/action.

func NewTransitionError

func NewTransitionError(current StateLabel, action Action) *TransitionError

func (*TransitionError) Error

func (te *TransitionError) Error() string

Jump to

Keyboard shortcuts

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