github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/errors/errors.go (about) 1 package errors 2 3 import ( 4 "fmt" 5 "runtime" 6 ) 7 8 // ---------------------------------------- 9 // Convenience method. 10 11 func Wrap(cause interface{}, format string, args ...interface{}) Error { 12 if causeCmnError, ok := cause.(*cmnError); ok { //nolint:gocritic 13 msg := fmt.Sprintf(format, args...) 14 return causeCmnError.Stacktrace().Trace(1, msg) 15 } else if cause == nil { 16 return newCmnError(FmtError{format, args}).Stacktrace() 17 } else { 18 // NOTE: causeCmnError is a typed nil here. 19 msg := fmt.Sprintf(format, args...) 20 return newCmnError(cause).Stacktrace().Trace(1, msg) 21 } 22 } 23 24 func Cause(err error) error { 25 if cerr, ok := err.(*cmnError); ok { 26 return cerr.Data().(error) 27 } else { 28 return err 29 } 30 } 31 32 // ---------------------------------------- 33 // Error & cmnError 34 35 /* 36 Usage with arbitrary error data: 37 38 ```go 39 40 // Error construction 41 type MyError struct{} 42 var err1 error = NewWithData(MyError{}, "my message") 43 ... 44 // Wrapping 45 var err2 error = Wrap(err1, "another message") 46 if (err1 != err2) { panic("should be the same") 47 ... 48 // Error handling 49 switch err2.Data().(type){ 50 case MyError: ... 51 default: ... 52 } 53 54 ``` 55 */ 56 type Error interface { 57 Error() string 58 Stacktrace() Error 59 Trace(offset int, format string, args ...interface{}) Error 60 Data() interface{} 61 } 62 63 // New Error with formatted message. 64 // The Error's Data will be a FmtError type. 65 func New(format string, args ...interface{}) Error { 66 err := FmtError{format, args} 67 return newCmnError(err) 68 } 69 70 // New Error with specified data. 71 func NewWithData(data interface{}) Error { 72 return newCmnError(data) 73 } 74 75 type cmnError struct { 76 data interface{} // associated data 77 msgtraces []msgtraceItem // all messages traced 78 stacktrace []uintptr // first stack trace 79 } 80 81 var _ Error = &cmnError{} 82 83 // NOTE: do not expose. 84 func newCmnError(data interface{}) *cmnError { 85 return &cmnError{ 86 data: data, 87 msgtraces: nil, 88 stacktrace: nil, 89 } 90 } 91 92 // Implements error. 93 func (err *cmnError) Error() string { 94 return fmt.Sprintf("%v", err) 95 } 96 97 // Implements Unwrap method for compat with stdlib errors.Is()/As(). 98 func (err *cmnError) Unwrap() error { 99 if err.data == nil { 100 return nil 101 } 102 werr, ok := err.data.(error) 103 if !ok { 104 return nil 105 } 106 return werr 107 } 108 109 // Captures a stacktrace if one was not already captured. 110 func (err *cmnError) Stacktrace() Error { 111 if err.stacktrace == nil { 112 offset := 3 113 depth := 32 114 err.stacktrace = captureStacktrace(offset, depth) 115 } 116 return err 117 } 118 119 // Add tracing information with msg. 120 // Set n=0 unless wrapped with some function, then n > 0. 121 func (err *cmnError) Trace(offset int, format string, args ...interface{}) Error { 122 msg := fmt.Sprintf(format, args...) 123 return err.doTrace(msg, offset) 124 } 125 126 // Return the "data" of this error. 127 // Data could be used for error handling/switching, 128 // or for holding general error/debug information. 129 func (err *cmnError) Data() interface{} { 130 return err.data 131 } 132 133 func (err *cmnError) doTrace(msg string, n int) Error { 134 // Ignoring linting on `runtime.Caller` for now, as it's 135 // a critical method that can't be currently reworked 136 //nolint:dogsled 137 pc, _, _, _ := runtime.Caller(n + 2) // +1 for doTrace(). +1 for the caller. 138 // Include file & line number & msg. 139 // Do not include the whole stack trace. 140 err.msgtraces = append(err.msgtraces, msgtraceItem{ 141 pc: pc, 142 msg: msg, 143 }) 144 return err 145 } 146 147 func (err *cmnError) Format(s fmt.State, verb rune) { 148 switch { 149 case verb == 'p': 150 s.Write([]byte(fmt.Sprintf("%p", &err))) 151 case verb == 'v' && s.Flag('+'): 152 s.Write([]byte("--= Error =--\n")) 153 // Write data. 154 fmt.Fprintf(s, "Data: %+v\n", err.data) 155 // Write msg trace items. 156 s.Write([]byte("Msg Traces:\n")) 157 for i, msgtrace := range err.msgtraces { 158 fmt.Fprintf(s, " %4d %s\n", i, msgtrace.String()) 159 } 160 s.Write([]byte("--= /Error =--\n")) 161 case verb == 'v' && s.Flag('#'): 162 s.Write([]byte("--= Error =--\n")) 163 // Write data. 164 fmt.Fprintf(s, "Data: %#v\n", err.data) 165 // Write msg trace items. 166 s.Write([]byte("Msg Traces:\n")) 167 for i, msgtrace := range err.msgtraces { 168 fmt.Fprintf(s, " %4d %s\n", i, msgtrace.String()) 169 } 170 // Write stack trace. 171 if err.stacktrace != nil { 172 s.Write([]byte("Stack Trace:\n")) 173 frames := runtime.CallersFrames(err.stacktrace) 174 for i := 0; ; i++ { 175 frame, more := frames.Next() 176 fmt.Fprintf(s, " %4d %s:%d\n", i, frame.File, frame.Line) 177 if !more { 178 break 179 } 180 } 181 } 182 s.Write([]byte("--= /Error =--\n")) 183 default: 184 // Write msg. 185 fmt.Fprintf(s, "%v", err.data) 186 } 187 } 188 189 // ---------------------------------------- 190 // stacktrace & msgtraceItem 191 192 func captureStacktrace(offset int, depth int) []uintptr { 193 pcs := make([]uintptr, depth) 194 n := runtime.Callers(offset, pcs) 195 return pcs[0:n] 196 } 197 198 type msgtraceItem struct { 199 pc uintptr 200 msg string 201 } 202 203 func (mti msgtraceItem) String() string { 204 fnc := runtime.FuncForPC(mti.pc) 205 file, line := fnc.FileLine(mti.pc) 206 return fmt.Sprintf("%s:%d - %s", 207 file, line, 208 mti.msg, 209 ) 210 } 211 212 // ---------------------------------------- 213 // fmt error 214 215 /* 216 FmtError is the data type for New() (e.g. New().Data().(FmtError)) 217 Theoretically it could be used to switch on the format string. 218 219 ```go 220 221 // Error construction 222 var err1 error = New("invalid username %v", "BOB") 223 var err2 error = New("another kind of error") 224 ... 225 // Error handling 226 switch err1.Data().(cmn.FmtError).Format() { 227 case "invalid username %v": ... 228 case "another kind of error": ... 229 default: ... 230 } 231 232 ``` 233 */ 234 type FmtError struct { 235 format string 236 args []interface{} 237 } 238 239 func (fe FmtError) Error() string { 240 return fmt.Sprintf(fe.format, fe.args...) 241 } 242 243 func (fe FmtError) String() string { 244 return fmt.Sprintf("FmtError{format:%v,args:%v}", 245 fe.format, fe.args) 246 } 247 248 func (fe FmtError) Format() string { 249 return fe.format 250 }