git.iko.soy/typeable/oauth2-proxy
on master help sign in
Switch Builder.Do() to return a Result
commitfbf406324523601e0f233e9ac75e6f1c5250ae89 authorJoel Speed <joel.speed@hotmail.co.uk> dateMon 6 Jul 2020, 17:32 (6 years ago) parent02410d3
3 files changed+138−103
pkg/requests/builder.go+23−78 pkg/requests/builder_test.go+17−25 pkg/requests/result.go+98
pkg/requests/builder.go+23−78
@@ -2,27 +2,23 @@ package requests
22
33import (
44 "context"
5− "encoding/json"
65 "fmt"
76 "io"
87 "io/ioutil"
98 "net/http"
10−
11− "github.com/bitly/go-simplejson"
129)
1310
14−// Builder allows users to construct a request and then either get the requests
15−// response via Do(), parse the response into a simplejson.Json via JSON(),
16−// or to parse the json response into an object via UnmarshalInto().
11+// Builder allows users to construct a request and then execute the
12+// request via Do().
13+// Do returns a Result which allows the user to get the body,
14+// unmarshal the body into an interface, or into a simplejson.Json.
1715type Builder interface {
1816 WithContext(context.Context) Builder
1917 WithBody(io.Reader) Builder
2018 WithMethod(string) Builder
2119 WithHeaders(http.Header) Builder
2220 SetHeader(key, value string) Builder
23− Do() (*http.Response, error)
24− UnmarshalInto(interface{}) error
25− UnmarshalJSON() (*simplejson.Json, error)
21+ Do() Result
2622}
2723
2824type builder struct {
@@ -31,7 +27,7 @@ type builder struct {
3127 endpoint string
3228 body io.Reader
3329 header http.Header
34− response *http.Response
30+ result *result
3531}
3632
3733// New provides a new Builder for the given endpoint.
@@ -80,10 +76,10 @@ func (r *builder) SetHeader(key, value string) Builder {
8076// Do performs the request and returns the response in its raw form.
8177// If the request has already been performed, returns the previous result.
8278// This will not allow you to repeat a request.
83−func (r *builder) Do() (*http.Response, error) {
84− if r.response != nil {
79+func (r *builder) Do() Result {
80+ if r.result != nil {
8581 // Request has already been done
86− return r.response, nil
82+ return r.result
8783 }
8884
8985 // Must provide a non-nil context to NewRequestWithContext
@@ -91,83 +87,32 @@ func (r *builder) Do() (*http.Response, error) {
9187 r.context = context.Background()
9288 }
9389
90+ return r.do()
91+}
92+
93+// do creates the request, executes it with the default client and extracts the
94+// the body into the response
95+func (r *builder) do() Result {
9496 req, err := http.NewRequestWithContext(r.context, r.method, r.endpoint, r.body)
9597 if err != nil {
96− return nil, fmt.Errorf("error creating request: %v", err)
98+ r.result = &result{err: fmt.Errorf("error creating request: %v", err)}
99+ return r.result
97100 }
98101 req.Header = r.header
99102
100103 resp, err := http.DefaultClient.Do(req)
101104 if err != nil {
102− return nil, fmt.Errorf("error performing request: %v", err)
103− }
104−
105− r.response = resp
106− return resp, nil
107−}
108−
109−// UnmarshalInto performs the request and attempts to unmarshal the response into the
110−// the given interface. The response body is assumed to be JSON.
111−// The response must have a 200 status otherwise an error will be returned.
112−func (r *builder) UnmarshalInto(into interface{}) error {
113− resp, err := r.Do()
114− if err != nil {
115− return err
105+ r.result = &result{err: fmt.Errorf("error performing request: %v", err)}
106+ return r.result
116107 }
117108
118− return UnmarshalInto(resp, into)
119−}
120−
121−// UnmarshalJSON performs the request and attempts to unmarshal the response into a
122−// simplejson.Json. The response body is assume to be JSON.
123−// The response must have a 200 status otherwise an error will be returned.
124−func (r *builder) UnmarshalJSON() (*simplejson.Json, error) {
125− resp, err := r.Do()
126− if err != nil {
127− return nil, err
128− }
129−
130− body, err := getResponseBody(resp)
131− if err != nil {
132− return nil, err
133− }
134−
135− data, err := simplejson.NewJson(body)
136− if err != nil {
137− return nil, fmt.Errorf("error reading json: %v", err)
138− }
139− return data, nil
140−}
141−
142−// UnmarshalInto attempts to unmarshal the response into the the given interface.
143−// The response body is assumed to be JSON.
144−// The response must have a 200 status otherwise an error will be returned.
145−func UnmarshalInto(resp *http.Response, into interface{}) error {
146− body, err := getResponseBody(resp)
147− if err != nil {
148− return err
149− }
150−
151− if err := json.Unmarshal(body, into); err != nil {
152− return fmt.Errorf("error unmarshalling body: %v", err)
153− }
154−
155− return nil
156−}
157−
158−// getResponseBody extracts the response body, but will only return the body
159−// if the response was successful.
160−func getResponseBody(resp *http.Response) ([]byte, error) {
161109 defer resp.Body.Close()
162110 body, err := ioutil.ReadAll(resp.Body)
163111 if err != nil {
164− return nil, fmt.Errorf("error reading response body: %v", err)
165− }
166−
167− // Only unmarshal body if the response was successful
168− if resp.StatusCode != http.StatusOK {
169− return nil, fmt.Errorf("unexpected status \"%d\": %s", resp.StatusCode, body)
112+ r.result = &result{err: fmt.Errorf("error reading response body: %v", err)}
113+ return r.result
170114 }
171115
172− return body, nil
116+ r.result = &result{response: resp, body: body}
117+ return r.result
173118}
pkg/requests/builder_test.go+17−25
@@ -6,7 +6,6 @@ import (
66 "encoding/base64"
77 "encoding/json"
88 "fmt"
9− "io/ioutil"
109 "net/http"
1110
1211 "github.com/bitly/go-simplejson"
@@ -215,8 +214,8 @@ var _ = Describe("Builder suite", func() {
215214
216215 Context("if the request has been completed and then modified", func() {
217216 BeforeEach(func() {
218− _, err := b.Do()
219− Expect(err).ToNot(HaveOccurred())
217+ result := b.Do()
218+ Expect(result.Error()).ToNot(HaveOccurred())
220219
221220 b.WithMethod("POST")
222221 })
@@ -250,25 +249,20 @@ var _ = Describe("Builder suite", func() {
250249
251250func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPRequest) {
252251 Context("Do", func() {
253− var resp *http.Response
252+ var result Result
254253
255254 BeforeEach(func() {
256− var err error
257− resp, err = builder().Do()
258− Expect(err).ToNot(HaveOccurred())
255+ result = builder().Do()
256+ Expect(result.Error()).ToNot(HaveOccurred())
259257 })
260258
261259 It("returns a successful status", func() {
262− Expect(resp.StatusCode).To(Equal(http.StatusOK))
260+ Expect(result.StatusCode()).To(Equal(http.StatusOK))
263261 })
264262
265263 It("made the expected request", func() {
266− body, err := ioutil.ReadAll(resp.Body)
267− Expect(err).ToNot(HaveOccurred())
268− resp.Body.Close()
269−
270264 actualRequest := testHTTPRequest{}
271− Expect(json.Unmarshal(body, &actualRequest)).To(Succeed())
265+ Expect(json.Unmarshal(result.Body(), &actualRequest)).To(Succeed())
272266
273267 Expect(actualRequest).To(Equal(expectedRequest))
274268 })
@@ -278,7 +272,7 @@ func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPReq
278272 var actualRequest testHTTPRequest
279273
280274 BeforeEach(func() {
281− Expect(builder().UnmarshalInto(&actualRequest)).To(Succeed())
275+ Expect(builder().Do().UnmarshalInto(&actualRequest)).To(Succeed())
282276 })
283277
284278 It("made the expected request", func() {
@@ -291,7 +285,7 @@ func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPReq
291285
292286 BeforeEach(func() {
293287 var err error
294− response, err = builder().UnmarshalJSON()
288+ response, err = builder().Do().UnmarshalJSON()
295289 Expect(err).ToNot(HaveOccurred())
296290 })
297291
@@ -328,16 +322,15 @@ func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPReq
328322func assertRequestError(builder func() Builder, errorMessage string) {
329323 Context("Do", func() {
330324 It("returns an error", func() {
331− resp, err := builder().Do()
332− Expect(err).To(MatchError(ContainSubstring(errorMessage)))
333− Expect(resp).To(BeNil())
325+ result := builder().Do()
326+ Expect(result.Error()).To(MatchError(ContainSubstring(errorMessage)))
334327 })
335328 })
336329
337330 Context("UnmarshalInto", func() {
338331 It("returns an error", func() {
339332 var actualRequest testHTTPRequest
340− err := builder().UnmarshalInto(&actualRequest)
333+ err := builder().Do().UnmarshalInto(&actualRequest)
341334 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
342335
343336 // Should be empty
@@ -347,7 +340,7 @@ func assertRequestError(builder func() Builder, errorMessage string) {
347340
348341 Context("UnmarshalJSON", func() {
349342 It("returns an error", func() {
350− resp, err := builder().UnmarshalJSON()
343+ resp, err := builder().Do().UnmarshalJSON()
351344 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
352345 Expect(resp).To(BeNil())
353346 })
@@ -357,16 +350,15 @@ func assertRequestError(builder func() Builder, errorMessage string) {
357350func assertJSONError(builder func() Builder, errorMessage string) {
358351 Context("Do", func() {
359352 It("does not return an error", func() {
360− resp, err := builder().Do()
361− Expect(err).To(BeNil())
362− Expect(resp).ToNot(BeNil())
353+ result := builder().Do()
354+ Expect(result.Error()).To(BeNil())
363355 })
364356 })
365357
366358 Context("UnmarshalInto", func() {
367359 It("returns an error", func() {
368360 var actualRequest testHTTPRequest
369− err := builder().UnmarshalInto(&actualRequest)
361+ err := builder().Do().UnmarshalInto(&actualRequest)
370362 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
371363
372364 // Should be empty
@@ -376,7 +368,7 @@ func assertJSONError(builder func() Builder, errorMessage string) {
376368
377369 Context("UnmarshalJSON", func() {
378370 It("returns an error", func() {
379− resp, err := builder().UnmarshalJSON()
371+ resp, err := builder().Do().UnmarshalJSON()
380372 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
381373 Expect(resp).To(BeNil())
382374 })
pkg/requests/result.gonew file+98
@@ -0,0 +1,98 @@
1+package requests
2+
3+import (
4+ "encoding/json"
5+ "fmt"
6+ "net/http"
7+
8+ "github.com/bitly/go-simplejson"
9+)
10+
11+// Result is the result of a request created by a Builder
12+type Result interface {
13+ Error() error
14+ StatusCode() int
15+ Headers() http.Header
16+ Body() []byte
17+ UnmarshalInto(interface{}) error
18+ UnmarshalJSON() (*simplejson.Json, error)
19+}
20+
21+type result struct {
22+ err error
23+ response *http.Response
24+ body []byte
25+}
26+
27+// Error returns an error from the result if present
28+func (r *result) Error() error {
29+ return r.err
30+}
31+
32+// StatusCode returns the response's status code
33+func (r *result) StatusCode() int {
34+ if r.response != nil {
35+ return r.response.StatusCode
36+ }
37+ return 0
38+}
39+
40+// Headers returns the response's headers
41+func (r *result) Headers() http.Header {
42+ if r.response != nil {
43+ return r.response.Header
44+ }
45+ return nil
46+}
47+
48+// Body returns the response's body
49+func (r *result) Body() []byte {
50+ return r.body
51+}
52+
53+// UnmarshalInto attempts to unmarshal the response into the the given interface.
54+// The response body is assumed to be JSON.
55+// The response must have a 200 status otherwise an error will be returned.
56+func (r *result) UnmarshalInto(into interface{}) error {
57+ body, err := r.getBodyForUnmarshal()
58+ if err != nil {
59+ return err
60+ }
61+
62+ if err := json.Unmarshal(body, into); err != nil {
63+ return fmt.Errorf("error unmarshalling body: %v", err)
64+ }
65+
66+ return nil
67+}
68+
69+// UnmarshalJSON performs the request and attempts to unmarshal the response into a
70+// simplejson.Json. The response body is assume to be JSON.
71+// The response must have a 200 status otherwise an error will be returned.
72+func (r *result) UnmarshalJSON() (*simplejson.Json, error) {
73+ body, err := r.getBodyForUnmarshal()
74+ if err != nil {
75+ return nil, err
76+ }
77+
78+ data, err := simplejson.NewJson(body)
79+ if err != nil {
80+ return nil, fmt.Errorf("error reading json: %v", err)
81+ }
82+ return data, nil
83+}
84+
85+// getBodyForUnmarshal returns the body if there wasn't an error and the status
86+// code was 200.
87+func (r *result) getBodyForUnmarshal() ([]byte, error) {
88+ if r.Error() != nil {
89+ return nil, r.Error()
90+ }
91+
92+ // Only unmarshal body if the response was successful
93+ if r.StatusCode() != http.StatusOK {
94+ return nil, fmt.Errorf("unexpected status \"%d\": %s", r.StatusCode(), r.Body())
95+ }
96+
97+ return r.Body(), nil
98+}