Skip to content

Commit 79bc001

Browse files
authored
Merge pull request #9 from joomcode/feature/immutability
Feature/immutability
2 parents 5f4a9e9 + 363e920 commit 79bc001

File tree

2 files changed

+54
-6
lines changed

2 files changed

+54
-6
lines changed

error.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,40 @@ var _ fmt.Formatter = (*Error)(nil)
2626
// It is a caller's responsibility to accumulate and update a property, if needed.
2727
// Dynamic properties is a brittle mechanism and should therefore be used with care and in a simple and robust manner.
2828
func (e *Error) WithProperty(key Property, value interface{}) *Error {
29-
if e.properties == nil {
30-
e.properties = make(map[Property]interface{}, 1)
29+
errorCopy := *e
30+
31+
if errorCopy.properties == nil {
32+
errorCopy.properties = make(map[Property]interface{}, 1)
33+
} else {
34+
errorCopy.properties = make(map[Property]interface{}, len(e.properties) + 1)
35+
for k, v := range e.properties {
36+
errorCopy.properties[k] = v
37+
}
3138
}
3239

33-
e.properties[key] = value
34-
return e
40+
errorCopy.properties[key] = value
41+
return &errorCopy
3542
}
3643

3744
// WithUnderlyingErrors adds multiple additional related (hidden, suppressed) errors to be used exclusively in error output.
3845
// Note that these errors make no other effect whatsoever: their traits, types, properties etc. are lost on the observer.
3946
// Consider using errorx.DecorateMany instead.
4047
func (e *Error) WithUnderlyingErrors(errs ...error) *Error {
48+
errorCopy := *e
49+
50+
newUnderying := make([]error, 0, len(e.underlying) + len(errs))
51+
newUnderying = append(newUnderying, e.underlying...)
52+
4153
for _, err := range errs {
4254
if err == nil {
4355
continue
4456
}
4557

46-
e.underlying = append(e.underlying, err)
58+
newUnderying = append(newUnderying, err)
4759
}
4860

49-
return e
61+
errorCopy.underlying = newUnderying
62+
return &errorCopy
5063
}
5164

5265
// Property extracts a dynamic property value from an error.

error_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,41 @@ func TestErrorMessages(t *testing.T) {
7272
})
7373
}
7474

75+
func TestImmutableError(t *testing.T) {
76+
t.Run("Property", func(t *testing.T) {
77+
err := testType.NewWithNoMessage()
78+
err1 := err.WithProperty(PropertyPayload(), 1)
79+
err2 := err1.WithProperty(PropertyPayload(), 2)
80+
81+
require.True(t, err.errorType.IsOfType(err2.errorType))
82+
require.Equal(t, err.message, err2.message)
83+
84+
payload, ok := ExtractPayload(err)
85+
require.False(t, ok)
86+
87+
payload, ok = ExtractPayload(err1)
88+
require.True(t, ok)
89+
require.EqualValues(t, 1, payload)
90+
91+
payload, ok = ExtractPayload(err2)
92+
require.True(t, ok)
93+
require.EqualValues(t, 2, payload)
94+
})
95+
96+
t.Run("Underlying", func(t *testing.T) {
97+
err := testType.NewWithNoMessage()
98+
err1 := err.WithUnderlyingErrors(testSubtype0.NewWithNoMessage())
99+
err2 := err1.WithUnderlyingErrors(testSubtype1.NewWithNoMessage())
100+
101+
require.True(t, err.errorType.IsOfType(err2.errorType))
102+
require.Equal(t, err.message, err2.message)
103+
104+
require.Len(t, err.underlying, 0)
105+
require.Len(t, err1.underlying, 1)
106+
require.Len(t, err2.underlying, 2)
107+
})
108+
}
109+
75110
func TestErrorStackTrace(t *testing.T) {
76111
err := createErrorFuncInStackTrace(testType)
77112
output := fmt.Sprintf("%+v", err)

0 commit comments

Comments
 (0)