Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
shindakioku committed Feb 25, 2024
1 parent e061a48 commit 81c92db
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 207 deletions.
81 changes: 50 additions & 31 deletions flow/bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ type Bus interface {
}

// describes the state to the [SimpleBus.states] value
type state struct {
type flowState struct {
// User's flow
flow *Flow
state *State
flow *Flow
state State
machine Machine
}

// SimpleBus implementation for the [Bus] contract
Expand Down Expand Up @@ -110,19 +111,18 @@ func (b *SimpleBus) Handle(c telebot.Context) error {
return UserDoesNotHaveActiveFlow
}

st := stV.(*state)
st := stV.(flowState)
// Update context for the state
// @TODO: do we need to persist the latest context every time?
st.state.Context = c
st.state.Add(StateContextKey, c)
// Get active step
step := st.flow.steps[st.state.Machine.ActiveStep()]
// Call validators if it defined
step := st.flow.steps[st.machine.ActiveStep()]
// Call validators if they are defined
validators := step.validators
if len(validators) > 0 {
for _, validator := range validators {
err := validator.Validate(st.state)
if err != nil {
if st.flow.useValidatorErrorsAsUserResponse {
if st.flow.UseValidatorErrorsAsUserResponse {
return c.Reply(err.Error())
} else {
return err
Expand All @@ -138,29 +138,43 @@ func (b *SimpleBus) Handle(c telebot.Context) error {
}
}

// Call [success] event if it's defined
if step.success != nil {
if err := step.success(st.state); err != nil {
activeStep := st.machine.ActiveStep()
// Call [then] event if it's defined
if step.then != nil {
if err := step.then(st.state, &step); err != nil {
return err
}
}

// It was the last step. Call the [success] handler
if len(st.flow.steps) <= st.state.Machine.ActiveStep()+1 {
// It was the last step. Call the [then] handler
if len(st.flow.steps) <= st.machine.ActiveStep()+1 {
b.removeState(c.Sender().ID)

return st.state.Machine.Success(st.state)
if st.flow.then == nil {
return nil
}

return st.flow.then(st.state)
}

// Process to the next step
err := st.state.Machine.Next(st.state)
if err != nil {
// Remove flow on any error occurring within flow logic.
// We need to call the [Fail] function because, typically,
// that handler should send something to the user like [Try again].
b.removeState(c.Sender().ID)
// Sometimes, the user may navigate through steps within handlers.
// If this occurs, we don't need to call the [next] function because navigating
// through the machine already triggers it.
if activeStep == st.machine.ActiveStep() {
// Process to the next step
err := st.machine.Next(st.state)
if err != nil {
// Remove flow on any error occurring within flow logic.
// We need to call the [Fail] function because, typically,
// that handler should send something to the user like [Try again].
b.removeState(c.Sender().ID)

if st.flow.catch == nil {
return nil
}

return st.state.Machine.Fail(st.state, err)
return st.flow.catch(st.state, err)
}
}

return nil
Expand All @@ -180,19 +194,24 @@ func (b *SimpleBus) Flow(factory *Factory) telebot.HandlerFunc {
// If the user already has a flow, we need to recall the active step.
stV, exists := b.states.Load(c.Sender().ID)
if exists {
st := stV.(*state)
st.state.Context = c
st := stV.(flowState)
// Update context
st.state.Add(StateContextKey, c)

return st.state.Machine.ToStep(st.state.Machine.ActiveStep(), st.state)
return st.machine.ToStep(st.machine.ActiveStep(), st.state)
}

machine := NewMachine(factory.flow)
state := NewRuntimeState(factory.userState).
Add(StateContextKey, c).
Add(StateMachineKey, machine)
// Register flow for the user
st := state{
flow: factory.flow,
state: NewState(machine, c, factory.userState),
st := flowState{
flow: factory.flow,
state: state,
machine: machine,
}
b.states.Store(c.Sender().ID, &st)
b.states.Store(c.Sender().ID, st)
// Call the machine for the start the first step
return machine.ToStep(0, st.state)
}
Expand All @@ -204,7 +223,7 @@ func (b *SimpleBus) removeIdleFlows() {
// For example, a developer may want to notify a user that their session has expired.
}

func NewBus(bot *telebot.Bot, flowSessionIsAvailableFor time.Duration) Bus {
func NewBus(flowSessionIsAvailableFor time.Duration) Bus {
bus := &SimpleBus{
flowSessionIsAvailableFor: flowSessionIsAvailableFor,
}
Expand Down
50 changes: 22 additions & 28 deletions flow/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,23 @@ func (f *Factory) WithState(userState map[interface{}]interface{}) *Factory {
return f
}

// Success sets a handler for the [Flow.Success] event.
func (f *Factory) Success(handler StateHandler) *Factory {
f.flow.success = handler

return f
}

// Fail sets a handler for the [Flow.Fail] event.
func (f *Factory) Fail(handler FailHandler) *Factory {
f.flow.fail = handler
// Next adds a step to the [Flow.Steps]
func (f *Factory) Next(step *StepFactory) *Factory {
f.flow.steps = append(f.flow.steps, *step.step)

return f
}

// Step adds a step to the [Flow.Steps]
func (f *Factory) Step(step *StepFactory) *Factory {
f.flow.steps = append(f.flow.steps, *step.step)
// Then sets a handler for the [Flow.Success] event.
func (f *Factory) Then(handler StateHandler) *Factory {
f.flow.then = handler

return f
}

// UseValidatorErrorsAsUserResponse sets a value for the [Flow.useValidatorErrorsAsUserResponse].
func (f *Factory) UseValidatorErrorsAsUserResponse(value bool) *Factory {
f.flow.useValidatorErrorsAsUserResponse = value
// Catch sets a handler for the [Flow.Fail] event.
func (f *Factory) Catch(handler FailHandler) *Factory {
f.flow.catch = handler

return f
}
Expand All @@ -60,18 +53,19 @@ func New() *Factory {
}
}

// NewWithConfiguration start describing the flow.
func NewWithConfiguration(flow Flow) *Factory {
return &Factory{
flow: &flow,
userState: make(map[interface{}]interface{}),
}
}

// StepFactory for creating a [Step] object.
type StepFactory struct {
step *Step
}

// Begin sets a handler for the [Step.begin] event.
func (f *StepFactory) Begin(handler StateHandler) *StepFactory {
f.step.begin = handler

return f
}

// Name sets a value for the [Step.name].
func (f *StepFactory) Name(name int) *StepFactory {
f.step.name = name
Expand All @@ -93,14 +87,14 @@ func (f *StepFactory) Assign(assign StateHandler) *StepFactory {
return f
}

// Success sets a value for the [Step.success].
func (f *StepFactory) Success(success StateHandler) *StepFactory {
f.step.success = success
// Then sets a value for the [Step.then].
func (f *StepFactory) Then(handler StepThenHandler) *StepFactory {
f.step.then = handler

return f
}

// NewStep initiates the description of a step for the flow.
func NewStep() *StepFactory {
return &StepFactory{step: &Step{}}
func NewStep(handler StateHandler) *StepFactory {
return &StepFactory{step: &Step{handler: handler}}
}
13 changes: 8 additions & 5 deletions flow/flow.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package flow

type FailHandler func(*State, error) error
type FailHandler func(State, error) error

// Flow describes a process from beginning to end. It retains all defined steps, the user's final handler, and more.
// Additionally, it offers a straightforward interface to access internal storage for marshaling and saving elsewhere.
type Flow struct {
// User's defined steps
steps []Step
// Calls after successfully passing full flow
success StateHandler
// Calls when user trigger fail step
fail FailHandler
then StateHandler
// Calls on any error (@TODO: update the comment)
catch FailHandler

// User options

// Determines whether we need to send errors from a validator to the user as a response.
// If true, errors from a validator are responded, otherwise, no response is sent.
useValidatorErrorsAsUserResponse bool
UseValidatorErrorsAsUserResponse bool
}
56 changes: 10 additions & 46 deletions flow/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@ import (
// Machine describes the contract for the flow handling
type Machine interface {
// Back move backward by step
Back(state *State) error
Back(state State) error
// Next move forward by step
Next(state *State) error
Next(state State) error
// ToStep Move to the step
ToStep(step int, state *State) error
// Success stop processing and call the final function
Success(state *State) error
// Fail stop processing and call the fail function
Fail(state *State, err error) error
ToStep(step int, state State) error
// ActiveStep returns the current step
ActiveStep() int
}
Expand All @@ -25,16 +21,12 @@ type Machine interface {
type SimpleMachine struct {
// User defined flow
flow *Flow

// Active step for the user
activeStep int

// Sets to true if failure was called.
failed bool
}

func (m *SimpleMachine) Back(state *State) error {
if m.activeStep-1 <= 0 {
func (m *SimpleMachine) Back(state State) error {
if m.activeStep <= 0 {
return errors.New("already first step")
}

Expand All @@ -43,7 +35,7 @@ func (m *SimpleMachine) Back(state *State) error {
return m.run(state)
}

func (m *SimpleMachine) Next(state *State) error {
func (m *SimpleMachine) Next(state State) error {
if m.activeStep+1 >= len(m.flow.steps) {
return errors.New("already last step")
}
Expand All @@ -53,7 +45,7 @@ func (m *SimpleMachine) Next(state *State) error {
return m.run(state)
}

func (m *SimpleMachine) ToStep(step int, state *State) error {
func (m *SimpleMachine) ToStep(step int, state State) error {
if step < 0 {
return errors.New("step cannot be less than zero")
}
Expand All @@ -67,52 +59,24 @@ func (m *SimpleMachine) ToStep(step int, state *State) error {
return m.run(state)
}

func (m *SimpleMachine) Success(state *State) error {
if m.failed {
return errors.New("flow was already failed")
}

if m.flow.success != nil {
return m.flow.success(state)
}

return nil
}

func (m *SimpleMachine) Fail(state *State, err error) error {
m.failed = true

if m.flow.fail != nil {
return m.flow.fail(state, err)
}

return nil
}

func (m *SimpleMachine) ActiveStep() int {
return m.activeStep
}

// Run the current step (this function should be called by [Back]/[Next]/[ToStep] functions).
func (m *SimpleMachine) run(state *State) error {
if m.failed {
return errors.New("flow was already failed")
}

func (m *SimpleMachine) run(state State) error {
if len(m.flow.steps) < m.activeStep {
return errors.New(fmt.Sprintf("step isn't defined (%d)", m.activeStep))
}

step := m.flow.steps[m.activeStep]
if step.begin != nil {
return step.begin(state)
if step.handler != nil {
return step.handler(state)
}

return nil
}

//func (m *SimpleMachine) Continue() {}

func NewMachine(flow *Flow) Machine {
return &SimpleMachine{
flow: flow,
Expand Down
Loading

0 comments on commit 81c92db

Please sign in to comment.