Add request builder to simplify request handling
This commit is contained in:
		
							parent
							
								
									9d39816709
								
							
						
					
					
						commit
						0bc0feb4bb
					
				|  | @ -0,0 +1,173 @@ | |||
| package requests | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/bitly/go-simplejson" | ||||
| ) | ||||
| 
 | ||||
| // Builder allows users to construct a request and then either get the requests
 | ||||
| // response via Do(), parse the response into a simplejson.Json via JSON(),
 | ||||
| // or to parse the json response into an object via UnmarshalInto().
 | ||||
| type Builder interface { | ||||
| 	WithContext(context.Context) Builder | ||||
| 	WithBody(io.Reader) Builder | ||||
| 	WithMethod(string) Builder | ||||
| 	WithHeaders(http.Header) Builder | ||||
| 	SetHeader(key, value string) Builder | ||||
| 	Do() (*http.Response, error) | ||||
| 	UnmarshalInto(interface{}) error | ||||
| 	UnmarshalJSON() (*simplejson.Json, error) | ||||
| } | ||||
| 
 | ||||
| type builder struct { | ||||
| 	context  context.Context | ||||
| 	method   string | ||||
| 	endpoint string | ||||
| 	body     io.Reader | ||||
| 	header   http.Header | ||||
| 	response *http.Response | ||||
| } | ||||
| 
 | ||||
| // New provides a new Builder for the given endpoint.
 | ||||
| func New(endpoint string) Builder { | ||||
| 	return &builder{ | ||||
| 		endpoint: endpoint, | ||||
| 		method:   "GET", | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // WithContext adds a context to the request.
 | ||||
| // If no context is provided, context.Background() is used instead.
 | ||||
| func (r *builder) WithContext(ctx context.Context) Builder { | ||||
| 	r.context = ctx | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // WithBody adds a body to the request.
 | ||||
| func (r *builder) WithBody(body io.Reader) Builder { | ||||
| 	r.body = body | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // WithMethod sets the request method. Defaults to "GET".
 | ||||
| func (r *builder) WithMethod(method string) Builder { | ||||
| 	r.method = method | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // WithHeaders replaces the request header map with the given header map.
 | ||||
| func (r *builder) WithHeaders(header http.Header) Builder { | ||||
| 	r.header = header | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // SetHeader sets a single header to the given value.
 | ||||
| // May be used to add multiple headers.
 | ||||
| func (r *builder) SetHeader(key, value string) Builder { | ||||
| 	if r.header == nil { | ||||
| 		r.header = make(http.Header) | ||||
| 	} | ||||
| 	r.header.Set(key, value) | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // Do performs the request and returns the response in its raw form.
 | ||||
| // If the request has already been performed, returns the previous result.
 | ||||
| // This will not allow you to repeat a request.
 | ||||
| func (r *builder) Do() (*http.Response, error) { | ||||
| 	if r.response != nil { | ||||
| 		// Request has already been done
 | ||||
| 		return r.response, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Must provide a non-nil context to NewRequestWithContext
 | ||||
| 	if r.context == nil { | ||||
| 		r.context = context.Background() | ||||
| 	} | ||||
| 
 | ||||
| 	req, err := http.NewRequestWithContext(r.context, r.method, r.endpoint, r.body) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error creating request: %v", err) | ||||
| 	} | ||||
| 	req.Header = r.header | ||||
| 
 | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error performing request: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	r.response = resp | ||||
| 	return resp, nil | ||||
| } | ||||
| 
 | ||||
| // UnmarshalInto performs the request and attempts to unmarshal the response into the
 | ||||
| // the given interface. The response body is assumed to be JSON.
 | ||||
| // The response must have a 200 status otherwise an error will be returned.
 | ||||
| func (r *builder) UnmarshalInto(into interface{}) error { | ||||
| 	resp, err := r.Do() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return UnmarshalInto(resp, into) | ||||
| } | ||||
| 
 | ||||
| // UnmarshalJSON performs the request and attempts to unmarshal the response into a
 | ||||
| // simplejson.Json. The response body is assume to be JSON.
 | ||||
| // The response must have a 200 status otherwise an error will be returned.
 | ||||
| func (r *builder) UnmarshalJSON() (*simplejson.Json, error) { | ||||
| 	resp, err := r.Do() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	body, err := getResponseBody(resp) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	data, err := simplejson.NewJson(body) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error reading json: %v", err) | ||||
| 	} | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // UnmarshalInto attempts to unmarshal the response into the the given interface.
 | ||||
| // The response body is assumed to be JSON.
 | ||||
| // The response must have a 200 status otherwise an error will be returned.
 | ||||
| func UnmarshalInto(resp *http.Response, into interface{}) error { | ||||
| 	body, err := getResponseBody(resp) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := json.Unmarshal(body, into); err != nil { | ||||
| 		return fmt.Errorf("error unmarshalling body: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // getResponseBody extracts the response body, but will only return the body
 | ||||
| // if the response was successful.
 | ||||
| func getResponseBody(resp *http.Response) ([]byte, error) { | ||||
| 	defer resp.Body.Close() | ||||
| 	body, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error reading response body: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Only unmarshal body if the response was successful
 | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		return nil, fmt.Errorf("unexpected status \"%d\": %s", resp.StatusCode, body) | ||||
| 	} | ||||
| 
 | ||||
| 	return body, nil | ||||
| } | ||||
		Loading…
	
		Reference in New Issue