Skip to content

Commit 10e23f7

Browse files
authored
Remove mutating state methods - breaking change (#85)
PR #83 changed the API behavior. Currently, `AddUserMessage()` and similar do not change the current struct but return a modified copy. This makes errors like that: ```go err.AddUserMessage("extra args 1") err.AddUserMessage("extar args 2") // err doesn't contain any user messages, but it used to. ``` This PR converts `Add.*` methods to `With*.` functions to make it clear that the returned error should be used and the state it not mutated. This should also eliminate the race condition mentioned in the previous PR.
1 parent 56e7c17 commit 10e23f7

File tree

3 files changed

+48
-51
lines changed

3 files changed

+48
-51
lines changed

.github/workflows/test.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ jobs:
1919
with:
2020
go-version: '1.17'
2121
- name: Test
22-
run: go test ./...
22+
run: go test -race ./...

trace.go

+43-46
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func Wrap(err error, args ...interface{}) Error {
6161
trace = newTrace(err, 2)
6262
}
6363
if len(args) > 0 {
64-
trace = trace.AddUserMessage(args[0], args[1:]...)
64+
trace = WithUserMessage(trace, args[0], args[1:]...)
6565
}
6666
return trace
6767
}
@@ -172,7 +172,7 @@ func WrapWithMessage(err error, message interface{}, args ...interface{}) Error
172172
} else {
173173
trace = newTrace(err, 2)
174174
}
175-
return trace.AddUserMessage(message, args...)
175+
return WithUserMessage(trace, message, args...)
176176
}
177177

178178
// Errorf is similar to fmt.Errorf except that it captures
@@ -253,7 +253,7 @@ type RawTrace struct {
253253
Fields map[string]interface{} `json:"fields,omitempty"`
254254
}
255255

256-
func (e *TraceErr) clone() *TraceErr {
256+
func (e *TraceErr) Clone() *TraceErr {
257257
if e == nil {
258258
return nil
259259
}
@@ -278,36 +278,6 @@ func (e *TraceErr) clone() *TraceErr {
278278
return tr
279279
}
280280

281-
// AddUserMessage adds user-friendly message describing the error nature
282-
func (e *TraceErr) AddUserMessage(formatArg interface{}, rest ...interface{}) *TraceErr {
283-
newMessage := fmt.Sprintf(fmt.Sprintf("%v", formatArg), rest...)
284-
errClone := e.clone()
285-
errClone.Messages = append(errClone.Messages, newMessage)
286-
return errClone
287-
}
288-
289-
// AddFields adds the given map of fields to the error being reported
290-
func (e *TraceErr) AddFields(fields map[string]interface{}) *TraceErr {
291-
errClone := e.clone()
292-
if errClone.Fields == nil {
293-
errClone.Fields = make(map[string]interface{}, len(fields))
294-
}
295-
for k, v := range fields {
296-
errClone.Fields[k] = v
297-
}
298-
return errClone
299-
}
300-
301-
// AddField adds a single field to the error wrapper as context for the error
302-
func (e *TraceErr) AddField(k string, v interface{}) *TraceErr {
303-
errClone := e.clone()
304-
if errClone.Fields == nil {
305-
errClone.Fields = make(map[string]interface{}, 1)
306-
}
307-
errClone.Fields[k] = v
308-
return errClone
309-
}
310-
311281
// UserMessage returns user-friendly error message
312282
func (e *TraceErr) UserMessage() string {
313283
if len(e.Messages) > 0 {
@@ -383,7 +353,7 @@ func (e *TraceErr) OrigError() error {
383353
}
384354

385355
// GoString formats this trace object for use with
386-
// with the "%#v" format string
356+
// the "%#v" format string
387357
func (e *TraceErr) GoString() string {
388358
return e.DebugReport()
389359
}
@@ -402,21 +372,48 @@ type Error interface {
402372
DebugReporter
403373
UserMessager
404374

405-
// AddUserMessage adds formatted user-facing message
406-
// to the error, depends on the implementation,
407-
// usually works as fmt.Sprintf(formatArg, rest...)
408-
// but implementations can choose another way, e.g. treat
409-
// arguments as structured args
410-
AddUserMessage(formatArg interface{}, rest ...interface{}) *TraceErr
375+
// GetFields returns any fields that have been added to the error
376+
GetFields() map[string]interface{}
377+
378+
// Clone returns a copy of the current Error.
379+
Clone() *TraceErr
380+
}
411381

412-
// AddField adds additional field information to the error
413-
AddField(key string, value interface{}) *TraceErr
382+
// WithUserMessage adds formatted user-facing message
383+
// to the error, depends on the implementation,
384+
// usually works as fmt.Sprintf(formatArg, rest...)
385+
// but implementations can choose another way, e.g. treat
386+
// arguments as structured args.
387+
func WithUserMessage(err Error, formatArg interface{}, rest ...interface{}) *TraceErr {
388+
errCopy := err.Clone()
414389

415-
// AddFields adds a map of additional fields to the error
416-
AddFields(fields map[string]interface{}) *TraceErr
390+
newMessage := fmt.Sprintf(fmt.Sprintf("%v", formatArg), rest...)
391+
errCopy.Messages = append(errCopy.Messages, newMessage)
392+
return errCopy
393+
}
417394

418-
// GetFields returns any fields that have been added to the error
419-
GetFields() map[string]interface{}
395+
// WithField adds additional field information to the error.
396+
func WithField(err Error, key string, value interface{}) *TraceErr {
397+
errCopy := err.Clone()
398+
399+
if errCopy.Fields == nil {
400+
errCopy.Fields = make(map[string]interface{}, 1)
401+
}
402+
errCopy.Fields[key] = value
403+
return errCopy
404+
}
405+
406+
// WithFields adds a map of additional fields to the error
407+
func WithFields(err Error, fields map[string]interface{}) *TraceErr {
408+
errCopy := err.Clone()
409+
410+
if errCopy.Fields == nil {
411+
errCopy.Fields = make(map[string]interface{}, len(fields))
412+
}
413+
for k, v := range fields {
414+
errCopy.Fields[k] = v
415+
}
416+
return errCopy
420417
}
421418

422419
// NewAggregate creates a new aggregate instance from the specified

trace_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (s *TraceSuite) TestUserMessageWithFields() {
9797
err := Wrap(testErr, "user message")
9898
s.Equal("user message\tdescription", line(UserMessageWithFields(err)))
9999

100-
err = err.AddField("test_key", "test_value")
100+
err = WithField(err, "test_key", "test_value")
101101
s.Equal("test_key=\"test_value\" user message\tdescription", line(UserMessageWithFields(err)))
102102
}
103103

@@ -108,7 +108,7 @@ func (s *TraceSuite) TestGetFields() {
108108
fields := map[string]interface{}{
109109
"test_key": "test_value",
110110
}
111-
err := Wrap(testErr).AddFields(fields)
111+
err := WithFields(Wrap(testErr), fields)
112112
s.Equal(fields, GetFields(err))
113113

114114
// ensure that you can get fields from a proxyError
@@ -521,12 +521,12 @@ func (s *TraceSuite) TestErrorf() {
521521
}
522522

523523
func (s *TraceSuite) TestWithField() {
524-
err := Wrap(Errorf("error")).AddField("testfield", true)
524+
err := WithField(Wrap(Errorf("error")), "testfield", true)
525525
s.Regexp(".*.testfield.*", line(DebugReport(err)))
526526
}
527527

528528
func (s *TraceSuite) TestWithFields() {
529-
err := Wrap(Errorf("error")).AddFields(map[string]interface{}{
529+
err := WithFields(Wrap(Errorf("error")), map[string]interface{}{
530530
"testfield1": true,
531531
"testfield2": "value2",
532532
})

0 commit comments

Comments
 (0)