lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xerr/xerr.go (about) 1 // Copyright (C) 2015-2020 Nexedi SA and Contributors. 2 // Kirill Smelkov <kirr@nexedi.com> 3 // 4 // This program is free software: you can Use, Study, Modify and Redistribute 5 // it under the terms of the GNU General Public License version 3, or (at your 6 // option) any later version, as published by the Free Software Foundation. 7 // 8 // You can also Link and Combine this program with other software covered by 9 // the terms of any of the Free Software licenses or any of the Open Source 10 // Initiative approved licenses and Convey the resulting work. Corresponding 11 // source of such a combination shall include the source code for all other 12 // software used. 13 // 14 // This program is distributed WITHOUT ANY WARRANTY; without even the implied 15 // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 // 17 // See COPYING file for full licensing terms. 18 // See https://www.nexedi.com/licensing for rationale and options. 19 20 // Package xerr provides addons for error-handling. 21 // 22 // # Error context 23 // 24 // Context and Contextf are handy to concisely add context to returned error, 25 // for example: 26 // 27 // func myfunc(arg1, arg2 string) (..., err error) { 28 // defer xerr.Contextf(&err, "doing something (%s, %s)", arg1, arg2) 29 // ... 30 // 31 // which will, if returned error is !nil, wrap it with the following prefix: 32 // 33 // "doing something (%s, %s):" % (arg1, arg2) 34 // 35 // The original unwrapped error will be still accessible as the cause of 36 // returned error. Please see package github.com/pkg/errors for details on 37 // this topic. 38 // 39 // # Error vector 40 // 41 // Sometimes there are several operations performed and we want to collect 42 // errors from them all. For this Errorv could be used which is vector of 43 // errors and at the same time an error itself. After collecting it is possible 44 // to extract resulting error from the vector in canonical form with 45 // Errorv.Err. Errorv also provides handy ways to append errors to the vector 46 // - see Errorv.Append* for details. 47 // 48 // For convenience Merge could be used to concisely construct error vector 49 // from !nil errors and extract its canonical form in one line, for example: 50 // 51 // err1 := op1(...) 52 // err2 := op2(...) 53 // err3 := op3(...) 54 // 55 // err := xerr.Merge(err1, err2, err3) 56 // return err 57 // 58 // There is also First counterpart to Merge, which returns only first !nil 59 // error. 60 // 61 // Since Errorv is actually a slice it cannot be generally compared - for example 62 // comparing 2 error interfaces that both have dynamic type Errorv will panic 63 // at runtime. However it is possible to compare Errorv to other error types, 64 // because interfaces with different dynamic types are always not equal. For 65 // example the following works: 66 // 67 // var err error = Errorv{...} // received as result from a function 68 // if err == io.EOF { 69 // ... 70 package xerr 71 72 import ( 73 "fmt" 74 75 "github.com/pkg/errors" 76 ) 77 78 // Errorv is error vector merging multiple errors (e.g. after collecting them from several parallel workers). 79 type Errorv []error 80 81 // Error returns string representation of error vector. 82 // 83 // - "" if len(errv)==0 84 // - errv[0].Error() if len(errv)==1 85 // - "<n> errors:\n" + string representation of every error on separate line, otherwise. 86 func (errv Errorv) Error() string { 87 switch len(errv) { 88 case 0: 89 return "" 90 case 1: 91 return errv[0].Error() 92 } 93 94 msg := fmt.Sprintf("%d errors:\n", len(errv)) 95 for _, e := range errv { 96 msg += fmt.Sprintf("\t- %s\n", e) 97 } 98 return msg 99 } 100 101 // Append appends err to error vector. 102 func (errv *Errorv) Append(err error) { 103 *errv = append(*errv, err) 104 } 105 106 // Appendif appends err to error vector if err != nil. 107 func (errv *Errorv) Appendif(err error) { 108 if err == nil { 109 return 110 } 111 errv.Append(err) 112 } 113 114 // Appendf appends formatted error string. 115 func (errv *Errorv) Appendf(format string, a ...interface{}) { 116 errv.Append(fmt.Errorf(format, a...)) 117 } 118 119 // Err returns error in canonical form accumulated in error vector. 120 // 121 // - nil if len(errv)==0 122 // - errv[0] if len(errv)==1 // XXX is this good idea? 123 // - errv otherwise 124 func (errv Errorv) Err() error { 125 switch len(errv) { 126 case 0: 127 return nil 128 case 1: 129 return errv[0] 130 default: 131 return errv 132 } 133 } 134 135 // Merge merges non-nil errors into one error. 136 // 137 // it returns: 138 // 139 // - nil if all errors are nil 140 // - single error if there is only one non-nil error 141 // - Errorv with non-nil errors if there is more than one non-nil error 142 func Merge(errv ...error) error { 143 ev := Errorv{} 144 for _, err := range errv { 145 ev.Appendif(err) 146 } 147 return ev.Err() 148 } 149 150 // First returns first non-nil error, or nil if there is no errors. 151 func First(errv ...error) error { 152 for _, err := range errv { 153 if err != nil { 154 return err 155 } 156 } 157 return nil 158 } 159 160 // ---------------------------------------- 161 162 // Context provides error context to be automatically added on error return. 163 // 164 // Intended to be used under defer like this: 165 // 166 // func myfunc(...) (..., err error) { 167 // defer xerr.Context(&err, "error context") 168 // ... 169 // 170 // It is also possible to use Context directly to add context to an error if it 171 // is non-nil: 172 // 173 // ..., myerr := f() 174 // xerr.Context(&myerr, "while doing something") 175 // 176 // which is equivalent to 177 // 178 // import "github.com/pkg/errors" 179 // 180 // ..., myerr := f() 181 // if myerr != nil { 182 // myerr = errors.WithMessage(myerr, "while doing something") 183 // } 184 func Context(errp *error, context string) { 185 if *errp == nil { 186 return 187 } 188 *errp = errors.WithMessage(*errp, context) 189 } 190 191 // Contextf provides formatted error context to be automatically added on error return. 192 // 193 // Contextf is formatted analog of Context. Please see Context for details on how to use. 194 func Contextf(errp *error, format string, argv ...interface{}) { 195 if *errp == nil { 196 return 197 } 198 199 *errp = errors.WithMessage(*errp, fmt.Sprintf(format, argv...)) 200 }