fsm

package
v0.7.4 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2024 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Example
//nolint:testableexamples
package main

import (
	"context"

	"github.com/google/uuid"

	"github.com/agurinov/gopl/fsm"
)

type (
	RegistrationContext struct {
		UUID uuid.UUID
	}
	RegistrationStateMachine       = fsm.StateMachine[RegistrationContext]
	RegistrationStateMachineOption = fsm.StateMachineOption[RegistrationContext]
	RegistrationEvent              = fsm.Event[RegistrationContext]
)

type (
	ChooseCountryContract      struct{ country string }
	PassportPhotoContract      struct{ blob []byte }
	SelfiePhotoContract        struct{ blob []byte }
	DriverLicensePhotoContract struct{ blob []byte }
	ReviewResponseContract     struct {
		passportPhotoValid bool
		selfiePhotoValid   bool
		driverLicenseValid bool
	}
)

var (
	brokenState = fsm.State{
		Name:   "broken",
		Broken: true,
		OnTransition: func(_ context.Context) error {
			println("broken state faced: log and metric for investigation")

			return nil
		},
	}
	chooseCountryState = fsm.State{
		Name:    "choose_country",
		Initial: true,
		PossibleStates: fsm.MustNewStateMap(
			uploadPassportState,
		),
	}
	uploadPassportState = fsm.State{
		Name: "upload_passport",
		PossibleStates: fsm.MustNewStateMap(
			uploadSelfieState,
		),
	}
	uploadSelfieState = fsm.State{
		Name: "upload_selfie",
		PossibleStates: fsm.MustNewStateMap(
			uploadDriverLicenseState,
		),
	}
	uploadDriverLicenseState = fsm.State{
		Name: "upload_driver_license",
		PossibleStates: fsm.MustNewStateMap(
			reviewState,
		),
	}
	reviewState = fsm.State{
		Name: "review_requested",
		PossibleStates: fsm.MustNewStateMap(
			deniedState,
			approvedState,
		),
	}
	deniedState = fsm.State{
		Name: "denied",
	}
	approvedState = fsm.State{
		Name:  "approved",
		Final: true,
		OnTransition: func(_ context.Context) error {
			println("approved state achieved: log and metric for analytic")

			return nil
		},
	}
)

func (c ChooseCountryContract) transition(ctx context.Context) (fsm.State, error) {
	// Push contract to db
	// db.SaveClientCountry(c.country)
	switch c.country {
	case "ru":
		return uploadPassportState, nil
	default:
		return brokenState, nil
	}
}

func (c PassportPhotoContract) transition(ctx context.Context) (fsm.State, error) {
	// Push contract to reviewer
	// reviewer.UploadPassportPhoto(c.blog)
	return uploadSelfieState, nil
}

func (c SelfiePhotoContract) transition(ctx context.Context) (fsm.State, error) {
	// Push contract to reviewer
	// reviewer.UploadSelfiePhoto(c.blog)
	return uploadDriverLicenseState, nil
}

func (c DriverLicensePhotoContract) transition(ctx context.Context) (fsm.State, error) {
	// Push contract to reviewer
	// reviewer.UploadDriverLicensePhoto(c.blog)
	return reviewState, nil
}

func (c ReviewResponseContract) transition(ctx context.Context) (fsm.State, error) {
	switch {
	case !c.passportPhotoValid:
		return uploadPassportState, nil
	case !c.selfiePhotoValid:
		return uploadSelfieState, nil
	case !c.driverLicenseValid:
		return uploadDriverLicenseState, nil
	default:
		return approvedState, nil
	}
}

func main() {
	opts := []RegistrationStateMachineOption{
		fsm.WithName[RegistrationContext]("registration_machine"),
		fsm.WithVersion[RegistrationContext]("v1"),
		fsm.WithStateStorage[RegistrationContext](nil),
		fsm.WithStateMap[RegistrationContext](
			brokenState,
			chooseCountryState,
			uploadPassportState,
			uploadSelfieState,
			uploadDriverLicenseState,
			reviewState,
			deniedState,
			approvedState,
		),
	}

	sm, err := fsm.New(opts...)
	if err != nil {
		panic(err)
	}

	var (
		ctx                 = context.Background()
		registrationContext = RegistrationContext{
			UUID: uuid.MustParse("10000000-0000-0000-0000-111111111111"),
		}
		event1 = ChooseCountryContract{country: "ru"}
		event2 = PassportPhotoContract{blob: []byte("passport photo")}
		event3 = SelfiePhotoContract{blob: []byte("selfie photo")}
		event4 = DriverLicensePhotoContract{blob: []byte("driver license")}
		event5 = ReviewResponseContract{
			passportPhotoValid: true,
			selfiePhotoValid:   true,
			driverLicenseValid: true,
		}
	)

	// Imitates event bus stream
	events := []RegistrationEvent{
		{Context: registrationContext, TransitionFunc: event1.transition},
		{Context: registrationContext, TransitionFunc: event2.transition},
		{Context: registrationContext, TransitionFunc: event3.transition},
		{Context: registrationContext, TransitionFunc: event4.transition},
		{Context: registrationContext, TransitionFunc: event5.transition},
	}

	var state fsm.State

	for i := range events {
		if state, err = sm.Transition(ctx, events[i]); err != nil {
			panic(err)
		}
	}

	if equal := state.Equal(approvedState); !equal {
		panic("unexpected state occurs")
	}
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var EmptyState = State{}
View Source
var EmptyStates = StateMap{}
View Source
var ErrInvalidEvent = errors.New("event: invalid")

Functions

This section is empty.

Types

type Context

type Context any

type Event

type Event[C Context] struct {
	Context        C
	TransitionFunc func(context.Context) (State, error)
}

func (Event[C]) Validate

func (e Event[C]) Validate() error

type State

type State struct {
	OnTransition   func(context.Context) error
	PossibleStates StateMap
	Name           string
	Initial        bool
	Final          bool
	Broken         bool
}

func (State) Equal

func (s State) Equal(other State) bool

type StateAlreadyPresentError

type StateAlreadyPresentError struct {
	StateName string
}

func (StateAlreadyPresentError) Error

func (e StateAlreadyPresentError) Error() string

type StateMachine

type StateMachine[C Context] struct {
	// contains filtered or unexported fields
}

func New

func New[C Context](opts ...StateMachineOption[C]) (StateMachine[C], error)

func (StateMachine[C]) Transition

func (m StateMachine[C]) Transition(
	ctx context.Context,
	event Event[C],
) (State, error)

type StateMachineOption

type StateMachineOption[C Context] c.Option[StateMachine[C]]

func WithName

func WithName[C Context](name string) StateMachineOption[C]

func WithStateMap

func WithStateMap[C Context](states ...State) StateMachineOption[C]

func WithStateStorage

func WithStateStorage[C Context](storage StateStorage[C]) StateMachineOption[C]

func WithVersion

func WithVersion[C Context](version string) StateMachineOption[C]

type StateMap

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

func MustNewStateMap

func MustNewStateMap(states ...State) StateMap

func NewStateMap

func NewStateMap(states ...State) (StateMap, error)

func (StateMap) Broken

func (sm StateMap) Broken() (State, bool)

func (StateMap) Final

func (sm StateMap) Final() (State, bool)

func (StateMap) Initial

func (sm StateMap) Initial() (State, bool)

func (StateMap) Len

func (sm StateMap) Len() int

func (StateMap) StateNames

func (sm StateMap) StateNames() string

func (StateMap) Validate

func (sm StateMap) Validate() error

type StateNotPresentError

type StateNotPresentError struct {
	StateName string
}

func (StateNotPresentError) Error

func (e StateNotPresentError) Error() string

type StateStorage

type StateStorage[C Context] interface {
	GetState(context.Context, C) (State, error)
	PushState(context.Context, C, State) error
}

type UnexpectedPossibleStatesError

type UnexpectedPossibleStatesError struct {
	PossibleStateNames string
	StateName          string
	MustBePresent      bool
}

func (UnexpectedPossibleStatesError) Error

Directories

Path Synopsis
Package gomock is a generated GoMock package.
Package gomock is a generated GoMock package.

Jump to

Keyboard shortcuts

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