Skip to content

Commit 0ba9f80

Browse files
Do not discard invalid error payloads in ReadError (#86)
This commit ensures that invalid error payloads are not discarded in case `ReadError` cannot parse them.
1 parent 10e23f7 commit 0ba9f80

File tree

2 files changed

+97
-26
lines changed

2 files changed

+97
-26
lines changed

httplib.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,11 @@ func unmarshalError(err error, responseBody []byte) error {
107107
}
108108
var raw RawTrace
109109
if err2 := json.Unmarshal(responseBody, &raw); err2 != nil {
110-
return err
110+
return errorOnInvalidJSON(err, responseBody)
111111
}
112112
if len(raw.Err) != 0 {
113-
err2 := json.Unmarshal(raw.Err, err)
114-
if err2 != nil {
115-
return err
113+
if err2 := json.Unmarshal(raw.Err, err); err2 != nil {
114+
return errorOnInvalidJSON(err, responseBody)
116115
}
117116
return &TraceErr{
118117
Traces: raw.Traces,
@@ -122,6 +121,19 @@ func unmarshalError(err error, responseBody []byte) error {
122121
Fields: raw.Fields,
123122
}
124123
}
125-
json.Unmarshal(responseBody, err)
124+
if err2 := json.Unmarshal(responseBody, err); err2 != nil {
125+
return errorOnInvalidJSON(err, responseBody)
126+
}
126127
return err
127128
}
129+
130+
// errorOnInvalidJSON is used to construct a TraceErr with the
131+
// input error as Err and the responseBody as Messages.
132+
// This function is used when the responseBody is not valid
133+
// JSON or it contains an unexpected JSON.
134+
func errorOnInvalidJSON(err error, responseBody []byte) error {
135+
return &TraceErr{
136+
Err: err,
137+
Messages: []string{string(responseBody)},
138+
}
139+
}

httplib_test.go

+80-21
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,35 @@ import (
2626

2727
func TestReplyJSON(t *testing.T) {
2828
t.Parallel()
29-
var (
30-
errCode = 400
31-
errText = "test error"
32-
expectedErrorResponse = "" +
33-
"{\n" +
34-
" \"error\": {\n" +
35-
" \"message\": \"" + errText + "\"\n" +
36-
" }\n" +
37-
"}"
38-
)
3929

40-
for _, tc := range []struct {
30+
var expectedErrorResponse = `{
31+
"error": {
32+
"message": "test error"
33+
}
34+
}`
35+
36+
tests := []struct {
4137
desc string
4238
err error
4339
}{
44-
{"plain error", errors.New("test error")},
45-
{"trace error", &TraceErr{Err: errors.New("test error")}},
46-
{"trace error with stacktrace", &TraceErr{Err: errors.New("test error"), Traces: Traces{{Path: "A", Func: "B", Line: 1}}}},
47-
} {
40+
{
41+
desc: "plain error",
42+
err: errors.New("test error"),
43+
},
44+
{
45+
desc: "trace error",
46+
err: &TraceErr{Err: errors.New("test error")},
47+
},
48+
{
49+
desc: "trace error with stacktrace",
50+
err: &TraceErr{Err: errors.New("test error"), Traces: Traces{{Path: "A", Func: "B", Line: 1}}},
51+
},
52+
}
53+
54+
for _, tc := range tests {
4855
t.Run(tc.desc, func(t *testing.T) {
4956
recorder := httptest.NewRecorder()
57+
const errCode = 400
5058
replyJSON(recorder, errCode, tc.err)
5159
require.JSONEq(t, expectedErrorResponse, recorder.Body.String())
5260
})
@@ -55,12 +63,63 @@ func TestReplyJSON(t *testing.T) {
5563

5664
func TestUnmarshalError(t *testing.T) {
5765
t.Parallel()
58-
testCase := func(t *testing.T, err error, response string, isExpectedErr func(error) bool, expectedMsg string) {
59-
readErr := unmarshalError(err, []byte(response))
60-
require.True(t, isExpectedErr(readErr))
61-
require.EqualError(t, readErr, expectedMsg)
66+
67+
tests := []struct {
68+
desc string
69+
inputErr error
70+
inputResponse string
71+
assertErr func(error) bool
72+
expectedMsg string
73+
}{
74+
{
75+
desc: "unmarshal not found error",
76+
inputErr: &NotFoundError{},
77+
inputResponse: `{"error": {"message": "ABC"}}`,
78+
assertErr: IsNotFound,
79+
expectedMsg: "ABC",
80+
},
81+
{
82+
desc: "unmarshal access denied error",
83+
inputErr: &AccessDeniedError{},
84+
inputResponse: `{"error": {"message": "ABC"}}`,
85+
assertErr: IsAccessDenied,
86+
expectedMsg: "ABC",
87+
},
88+
{
89+
desc: "unmarshal error without error JSON key",
90+
inputErr: &AccessDeniedError{},
91+
inputResponse: `{"message": "ABC"}`,
92+
assertErr: IsAccessDenied,
93+
expectedMsg: "ABC",
94+
},
95+
{
96+
desc: "unmarshal invalid error",
97+
inputErr: &AccessDeniedError{},
98+
inputResponse: `{"error": "message ABC"}`,
99+
assertErr: IsAccessDenied,
100+
expectedMsg: "{\"error\": \"message ABC\"}\n\taccess denied",
101+
},
102+
{
103+
desc: "unmarshal invalid error without error JSON key",
104+
inputErr: &AccessDeniedError{},
105+
inputResponse: `["error message ABC"]`,
106+
assertErr: IsAccessDenied,
107+
expectedMsg: "[\"error message ABC\"]\n\taccess denied",
108+
},
109+
{
110+
desc: "unmarshal error with non-JSON body",
111+
inputErr: &AccessDeniedError{},
112+
inputResponse: "error message ABC",
113+
assertErr: IsAccessDenied,
114+
expectedMsg: "error message ABC\n\taccess denied",
115+
},
62116
}
63117

64-
testCase(t, &NotFoundError{}, `{"error": {"message": "ABC"}}`, IsNotFound, "ABC")
65-
testCase(t, &AccessDeniedError{}, `{"error": {"message": "ABC"}}`, IsAccessDenied, "ABC")
118+
for _, tc := range tests {
119+
t.Run(tc.desc, func(t *testing.T) {
120+
readErr := unmarshalError(tc.inputErr, []byte(tc.inputResponse))
121+
require.True(t, tc.assertErr(readErr))
122+
require.EqualError(t, readErr, tc.expectedMsg)
123+
})
124+
}
66125
}

0 commit comments

Comments
 (0)