terraform/vendor/github.com/sethvargo/go-fastly/client.go

213 lines
6.6 KiB
Go
Raw Normal View History

package fastly
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"github.com/ajg/form"
"github.com/hashicorp/go-cleanhttp"
"github.com/mitchellh/mapstructure"
)
// APIKeyEnvVar is the name of the environment variable where the Fastly API
// key should be read from.
const APIKeyEnvVar = "FASTLY_API_KEY"
// APIKeyHeader is the name of the header that contains the Fastly API key.
const APIKeyHeader = "Fastly-Key"
// DefaultEndpoint is the default endpoint for Fastly. Since Fastly does not
// support an on-premise solution, this is likely to always be the default.
const DefaultEndpoint = "https://api.fastly.com"
// ProjectURL is the url for this library.
var ProjectURL = "github.com/sethvargo/go-fastly"
// ProjectVersion is the version of this library.
var ProjectVersion = "0.1"
// UserAgent is the user agent for this particular client.
var UserAgent = fmt.Sprintf("FastlyGo/%s (+%s; %s)",
ProjectVersion, ProjectURL, runtime.Version())
// Client is the main entrypoint to the Fastly golang API library.
type Client struct {
// Address is the address of Fastly's API endpoint.
Address string
// HTTPClient is the HTTP client to use. If one is not provided, a default
// client will be used.
HTTPClient *http.Client
// apiKey is the Fastly API key to authenticate requests.
apiKey string
// url is the parsed URL from Address
url *url.URL
}
// DefaultClient instantiates a new Fastly API client. This function requires
// the environment variable `FASTLY_API_KEY` is set and contains a valid API key
// to authenticate with Fastly.
func DefaultClient() *Client {
client, err := NewClient(os.Getenv(APIKeyEnvVar))
if err != nil {
panic(err)
}
return client
}
// NewClient creates a new API client with the given key. Because Fastly allows
// some requests without an API key, this function will not error if the API
// token is not supplied. Attempts to make a request that requires an API key
// will return a 403 response.
func NewClient(key string) (*Client, error) {
client := &Client{apiKey: key}
return client.init()
}
func (c *Client) init() (*Client, error) {
if len(c.Address) == 0 {
c.Address = DefaultEndpoint
}
u, err := url.Parse(c.Address)
if err != nil {
return nil, err
}
c.url = u
if c.HTTPClient == nil {
c.HTTPClient = cleanhttp.DefaultClient()
}
return c, nil
}
// Get issues an HTTP GET request.
func (c *Client) Get(p string, ro *RequestOptions) (*http.Response, error) {
return c.Request("GET", p, ro)
}
// Head issues an HTTP HEAD request.
func (c *Client) Head(p string, ro *RequestOptions) (*http.Response, error) {
return c.Request("HEAD", p, ro)
}
// Post issues an HTTP POST request.
func (c *Client) Post(p string, ro *RequestOptions) (*http.Response, error) {
return c.Request("POST", p, ro)
}
// PostForm issues an HTTP POST request with the given interface form-encoded.
func (c *Client) PostForm(p string, i interface{}, ro *RequestOptions) (*http.Response, error) {
return c.RequestForm("POST", p, i, ro)
}
// Put issues an HTTP PUT request.
func (c *Client) Put(p string, ro *RequestOptions) (*http.Response, error) {
return c.Request("PUT", p, ro)
}
// PutForm issues an HTTP PUT request with the given interface form-encoded.
func (c *Client) PutForm(p string, i interface{}, ro *RequestOptions) (*http.Response, error) {
return c.RequestForm("PUT", p, i, ro)
}
// Delete issues an HTTP DELETE request.
func (c *Client) Delete(p string, ro *RequestOptions) (*http.Response, error) {
return c.Request("DELETE", p, ro)
}
// Request makes an HTTP request against the HTTPClient using the given verb,
// Path, and request options.
func (c *Client) Request(verb, p string, ro *RequestOptions) (*http.Response, error) {
req, err := c.RawRequest(verb, p, ro)
if err != nil {
return nil, err
}
resp, err := checkResp(c.HTTPClient.Do(req))
if err != nil {
return resp, err
}
return resp, nil
}
// RequestForm makes an HTTP request with the given interface being encoded as
// form data.
func (c *Client) RequestForm(verb, p string, i interface{}, ro *RequestOptions) (*http.Response, error) {
values, err := form.EncodeToValues(i)
if err != nil {
return nil, err
}
if ro == nil {
ro = new(RequestOptions)
}
if ro.Headers == nil {
ro.Headers = make(map[string]string)
}
ro.Headers["Content-Type"] = "application/x-www-form-urlencoded"
// There is a super-jank implementation in the form library where fields with
// a "dot" are replaced with "/.". That is then URL encoded and Fastly just
// dies. We fix that here.
body := strings.Replace(values.Encode(), "%5C.", ".", -1)
ro.Body = strings.NewReader(body)
ro.BodyLength = int64(len(body))
return c.Request(verb, p, ro)
}
// checkResp wraps an HTTP request from the default client and verifies that the
// request was successful. A non-200 request returns an error formatted to
// included any validation problems or otherwise.
func checkResp(resp *http.Response, err error) (*http.Response, error) {
// If the err is already there, there was an error higher up the chain, so
// just return that.
if err != nil {
return resp, err
}
switch resp.StatusCode {
case 200, 201, 202, 204, 205, 206:
return resp, nil
default:
return resp, NewHTTPError(resp)
}
}
// decodeJSON is used to decode an HTTP response body into an interface as JSON.
func decodeJSON(out interface{}, body io.ReadCloser) error {
defer body.Close()
var parsed interface{}
dec := json.NewDecoder(body)
if err := dec.Decode(&parsed); err != nil {
return err
}
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapToHTTPHeaderHookFunc(),
stringToTimeHookFunc(),
),
WeaklyTypedInput: true,
Result: out,
})
if err != nil {
return err
}
return decoder.Decode(parsed)
}