Skip to content

Commit 2472e06

Browse files
committed
Marshal error with full context chain
When using the new `Augment` function to add context and wrap errors, the error message in Go is chained and displayed with the full chain of context: ``` err := InternalService("code", "message", nil) augmentedErr := Augment(err, "context", nil) // This is "internal_service.code: context: message" augmentedErr.Error() ``` But the marshaled error only displays the first level of context, i.e. ``` // This is just "context" Marshal(augmentedErr.(*Error)).Message ``` This commit changes the Marshal behaviour to include the full context chain of error, so it's actually useful outside of Go when marshaled. ``` // This is now "context: message" Marshal(augmentedErr.(*Error)).Message ```
1 parent c706d80 commit 2472e06

2 files changed

Lines changed: 113 additions & 3 deletions

File tree

marshaling.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package terrors
22

33
import (
4+
"strings"
5+
46
pe "github.com/monzo/terrors/proto"
57
"github.com/monzo/terrors/stack"
68
)
@@ -15,22 +17,38 @@ func Marshal(e *Error) *pe.Error {
1517
}
1618
}
1719

20+
// Build message with all the context
21+
errMessage := strings.Builder{}
22+
errMessage.WriteString(e.Message)
23+
next := e.cause
24+
for next != nil {
25+
errMessage.WriteString(": ")
26+
switch typed := next.(type) {
27+
case *Error:
28+
errMessage.WriteString(typed.Message)
29+
next = typed.cause
30+
case error:
31+
errMessage.WriteString(typed.Error())
32+
next = nil
33+
}
34+
}
35+
1836
retryable := &pe.BoolValue{}
1937
if e.IsRetryable != nil {
2038
retryable.Value = *e.IsRetryable
2139
}
2240

23-
err := &pe.Error{
41+
err := pe.Error{
2442
Code: e.Code,
25-
Message: e.Message,
43+
Message: errMessage.String(),
2644
Stack: stackToProto(e.StackFrames),
2745
Params: e.Params,
2846
Retryable: retryable,
2947
}
3048
if err.Code == "" {
3149
err.Code = ErrUnknown
3250
}
33-
return err
51+
return &err
3452
}
3553

3654
// Unmarshal a protobuf error into a local error

marshaling_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package terrors
22

33
import (
4+
"fmt"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
@@ -122,6 +123,61 @@ var marshalTestCases = []struct {
122123
Retryable: nil,
123124
},
124125
},
126+
// Wrapped errors
127+
{
128+
Augment(&Error{
129+
Code: ErrInternalService,
130+
Message: "bar",
131+
}, "foo", nil).(*Error),
132+
&pe.Error{
133+
Code: ErrInternalService,
134+
Message: "foo: bar",
135+
Retryable: nil,
136+
Params: map[string]string{},
137+
},
138+
},
139+
{
140+
Augment(&Error{
141+
Code: ErrInternalService,
142+
Message: "bar",
143+
}, "foo", map[string]string{"key": "value"}).(*Error),
144+
&pe.Error{
145+
Code: ErrInternalService,
146+
Message: "foo: bar",
147+
Retryable: nil,
148+
Params: map[string]string{"key": "value"},
149+
},
150+
},
151+
{
152+
// Nested Augment
153+
Augment(
154+
Augment(&Error{
155+
Code: ErrInternalService,
156+
Message: "baz",
157+
},
158+
"bar",
159+
map[string]string{"key": "value"},
160+
),
161+
"foo",
162+
map[string]string{"key2": "value2"},
163+
).(*Error),
164+
&pe.Error{
165+
Code: ErrInternalService,
166+
Message: "foo: bar: baz",
167+
Retryable: nil,
168+
Params: map[string]string{"key": "value", "key2": "value2"},
169+
},
170+
},
171+
{
172+
// Wrapping a Go error
173+
Augment(fmt.Errorf("a go error"), "boom", map[string]string{"key": "value"}).(*Error),
174+
&pe.Error{
175+
Code: ErrInternalService,
176+
Message: "boom: a go error",
177+
Retryable: nil,
178+
Params: map[string]string{"key": "value"},
179+
},
180+
},
125181
}
126182

127183
func TestMarshal(t *testing.T) {
@@ -235,6 +291,42 @@ var unmarshalTestCases = []struct {
235291
Retryable: nil,
236292
},
237293
},
294+
// Wrapped errors only gets unmarshaled as a single error
295+
{
296+
&Error{
297+
Code: ErrInternalService,
298+
Message: "foo: bar: baz", // Augment(Augment(bazErr, "bar", nil), "foo", nil)
299+
Params: map[string]string{},
300+
},
301+
&pe.Error{
302+
Code: ErrInternalService,
303+
Message: "foo: bar: baz",
304+
Retryable: &pe.BoolValue{
305+
Value: false,
306+
},
307+
},
308+
},
309+
{
310+
&Error{
311+
Code: ErrInternalService,
312+
Message: "foo: bar",
313+
Params: map[string]string{
314+
"key": "value",
315+
"key2": "value2",
316+
},
317+
},
318+
&pe.Error{
319+
Code: ErrInternalService,
320+
Message: "foo: bar",
321+
Retryable: &pe.BoolValue{
322+
Value: false,
323+
},
324+
Params: map[string]string{
325+
"key": "value",
326+
"key2": "value2",
327+
},
328+
},
329+
},
238330
}
239331

240332
func TestUnmarshal(t *testing.T) {

0 commit comments

Comments
 (0)