lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/exc/error.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 exc provides exception-style error handling for Go. 21 // 22 // Raise and Catch allow to raise and catch exceptions. 23 // 24 // By default the error caught is the same error that was raised. However with 25 // Context* functions can arrange for context related to what they are doing to 26 // be added to raised error as prefix, for example 27 // 28 // func doSomething(path string) { 29 // defer exc.Context(func() interface{} { 30 // return fmt.Sprintf("doing something %s", path) 31 // })() 32 // 33 // or 34 // 35 // func doSomething(path string) { 36 // defer exc.Contextf("doing something %s", path) 37 // ... 38 // 39 // Lacking such Context annotations Addcallingcontext allows to add function 40 // names up to the exception point as the calling context. However this way 41 // only actions without corresponding arguments (path in the above example) can 42 // be shown, and there have to be direct 1-1 relation between program and 43 // operational structures. 44 // 45 // Runx allows to run a function which raises exception, and return exception 46 // as regular error, if any. Similarly XRun allows to run a function which 47 // returns regular error, and raise exception if error is not nil. 48 // 49 // Last but not least it has to be taken into account that exceptions 50 // complicate control flow and are directly applicable only to serial programs. 51 // Their use is thus justified in only limited number of cases and by default 52 // one should always first strongly consider using explicit error returns 53 // programming style which is canonical in Go. 54 package exc 55 56 import ( 57 "fmt" 58 "runtime" 59 "strings" 60 61 "lab.nexedi.com/kirr/go123/my" 62 "lab.nexedi.com/kirr/go123/xruntime" 63 ) 64 65 // Error is the type which is raised by Raise(arg). 66 type Error struct { 67 arg interface{} 68 link *Error // chain of linked Error(s) - see e.g. Context() 69 } 70 71 func (e *Error) Error() string { 72 msgv := []string{} 73 msg := "" 74 for e != nil { 75 if f, ok := e.arg.(runtime.Frame); ok { 76 //msg = f.Function 77 //msg = fmt.Sprintf("%s (%s:%d)", f.Function, f.File, f.Line) 78 msg = strings.TrimPrefix(f.Function, _errorpkgdot) // XXX -> better prettyfunc 79 } else { 80 msg = fmt.Sprint(e.arg) 81 } 82 msgv = append(msgv, msg) 83 e = e.link 84 } 85 86 return strings.Join(msgv, ": ") 87 } 88 89 // Aserror turns any value into Error. 90 // 91 // if v is already Error - it stays the same, 92 // otherwise new Error is created. 93 func Aserror(v interface{}) *Error { 94 if e, ok := v.(*Error); ok { 95 return e 96 } 97 return &Error{v, nil} 98 } 99 100 // Raise raise error to upper level. 101 // 102 // See Catch which receives raised error. 103 func Raise(arg interface{}) { 104 panic(Aserror(arg)) 105 } 106 107 // Raisef raises formatted string. 108 func Raisef(format string, a ...interface{}) { 109 panic(Aserror(fmt.Sprintf(format, a...))) 110 } 111 112 // Raiseif raises if err != nil. 113 // 114 // NOTE err can be != nil even if typed obj = nil: 115 // 116 // var obj *T; 117 // err = obj 118 // err != nil is true 119 func Raiseif(err error) { 120 //if err != nil && !reflect.ValueOf(err).IsNil() { 121 if err != nil { 122 panic(Aserror(err)) 123 } 124 } 125 126 // _errcatch checks recovered value to be of type *Error. 127 // 128 // if there is non-Error error - repanic it, 129 // otherwise return Error either nil (no panic), or actual value. 130 func _errcatch(r interface{}) *Error { 131 e, _ := r.(*Error) 132 if e == nil && r != nil { 133 panic(r) 134 } 135 return e 136 } 137 138 // Catch catches error and calls f(e) if it was caught. 139 // 140 // Must be called under defer. 141 func Catch(f func(e *Error)) { 142 e := _errcatch(recover()) 143 if e == nil { 144 return 145 } 146 147 f(e) 148 } 149 150 // Onunwind installs error filter to be applied on error unwinding. 151 // 152 // It hooks into unwinding process with f() call. Returned error is reraised. 153 // see also: Context() 154 // 155 // Must be called under defer. 156 func Onunwind(f func(e *Error) *Error) { 157 // cannot do Catch(...) 158 // as recover() works only in first-level called functions 159 e := _errcatch(recover()) 160 if e == nil { 161 return 162 } 163 164 e = f(e) 165 panic(e) 166 } 167 168 // Context provides error context to be added on unwinding. 169 // 170 // f is called if error unwinding is happening and its 171 // result is added to raised error as "prefix" context. 172 // 173 // Must be called under defer. 174 func Context(f func() interface{}) { 175 e := _errcatch(recover()) 176 if e == nil { 177 return 178 } 179 180 arg := f() 181 panic(Addcontext(e, arg)) 182 } 183 184 // Contextf provides formatted string to be added to error as context on unwinding. 185 // 186 // It is shorthand for Context wrapping fmt.Sprintf. 187 // 188 // Must be called under defer. 189 func Contextf(format string, argv ...interface{}) { 190 e := _errcatch(recover()) 191 if e == nil { 192 return 193 } 194 195 panic(Addcontext(e, fmt.Sprintf(format, argv...))) 196 } 197 198 // Addcontext adds "prefix" context to error. 199 func Addcontext(e *Error, arg interface{}) *Error { 200 return &Error{arg, e} 201 } 202 203 var ( 204 _errorpkgname string // package name under which error.go lives 205 _errorpkgdot string // errorpkg. 206 _errorraise string // errorpkg.Raise 207 ) 208 209 func init() { 210 _errorpkgname = my.PkgName() 211 _errorpkgdot = _errorpkgname + "." 212 _errorraise = _errorpkgname + ".Raise" 213 } 214 215 // Addcallingcontext adds calling context to error. 216 // 217 // Add calling function frames as error context up-to topfunc not including. 218 // 219 // See also: Addcontext() 220 func Addcallingcontext(topfunc string, e *Error) *Error { 221 seenraise := false 222 for _, f := range xruntime.Traceback(2) { 223 // do not show anything after raise*() 224 if !seenraise && strings.HasPrefix(f.Function, _errorraise) { 225 seenraise = true 226 continue 227 } 228 if !seenraise { 229 continue 230 } 231 232 // do not go beyond topfunc 233 if topfunc != "" && f.Function == topfunc { 234 break 235 } 236 237 // skip intermediates 238 if strings.HasSuffix(f.Function, "_") { // XXX -> better skipfunc 239 continue 240 } 241 242 e = &Error{f, e} 243 } 244 245 return e 246 } 247 248 // Runx runs a function which raises exception, and return exception as regular error, if any. 249 // 250 // the error, if non-nil, will be returned with added calling context - see 251 // Addcallingcontext for details. 252 // 253 // See also: Funcx. 254 func Runx(xf func()) (err error) { 255 return Funcx(xf)() 256 } 257 258 // Funcx converts a function raising exception, to function returning regular error. 259 // 260 // Returned function calls xf and converts exception, if any, to error. 261 // 262 // See also: Runx. 263 func Funcx(xf func()) func() error { 264 return func() (err error) { 265 here := my.FuncName() 266 defer Catch(func(e *Error) { 267 err = Addcallingcontext(here, e) 268 }) 269 270 xf() 271 return 272 } 273 } 274 275 // XRun runs a function which returns regular error, and raise exception if error is not nil. 276 // 277 // See also: XFunc. 278 func XRun(f func() error) { 279 XFunc(f)() 280 } 281 282 // XFunc converts a function returning regular error, to function raising exception. 283 // 284 // Returned function calls f and raises appropriate exception if error is not nil. 285 // 286 // See also: XRun. 287 func XFunc(f func() error) func() { 288 return func() { 289 err := f() 290 Raiseif(err) 291 } 292 }