go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/errors/multierror.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package errors 16 17 import ( 18 "fmt" 19 ) 20 21 // MultiError is a simple `error` implementation which represents multiple 22 // `error` objects in one. 23 type MultiError []error 24 25 // MaybeAdd will add `err` to `m` if `err` is not nil. 26 func (m *MultiError) MaybeAdd(err error) { 27 if err == nil { 28 return 29 } 30 *m = append(*m, err) 31 } 32 33 func (m MultiError) Error() string { 34 n, e := m.Summary() 35 switch n { 36 case 0: 37 return "(0 errors)" 38 case 1: 39 return e.Error() 40 case 2: 41 return e.Error() + " (and 1 other error)" 42 } 43 return fmt.Sprintf("%s (and %d other errors)", e, n-1) 44 } 45 46 // AsError returns an `error` interface for this MultiError only if it has >0 47 // length. 48 func (m MultiError) AsError() error { 49 if len(m) == 0 { 50 return nil 51 } 52 return m 53 } 54 55 // Summary gets the total count of non-nil errors and returns the first one. 56 func (m MultiError) Summary() (n int, first error) { 57 for _, e := range m { 58 if e != nil { 59 if n == 0 { 60 first = e 61 } 62 n++ 63 } 64 } 65 return 66 } 67 68 // First returns the first non-nil error. 69 func (m MultiError) First() error { 70 for _, e := range m { 71 if e != nil { 72 return e 73 } 74 } 75 return nil 76 } 77 78 func (m MultiError) stackContext() stackContext { 79 n, _ := m.Summary() 80 81 return stackContext{ 82 internalReason: fmt.Sprintf( 83 "MultiError %d/%d: following first non-nil error.", n, len(m)), 84 } 85 } 86 87 // NewMultiError create new multi error from given errors. 88 // 89 // Can be used to workaround 'go vet' confusion "composite literal uses unkeyed 90 // fields" or if you do not want to remember that MultiError is in fact []error. 91 func NewMultiError(errors ...error) MultiError { 92 return errors 93 } 94 95 // SingleError provides a simple way to uwrap a MultiError if you know that it 96 // could only ever contain one element. 97 // 98 // If err is a MultiError, return its first element. Otherwise, return err. 99 func SingleError(err error) error { 100 if me, ok := err.(MultiError); ok { 101 if len(me) == 0 { 102 return nil 103 } 104 return me[0] 105 } 106 return err 107 } 108 109 // Flatten collapses a multi-dimensional MultiError space into a flat 110 // MultiError, removing "nil" errors. 111 // 112 // If err is not an errors.MultiError, will return err directly. 113 // 114 // As a special case, if merr contains no non-nil errors, nil will be returned. 115 func Flatten(err error) error { 116 var ret MultiError 117 flattenRec(&ret, err) 118 if len(ret) == 0 { 119 return nil 120 } 121 return ret 122 } 123 124 func flattenRec(ret *MultiError, err error) { 125 switch et := err.(type) { 126 case nil: 127 case MultiError: 128 for _, e := range et { 129 flattenRec(ret, e) 130 } 131 default: 132 *ret = append(*ret, et) 133 } 134 } 135 136 // extract removes unnecessary singletons and empty multierrors from a MultiError. 137 // 138 // It is not recursive. 139 func extract(e error) error { 140 switch et := e.(type) { 141 case nil: 142 return nil 143 case MultiError: 144 switch len(et) { 145 case 0: 146 return nil 147 case 1: 148 return et[0] 149 } 150 } 151 return e 152 } 153 154 // Append takes a list of errors, whether they are nil, MultiErrors, or regular errors, and combines them into a single error. 155 // 156 // The resulting error is not a multierror unless it needs to be. 157 // 158 // Sample usage shown below: 159 // 160 // err := DoSomething() 161 // err = errors.Append(err, DoSomethingElse()) 162 // err = errors.Append(err, DoAThirdThing()) 163 // 164 // if err != nil { 165 // // log an error or something, I don't know 166 // } 167 // // proceed as normal 168 func Append(errs ...error) error { 169 return extract(Flatten(MultiError(errs))) 170 }