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