Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.

Commit 777ed74

Browse files
committed
Destructure Wrap{,f} into WithStack(WithMessage(err, msg))
Introduces WithMessage as well as errors.fundamental, errors.withMessage and errors.withStack internal types. Adjust tests for the new wrapped format when combining fundamental and wrapped errors.
1 parent 785921b commit 777ed74

File tree

3 files changed

+135
-73
lines changed

3 files changed

+135
-73
lines changed

errors.go

Lines changed: 89 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -87,71 +87,76 @@
8787
package errors
8888

8989
import (
90-
"errors"
9190
"fmt"
9291
"io"
9392
)
9493

9594
// New returns an error with the supplied message.
95+
// New also records the stack trace at the point it was called.
9696
func New(message string) error {
97-
err := errors.New(message)
98-
return &withStack{
99-
err,
100-
callers(),
97+
return &fundamental{
98+
msg: message,
99+
stack: callers(),
101100
}
102101
}
103102

104103
// Errorf formats according to a format specifier and returns the string
105104
// as a value that satisfies error.
105+
// Errorf also records the stack trace at the point it was called.
106106
func Errorf(format string, args ...interface{}) error {
107-
err := fmt.Errorf(format, args...)
108-
return &withStack{
109-
err,
110-
callers(),
107+
return &fundamental{
108+
msg: fmt.Sprintf(format, args...),
109+
stack: callers(),
111110
}
112111
}
113112

114-
type withStack struct {
115-
error
113+
// fundamental is an error that has a message and a stack, but no caller.
114+
type fundamental struct {
115+
msg string
116116
*stack
117117
}
118118

119-
func (w *withStack) Format(s fmt.State, verb rune) {
119+
func (f *fundamental) Error() string { return f.msg }
120+
121+
func (f *fundamental) Format(s fmt.State, verb rune) {
120122
switch verb {
121123
case 'v':
122124
if s.Flag('+') {
123-
io.WriteString(s, w.Error())
124-
w.stack.Format(s, verb)
125+
io.WriteString(s, f.msg)
126+
f.stack.Format(s, verb)
125127
return
126128
}
127129
fallthrough
128-
case 's':
129-
io.WriteString(s, w.Error())
130+
case 's', 'q':
131+
io.WriteString(s, f.msg)
130132
}
131133
}
132134

133-
type cause struct {
134-
cause error
135-
msg string
135+
// WithStack annotates err with a stack trace at the point WithStack was called.
136+
// If err is nil, WithStack returns nil.
137+
func WithStack(err error) error {
138+
if err == nil {
139+
return nil
140+
}
141+
return &withStack{
142+
err,
143+
callers(),
144+
}
136145
}
137146

138-
func (c cause) Error() string { return fmt.Sprintf("%s: %v", c.msg, c.Cause()) }
139-
func (c cause) Cause() error { return c.cause }
140-
141-
// wrapper is an error implementation returned by Wrap and Wrapf
142-
// that implements its own fmt.Formatter.
143-
type wrapper struct {
144-
cause
147+
type withStack struct {
148+
error
145149
*stack
146150
}
147151

148-
func (w wrapper) Format(s fmt.State, verb rune) {
152+
func (w *withStack) Cause() error { return w.error }
153+
154+
func (w *withStack) Format(s fmt.State, verb rune) {
149155
switch verb {
150156
case 'v':
151157
if s.Flag('+') {
152-
fmt.Fprintf(s, "%+v\n", w.Cause())
153-
io.WriteString(s, w.msg)
154-
fmt.Fprintf(s, "%+v", w.StackTrace())
158+
fmt.Fprintf(s, "%+v", w.Cause())
159+
w.stack.Format(s, verb)
155160
return
156161
}
157162
fallthrough
@@ -164,31 +169,73 @@ func (w wrapper) Format(s fmt.State, verb rune) {
164169

165170
// Wrap returns an error annotating err with message.
166171
// If err is nil, Wrap returns nil.
172+
// Wrap is conceptually the same as calling
173+
//
174+
// errors.WithStack(errors.WithMessage(err, msg))
167175
func Wrap(err error, message string) error {
168176
if err == nil {
169177
return nil
170178
}
171-
return wrapper{
172-
cause: cause{
173-
cause: err,
174-
msg: message,
175-
},
176-
stack: callers(),
179+
err = &withMessage{
180+
cause: err,
181+
msg: message,
182+
}
183+
return &withStack{
184+
err,
185+
callers(),
177186
}
178187
}
179188

180189
// Wrapf returns an error annotating err with the format specifier.
181190
// If err is nil, Wrapf returns nil.
191+
// Wrapf is conceptually the same as calling
192+
//
193+
// errors.WithStack(errors.WithMessage(err, format, args...))
182194
func Wrapf(err error, format string, args ...interface{}) error {
183195
if err == nil {
184196
return nil
185197
}
186-
return wrapper{
187-
cause: cause{
188-
cause: err,
189-
msg: fmt.Sprintf(format, args...),
190-
},
191-
stack: callers(),
198+
err = &withMessage{
199+
cause: err,
200+
msg: fmt.Sprintf(format, args...),
201+
}
202+
return &withStack{
203+
err,
204+
callers(),
205+
}
206+
}
207+
208+
// WithMessage annotates err with a new message.
209+
// If err is nil, WithStack returns nil.
210+
func WithMessage(err error, message string) error {
211+
if err == nil {
212+
return nil
213+
}
214+
return &withMessage{
215+
cause: err,
216+
msg: message,
217+
}
218+
}
219+
220+
type withMessage struct {
221+
cause error
222+
msg string
223+
}
224+
225+
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
226+
func (w *withMessage) Cause() error { return w.cause }
227+
228+
func (w *withMessage) Format(s fmt.State, verb rune) {
229+
switch verb {
230+
case 'v':
231+
if s.Flag('+') {
232+
fmt.Fprintf(s, "%+v\n", w.Cause())
233+
io.WriteString(s, w.msg)
234+
return
235+
}
236+
fallthrough
237+
case 's', 'q':
238+
io.WriteString(s, w.Error())
192239
}
193240
}
194241

format_test.go

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ func TestFormatNew(t *testing.T) {
2929
"\t.+/github.com/pkg/errors/format_test.go:25",
3030
}}
3131

32-
for _, tt := range tests {
33-
testFormatRegexp(t, tt.error, tt.format, tt.want)
32+
for i, tt := range tests {
33+
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
3434
}
3535
}
3636

@@ -55,8 +55,8 @@ func TestFormatErrorf(t *testing.T) {
5555
"\t.+/github.com/pkg/errors/format_test.go:51",
5656
}}
5757

58-
for _, tt := range tests {
59-
testFormatRegexp(t, tt.error, tt.format, tt.want)
58+
for i, tt := range tests {
59+
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
6060
}
6161
}
6262

@@ -83,14 +83,32 @@ func TestFormatWrap(t *testing.T) {
8383
Wrap(io.EOF, "error"),
8484
"%s",
8585
"error: EOF",
86+
}, {
87+
Wrap(io.EOF, "error"),
88+
"%v",
89+
"error: EOF",
90+
}, {
91+
Wrap(io.EOF, "error"),
92+
"%+v",
93+
"EOF\n" +
94+
"error\n" +
95+
"github.com/pkg/errors.TestFormatWrap\n" +
96+
"\t.+/github.com/pkg/errors/format_test.go:91",
97+
}, {
98+
Wrap(Wrap(io.EOF, "error1"), "error2"),
99+
"%+v",
100+
"EOF\n" +
101+
"error1\n" +
102+
"github.com/pkg/errors.TestFormatWrap\n" +
103+
"\t.+/github.com/pkg/errors/format_test.go:98\n",
86104
}, {
87105
Wrap(New("error with space"), "context"),
88106
"%q",
89107
`"context: error with space"`,
90108
}}
91109

92-
for _, tt := range tests {
93-
testFormatRegexp(t, tt.error, tt.format, tt.want)
110+
for i, tt := range tests {
111+
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
94112
}
95113
}
96114

@@ -100,20 +118,24 @@ func TestFormatWrapf(t *testing.T) {
100118
format string
101119
want string
102120
}{{
103-
Wrapf(New("error"), "error%d", 2),
121+
Wrapf(io.EOF, "error%d", 2),
104122
"%s",
105-
"error2: error",
123+
"error2: EOF",
106124
}, {
107-
Wrap(io.EOF, "error"),
125+
Wrapf(io.EOF, "error%d", 2),
108126
"%v",
109-
"error: EOF",
127+
"error2: EOF",
110128
}, {
111-
Wrap(io.EOF, "error"),
129+
Wrapf(io.EOF, "error%d", 2),
112130
"%+v",
113131
"EOF\n" +
114-
"error\n" +
132+
"error2\n" +
115133
"github.com/pkg/errors.TestFormatWrapf\n" +
116-
"\t.+/github.com/pkg/errors/format_test.go:111",
134+
"\t.+/github.com/pkg/errors/format_test.go:129",
135+
}, {
136+
Wrapf(New("error"), "error%d", 2),
137+
"%s",
138+
"error2: error",
117139
}, {
118140
Wrapf(New("error"), "error%d", 2),
119141
"%v",
@@ -123,22 +145,15 @@ func TestFormatWrapf(t *testing.T) {
123145
"%+v",
124146
"error\n" +
125147
"github.com/pkg/errors.TestFormatWrapf\n" +
126-
"\t.+/github.com/pkg/errors/format_test.go:122",
127-
}, {
128-
Wrap(Wrap(io.EOF, "error1"), "error2"),
129-
"%+v",
130-
"EOF\n" +
131-
"error1\n" +
132-
"github.com/pkg/errors.TestFormatWrapf\n" +
133-
"\t.+/github.com/pkg/errors/format_test.go:128\n",
148+
"\t.+/github.com/pkg/errors/format_test.go:144",
134149
}}
135150

136-
for _, tt := range tests {
137-
testFormatRegexp(t, tt.error, tt.format, tt.want)
151+
for i, tt := range tests {
152+
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
138153
}
139154
}
140155

141-
func testFormatRegexp(t *testing.T, arg interface{}, format, want string) {
156+
func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
142157
got := fmt.Sprintf(format, arg)
143158
lines := strings.SplitN(got, "\n", -1)
144159
for i, w := range strings.SplitN(want, "\n", -1) {
@@ -147,7 +162,7 @@ func testFormatRegexp(t *testing.T, arg interface{}, format, want string) {
147162
t.Fatal(err)
148163
}
149164
if !match {
150-
t.Errorf("fmt.Sprintf(%q, err): got: %q, want: %q", format, got, want)
165+
t.Errorf("test %d: line %d: fmt.Sprintf(%q, err): got: %q, want: %q", n+1, i+1, format, got, want)
151166
}
152167
}
153168
}

stack_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ func TestFrameFormat(t *testing.T) {
120120
"unknown:0",
121121
}}
122122

123-
for _, tt := range tests {
124-
testFormatRegexp(t, tt.Frame, tt.format, tt.want)
123+
for i, tt := range tests {
124+
testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
125125
}
126126
}
127127

@@ -204,7 +204,7 @@ func TestStackTrace(t *testing.T) {
204204
"\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
205205
},
206206
}}
207-
for _, tt := range tests {
207+
for i, tt := range tests {
208208
x, ok := tt.err.(interface {
209209
StackTrace() StackTrace
210210
})
@@ -214,7 +214,7 @@ func TestStackTrace(t *testing.T) {
214214
}
215215
st := x.StackTrace()
216216
for j, want := range tt.want {
217-
testFormatRegexp(t, st[j], "%+v", want)
217+
testFormatRegexp(t, i, st[j], "%+v", want)
218218
}
219219
}
220220
}
@@ -286,7 +286,7 @@ func TestStackTraceFormat(t *testing.T) {
286286
`\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`,
287287
}}
288288

289-
for _, tt := range tests {
290-
testFormatRegexp(t, tt.StackTrace, tt.format, tt.want)
289+
for i, tt := range tests {
290+
testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
291291
}
292292
}

0 commit comments

Comments
 (0)