blob: 88e937679da32fab515c2b660403a50f7d38b11e [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package errors
18
19import (
20 "errors"
21 "fmt"
22)
23
24// MessageCountMap contains occurrence for each error message.
25type MessageCountMap map[string]int
26
27// Aggregate represents an object that contains multiple errors, but does not
28// necessarily have singular semantic meaning.
29type Aggregate interface {
30 error
31 Errors() []error
32}
33
34// NewAggregate converts a slice of errors into an Aggregate interface, which
35// is itself an implementation of the error interface. If the slice is empty,
36// this returns nil.
37// It will check if any of the element of input error list is nil, to avoid
38// nil pointer panic when call Error().
39func NewAggregate(errlist []error) Aggregate {
40 if len(errlist) == 0 {
41 return nil
42 }
43 // In case of input error list contains nil
44 var errs []error
45 for _, e := range errlist {
46 if e != nil {
47 errs = append(errs, e)
48 }
49 }
50 if len(errs) == 0 {
51 return nil
52 }
53 return aggregate(errs)
54}
55
56// This helper implements the error and Errors interfaces. Keeping it private
57// prevents people from making an aggregate of 0 errors, which is not
58// an error, but does satisfy the error interface.
59type aggregate []error
60
61// Error is part of the error interface.
62func (agg aggregate) Error() string {
63 if len(agg) == 0 {
64 // This should never happen, really.
65 return ""
66 }
67 if len(agg) == 1 {
68 return agg[0].Error()
69 }
70 result := fmt.Sprintf("[%s", agg[0].Error())
71 for i := 1; i < len(agg); i++ {
72 result += fmt.Sprintf(", %s", agg[i].Error())
73 }
74 result += "]"
75 return result
76}
77
78// Errors is part of the Aggregate interface.
79func (agg aggregate) Errors() []error {
80 return []error(agg)
81}
82
83// Matcher is used to match errors. Returns true if the error matches.
84type Matcher func(error) bool
85
86// FilterOut removes all errors that match any of the matchers from the input
87// error. If the input is a singular error, only that error is tested. If the
88// input implements the Aggregate interface, the list of errors will be
89// processed recursively.
90//
91// This can be used, for example, to remove known-OK errors (such as io.EOF or
92// os.PathNotFound) from a list of errors.
93func FilterOut(err error, fns ...Matcher) error {
94 if err == nil {
95 return nil
96 }
97 if agg, ok := err.(Aggregate); ok {
98 return NewAggregate(filterErrors(agg.Errors(), fns...))
99 }
100 if !matchesError(err, fns...) {
101 return err
102 }
103 return nil
104}
105
106// matchesError returns true if any Matcher returns true
107func matchesError(err error, fns ...Matcher) bool {
108 for _, fn := range fns {
109 if fn(err) {
110 return true
111 }
112 }
113 return false
114}
115
116// filterErrors returns any errors (or nested errors, if the list contains
117// nested Errors) for which all fns return false. If no errors
118// remain a nil list is returned. The resulting silec will have all
119// nested slices flattened as a side effect.
120func filterErrors(list []error, fns ...Matcher) []error {
121 result := []error{}
122 for _, err := range list {
123 r := FilterOut(err, fns...)
124 if r != nil {
125 result = append(result, r)
126 }
127 }
128 return result
129}
130
131// Flatten takes an Aggregate, which may hold other Aggregates in arbitrary
132// nesting, and flattens them all into a single Aggregate, recursively.
133func Flatten(agg Aggregate) Aggregate {
134 result := []error{}
135 if agg == nil {
136 return nil
137 }
138 for _, err := range agg.Errors() {
139 if a, ok := err.(Aggregate); ok {
140 r := Flatten(a)
141 if r != nil {
142 result = append(result, r.Errors()...)
143 }
144 } else {
145 if err != nil {
146 result = append(result, err)
147 }
148 }
149 }
150 return NewAggregate(result)
151}
152
153// CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate
154func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate {
155 if m == nil {
156 return nil
157 }
158 result := make([]error, 0, len(m))
159 for errStr, count := range m {
160 var countStr string
161 if count > 1 {
162 countStr = fmt.Sprintf(" (repeated %v times)", count)
163 }
164 result = append(result, fmt.Errorf("%v%v", errStr, countStr))
165 }
166 return NewAggregate(result)
167}
168
169// Reduce will return err or, if err is an Aggregate and only has one item,
170// the first item in the aggregate.
171func Reduce(err error) error {
172 if agg, ok := err.(Aggregate); ok && err != nil {
173 switch len(agg.Errors()) {
174 case 1:
175 return agg.Errors()[0]
176 case 0:
177 return nil
178 }
179 }
180 return err
181}
182
183// AggregateGoroutines runs the provided functions in parallel, stuffing all
184// non-nil errors into the returned Aggregate.
185// Returns nil if all the functions complete successfully.
186func AggregateGoroutines(funcs ...func() error) Aggregate {
187 errChan := make(chan error, len(funcs))
188 for _, f := range funcs {
189 go func(f func() error) { errChan <- f() }(f)
190 }
191 errs := make([]error, 0)
192 for i := 0; i < cap(errChan); i++ {
193 if err := <-errChan; err != nil {
194 errs = append(errs, err)
195 }
196 }
197 return NewAggregate(errs)
198}
199
200// ErrPreconditionViolated is returned when the precondition is violated
201var ErrPreconditionViolated = errors.New("precondition is violated")