git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add request builder to simplify request handling
commit0bc0feb4bb20c6d79b06cd8450f41b90bba8f987 authorJoel Speed <joel.speed@hotmail.co.uk> dateFri 3 Jul 2020, 18:38 (6 years ago) parent9d39816
1 file changed+173
pkg/requests/builder.go+173
pkg/requests/builder.gonew file+173
@@ -0,0 +1,173 @@
1+package requests
2+
3+import (
4+ "context"
5+ "encoding/json"
6+ "fmt"
7+ "io"
8+ "io/ioutil"
9+ "net/http"
10+
11+ "github.com/bitly/go-simplejson"
12+)
13+
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().
17+type Builder interface {
18+ WithContext(context.Context) Builder
19+ WithBody(io.Reader) Builder
20+ WithMethod(string) Builder
21+ WithHeaders(http.Header) Builder
22+ SetHeader(key, value string) Builder
23+ Do() (*http.Response, error)
24+ UnmarshalInto(interface{}) error
25+ UnmarshalJSON() (*simplejson.Json, error)
26+}
27+
28+type builder struct {
29+ context context.Context
30+ method string
31+ endpoint string
32+ body io.Reader
33+ header http.Header
34+ response *http.Response
35+}
36+
37+// New provides a new Builder for the given endpoint.
38+func New(endpoint string) Builder {
39+ return &builder{
40+ endpoint: endpoint,
41+ method: "GET",
42+ }
43+}
44+
45+// WithContext adds a context to the request.
46+// If no context is provided, context.Background() is used instead.
47+func (r *builder) WithContext(ctx context.Context) Builder {
48+ r.context = ctx
49+ return r
50+}
51+
52+// WithBody adds a body to the request.
53+func (r *builder) WithBody(body io.Reader) Builder {
54+ r.body = body
55+ return r
56+}
57+
58+// WithMethod sets the request method. Defaults to "GET".
59+func (r *builder) WithMethod(method string) Builder {
60+ r.method = method
61+ return r
62+}
63+
64+// WithHeaders replaces the request header map with the given header map.
65+func (r *builder) WithHeaders(header http.Header) Builder {
66+ r.header = header
67+ return r
68+}
69+
70+// SetHeader sets a single header to the given value.
71+// May be used to add multiple headers.
72+func (r *builder) SetHeader(key, value string) Builder {
73+ if r.header == nil {
74+ r.header = make(http.Header)
75+ }
76+ r.header.Set(key, value)
77+ return r
78+}
79+
80+// Do performs the request and returns the response in its raw form.
81+// If the request has already been performed, returns the previous result.
82+// This will not allow you to repeat a request.
83+func (r *builder) Do() (*http.Response, error) {
84+ if r.response != nil {
85+ // Request has already been done
86+ return r.response, nil
87+ }
88+
89+ // Must provide a non-nil context to NewRequestWithContext
90+ if r.context == nil {
91+ r.context = context.Background()
92+ }
93+
94+ req, err := http.NewRequestWithContext(r.context, r.method, r.endpoint, r.body)
95+ if err != nil {
96+ return nil, fmt.Errorf("error creating request: %v", err)
97+ }
98+ req.Header = r.header
99+
100+ resp, err := http.DefaultClient.Do(req)
101+ 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
116+ }
117+
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()
162+ body, err := ioutil.ReadAll(resp.Body)
163+ 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)
170+ }
171+
172+ return body, nil
173+}