github.com/haraldrudell/parl@v0.4.176/perrors/accessors.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package perrors 7 8 import ( 9 "errors" 10 "reflect" 11 "slices" 12 13 "github.com/haraldrudell/parl/perrors/errorglue" 14 "github.com/haraldrudell/parl/pruntime" 15 ) 16 17 const e116PackFuncStackFrames = 1 18 19 // ErrorData returns any embedded data values from err and its error chain as a list and map 20 // - list contains values where key was empty, oldest first 21 // - keyValues are string values associated with a key string, overwriting older values 22 // - err list keyValues may be nil 23 func ErrorData(err error) (list []string, keyValues map[string]string) { 24 25 // traverse the err and its error chain, newest first 26 // - only errors with data matter 27 for ; err != nil; err = errors.Unwrap(err) { 28 29 // ignore errrors without key/value pair 30 var e, ok = err.(errorglue.ErrorHasData) 31 if !ok { 32 continue 33 } 34 35 // empty key is appended to slice 36 // - oldest value first 37 var key, value = e.KeyValue() 38 if key == "" { // for the slice 39 list = append(list, value) // newest first 40 continue 41 } 42 43 // for the map 44 if keyValues == nil { 45 keyValues = map[string]string{key: value} 46 continue 47 } 48 // values are added newset first 49 // - do not overwrite newer values with older 50 if _, ok := keyValues[key]; !ok { 51 keyValues[key] = value 52 } 53 } 54 slices.Reverse(list) // oldest first 55 56 return 57 } 58 59 // ErrorList returns all error instances from a possible error chain. 60 // — If err is nil an empty slice is returned. 61 // — If err does not have associated errors, a slice of err, length 1, is returned. 62 // — otherwise, the first error of the returned slice is err followed by 63 // 64 // other errors oldest first. 65 // - Cyclic error values are dropped 66 func ErrorList(err error) (errs []error) { 67 return errorglue.ErrorList(err) 68 } 69 70 // HasStack detects if the error chain already contains a stack trace 71 func HasStack(err error) (hasStack bool) { 72 if err == nil { 73 return 74 } 75 var e errorglue.ErrorCallStacker 76 return errors.As(err, &e) 77 } 78 79 // IsWarning determines if an error has been flagged as a warning. 80 func IsWarning(err error) (isWarning bool) { 81 for ; err != nil; err = errors.Unwrap(err) { 82 if _, isWarning = err.(*errorglue.WarningType); isWarning { 83 return // is warning 84 } 85 } 86 return // not a warning 87 } 88 89 // error116.PackFunc returns the package name and function name 90 // of the caller: 91 // 92 // error116.FuncName 93 func PackFunc() (packageDotFunction string) { 94 var frames = 1 // cpunt PackFunc frame 95 return PackFuncN(frames) 96 } 97 98 func PackFuncN(skipFrames int) (packageDotFunction string) { 99 if skipFrames < 0 { 100 skipFrames = 0 101 } 102 var cL = pruntime.NewCodeLocation(e116PackFuncStackFrames + skipFrames) 103 packageDotFunction = cL.Name() 104 if pack := cL.Package(); pack != "main" { 105 packageDotFunction = pack + "." + packageDotFunction 106 } 107 return 108 } 109 110 // ErrpString returns the error message possibly 111 // contained in *errp 112 // - if errp or *errp is nil: “OK” 113 func ErrpString(errp *error) (s string) { 114 var err error 115 if errp != nil { 116 err = *errp 117 } 118 if err == nil { 119 s = "OK" 120 return 121 } 122 s = err.Error() 123 return 124 } 125 126 // LongShort picks output format 127 // - no error: “OK” 128 // - no panic: error-message at runtime.gopanic:26 129 // - panic: long format with all stack traces and values 130 // - associated errors are always printed 131 func LongShort(err error) (message string) { 132 var format errorglue.CSFormat 133 if err == nil { 134 format = errorglue.ShortFormat 135 } else if isPanic, _, _, _ := IsPanic(err); isPanic { 136 format = errorglue.LongFormat 137 } else { 138 format = errorglue.ShortFormat 139 } 140 message = errorglue.ChainString(err, format) 141 142 return 143 } 144 145 // perrors.Short gets a one-line location string similar to printf %-v and ShortFormat. 146 // Short() does not print stack traces, data and associated errors. 147 // Short() does print a one-liner of the error message and a brief code location: 148 // 149 // error-message at error116.(*csTypeName).FuncName-chainstring_test.go:26 150 func Short(err error) string { 151 return errorglue.ChainString(err, errorglue.ShortFormat) 152 } 153 154 // Deferr invokes a printing function for an error pointer. 155 // Deferr returns the message. 156 // Deferr is deferrable. 157 // A colon and a space is appended to label. 158 // If *errp is nil, OK is printed. 159 // If errp is nil, a message is printed. 160 // if fn is nil, nothing is printed. 161 // If *errp contains an error it is printed in Short form: 162 // 163 // label: Error message at error116.(*csTypeName).FuncName-chainstring_test.go:26 164 func Deferr(label string, errp *error, fn func(format string, a ...interface{})) string { 165 if errp == nil { 166 return "perrors.Deferr: errp nil" 167 } 168 if label != "" { 169 label += ":\x20" 170 } 171 s := label + errorglue.ChainString(*errp, errorglue.ShortFormat) 172 if fn != nil { 173 fn(s) 174 } 175 176 return s 177 } 178 179 // error116.Long() gets a comprehensive string representation similar to printf %+v and LongFormat. 180 // ShortFormat does not print stack traces, data and associated errors. 181 // Long() prints full stack traces, string key-value and list values for both the error chain 182 // of err, and associated errors and their chains 183 // 184 // error-message 185 // github.com/haraldrudell/parl/error116.(*csTypeName).FuncName 186 // /opt/sw/privates/parl/error116/chainstring_test.go:26 187 // runtime.goexit 188 // /opt/homebrew/Cellar/go/1.17.8/libexec/src/runtime/asm_arm64.s:1133 189 func Long(err error) string { 190 return errorglue.ChainString(err, errorglue.LongFormat) 191 } 192 193 /* 194 IsType determines if the chain of err contains an error of type target. 195 IsType is different from errors.Is in that IsType matches the type of err, 196 not its value. 197 IsType is different from errors.Is in that it works for error implementations missing 198 the Is() method. 199 IsType uses reflection. 200 pointerToErrorValue argument is a pointer to an error implementation value, ie: 201 202 if the target struct has pointer reciever, the argument type *targetStruct 203 if the target struct has value receiver, the argument type targetStruct 204 */ 205 func IsType(err error, pointerToErrorValue interface{}) (hadErrpType bool) { 206 207 // ensure pointerToErrorValue is non-nil pointer 208 // reflection returns nil for nil pointer 209 if pointerToErrorValue == nil { 210 panic(New("perrors.IsType: pointerToErrorValue nil")) 211 } 212 213 // ensure pointerToErrorValue is pointer 214 pointerType := reflect.TypeOf(pointerToErrorValue) 215 if pointerType.Kind() != reflect.Ptr { 216 panic(New("perrors.IsType: pointerToErrorValue not pointer")) 217 } 218 219 // get the error implementation type we are looking for 220 // this is what pointerToErrorValue points to 221 targetType := pointerType.Elem() 222 223 // traverse err’s error chain 224 for ; err != nil; err = errors.Unwrap(err) { 225 226 // get the type assigned to err interface 227 errType := reflect.TypeOf(err) 228 229 // check if the err type is the one we are looking for 230 if errType == targetType { 231 reflect.Indirect(reflect.ValueOf(pointerToErrorValue)).Set(reflect.ValueOf(err)) 232 return true // err match exit 233 } 234 235 // also check for what err points to 236 if errType.Kind() == reflect.Ptr { 237 errPointsToType := errType.Elem() 238 if errPointsToType == targetType { 239 reflect.Indirect(reflect.ValueOf(pointerToErrorValue)).Set(reflect.Indirect(reflect.ValueOf(err))) 240 return true // *err match exit 241 } 242 } 243 } 244 return // no match exit 245 } 246 247 func Error0(err error) (e error) { 248 for ; err != nil; err = errors.Unwrap(err) { 249 e = err 250 } 251 return 252 } 253 254 // IsError determines if err represents error condition for all error implementations 255 // - eg. unix.Errno that is uintptr 256 func IsError[T error](err T) (isError bool) { 257 258 // err is interface, obtain the runtime type and check for nil runtime value 259 var reflectValue = reflect.ValueOf(err) 260 if !reflectValue.IsValid() { 261 return // err interface has nil runtime-value return: false 262 } 263 264 // if err is not a zero-value, it is eror condition 265 isError = !reflectValue.IsZero() 266 267 return 268 }