-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwrap.go
164 lines (136 loc) · 4 KB
/
wrap.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
package tortilla
import (
"bytes"
"fmt"
"strings"
"text/template"
)
const stackPrettyPrintTpl = `
{{- range $layer := .}}{{range $target, $chain := .}}{{$target}}:{{range $chain}}
....{{.}}{{end}}
{{end}}{{end}}`
var parsedTpl *template.Template
func init() {
tpl, err := template.New("pretty-print").Parse(stackPrettyPrintTpl)
if err != nil {
err = fmt.Errorf("tortilla: unable to parse the pretty print tpl: %s", err)
panic(err)
}
parsedTpl = tpl
}
// Stack is an alias for the slice of layers returned by Tortilla.RollOut()
type Stack []map[string][]string
// PrettyPrint allows to render a visual string of the error stack.
// The format is a slice of maps, each map is "wrapping error": []list of wrapped error. Example:
//
// last error used to wrap others:
// ....last wrapped error
// ....older error
// older wrapping error:
// ....blablabla
func (s Stack) PrettyPrint() string {
output := new(bytes.Buffer)
err := parsedTpl.Execute(output, s)
if err != nil {
return "Pretty print error"
}
return strings.TrimSpace(output.String())
}
type layer struct {
target error
chain []error
}
// Tortilla holds the layers of the errors added in the stack.
// Create a Tortilla with New(err).
//
// Then use Wrap(err) to wrap your Tortilla with the a new error. This is the
// equivalent of using fmt.Errorf("%w %s", wrapWithErr, initialErr)
//
// You can also add an error in the stack without wrapping with the Add(err) method. This
// can be useful to keep the history of what happened in your program without "typing"
// with (i.e. errors.Is or errors.As won't return true).
//
// If an error is printed with Error(), a string will be generated with the errors in the
// stack in an inlined form. The errors are sorted in reverse order of creation.
//
// Of course a Tortilla can be rolled out! The RollOut method returns the layers of your
// Tortilla as a Stack type (an alias of []map[string][]string). Then you can use the
// method Stack.PrettyPrint method to display a hierarchy of the errors wrapped and added
// in your Tortilla lifetime.
type Tortilla struct {
layers []layer
}
// Error returns a flattened string composed by the errors in the stack.
func (t Tortilla) Error() string {
var msg string
for _, layer := range t.layers {
msg += buildLayerMsg(layer)
}
return strings.TrimSpace(msg)
}
// Unwrap allows to compare a Tortilla with errors.Is and errors.As.
func (t Tortilla) Unwrap() error {
return t.layers[0].target
}
// Wrap wraps your Tortilla with a new error.
func (t Tortilla) Wrap(target error) Tortilla {
return t.appendLayer(target, nil)
}
// Add adds a new error in the error chain of the last wrapping.
func (t Tortilla) Add(err error) Tortilla {
chain := []error{err}
chain = append(chain, t.layers[0].chain...)
layers := t.layers
layers[0].chain = chain
return Tortilla{layers: layers}
}
// RollOut allows you to see what's inside your Tortilla.
func (t Tortilla) RollOut() Stack {
s := make(Stack, 0, len(t.layers))
for _, l := range t.layers {
c := make([]string, 0, len(l.chain))
for _, e := range l.chain {
c = append(c, e.Error())
}
layer := map[string][]string{
l.target.Error(): c,
}
s = append(s, layer)
}
return s
}
func (t Tortilla) appendLayer(target error, chain []error) Tortilla {
layers := []layer{{target: target, chain: chain}}
layers = append(layers, t.layers...)
return Tortilla{
layers: layers,
}
}
// New creates a new Tortilla from the given error.
// If err is an existing Tortilla, the returned value is that Tortilla.
// Otherwise a new Tortilla is created from err.
func New(err error) Tortilla {
t, ok := err.(Tortilla)
if !ok {
return Tortilla{
layers: []layer{
{target: err},
},
}
}
return t
}
func buildChainMsg(chain []error) (msg string) {
for _, err := range chain {
msg += err.Error() + ", "
}
msg = strings.TrimRight(msg, ", ")
return
}
func buildLayerMsg(layer layer) (msg string) {
msg = layer.target.Error() + ": "
msg += buildChainMsg(layer.chain)
msg = strings.TrimRight(msg, ": ")
msg += ". "
return
}