-
Notifications
You must be signed in to change notification settings - Fork 5
/
error.go
189 lines (170 loc) · 4.56 KB
/
error.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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" + strings.ReplaceAll(e.Err.Error(), "\n", "\n\t")
}
return rv
}
func (e StepResult) Unwrap() error { return e.Err }
// 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 {
var builder strings.Builder
builder.WriteString("Cycle Dependency Error:")
for step, ups := range e {
depsStr := []string{}
for _, up := range ups {
depsStr = append(depsStr, String(up))
}
builder.WriteRune('\n')
builder.WriteString(fmt.Sprintf(
"%s: [%s]",
String(step), strings.Join(depsStr, ", "),
))
}
return builder.String()
}