Documentation
¶
Overview ¶
Package statemachine provides a simple routing state machine implementation. This is useful for implementing complex state machines that require routing logic. The state machine is implemented as a series of state functions that take a Request and returns a Request with the next State or an error. An error causes the state machine to stop and return the error. A nil state causes the state machine to stop.
You may build a state machine using either function calls or method calls. The Request.Data object you define can be a stack or heap allocated object. Using a stack allocated object is useful when running a lot of state machines in parallel, as it reduces the amount of memory allocation and garbage collection required.
State machines of this design can reduce testing complexity and improve code readability. You can read about how here: https://medium.com/@johnsiilver/go-state-machine-patterns-3b667f345b5e
This package is has OTEL support built in. If the Context passed to the state machine has a span, the state machine will create a child span for each state. If the state machine returns an error, the span will be marked as an error.
Example:
package main import ( "context" "fmt" "io" "log" "net/http" "github.com/gostdlib/ops/statemachine" ) var ( author = flag.String("author", "", "The author of the quote, if not set will choose a random one") ) // Data is the data passed to through the state machine. It can be modified by the state functions. type Data struct { // This section is data set before starting the state machine. // Author is the author of the quote. If not set it will be chosen at random. Author string // This section is data set during the state machine. // Quote is a quote from the author. It is set in the state machine. Quote string // httpClient is the http client used to make requests. httpClient *http.Client } func Start(req statemachine.Request[Data]) statemachine.Request[Data] { if req.Data.httpClient == nil { req.Data.httpClient = &http.Client{} } if req.Data.Author == "" { req.Next = RandomAuthor return req } req.Next = RandomQuote return req } func RandomAuthor(req statemachine.Request[Data]) statemachine.Request[Data] { const url = "https://api.quotable.io/randomAuthor" // This is a fake URL req, err := http.NewRequest("GET", url, nil) if err != nil { req.Err = err return req } req = req.WithContext(ctx) resp, err := args.Data.httpClient.Do(req) if err != nil { req.Err = err return req } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { req.Err = fmt.Errorf("unexpected status code: %d", resp.StatusCode) return req } b, err := io.ReadAll(resp.Body) if err != nil { req.Err = err return req } args.Data.Author = string(b) req.Next = RandomQuote return req } func RandomQuote(req statemachine.Request[Data]) statemachine.Request[Data] { const url = "https://api.quotable.io/randomQuote" // This is a fake URL req, err := http.NewRequest("GET", url, nil) if err != nil { req.Err = err return req } req = req.WithContext(ctx) resp, err := args.Data.httpClient.Do(req) if err != nil { req.Err = err return req } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { req.Err = fmt.Errorf("unexpected status code: %d", resp.StatusCode) return req } b, err := io.ReadAll(resp.Body) if err != nil { req.Err = err return req } args.Data.Quote = string(b) req.Next = nil // This is not needed, but a good way to show that the state machine is done. return req } func main() { flag.Parse() req := statemachine.Request{ Ctx: context.Background(), Data: Data{ Author: *author, httpClient: &http.Client{}, }, Next: Start, } err := statemachine.Run("Get author quotes", req) if err != nil { log.Fatal(err) } fmt.Println(data.Author, "said", data.Quote) }
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func MethodName ¶
MethodName takes a function or a method and returns its name. This is useful in testing to determine the name of the next state in a state machine and do a comparison.
Types ¶
type DeferFn ¶
Defer is a function that is called when the state machine stops. This function can change the data passed and it will modify Request.Data before it is returned by Run(). err indicates if you had an error and what it was, otherwise the Request completed.
type ErrCyclic ¶
type ErrCyclic struct { // SMName is the name of the state machine that detected the cyclic error. SMName string // LastStage is the name of the stage that would have been executed a second time. This caused the cyclic error. LastStage string // Stages lists the stages that were executed before the cyclic error was detected. Stages string }
ErrCyclic is an error that is returned when a state machine detects a cyclic error.
type ErrValidation ¶
type ErrValidation struct {
// contains filtered or unexported fields
}
ErrValidation is an error when trying to run a state machine with invalid arguments.
func (ErrValidation) Error ¶
func (v ErrValidation) Error() string
Error implements the error interface.
type Option ¶
Option is an option for the Run() function. This is currently unused, but exists for future expansion.
type Request ¶
type Request[T any] struct { // Ctx is the context passed to the state function. Ctx context.Context // Data is the data to be passed to the next state. Data T // Err is the error to be returned by the state machine. If Err is not nil, the state machine stops. Err error // Next is the next state to be executed. If Next is nil, the state machine stops. // Must be set to the initial state to execute before calling Run(). Next State[T] // Defers is a list of functions to be called when the state machine stops. This is // useful for cleaning up resources or modifying the data before it is returned. Defers []DeferFn[T] // contains filtered or unexported fields }
Request are the request passed to a state function.
func Run ¶
Run runs the state machine with the given a Request. name is the name of the statemachine for the purpose of OTEL tracing. An error is returned if the state machine fails, name is empty, the Request Ctx/Next is nil or the Err field is not nil.
func WithCyclicCheck ¶
WithCyclicCheck is an option that causes the state machine to error if a state is called more than once. This effectively turns the state machine into a directed acyclic graph.
type State ¶
State is a function that takes a Request and returns a Request. If the returned Request has a nil Next, the state machine stops. If the returned Request has a non-nil Err, the state machine stops and returns the error. If the returned Request has a non-nil next, the state machine continues with the next state.