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