github.com/haraldrudell/parl@v0.4.176/perrors/errorglue/chain-string.go (about) 1 /* 2 © 2020–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package errorglue 7 8 import ( 9 "errors" 10 "fmt" 11 "strings" 12 13 "github.com/haraldrudell/parl/pruntime" 14 ) 15 16 const ( 17 // string prepended to code location: “ at ” 18 atStringChain = "\x20at\x20" 19 // the string error value for error nil “OK” 20 errorIsNilString = "OK" 21 ) 22 23 // ChainString() gets a string representation of a single error chain 24 // TODO 220319 finish comment 25 func ChainString(err error, format CSFormat) (s string) { 26 27 // no error case 28 if err == nil { 29 return errorIsNilString // no error return "OK" 30 } 31 32 switch format { 33 case DefaultFormat: // like printf %v, printf %s and error.Error() 34 s = err.Error() // the first error in the chain has our error message 35 return 36 case CodeLocation: // only one errror, with code location 37 s = shortFormat(err) 38 return 39 case ShortFormat: // one-liner with code location and associated errors 40 s = shortFormat(err) 41 42 //add appended errors at the end 2[…] 43 var list = ErrorList(err) 44 if len(list) > 1 { 45 list = list[1:] // skip err itself 46 var sList = make([]string, len(list)) 47 for i, e := range list { 48 sList[i] = shortFormat(e) 49 } 50 s += fmt.Sprintf(" %d[%s]", len(list), strings.Join(sList, ", ")) 51 } 52 53 return 54 case ShortSuffix: // for stackError, this provide the code-location without leading “ at ” 55 s = codeLocation(err) 56 return 57 case LongFormat: 58 // all errors with message and type 59 // - stack traces 60 // - related data 61 // - associated errors 62 case LongSuffix: 63 // first error of each error-chain in long format 64 // - an error chain is the initial error and any related errors 65 // - stack traces and data for all errors 66 default: 67 var stack = pruntime.NewStack(0) 68 var packFuncS string 69 if len(stack.Frames()) > 0 { 70 packFuncS = stack.Frames()[0].Loc().PackFunc() + "\x20" 71 } 72 var e = fmt.Errorf("%sbad format: %s", packFuncS, format) 73 panic(NewErrorStack(e, stack)) 74 } 75 // LongFormat, LongSuffix: recursive printing and associated errors 76 77 // errorMap is a map of errors already printed 78 // - it is used to avoid cyclic printing 79 var errorMap = map[error]bool{} 80 // errorsToPrint: list of discovered associated errors to print 81 var errorsToPrint = []error{err} 82 83 // traverse all error instances 84 // - the initial error and any unique related error 85 for i := 0; i < len(errorsToPrint); i++ { 86 87 // every error is an error chain 88 // - traverse error chain 89 var isFirstInChain = true 90 for err = errorsToPrint[i]; err != nil; err = errors.Unwrap(err) { 91 92 // look for associated errors 93 if relatedHolder, ok := err.(RelatedError); ok { 94 if relatedErr := relatedHolder.AssociatedError(); relatedErr != nil { 95 // add any new errors to errorsToPrint 96 if !errorMap[relatedErr] { 97 errorMap[relatedErr] = true 98 errorsToPrint = append(errorsToPrint, relatedErr) 99 } 100 } 101 } 102 103 // ChainStringer errors produce their own representations 104 var errorAsString string 105 if richError, ok := err.(ChainStringer); ok { 106 if isFirstInChain { 107 errorAsString = richError.ChainString(LongFormat) 108 } else { 109 errorAsString = richError.ChainString(format) 110 } 111 } else { 112 113 // regular errors 114 // - LongFormat prints all with type 115 // - LongSuffix only prints if first in chain 116 if format == LongFormat || isFirstInChain { 117 errorAsString = fmt.Sprintf("%s [%T]", err.Error(), err) 118 } 119 } 120 121 if len(errorAsString) > 0 { 122 if len(s) > 0 { 123 s += "\n" + errorAsString 124 } else { 125 s = errorAsString 126 } 127 } 128 isFirstInChain = false 129 } 130 } 131 132 return 133 } 134 135 // shortFormat: “message at runtime/panic.go:914” 136 // - if err or its error-chain does not have location: “message” like [error.Error] 137 // - if err or its error-chain has panic, location is the code line 138 // that caused the first panic 139 // - if err or its error-chain has location but no panic, 140 // location is where the oldest error with stack was created 141 // - err is non-nil 142 func shortFormat(err error) (s string) { 143 144 // append the top frame of the oldest, innermost stack trace code location 145 s = codeLocation(err) 146 if s != "" { 147 s = err.Error() + atStringChain + s 148 } else { 149 s = err.Error() 150 } 151 152 return 153 } 154 155 // codeLocation: “runtime/panic.go:914” 156 // - if err or its error-chain does not have location: empty string 157 // - if err or its error-chain has panic, location is the code line 158 // that caused the first panic 159 // - if err or its error-chain has location but no panic, 160 // location is where the oldest error with stack was created 161 // - err is non-nil, no “ at ” prefix 162 func codeLocation(err error) (message string) { 163 164 // err or err’s error-chain may contain stacks 165 // - any of the stacks may contain a panic 166 // - an error with stack is able to locate any panic it or its chain has 167 // - therefore scan for any error with stack and ask the first one for location 168 for e := err; e != nil; e = errors.Unwrap(e) { 169 if _, ok := e.(ErrorCallStacker); !ok { 170 continue // e does not have stack 171 } 172 var _ = (&errorStack{}).ChainString 173 message = e.(ChainStringer).ChainString(ShortSuffix) 174 return // found location return 175 } 176 177 return // no location return 178 } 179 180 // PrintfFormat gets the ErrorFormat to use when executing 181 // the Printf value verb 'v' 182 // - %+v: [LongFormat] 183 // - %-v: [ShortFormat] 184 // - %v: [DefaultFormat] 185 func PrintfFormat(s fmt.State) CSFormat { 186 if IsPlusFlag(s) { 187 return LongFormat 188 } else if IsMinusFlag(s) { 189 return ShortFormat 190 } 191 return DefaultFormat 192 }