Documentation
¶
Overview ¶
Package ssm implements a Simple State Machine
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrTransitionDenied = errors.New("transition denied")
ErrTransitionDenied is a sentinel wrapping all transition failures. Use errors.Is(err, ErrTransitionDenied) for generic error handling.
Functions ¶
This section is empty.
Types ¶
type ErrEventNotPermitted ¶
type ErrEventNotPermitted[Event, State any] struct { Event Event State State }
ErrEventNotPermitted indicates the event cannot be handled in the current state.
func (ErrEventNotPermitted[Event, State]) Error ¶
func (e ErrEventNotPermitted[Event, State]) Error() string
type ErrTransitionExists ¶
type ErrTransitionExists[State any] struct { From State To State }
ErrTransitionExists indicates a duplicate transition was registered.
func (ErrTransitionExists[State]) Error ¶
func (e ErrTransitionExists[State]) Error() string
type ErrTransitionNotFound ¶
type ErrTransitionNotFound[State any] struct { From State To State }
ErrTransitionNotFound indicates no transition exists between the given states.
func (ErrTransitionNotFound[State]) Error ¶
func (e ErrTransitionNotFound[State]) Error() string
type EventResult ¶
type EventResult[Event, State comparable] struct { From State To State Event Event }
EventResult describes the resolved transition for an event.
func (EventResult[Event, State]) Reentry ¶
func (r EventResult[Event, State]) Reentry() bool
Reentry returns true when the transition is a self-transition (From == To).
type EventStateMachine ¶
type EventStateMachine[Entity any, Event, State comparable] struct { // contains filtered or unexported fields }
EventStateMachine is an event-driven state machine. Immutable after construction, safe for concurrent use.
func NewWithEvents ¶
func NewWithEvents[Entity any, Event, State comparable]( getState func(e Entity) State, transitions []EventTransition[Entity, Event, State], ) (*EventStateMachine[Entity, Event, State], error)
NewWithEvents constructs an event-driven state machine.
Example ¶
package main
import (
"fmt"
"github.com/karolusz/ssm"
)
type PaymentEvent string
const (
EventSubmit PaymentEvent = "submit"
EventApprove PaymentEvent = "approve"
EventReject PaymentEvent = "reject"
)
type PaymentStatus string
const (
PaymentPending PaymentStatus = "pending"
PaymentApproved PaymentStatus = "approved"
PaymentRejected PaymentStatus = "rejected"
)
type Payment struct {
Status PaymentStatus
}
func main() {
sm, err := ssm.NewWithEvents(
func(p Payment) PaymentStatus { return p.Status },
[]ssm.EventTransition[Payment, PaymentEvent, PaymentStatus]{
{On: EventSubmit, From: PaymentPending, To: PaymentPending},
{On: EventApprove, From: PaymentPending, To: PaymentApproved},
{On: EventReject, From: PaymentPending, To: PaymentRejected},
},
)
if err != nil {
panic(err)
}
payment := Payment{Status: PaymentPending}
// Fire an event — the SM resolves the target state
result, err := sm.Can(payment, EventApprove)
fmt.Printf("approve: %s -> %s (err: %v)\n", result.From, result.To, err)
// Self-transition detection
result, err = sm.Can(payment, EventSubmit)
fmt.Printf("submit: reentry=%v (err: %v)\n", result.Reentry(), err)
// Event not permitted in current state
approved := Payment{Status: PaymentApproved}
_, err = sm.Can(approved, EventApprove)
fmt.Println("approve when approved:", err)
}
Output: approve: pending -> approved (err: <nil>) submit: reentry=true (err: <nil>) approve when approved: transition denied: event approve not permitted in state approved
func (*EventStateMachine[Entity, Event, State]) AvailableEvents ¶
func (s *EventStateMachine[Entity, Event, State]) AvailableEvents(from State) []Event
AvailableEvents returns the events that can be handled from the given state.
func (*EventStateMachine[Entity, Event, State]) Can ¶
func (s *EventStateMachine[Entity, Event, State]) Can( e Entity, ev Event, ) (EventResult[Event, State], error)
Can checks if the event can be handled for the entity's current state. Returns an EventResult describing the resolved transition, or an error.
type EventTransition ¶
type EventTransition[Entity any, Event, State comparable] struct { On Event From State To State Guard Guard[Entity] }
EventTransition represents a legal state transition triggered by an event.
type Guard ¶
Guard is a function that determines whether a transition is allowed to occur.
func All ¶
All requires every guard to pass for the combined guard to pass.
Example ¶
package main
import (
"fmt"
"github.com/karolusz/ssm"
)
type OrderStatus string
const (
OrderCreated OrderStatus = "created"
OrderCancelled OrderStatus = "cancelled"
)
type Order struct {
ID string
Status OrderStatus
}
func main() {
minAmount := func(o Order) error {
if o.ID == "" {
return fmt.Errorf("order ID is required")
}
return nil
}
isNotCancelled := func(o Order) error {
if o.Status == OrderCancelled {
return fmt.Errorf("order is cancelled")
}
return nil
}
combined := ssm.All(minAmount, isNotCancelled)
err := combined(Order{ID: "123", Status: OrderCreated})
fmt.Println("valid order:", err)
err = combined(Order{ID: "", Status: OrderCreated})
fmt.Println("missing ID:", err)
}
Output: valid order: <nil> missing ID: order ID is required
type StateMachine ¶
type StateMachine[Entity any, State comparable] struct { // contains filtered or unexported fields }
StateMachine is a simple state machine. Immutable after construction, safe for concurrent use.
func New ¶
func New[Entity any, State comparable]( getState func(e Entity) State, transitions []Transition[Entity, State], ) (*StateMachine[Entity, State], error)
New constructs a simple state machine.
Example ¶
package main
import (
"fmt"
"github.com/karolusz/ssm"
)
type OrderStatus string
const (
OrderCreated OrderStatus = "created"
OrderPaid OrderStatus = "paid"
OrderShipped OrderStatus = "shipped"
OrderDelivered OrderStatus = "delivered"
OrderCancelled OrderStatus = "cancelled"
)
type Order struct {
ID string
Status OrderStatus
}
func main() {
sm, err := ssm.New(
func(o Order) OrderStatus { return o.Status },
[]ssm.Transition[Order, OrderStatus]{
{From: OrderCreated, To: OrderPaid},
{From: OrderCreated, To: OrderCancelled},
{From: OrderPaid, To: OrderShipped},
{From: OrderPaid, To: OrderCancelled},
{From: OrderShipped, To: OrderDelivered},
},
)
if err != nil {
panic(err)
}
order := Order{ID: "123", Status: OrderCreated}
// Check if a transition is allowed
err = sm.Can(order, OrderPaid)
fmt.Println("created -> paid:", err)
// Check an illegal transition
err = sm.Can(order, OrderDelivered)
fmt.Println("created -> delivered:", err)
}
Output: created -> paid: <nil> created -> delivered: transition denied: transition not found: created -> delivered
func (*StateMachine[Entity, State]) Can ¶
func (s *StateMachine[Entity, State]) Can( e Entity, to State, ) error
Can checks if a transition is possible. Returns an error if the transition is not possible.
func (*StateMachine[Entity, State]) Transitions ¶
func (s *StateMachine[Entity, State]) Transitions(from State) []State
Transitions returns all states reachable from the given state.
type Transition ¶
type Transition[Entity any, State comparable] struct { From State To State Guard Guard[Entity] }
Transition represents a legal state transition.