-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
497 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
gosteps "github.com/TanmoySG/go-steps/v0" | ||
"github.com/TanmoySG/go-steps/example/v0/funcs" | ||
) | ||
|
||
func main() { | ||
|
||
intsToAdd := []int{1, 4, 7, 10} | ||
|
||
var step *gosteps.Step | ||
for _, val := range intsToAdd { | ||
step = addStepToChain(step, funcs.Add, []interface{}{val}) | ||
} | ||
|
||
finalOutput, err := step.Execute(1) | ||
if err != nil { | ||
fmt.Printf("error executing steps: %s, final output: [%s]\n", err, finalOutput) | ||
} | ||
|
||
fmt.Printf("Final Output: [%v]\n", finalOutput) | ||
} | ||
|
||
// step to add new next step to step-chain; basically a linked-list insertion | ||
func addStepToChain(step *gosteps.Step, stepFunc gosteps.StepFn, additionalArgs []interface{}) *gosteps.Step { | ||
temp := gosteps.Step{ | ||
Function: stepFunc, | ||
StepArgs: additionalArgs, | ||
} | ||
|
||
if step == nil { | ||
step = &temp | ||
return step | ||
} | ||
|
||
curr := step | ||
for curr.NextStep != nil { | ||
curr = curr.NextStep | ||
} | ||
|
||
curr.NextStep = &temp | ||
return step | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package funcs | ||
|
||
import "fmt" | ||
|
||
var itr int = 1 | ||
|
||
func Add(args ...any) ([]interface{}, error) { | ||
fmt.Printf("Adding %v\n", args) | ||
return []interface{}{args[0].(int) + args[1].(int)}, nil | ||
} | ||
|
||
func Sub(args ...any) ([]interface{}, error) { | ||
fmt.Printf("Sub %v\n", args) | ||
return []interface{}{args[0].(int) - args[1].(int)}, nil | ||
} | ||
|
||
func Multiply(args ...any) ([]interface{}, error) { | ||
fmt.Printf("Multiply %v\n", args) | ||
return []interface{}{args[0].(int) * args[1].(int)}, nil | ||
} | ||
|
||
func Divide(args ...any) ([]interface{}, error) { | ||
fmt.Printf("Divide %v\n", args) | ||
return []interface{}{args[0].(int) / args[1].(int)}, nil | ||
} | ||
|
||
// Step will error 3times and return arg*30 and arg*31 on the 4th try | ||
func StepWillError3Times(args ...any) ([]interface{}, error) { | ||
fmt.Printf("Running fake error function for arg [%v]\n", args) | ||
if itr == 3 { | ||
return []interface{}{args[0].(int) * 30, args[0].(int) * 50}, nil | ||
} | ||
|
||
itr += 1 | ||
return nil, fmt.Errorf("error to retry") | ||
} | ||
|
||
// Step will error infinitely | ||
func StepWillErrorInfinitely(args ...any) ([]interface{}, error) { | ||
fmt.Printf("Running infinite fake error function for arg [%v]\n", args) | ||
return nil, fmt.Errorf("error to retry") | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
|
||
gosteps "github.com/TanmoySG/go-steps/v0" | ||
"github.com/TanmoySG/go-steps/example/v0/funcs" | ||
) | ||
|
||
const ( | ||
stepMultiply = "Multiply" | ||
stepDivide = "Divide" | ||
) | ||
|
||
// reading/maintaining this is a bit tricky will add | ||
// a functional way to create this in the next version | ||
var steps = gosteps.Step{ | ||
Function: funcs.Add, | ||
StepArgs: []interface{}{2}, | ||
NextStep: &gosteps.Step{ | ||
Function: funcs.Sub, | ||
StepArgs: []interface{}{4}, | ||
NextStepResolver: nextStepResolver, | ||
PossibleNextSteps: gosteps.PossibleNextSteps{ | ||
{ | ||
Name: stepMultiply, | ||
Function: funcs.Multiply, | ||
StepArgs: []interface{}{-5}, | ||
NextStep: &gosteps.Step{ | ||
Function: funcs.Add, | ||
StepArgs: []interface{}{100}, | ||
NextStep: &gosteps.Step{ | ||
Function: funcs.StepWillError3Times, | ||
ErrorsToRetry: []error{ | ||
fmt.Errorf("error"), | ||
}, | ||
NextStep: &gosteps.Step{ | ||
Function: funcs.StepWillErrorInfinitely, | ||
ErrorsToRetry: []error{ | ||
fmt.Errorf("error"), | ||
}, | ||
NextStep: &gosteps.Step{ | ||
Function: funcs.Multiply, | ||
}, | ||
StrictErrorCheck: false, | ||
MaxAttempts: 5, // use gosteps.MaxMaxAttempts for Maximum Possible reattempts | ||
}, | ||
MaxAttempts: 5, | ||
RetrySleep: 1 * time.Second, | ||
}, | ||
}, | ||
}, | ||
{ | ||
Name: stepDivide, | ||
Function: funcs.Divide, | ||
StepArgs: []interface{}{-2}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
func main() { | ||
initArgs := []interface{}{5} | ||
finalOutput, err := steps.Execute(initArgs...) | ||
if err != nil { | ||
fmt.Printf("error executing steps: %s, final output: [%s]\n", err, finalOutput) | ||
} | ||
|
||
fmt.Printf("Final Output: [%v]\n", finalOutput) | ||
} | ||
|
||
// step resolver | ||
func nextStepResolver(args ...any) string { | ||
if args[0].(int) < 0 { | ||
fmt.Printf("StepResolver [%v]: Arguments is Negative, going with Multiply\n", args) | ||
return stepMultiply | ||
} | ||
|
||
fmt.Printf("StepResolver [%v]: Arguments is Positive, going with Divide\n", args) | ||
return stepDivide | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package v0 | ||
|
||
import "time" | ||
|
||
// StepName type defined the name of the step | ||
type StepName string | ||
|
||
// StepFn type defines the Step's Function | ||
type StepFn func(...interface{}) ([]interface{}, error) | ||
|
||
// PossibleNextSteps type is a list/array of Step objects | ||
type PossibleNextSteps []Step | ||
|
||
// Step type defines a step with all configurations for the step | ||
type Step struct { | ||
Name StepName | ||
Function StepFn | ||
UseArguments stepArgChainingType | ||
StepArgs []interface{} | ||
NextStep *Step | ||
PossibleNextSteps PossibleNextSteps | ||
NextStepResolver interface{} | ||
ErrorsToRetry []error | ||
StrictErrorCheck bool | ||
SkipRetry bool | ||
MaxAttempts int | ||
RetrySleep time.Duration | ||
} | ||
|
||
// enum type for step arguments chaining | ||
type stepArgChainingType string |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package v0 | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"time" | ||
) | ||
|
||
func (step *Step) Execute(initArgs ...any) ([]interface{}, error) { | ||
// final output for step execution | ||
var finalOutput []interface{} | ||
|
||
// initialize step output and step error | ||
var stepOutput []interface{} | ||
var stepError error | ||
|
||
// no initial step or function | ||
if step == nil || step.Function == nil { | ||
return nil, nil | ||
} | ||
|
||
// entry step | ||
var isEntryStep bool = true | ||
|
||
// step reattepts | ||
var stepReAttemptsLeft int = step.MaxAttempts | ||
|
||
for { | ||
// piping output from previous step as arguments for current step | ||
var stepArgs []interface{} | ||
|
||
// only runs for first step in step | ||
if isEntryStep { | ||
step.StepArgs = append(step.StepArgs, initArgs...) | ||
isEntryStep = false | ||
} | ||
|
||
// resolve step arguments based on step.UseArguments | ||
stepArgs = step.resolveStepArguments(stepOutput) | ||
|
||
// execute current step passing step arguments | ||
stepOutput, stepError = step.Function(stepArgs...) | ||
if stepError != nil { | ||
if !step.SkipRetry && step.shouldRetry(stepError) && stepReAttemptsLeft > 0 { | ||
// piping args as output for re-running same step | ||
stepOutput = stepArgs | ||
|
||
// decrementing re-attempts left for current run | ||
stepReAttemptsLeft -= 1 | ||
|
||
// sleep step.RetrySleep duration if set | ||
if step.RetrySleep > 0 { | ||
time.Sleep(step.RetrySleep) | ||
} | ||
|
||
continue | ||
} | ||
|
||
// skip retry as step error not retryable | ||
// return output of previous step and error | ||
return stepArgs, stepError | ||
} | ||
|
||
// no next step, this is the final step | ||
if step.NextStep == nil && step.PossibleNextSteps == nil { | ||
finalOutput = stepOutput | ||
return finalOutput, nil | ||
} | ||
|
||
// next step is dependant on conditions | ||
if step.PossibleNextSteps != nil && step.NextStepResolver != nil { | ||
nextStepName := step.NextStepResolver.(func(...interface{}) string)(stepOutput...) | ||
resolvedStep := step.resolveNextStep(StepName(nextStepName)) | ||
if resolvedStep == nil { | ||
return stepOutput, fmt.Errorf(unresolvedStepError, step.Name) | ||
} | ||
step.NextStep = resolvedStep | ||
} | ||
|
||
// set step as resolved or default nextStep | ||
step = step.NextStep | ||
|
||
// if step.MaxAttempts is not set, set default max value | ||
if step.MaxAttempts < 1 { | ||
step.MaxAttempts = DefaultMaxAttempts | ||
} | ||
|
||
// reset step re-attempts | ||
stepReAttemptsLeft = step.MaxAttempts - 1 | ||
} | ||
} | ||
|
||
// should retry for error | ||
func (step Step) shouldRetry(err error) bool { | ||
for _, errorToRetry := range step.ErrorsToRetry { | ||
if step.StrictErrorCheck && err.Error() == errorToRetry.Error() { | ||
return true | ||
} else if !step.StrictErrorCheck && strings.Contains(errorToRetry.Error(), err.Error()) { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
// resolve next step by step name | ||
func (step Step) resolveNextStep(stepName StepName) *Step { | ||
for _, nextStep := range step.PossibleNextSteps { | ||
if nextStep.Name == stepName { | ||
return &nextStep | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (step Step) resolveStepArguments(previousStepReturns []interface{}) []interface{} { | ||
var resolvedStepArgs []interface{} | ||
|
||
switch step.UseArguments { | ||
case PreviousStepReturns: | ||
resolvedStepArgs = previousStepReturns | ||
case CurrentStepArgs: | ||
resolvedStepArgs = step.StepArgs | ||
case PreviousReturnsWithCurrentStepArgs: | ||
resolvedStepArgs = append(resolvedStepArgs, previousStepReturns...) | ||
resolvedStepArgs = append(resolvedStepArgs, step.StepArgs...) | ||
default: // covers UseCurrentStepArgsWithPreviousReturns too | ||
resolvedStepArgs = append(resolvedStepArgs, step.StepArgs...) | ||
resolvedStepArgs = append(resolvedStepArgs, previousStepReturns...) | ||
} | ||
|
||
return resolvedStepArgs | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package v0 | ||
|
||
import ( | ||
"math" | ||
) | ||
|
||
const ( | ||
// to avoid infinite runs due to the MaxAttempts not being set, we're keeping the default attempts to 100 | ||
// if required, import and use the MaxMaxAttempts in the step.MaxAttempts field | ||
DefaultMaxAttempts = 100 | ||
|
||
// the Max value is 9223372036854775807, which is not infinite but a huge number of attempts | ||
MaxMaxAttempts = math.MaxInt | ||
) | ||
|
||
var ( | ||
// only previous step return will be passed to current step as arguments | ||
PreviousStepReturns stepArgChainingType = "PreviousStepReturns" | ||
|
||
// only current step arguments (StepArgs) will be passed to current step as arguments | ||
CurrentStepArgs stepArgChainingType = "CurrentStepArgs" | ||
|
||
// both previous step returns and current step arguments (StepArgs) will be passed | ||
// to current step as arguments - previous step returns, followed by current step args, | ||
PreviousReturnsWithCurrentStepArgs stepArgChainingType = "PreviousReturnsWithCurrentStepArgs" | ||
|
||
// both previous step returns and current step arguments (StepArgs) will be passed | ||
// to current step as arguments - current step args, followed by previous step returns | ||
CurrentStepArgsWithPreviousReturns stepArgChainingType = "CurrentStepArgsWithPreviousReturns" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package v0 | ||
|
||
var ( | ||
unresolvedStepError = "error: step [%s] is unresolved, no step found with this name." | ||
) |
Oops, something went wrong.