Skip to content

Commit 2114af0

Browse files
authored
Merge pull request #14 from machinebox/one_make_request
simple abstraction and implementation in Suggestionbox
2 parents db9e9de + 19649d2 commit 2114af0

File tree

8 files changed

+265
-199
lines changed

8 files changed

+265
-199
lines changed

internal/mbhttp/client.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package mbhttp
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
"net/http"
7+
8+
"github.com/pkg/errors"
9+
)
10+
11+
// Client makes requests and handles common Machine Box error cases.
12+
type Client struct {
13+
boxname string
14+
15+
// HTTPClient is the underlying http.Client that will be
16+
// used to make requests.
17+
HTTPClient *http.Client
18+
}
19+
20+
// New makes a new Client.
21+
func New(boxname string, client *http.Client) *Client {
22+
return &Client{
23+
boxname: boxname,
24+
HTTPClient: client,
25+
}
26+
}
27+
28+
// Do makes the request and unmarshals the response into v.
29+
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
30+
resp, err := c.HTTPClient.Do(req)
31+
if err != nil {
32+
return nil, err
33+
}
34+
defer resp.Body.Close()
35+
b, err := ioutil.ReadAll(resp.Body)
36+
if err != nil {
37+
return nil, errors.Wrap(err, "read response data")
38+
}
39+
var o struct {
40+
Success bool
41+
Error string
42+
}
43+
if err := json.Unmarshal(b, &o); err != nil {
44+
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
45+
return nil, errors.Errorf("%s: %s", c.boxname, resp.Status)
46+
}
47+
return nil, errors.Wrap(err, "decode common response data")
48+
}
49+
if !o.Success {
50+
if o.Error == "" {
51+
o.Error = "an unknown error occurred in the box"
52+
}
53+
return nil, errors.Errorf("%s: %s", c.boxname, o.Error)
54+
}
55+
if err := json.Unmarshal(b, &v); err != nil {
56+
return nil, errors.Wrap(err, "decode response data")
57+
}
58+
return resp, nil
59+
}

internal/mbhttp/client_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package mbhttp_test
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
10+
"github.com/machinebox/sdk-go/internal/mbhttp"
11+
"github.com/matryer/is"
12+
)
13+
14+
func TestDo(t *testing.T) {
15+
is := is.New(t)
16+
type obj struct {
17+
Field1 string `json:"field1"`
18+
Field2 int `json:"field2"`
19+
Field3 bool `json:"field3"`
20+
}
21+
in := obj{Field1: "in", Field2: 123, Field3: true}
22+
out := obj{Field1: "in", Field2: 123, Field3: true}
23+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24+
var requestObj obj
25+
is.Equal(r.Method, http.MethodPost)
26+
is.Equal(r.URL.Path, "/something")
27+
is.NoErr(json.NewDecoder(r.Body).Decode(&requestObj))
28+
is.Equal(requestObj.Field1, in.Field1)
29+
is.Equal(requestObj.Field2, in.Field2)
30+
is.Equal(requestObj.Field3, in.Field3)
31+
is.NoErr(json.NewEncoder(w).Encode(struct {
32+
Success bool `json:"success"`
33+
obj
34+
}{
35+
Success: true,
36+
obj: out,
37+
}))
38+
}))
39+
defer srv.Close()
40+
var buf bytes.Buffer
41+
is.NoErr(json.NewEncoder(&buf).Encode(in))
42+
req, err := http.NewRequest(http.MethodPost, srv.URL+"/something", &buf)
43+
c := mbhttp.New("testbox", http.DefaultClient)
44+
var actualOut obj
45+
resp, err := c.Do(req, &actualOut)
46+
is.NoErr(err)
47+
defer resp.Body.Close()
48+
is.Equal(actualOut.Field1, out.Field1)
49+
is.Equal(actualOut.Field2, out.Field2)
50+
is.Equal(actualOut.Field3, out.Field3)
51+
}
52+
53+
func TestDoBoxError(t *testing.T) {
54+
is := is.New(t)
55+
type obj struct {
56+
Field1 string `json:"field1"`
57+
Field2 int `json:"field2"`
58+
Field3 bool `json:"field3"`
59+
}
60+
in := obj{Field1: "in", Field2: 123, Field3: true}
61+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
62+
var requestObj obj
63+
is.Equal(r.Method, http.MethodPost)
64+
is.Equal(r.URL.Path, "/something")
65+
is.NoErr(json.NewDecoder(r.Body).Decode(&requestObj))
66+
is.Equal(requestObj.Field1, in.Field1)
67+
is.Equal(requestObj.Field2, in.Field2)
68+
is.Equal(requestObj.Field3, in.Field3)
69+
is.NoErr(json.NewEncoder(w).Encode(struct {
70+
Success bool `json:"success"`
71+
Error string `json:"error"`
72+
}{
73+
Success: false,
74+
Error: "something went wrong",
75+
}))
76+
}))
77+
defer srv.Close()
78+
var buf bytes.Buffer
79+
is.NoErr(json.NewEncoder(&buf).Encode(in))
80+
req, err := http.NewRequest(http.MethodPost, srv.URL+"/something", &buf)
81+
c := mbhttp.New("testbox", http.DefaultClient)
82+
var actualOut obj
83+
_, err = c.Do(req, &actualOut)
84+
is.True(err != nil)
85+
is.Equal(err.Error(), "testbox: something went wrong")
86+
}
87+
88+
func TestDoBoxMissingError(t *testing.T) {
89+
is := is.New(t)
90+
type obj struct {
91+
Field1 string `json:"field1"`
92+
Field2 int `json:"field2"`
93+
Field3 bool `json:"field3"`
94+
}
95+
in := obj{Field1: "in", Field2: 123, Field3: true}
96+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
97+
var requestObj obj
98+
is.Equal(r.Method, http.MethodPost)
99+
is.Equal(r.URL.Path, "/something")
100+
is.NoErr(json.NewDecoder(r.Body).Decode(&requestObj))
101+
is.Equal(requestObj.Field1, in.Field1)
102+
is.Equal(requestObj.Field2, in.Field2)
103+
is.Equal(requestObj.Field3, in.Field3)
104+
is.NoErr(json.NewEncoder(w).Encode(struct {
105+
Success bool `json:"success"`
106+
Error string `json:"error"`
107+
}{
108+
Success: false,
109+
}))
110+
}))
111+
defer srv.Close()
112+
var buf bytes.Buffer
113+
is.NoErr(json.NewEncoder(&buf).Encode(in))
114+
req, err := http.NewRequest(http.MethodPost, srv.URL+"/something", &buf)
115+
c := mbhttp.New("testbox", http.DefaultClient)
116+
var actualOut obj
117+
_, err = c.Do(req, &actualOut)
118+
is.True(err != nil)
119+
is.Equal(err.Error(), "testbox: an unknown error occurred in the box")
120+
}
121+
122+
func TestDoHTTPError(t *testing.T) {
123+
is := is.New(t)
124+
type obj struct {
125+
Field1 string `json:"field1"`
126+
Field2 int `json:"field2"`
127+
Field3 bool `json:"field3"`
128+
}
129+
in := obj{Field1: "in", Field2: 123, Field3: true}
130+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
131+
var requestObj obj
132+
is.Equal(r.Method, http.MethodPost)
133+
is.Equal(r.URL.Path, "/something")
134+
is.NoErr(json.NewDecoder(r.Body).Decode(&requestObj))
135+
is.Equal(requestObj.Field1, in.Field1)
136+
is.Equal(requestObj.Field2, in.Field2)
137+
is.Equal(requestObj.Field3, in.Field3)
138+
http.Error(w, "something went wrong", http.StatusInternalServerError)
139+
}))
140+
defer srv.Close()
141+
var buf bytes.Buffer
142+
is.NoErr(json.NewEncoder(&buf).Encode(in))
143+
req, err := http.NewRequest(http.MethodPost, srv.URL+"/something", &buf)
144+
c := mbhttp.New("testbox", http.DefaultClient)
145+
var actualOut obj
146+
_, err = c.Do(req, &actualOut)
147+
is.True(err != nil)
148+
is.Equal(err.Error(), "testbox: 500 Internal Server Error")
149+
}

suggestionbox/suggestionbox.go

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,36 @@
22
package suggestionbox
33

44
import (
5-
"encoding/json"
65
"errors"
76
"net/http"
87
"net/url"
98
"time"
109

1110
"github.com/machinebox/sdk-go/boxutil"
11+
"github.com/machinebox/sdk-go/internal/mbhttp"
1212
)
1313

1414
// Client is an HTTP client that can make requests to the box.
1515
type Client struct {
16-
addr string
17-
18-
// HTTPClient is the http.Client that will be used to
19-
// make requests.
20-
HTTPClient *http.Client
16+
addr string
17+
client *mbhttp.Client
2118
}
2219

2320
// make sure the Client implements boxutil.Box
2421
var _ boxutil.Box = (*Client)(nil)
2522

2623
// New makes a new Client for the box at the specified address.
2724
func New(addr string) *Client {
28-
return &Client{
25+
c := &Client{
2926
addr: addr,
30-
HTTPClient: &http.Client{
31-
Timeout: 1 * time.Minute,
32-
},
3327
}
28+
c.SetClient(&http.Client{Timeout: 1 * time.Minute})
29+
return c
30+
}
31+
32+
// SetClient sets the http.Client to use when making requests.
33+
func (c *Client) SetClient(client *http.Client) {
34+
c.client = mbhttp.New("suggestionbox", client)
3435
}
3536

3637
// Info gets the details about the box.
@@ -48,23 +49,9 @@ func (c *Client) Info() (*boxutil.Info, error) {
4849
return nil, err
4950
}
5051
req.Header.Set("Accept", "application/json; charset=utf-8")
51-
resp, err := c.HTTPClient.Do(req)
52+
_, err = c.client.Do(req, &info)
5253
if err != nil {
5354
return nil, err
5455
}
55-
defer resp.Body.Close()
56-
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
57-
return nil, err
58-
}
5956
return &info, nil
6057
}
61-
62-
// ErrSuggestionbox represents an error from suggestionbox.
63-
type ErrSuggestionbox string
64-
65-
func (e ErrSuggestionbox) Error() string {
66-
if string(e) == "" {
67-
return "suggestionbox: success != true (unknown error)"
68-
}
69-
return "suggestionbox: " + string(e)
70-
}

0 commit comments

Comments
 (0)