Skip to content

Commit 259b951

Browse files
committed
Return error for unhandled HTTP status codes in REST client
Previously, any status not explicitly handled (e.g. 409, 403, 500, 502) fell through to the JSON decode path, silently producing zero-value results. Now all >= 400 statuses return an error. Also fix Decode(&resdata) -> Decode(resdata) to avoid unnecessary *interface{} indirection.
1 parent 015bc46 commit 259b951

File tree

3 files changed

+87
-1
lines changed

3 files changed

+87
-1
lines changed

ucare/error.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,11 @@ func (e reqValidationErr) Error() string {
5050
}
5151

5252
type reqForbiddenErr struct{ respErr }
53+
54+
type unexpectedStatusErr struct {
55+
StatusCode int
56+
}
57+
58+
func (e unexpectedStatusErr) Error() string {
59+
return fmt.Sprintf("unexpected HTTP status: %d", e.StatusCode)
60+
}

ucare/restapi.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,22 @@ try:
157157
}
158158
goto try
159159
default:
160+
if resp.StatusCode >= 400 {
161+
var apiErr respErr
162+
if e := json.NewDecoder(resp.Body).Decode(&apiErr); e != nil || apiErr.Details == "" {
163+
resp.Body.Close()
164+
return unexpectedStatusErr{StatusCode: resp.StatusCode}
165+
}
166+
resp.Body.Close()
167+
return apiErr
168+
}
160169
}
161170

162171
if resdata == nil || reflect.ValueOf(resdata).IsNil() {
163172
return nil
164173
}
165174

166-
err = json.NewDecoder(resp.Body).Decode(&resdata)
175+
err = json.NewDecoder(resp.Body).Decode(resdata)
167176
if err != nil {
168177
return err
169178
}

ucare/restapi_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"io"
77
"net/http"
8+
"net/http/httptest"
89
"strings"
910
"testing"
1011
"time"
@@ -92,3 +93,71 @@ func TestRESTAPIClient(t *testing.T) {
9293
})
9394
}
9495
}
96+
97+
func TestDo_UnhandledStatusWithDetail(t *testing.T) {
98+
t.Parallel()
99+
100+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
101+
w.Header().Set("Content-Type", "application/json")
102+
w.WriteHeader(http.StatusConflict)
103+
w.Write([]byte(`{"detail":"Addon is already running for this file."}`))
104+
}))
105+
defer srv.Close()
106+
107+
client := &restAPIClient{conn: srv.Client()}
108+
req, err := http.NewRequest(http.MethodPost, srv.URL+"/addons/uc_clamav_virus_scan/execute/", nil)
109+
assert.NoError(t, err)
110+
111+
var result struct {
112+
RequestID string `json:"request_id"`
113+
}
114+
err = client.Do(req, &result)
115+
116+
assert.Error(t, err)
117+
var apiErr respErr
118+
assert.True(t, errors.As(err, &apiErr))
119+
assert.Equal(t, "Addon is already running for this file.", apiErr.Details)
120+
assert.Equal(t, "", result.RequestID)
121+
}
122+
123+
func TestDo_UnhandledStatusWithoutDetail(t *testing.T) {
124+
t.Parallel()
125+
126+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
127+
w.WriteHeader(http.StatusBadGateway)
128+
w.Write([]byte("Bad Gateway"))
129+
}))
130+
defer srv.Close()
131+
132+
client := &restAPIClient{conn: srv.Client()}
133+
req, err := http.NewRequest(http.MethodGet, srv.URL+"/files/", nil)
134+
assert.NoError(t, err)
135+
136+
var result map[string]string
137+
err = client.Do(req, &result)
138+
139+
assert.Error(t, err)
140+
var statusErr unexpectedStatusErr
141+
assert.True(t, errors.As(err, &statusErr))
142+
assert.Equal(t, http.StatusBadGateway, statusErr.StatusCode)
143+
}
144+
145+
func TestDo_UnhandledStatusNilResdata(t *testing.T) {
146+
t.Parallel()
147+
148+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
149+
w.Header().Set("Content-Type", "application/json")
150+
w.WriteHeader(http.StatusConflict)
151+
w.Write([]byte(`{"detail":"Conflict"}`))
152+
}))
153+
defer srv.Close()
154+
155+
client := &restAPIClient{conn: srv.Client()}
156+
req, err := http.NewRequest(http.MethodDelete, srv.URL+"/groups/abc~1/", nil)
157+
assert.NoError(t, err)
158+
159+
err = client.Do(req, nil)
160+
161+
assert.Error(t, err)
162+
assert.Contains(t, err.Error(), "Conflict")
163+
}

0 commit comments

Comments
 (0)