forked from sudo-suhas/xgo
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherror.go
More file actions
213 lines (181 loc) · 4.41 KB
/
error.go
File metadata and controls
213 lines (181 loc) · 4.41 KB
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package errors
import (
"bytes"
)
// Package types are inspired by the following articles:
// - https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html
// - https://middlemost.com/failure-is-your-domain/
// - https://about.sourcegraph.com/go/gophercon-2019-handling-go-errors
// Error is the type that implements the error interface. An Error value
// may leave some values unset.
//
// If the error is printed, only those items that have been set to
// non-zero values will appear in the result.
//
// If Kind is not specified or Unknown, we try to set it to the Kind of
// the underlying error.
type Error struct {
// Op describes an operation, usually as the package and method,
// such as "key/server.Lookup".
Op string
// Kind is the class of error, such as permission failure,
// or "Unknown" if its class is unknown or irrelevant.
Kind Kind
// Text is the error string. Text is not expected to be suitable to
// be shown to the end user.
Text string
// UserMsg is the error message suitable to be shown to the end
// user.
UserMsg string
// Data is arbitrary value associated with the error. Data is not
// expected to be suitable to be shown to the end user.
Data interface{}
// Err is the underlying error that triggered this one, if any.
Err error
// ToJSON is used to override the default implementation of
// converting the Error instance into a JSON value. Optional.
ToJSON JSONFunc
}
// E builds an error value with the provided options.
//
// if err := svc.ProcessSomething(); err != nil {
// return errors.E(errors.WithOp(op), errors.WithErr(err))
// }
func E(opt Option, opts ...Option) error {
var e Error
for _, opt := range prepend(opt, opts) {
opt.Apply(&e)
}
e.promoteFields()
return &e
}
func (e *Error) Error() string {
var b bytes.Buffer
e.appendError(&b)
if b.Len() == 0 {
return "no error"
}
return b.String()
}
func (e *Error) appendError(b *bytes.Buffer) {
writePaddedStr(b, e.Op)
if e.Kind != Unknown {
writePaddedStr(b, e.Kind.String())
}
writePaddedStr(b, e.Text)
if e.Err == nil {
return
}
err, ok := e.Err.(*Error)
if !ok {
writePaddedStr(b, e.Err.Error())
return
}
err.appendError(b)
}
// Ops returns the "stack" of operations for the error.
func (e *Error) Ops() []string {
var ops []string
walk(e, func(err *Error) {
if err.Op != "" {
ops = append(ops, err.Op)
}
})
return ops
}
// GetKind implements the GetKind interface.
func (e *Error) GetKind() Kind { return e.Kind }
// Unwrap unpacks wrapped errors. It is used by functions errors.Is and
// errors.As.
func (e *Error) Unwrap() error { return e.Err }
func (e *Error) promoteFields() {
prev, ok := e.Err.(*Error)
if !ok {
return
}
// The previous error was also one of ours. Suppress duplications
// so the message won't contain the same op, kind etc.
if e.Op == prev.Op {
prev.Op = ""
}
if prev.Kind == e.Kind {
prev.Kind = Unknown
}
if prev.UserMsg == e.UserMsg {
prev.UserMsg = ""
}
if prev.Text == e.Text {
prev.Text = ""
}
// If this error has Op/UserMsg/Kind/Data/ToJSON unset, pull up the
// inner one.
if e.Op == "" {
e.Op, prev.Op = prev.Op, ""
}
if e.Kind == Unknown {
e.Kind, prev.Kind = prev.Kind, Unknown
}
if e.UserMsg == "" {
e.UserMsg, prev.UserMsg = prev.UserMsg, ""
}
if e.Data == nil {
e.Data, prev.Data = prev.Data, nil
}
if e.ToJSON == nil {
e.ToJSON, prev.ToJSON = prev.ToJSON, nil
}
if prev.Op != "" || prev.Kind != Unknown {
// If Op/Kind is present, neither Text nor Err can be promoted up.
return
}
if e.Text == "" {
e.Text, prev.Text = prev.Text, ""
}
if prev.isZero() {
e.Err = nil
} else if hasOnlyErr(*prev) {
e.Err = prev.Err
}
}
func (e *Error) isZero() bool {
return e.Op == "" &&
e.Kind == Unknown &&
e.Text == "" &&
e.Err == nil &&
e.UserMsg == "" &&
e.Data == nil &&
e.ToJSON == nil
}
func walk(e *Error, f func(*Error)) {
for e != nil {
f(e)
var ok bool
if e, ok = e.Err.(*Error); !ok {
return
}
}
}
func prepend(head Option, body []Option) []Option {
opts := make([]Option, len(body)+1)
opts[0] = head
copy(opts[1:], body)
return opts
}
func writePaddedStr(b *bytes.Buffer, s string) {
if s == "" {
return
}
pad(b, ": ")
b.WriteString(s)
}
// pad appends str to the buffer if the buffer already has some data.
func pad(b *bytes.Buffer, str string) {
if b.Len() == 0 {
return
}
b.WriteString(str)
}
func hasOnlyErr(e Error) bool {
e.Err = nil
return e.isZero()
}