error.go (145 lines of code) (raw):
package flow
import (
"fmt"
"runtime"
"strings"
)
// Succeed marks the current step as `Succeeded`, while still reports the error.
func Succeed(err error) ErrSucceed { return ErrSucceed{err} }
// Cancel marks the current step as `Canceled`, and reports the error.
func Cancel(err error) ErrCancel { return ErrCancel{err} }
// Skip marks the current step as `Skipped`, and reports the error.
func Skip(err error) ErrSkip { return ErrSkip{err} }
type ErrSucceed struct{ error }
type ErrCancel struct{ error }
type ErrSkip struct{ error }
type ErrPanic struct{ error }
type ErrBeforeStep struct{ error }
func (e ErrSucceed) Unwrap() error { return e.error }
func (e ErrCancel) Unwrap() error { return e.error }
func (e ErrSkip) Unwrap() error { return e.error }
func (e ErrPanic) Unwrap() error { return e.error }
func (e ErrBeforeStep) Unwrap() error { return e.error }
// WithStackTraces saves stack frames into error
func WithStackTraces(skip, depth int, ignores ...func(runtime.Frame) bool) func(error) error {
return func(err error) error {
pc := make([]uintptr, depth)
i := runtime.Callers(skip, pc)
pc = pc[:i]
frames := runtime.CallersFrames(pc)
withStackTraces := ErrWithStackTraces{Err: err}
for {
frame, more := frames.Next()
if !more {
break
}
isIgnored := false
for _, ignore := range ignores {
if ignore(frame) {
isIgnored = true
break
}
}
if !isIgnored {
withStackTraces.Frames = append(withStackTraces.Frames, frame)
}
}
return withStackTraces
}
}
// ErrWithStackTraces saves stack frames into error, and prints error into
//
// error message
//
// Stack Traces:
// file:line
type ErrWithStackTraces struct {
Err error
Frames []runtime.Frame
}
func (e ErrWithStackTraces) Unwrap() error { return e.Err }
func (e ErrWithStackTraces) Error() string {
if st := e.StackTraces(); len(st) > 0 {
return fmt.Sprintf("%s\n\nStack Traces:\n\t%s\n", e.Err, strings.Join(st, "\n\t"))
}
return e.Err.Error()
}
func (e ErrWithStackTraces) StackTraces() []string {
stacks := make([]string, 0, len(e.Frames))
for i := range e.Frames {
stacks = append(stacks, fmt.Sprintf("%s:%d", e.Frames[i].File, e.Frames[i].Line))
}
return stacks
}
// StatusFromError gets the StepStatus from error.
func StatusFromError(err error) StepStatus {
if err == nil {
return Succeeded
}
for {
switch typedErr := err.(type) {
case ErrSucceed:
return Succeeded
case ErrCancel:
return Canceled
case ErrSkip:
return Skipped
case interface{ Unwrap() error }:
err = typedErr.Unwrap()
default:
return Failed
}
}
}
// StepResult contains the status and error of a Step.
type StepResult struct {
Status StepStatus
Err error
}
// StatusError will be printed as:
//
// [Status]
// error message
func (e StepResult) Error() string {
rv := fmt.Sprintf("[%s]", e.Status)
if e.Err != nil {
rv += "\n\t" + indent(e.Err.Error())
}
return rv
}
func (e StepResult) Unwrap() error { return e.Err }
func indent(s string) string { return strings.ReplaceAll(s, "\n", "\n\t") }
// ErrWorkflow contains all errors reported from terminated Steps in Workflow.
//
// Keys are root Steps, values are its status and error.
type ErrWorkflow map[Steper]StepResult
func (e ErrWorkflow) Unwrap() []error {
rv := make([]error, 0, len(e))
for _, sErr := range e {
rv = append(rv, sErr.Err)
}
return rv
}
// ErrWorkflow will be printed as:
//
// Step: [Status]
// error message
func (e ErrWorkflow) Error() string {
var builder strings.Builder
for step, serr := range e {
builder.WriteString(fmt.Sprintf("%s: ", String(step)))
builder.WriteString(fmt.Sprintln(serr.Error()))
}
return builder.String()
}
func (e ErrWorkflow) AllSucceeded() bool {
for _, sErr := range e {
if sErr.Status != Succeeded {
return false
}
}
return true
}
func (e ErrWorkflow) AllSucceededOrSkipped() bool {
for _, sErr := range e {
switch sErr.Status {
case Succeeded, Skipped: // skipped step can have error to indicate why it's skipped
default:
return false
}
}
return true
}
var ErrWorkflowIsRunning = fmt.Errorf("Workflow is running, please wait for it terminated")
// ErrCycleDependency means there is a cycle-dependency in your Workflow!!!
type ErrCycleDependency map[Steper][]Steper
func (e ErrCycleDependency) Error() string {
depErr := make([]string, 0, len(e))
for step, ups := range e {
depsStr := []string{}
for _, up := range ups {
depsStr = append(depsStr, String(up))
}
depErr = append(depErr, fmt.Sprintf(
"%s depends on [\n\t%s\n]",
String(step), indent(strings.Join(depsStr, "\n")),
))
}
return fmt.Sprintf("Cycle Dependency Error:\n\t%s", indent(strings.Join(depErr, "\n")))
}