git.iko.soy/typeable/oauth2-proxy
on master help sign in
Switch Builder.Do() to return a Result
fbf4063Joel Speed
pkg/requests/builder.go+23−78
@@ -2,27 +2,23 @@ package requests
2
2
3import (
3import (
4 "context"
4 "context"
5− "encoding/json"
6 "fmt"
5 "fmt"
7 "io"
6 "io"
8 "io/ioutil"
7 "io/ioutil"
9 "net/http"
8 "net/http"
10−
11− "github.com/bitly/go-simplejson"
12)
9)
13
10
14−// Builder allows users to construct a request and then either get the requests
11+// Builder allows users to construct a request and then execute the
15−// response via Do(), parse the response into a simplejson.Json via JSON(),
12+// request via Do().
16−// or to parse the json response into an object via UnmarshalInto().
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.
17type Builder interface {
15type Builder interface {
18 WithContext(context.Context) Builder
16 WithContext(context.Context) Builder
19 WithBody(io.Reader) Builder
17 WithBody(io.Reader) Builder
20 WithMethod(string) Builder
18 WithMethod(string) Builder
21 WithHeaders(http.Header) Builder
19 WithHeaders(http.Header) Builder
22 SetHeader(key, value string) Builder
20 SetHeader(key, value string) Builder
23− Do() (*http.Response, error)
21+ Do() Result
24− UnmarshalInto(interface{}) error
25− UnmarshalJSON() (*simplejson.Json, error)
26}
22}
27
23
28type builder struct {
24type builder struct {
@@ -31,7 +27,7 @@ type builder struct {
31 endpoint string
27 endpoint string
32 body io.Reader
28 body io.Reader
33 header http.Header
29 header http.Header
34− response *http.Response
30+ result *result
35}
31}
36
32
37// New provides a new Builder for the given endpoint.
33// New provides a new Builder for the given endpoint.
@@ -80,10 +76,10 @@ func (r *builder) SetHeader(key, value string) Builder {
80// Do performs the request and returns the response in its raw form.
76// Do performs the request and returns the response in its raw form.
81// If the request has already been performed, returns the previous result.
77// If the request has already been performed, returns the previous result.
82// This will not allow you to repeat a request.
78// This will not allow you to repeat a request.
83−func (r *builder) Do() (*http.Response, error) {
79+func (r *builder) Do() Result {
84− if r.response != nil {
80+ if r.result != nil {
85 // Request has already been done
81 // Request has already been done
86− return r.response, nil
82+ return r.result
87 }
83 }
88
84
89 // Must provide a non-nil context to NewRequestWithContext
85 // Must provide a non-nil context to NewRequestWithContext
@@ -91,83 +87,32 @@ func (r *builder) Do() (*http.Response, error) {
91 r.context = context.Background()
87 r.context = context.Background()
92 }
88 }
93
89
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 {
94 req, err := http.NewRequestWithContext(r.context, r.method, r.endpoint, r.body)
96 req, err := http.NewRequestWithContext(r.context, r.method, r.endpoint, r.body)
95 if err != nil {
97 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
97 }
100 }
98 req.Header = r.header
101 req.Header = r.header
99
102
100 resp, err := http.DefaultClient.Do(req)
103 resp, err := http.DefaultClient.Do(req)
101 if err != nil {
104 if err != nil {
102− return nil, fmt.Errorf("error performing request: %v", err)
105+ r.result = &result{err: fmt.Errorf("error performing request: %v", err)}
103− }
104−
105− r.response = resp
106− return resp, nil
106+ return r.result
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
116 }
107 }
117
108
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) {
161 defer resp.Body.Close()
109 defer resp.Body.Close()
162 body, err := ioutil.ReadAll(resp.Body)
110 body, err := ioutil.ReadAll(resp.Body)
163 if err != nil {
111 if err != nil {
164− return nil, fmt.Errorf("error reading response body: %v", err)
112+ r.result = &result{err: fmt.Errorf("error reading response body: %v", err)}
165− }
113+ return r.result
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)
170 }
114 }
171
115
116+ r.result = &result{response: resp, body: body}
172− return body, nil
117+ return r.result
173}
118}
pkg/requests/builder_test.go+17−25
@@ -6,7 +6,6 @@ import (
6 "encoding/base64"
6 "encoding/base64"
7 "encoding/json"
7 "encoding/json"
8 "fmt"
8 "fmt"
9− "io/ioutil"
10 "net/http"
9 "net/http"
11
10
12 "github.com/bitly/go-simplejson"
11 "github.com/bitly/go-simplejson"
@@ -215,8 +214,8 @@ var _ = Describe("Builder suite", func() {
215
214
216 Context("if the request has been completed and then modified", func() {
215 Context("if the request has been completed and then modified", func() {
217 BeforeEach(func() {
216 BeforeEach(func() {
218− _, err := b.Do()
217+ result := b.Do()
219− Expect(err).ToNot(HaveOccurred())
218+ Expect(result.Error()).ToNot(HaveOccurred())
220
219
221 b.WithMethod("POST")
220 b.WithMethod("POST")
222 })
221 })
@@ -250,25 +249,20 @@ var _ = Describe("Builder suite", func() {
250
249
251func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPRequest) {
250func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPRequest) {
252 Context("Do", func() {
251 Context("Do", func() {
253− var resp *http.Response
252+ var result Result
254
253
255 BeforeEach(func() {
254 BeforeEach(func() {
256− var err error
257− resp, err = builder().Do()
255+ result = builder().Do()
258− Expect(err).ToNot(HaveOccurred())
256+ Expect(result.Error()).ToNot(HaveOccurred())
259 })
257 })
260
258
261 It("returns a successful status", func() {
259 It("returns a successful status", func() {
262− Expect(resp.StatusCode).To(Equal(http.StatusOK))
260+ Expect(result.StatusCode()).To(Equal(http.StatusOK))
263 })
261 })
264
262
265 It("made the expected request", func() {
263 It("made the expected request", func() {
266− body, err := ioutil.ReadAll(resp.Body)
267− Expect(err).ToNot(HaveOccurred())
268− resp.Body.Close()
269−
270 actualRequest := testHTTPRequest{}
264 actualRequest := testHTTPRequest{}
271− Expect(json.Unmarshal(body, &actualRequest)).To(Succeed())
265+ Expect(json.Unmarshal(result.Body(), &actualRequest)).To(Succeed())
272
266
273 Expect(actualRequest).To(Equal(expectedRequest))
267 Expect(actualRequest).To(Equal(expectedRequest))
274 })
268 })
@@ -278,7 +272,7 @@ func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPReq
278 var actualRequest testHTTPRequest
272 var actualRequest testHTTPRequest
279
273
280 BeforeEach(func() {
274 BeforeEach(func() {
281− Expect(builder().UnmarshalInto(&actualRequest)).To(Succeed())
275+ Expect(builder().Do().UnmarshalInto(&actualRequest)).To(Succeed())
282 })
276 })
283
277
284 It("made the expected request", func() {
278 It("made the expected request", func() {
@@ -291,7 +285,7 @@ func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPReq
291
285
292 BeforeEach(func() {
286 BeforeEach(func() {
293 var err error
287 var err error
294− response, err = builder().UnmarshalJSON()
288+ response, err = builder().Do().UnmarshalJSON()
295 Expect(err).ToNot(HaveOccurred())
289 Expect(err).ToNot(HaveOccurred())
296 })
290 })
297
291
@@ -328,16 +322,15 @@ func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPReq
328func assertRequestError(builder func() Builder, errorMessage string) {
322func assertRequestError(builder func() Builder, errorMessage string) {
329 Context("Do", func() {
323 Context("Do", func() {
330 It("returns an error", func() {
324 It("returns an error", func() {
331− resp, err := builder().Do()
325+ result := builder().Do()
332− Expect(err).To(MatchError(ContainSubstring(errorMessage)))
326+ Expect(result.Error()).To(MatchError(ContainSubstring(errorMessage)))
333− Expect(resp).To(BeNil())
334 })
327 })
335 })
328 })
336
329
337 Context("UnmarshalInto", func() {
330 Context("UnmarshalInto", func() {
338 It("returns an error", func() {
331 It("returns an error", func() {
339 var actualRequest testHTTPRequest
332 var actualRequest testHTTPRequest
340− err := builder().UnmarshalInto(&actualRequest)
333+ err := builder().Do().UnmarshalInto(&actualRequest)
341 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
334 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
342
335
343 // Should be empty
336 // Should be empty
@@ -347,7 +340,7 @@ func assertRequestError(builder func() Builder, errorMessage string) {
347
340
348 Context("UnmarshalJSON", func() {
341 Context("UnmarshalJSON", func() {
349 It("returns an error", func() {
342 It("returns an error", func() {
350− resp, err := builder().UnmarshalJSON()
343+ resp, err := builder().Do().UnmarshalJSON()
351 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
344 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
352 Expect(resp).To(BeNil())
345 Expect(resp).To(BeNil())
353 })
346 })
@@ -357,16 +350,15 @@ func assertRequestError(builder func() Builder, errorMessage string) {
357func assertJSONError(builder func() Builder, errorMessage string) {
350func assertJSONError(builder func() Builder, errorMessage string) {
358 Context("Do", func() {
351 Context("Do", func() {
359 It("does not return an error", func() {
352 It("does not return an error", func() {
360− resp, err := builder().Do()
353+ result := builder().Do()
361− Expect(err).To(BeNil())
354+ Expect(result.Error()).To(BeNil())
362− Expect(resp).ToNot(BeNil())
363 })
355 })
364 })
356 })
365
357
366 Context("UnmarshalInto", func() {
358 Context("UnmarshalInto", func() {
367 It("returns an error", func() {
359 It("returns an error", func() {
368 var actualRequest testHTTPRequest
360 var actualRequest testHTTPRequest
369− err := builder().UnmarshalInto(&actualRequest)
361+ err := builder().Do().UnmarshalInto(&actualRequest)
370 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
362 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
371
363
372 // Should be empty
364 // Should be empty
@@ -376,7 +368,7 @@ func assertJSONError(builder func() Builder, errorMessage string) {
376
368
377 Context("UnmarshalJSON", func() {
369 Context("UnmarshalJSON", func() {
378 It("returns an error", func() {
370 It("returns an error", func() {
379− resp, err := builder().UnmarshalJSON()
371+ resp, err := builder().Do().UnmarshalJSON()
380 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
372 Expect(err).To(MatchError(ContainSubstring(errorMessage)))
381 Expect(resp).To(BeNil())
373 Expect(resp).To(BeNil())
382 })
374 })
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+}