/
errors.go
242 lines (209 loc) · 7.05 KB
/
errors.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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// Copyright 2018 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import (
"fmt"
"io"
"github.com/hashicorp/go-multierror"
)
type withSuppressed struct {
cause error
suppressed error
}
func (w *withSuppressed) Error() string {
return fmt.Sprintf("%s, suppressed: %s", w.cause.Error(), w.suppressed.Error())
}
func (w *withSuppressed) Unwrap() error {
return w.cause
}
func (w *withSuppressed) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
_, _ = fmt.Fprintf(s, "%+v\nsuppressed: %+v", w.Unwrap(), w.suppressed)
return
}
fallthrough
case 's', 'q':
_, _ = io.WriteString(s, w.Error())
}
}
func (w *withSuppressed) Suppressed() error {
return w.suppressed
}
// Suppressed retrieves the suppressed error of the given error, if any.
// An error has a suppressed error if it implements the following interface:
//
// type suppressor interface {
// Suppressed() error
// }
//
// If the error does not implement the interface, nil is returned.
func Suppressed(err error) error {
type suppressor interface {
Suppressed() error
}
if w, ok := err.(suppressor); ok {
return w.Suppressed()
}
return nil
}
// WithSuppressed annotates err with a suppressed error.
// If err is nil, WithSuppressed returns nil.
// If suppressed is nil, WithSuppressed returns err.
func WithSuppressed(err, suppressed error) error {
if err == nil || suppressed == nil {
return err
}
return &withSuppressed{
cause: err,
suppressed: suppressed,
}
}
// reconciliationError implements ErrorIDer
type reconciliationError struct {
error
errorID string
}
// WithID annotates the error with the given errorID which can afterwards be retrieved by ErrorID()
func WithID(id string, err error) error {
return &reconciliationError{err, id}
}
// ErrorID implements the errorIDer interface and returns the id of the reconciliationError
func (t *reconciliationError) ErrorID() string {
return t.errorID
}
func (t *reconciliationError) Unwrap() error {
return t.error
}
// GetID returns the ID of the error if possible.
// If err does not implement ErrorID or is nil an empty string will be returned.
func GetID(err error) string {
type errorIDer interface {
ErrorID() string
}
var id string
if err != nil {
if errWithID, ok := err.(errorIDer); ok {
id = errWithID.ErrorID()
}
}
return id
}
// The ErrorContext holds the lastError IDs from the previous reconciliaton and the IDs of the errors that are processed in this context during the current reconciliation
type ErrorContext struct {
name string
lastErrorIDs []string
errorIDs map[string]struct{}
}
// NewErrorContext creates a new error context with the given name and lastErrors from the previous reconciliation
func NewErrorContext(name string, lastErrorIDs []string) *ErrorContext {
return &ErrorContext{
name: name,
lastErrorIDs: lastErrorIDs,
errorIDs: map[string]struct{}{},
}
}
// AddErrorID adds an error ID which will be tracked by the context and panics if more than one error have the same ID
func (e *ErrorContext) AddErrorID(errorID string) {
if e.HasErrorWithID(errorID) {
panic(fmt.Sprintf("Error with id %q already exists in error context %q", errorID, e.name))
}
e.errorIDs[errorID] = struct{}{}
}
// HasErrorWithID checks if the ErrorContext already contains an error with id errorID
func (e *ErrorContext) HasErrorWithID(errorID string) bool {
_, ok := e.errorIDs[errorID]
return ok
}
// HasLastErrorWithID checks if the previous reconciliation had encountered an error with id errorID
func (e *ErrorContext) HasLastErrorWithID(errorID string) bool {
for _, lastErrorID := range e.lastErrorIDs {
if errorID == lastErrorID {
return true
}
}
return false
}
// FailureHandler is a function which is called when an error occurs
type FailureHandler func(string, error) error
// SuccessHandler is called when a task completes successfully
type SuccessHandler func(string) error
// TaskFunc is an interface for a task which should belong to an ErrorContext and can trigger OnSuccess and OnFailure callbacks depending on whether it completes successfully or not
type TaskFunc interface {
Do(errorContext *ErrorContext) (string, error)
}
// taskFunc implements TaskFunc
type taskFunc func(*ErrorContext) (string, error)
func (f taskFunc) Do(errorContext *ErrorContext) (string, error) {
return f(errorContext)
}
func defaultFailureHandler(errorID string, err error) error {
err = fmt.Errorf("%s failed (%v)", errorID, err)
return WithID(errorID, err)
}
// ToExecute takes an errorID and a function and creates a TaskFunc from them.
func ToExecute(errorID string, task func() error) TaskFunc {
return taskFunc(func(errorContext *ErrorContext) (string, error) {
errorContext.AddErrorID(errorID)
err := task()
if err != nil {
return errorID, err
}
return errorID, nil
})
}
// HandleErrors takes a reference to an ErrorContext, onSuccess and onFailure callback functions and a variadic list of taskFuncs.
// It sequentially adds the Tasks' errorIDs to the provided ErrorContext and executes them.
// If the ErrorContext has errors from the previous reconciliation and the tasks which caused errors complete successfully OnSuccess is called.
// If a task fails OnFailure is called
func HandleErrors(errorContext *ErrorContext, onSuccess SuccessHandler, onFailure FailureHandler, tasks ...TaskFunc) error {
for _, task := range tasks {
errorID, err := task.Do(errorContext)
if err != nil {
return handleFailure(onFailure, errorID, err)
}
if handlerErr := handleSuccess(errorContext, onSuccess, errorID); handlerErr != nil {
return handlerErr
}
}
return nil
}
func handleFailure(onFailure FailureHandler, errorID string, err error) error {
if onFailure != nil {
return onFailure(errorID, err)
}
return defaultFailureHandler(errorID, err)
}
func handleSuccess(errorContext *ErrorContext, onSuccess SuccessHandler, errorID string) error {
if onSuccess != nil && errorContext.HasLastErrorWithID(errorID) {
if err := onSuccess(errorID); err != nil {
return err
}
}
return nil
}
// Errors returns a list of all nested errors of the given error.
// If the error is nil, nil is returned.
// If the error is a multierror, it returns all its errors.
// Otherwise, it returns a slice containing the error as single element.
func Errors(err error) []error {
if err == nil {
return nil
}
if errs, ok := err.(*multierror.Error); ok {
return errs.Errors
}
return []error{err}
}