Skip to content

Commit e63ada9

Browse files
committed
add DoWithStringResponse function
1 parent c7a31a1 commit e63ada9

File tree

4 files changed

+150
-31
lines changed

4 files changed

+150
-31
lines changed

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,19 @@ params := request.Params{
2424
Headers: map[string]string{"my-header":"value", "another-header":"value2"},
2525
Body: Input{RequestValue: "someValueIn"},
2626
Query: map[string]string{"key": "value"},
27+
Timeout: 10 * time.Second,
28+
ExpectedResponseCode: 201,
2729
}
2830

2931
result := &Output{}
3032
err := request.Do(params, result)
3133
```
34+
All parameters besides the `URL` and the `Method` are optional and can be omitted.
35+
36+
If you want to retrieve the response body as a string, e.g. for debugging or testing purposes, you can use `DoWithStringResponse` instead.
37+
```go
38+
result, err := request.DoWithStringResponse(params)
39+
```
3240

3341
## Convenience wrappers
3442
```go
@@ -38,11 +46,11 @@ err := request.Post("http://example.com", Input{RequestValue: "someValueIn"}, re
3846
```
3947

4048
## Defaults
41-
* All `2xx` response codes are treated as success, all other codes lead to an error being returned
49+
* All `2xx` response codes are treated as success, all other codes lead to an error being returned, if you want to check for a specific response code set `ExpectedResponseCode` in the parameters
4250
* If an HTTPError is returned it contains the response body as message if there was one
4351
* The request package takes care of closing the response body after sending the request
4452
* The http client does not follow redirects
45-
* The http client timeout is set to 30 seconds
53+
* The http client timeout is set to 30 seconds, use the `Timeout` parameter in case you want to define a different timeout for one of the requests
4654
* `Accept` and `Content-Type` request header are set to `application/json` and can be overwritten via the Headers parameter
4755

4856
## Streaming
@@ -98,7 +106,8 @@ if err != nil {
98106
return err
99107
}
100108
defer func() {
101-
res.Body.Close()
109+
err = res.Body.Close()
110+
// handle err somehow
102111
}()
103112

104113
result := &Output{}

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
77
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
88
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
99
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10+
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
1011
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1112
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
1213
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=

request.go

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package request
33
import (
44
"bytes"
55
"encoding/json"
6+
"fmt"
67
"io"
78
"io/ioutil"
89
"net/http"
@@ -40,12 +41,13 @@ func GetClient() *http.Client {
4041

4142
// Params holds all information necessary to set up the request instance.
4243
type Params struct {
43-
Method string
44-
URL string
45-
Headers map[string]string
46-
Body interface{}
47-
Query map[string]string
48-
Timeout time.Duration
44+
URL string
45+
Method string
46+
Headers map[string]string
47+
Body interface{}
48+
Query map[string]string
49+
Timeout time.Duration
50+
ExpectedResponseCode int
4951
}
5052

5153
// Do executes the request as specified in the request params
@@ -56,14 +58,7 @@ func Do(params Params, responseBody interface{}) (returnErr error) {
5658
return err
5759
}
5860

59-
var client *http.Client
60-
if params.Timeout != 0 {
61-
client = GetClient()
62-
client.Timeout = params.Timeout
63-
} else {
64-
client = getCachedClient()
65-
}
66-
61+
client := getClient(params.Timeout)
6762
res, err := client.Do(req)
6863
if err != nil {
6964
return errors.Wrap(err, "failed to send request")
@@ -75,13 +70,9 @@ func Do(params Params, responseBody interface{}) (returnErr error) {
7570
}
7671
}()
7772

78-
if !isSuccessCode(res.StatusCode) {
79-
bodyBytes, err := ioutil.ReadAll(res.Body)
80-
if err != nil || len(bodyBytes) == 0 {
81-
return httperrors.New(res.StatusCode, nil)
82-
}
83-
84-
return httperrors.New(res.StatusCode, string(bodyBytes))
73+
err = checkResponseCode(res, params.ExpectedResponseCode)
74+
if err != nil {
75+
return err
8576
}
8677

8778
if responseBody == nil {
@@ -91,6 +82,39 @@ func Do(params Params, responseBody interface{}) (returnErr error) {
9182
return json.NewDecoder(res.Body).Decode(responseBody)
9283
}
9384

85+
// DoWithStringResponse is the same as Do but the response body is returned as string
86+
// instead of being parsed into the provided struct.
87+
func DoWithStringResponse(params Params) (result string, returnErr error) {
88+
req, err := createRequest(params)
89+
if err != nil {
90+
return "", err
91+
}
92+
93+
client := getClient(params.Timeout)
94+
res, err := client.Do(req)
95+
if err != nil {
96+
return "", errors.Wrap(err, "failed to send request")
97+
}
98+
99+
defer func() {
100+
if cErr := res.Body.Close(); cErr != nil && returnErr == nil {
101+
returnErr = cErr
102+
}
103+
}()
104+
105+
err = checkResponseCode(res, params.ExpectedResponseCode)
106+
if err != nil {
107+
return "", err
108+
}
109+
110+
bodyBytes, err := ioutil.ReadAll(res.Body)
111+
if err != nil {
112+
return "", fmt.Errorf("failed to read response body: %w", err)
113+
}
114+
115+
return string(bodyBytes), nil
116+
}
117+
94118
func createRequest(params Params) (*http.Request, error) {
95119
reader, err := convertToReader(params.Body)
96120
if err != nil {
@@ -128,10 +152,6 @@ func Post(url string, requestBody interface{}, responseBody interface{}) error {
128152
return Do(Params{Method: "POST", URL: url, Body: requestBody}, responseBody)
129153
}
130154

131-
func isSuccessCode(statusCode int) bool {
132-
return 200 <= statusCode && statusCode <= 299
133-
}
134-
135155
func convertToReader(body interface{}) (io.Reader, error) {
136156
if body == nil {
137157
return nil, nil
@@ -150,3 +170,34 @@ func convertToReader(body interface{}) (io.Reader, error) {
150170

151171
return buffer, nil
152172
}
173+
174+
func getClient(timeout time.Duration) *http.Client {
175+
if timeout != 0 {
176+
client = GetClient()
177+
client.Timeout = timeout
178+
return client
179+
}
180+
181+
return getCachedClient()
182+
}
183+
184+
func checkResponseCode(res *http.Response, expectedResponseCode int) error {
185+
if expectedResponseCode != 0 && res.StatusCode != expectedResponseCode {
186+
return fmt.Errorf("expected response code %d but got %d", expectedResponseCode, res.StatusCode)
187+
}
188+
189+
if !isSuccessCode(res.StatusCode) {
190+
bodyBytes, err := ioutil.ReadAll(res.Body)
191+
if err != nil || len(bodyBytes) == 0 {
192+
return httperrors.New(res.StatusCode, nil)
193+
}
194+
195+
return httperrors.New(res.StatusCode, string(bodyBytes))
196+
}
197+
198+
return nil
199+
}
200+
201+
func isSuccessCode(statusCode int) bool {
202+
return 200 <= statusCode && statusCode <= 299
203+
}

request_test.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,17 @@ func TestDoSuccessful(t *testing.T) {
6363
body, _ := ioutil.ReadAll(r.Body)
6464
assert.Equal(t, `{"requestValue":"someValueIn"}`+"\n", string(body))
6565
assert.Equal(t, r.Method, "POST")
66+
w.WriteHeader(http.StatusCreated)
6667
_, err := w.Write([]byte(`{"responseValue":"someValueOut"}`))
6768
assert.NoError(t, err)
6869
}))
6970
defer ts.Close()
7071

7172
params := Params{
72-
URL: ts.URL,
73-
Method: "POST",
74-
Body: Input{RequestValue: "someValueIn"},
73+
URL: ts.URL,
74+
Method: "POST",
75+
Body: Input{RequestValue: "someValueIn"},
76+
ExpectedResponseCode: http.StatusCreated,
7577
}
7678

7779
result := &Output{}
@@ -279,6 +281,41 @@ func TestDoOtherErrors(t *testing.T) {
279281
assert.Contains(t, err.Error(), "failed to send request")
280282
}
281283
})
284+
285+
t.Run("wrong response code", func(t *testing.T) {
286+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
287+
w.WriteHeader(http.StatusCreated)
288+
}))
289+
defer ts.Close()
290+
291+
params := Params{
292+
URL: ts.URL,
293+
ExpectedResponseCode: http.StatusOK,
294+
}
295+
296+
err := Do(params, nil)
297+
assert.Error(t, err)
298+
assert.EqualError(t, err, "expected response code 200 but got 201")
299+
})
300+
}
301+
302+
func TestDoWithStringResponse(t *testing.T) {
303+
t.Run("success", func(t *testing.T) {
304+
response := `{"responseValue":"someValueOut"}`
305+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
306+
_, err := w.Write([]byte(response))
307+
assert.NoError(t, err)
308+
}))
309+
defer ts.Close()
310+
311+
params := Params{
312+
URL: ts.URL,
313+
}
314+
315+
result, err := DoWithStringResponse(params)
316+
assert.NoError(t, err)
317+
assert.Equal(t, response, result)
318+
})
282319
}
283320

284321
func TestGet(t *testing.T) {
@@ -337,3 +374,24 @@ func ExampleDo() {
337374
//
338375
// someValueOut <nil>
339376
}
377+
378+
func ExampleDoWithStringResponse() {
379+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
380+
_, err := w.Write([]byte(`{"responseValue":"someValueOut"}`))
381+
if err != nil {
382+
panic(err)
383+
}
384+
}))
385+
defer ts.Close()
386+
387+
params := Params{
388+
URL: ts.URL,
389+
Method: "POST",
390+
}
391+
392+
result, err := DoWithStringResponse(params)
393+
394+
fmt.Println(result, err)
395+
// Output:
396+
// {"responseValue":"someValueOut"} <nil>
397+
}

0 commit comments

Comments
 (0)