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