terraform/vendor/gopkg.in/ns1/ns1-go.v2/rest/client.go

289 lines
7.4 KiB
Go

package rest
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"time"
)
const (
clientVersion = "2.0.0"
defaultEndpoint = "https://api.nsone.net/v1/"
defaultUserAgent = "go-ns1/" + clientVersion
headerAuth = "X-NSONE-Key"
headerRateLimit = "X-Ratelimit-Limit"
headerRateRemaining = "X-Ratelimit-Remaining"
headerRatePeriod = "X-Ratelimit-Period"
)
// Doer is a single method interface that allows a user to extend/augment an http.Client instance.
// Note: http.Client satisfies the Doer interface.
type Doer interface {
Do(*http.Request) (*http.Response, error)
}
// Client manages communication with the NS1 Rest API.
type Client struct {
// httpClient handles all rest api communication,
// and expects an *http.Client.
httpClient Doer
// NS1 rest endpoint, overrides default if given.
Endpoint *url.URL
// NS1 api key (value for http request header 'X-NSONE-Key').
APIKey string
// NS1 go rest user agent (value for http request header 'User-Agent').
UserAgent string
// Func to call after response is returned in Do
RateLimitFunc func(RateLimit)
// From the excellent github-go client.
common service // Reuse a single struct instead of allocating one for each service on the heap.
// Services used for communicating with different components of the NS1 API.
APIKeys *APIKeysService
DataFeeds *DataFeedsService
DataSources *DataSourcesService
Jobs *JobsService
Notifications *NotificationsService
Records *RecordsService
Settings *SettingsService
Teams *TeamsService
Users *UsersService
Warnings *WarningsService
Zones *ZonesService
}
// NewClient constructs and returns a reference to an instantiated Client.
func NewClient(httpClient Doer, options ...func(*Client)) *Client {
endpoint, _ := url.Parse(defaultEndpoint)
if httpClient == nil {
httpClient = http.DefaultClient
}
c := &Client{
httpClient: httpClient,
Endpoint: endpoint,
RateLimitFunc: defaultRateLimitFunc,
UserAgent: defaultUserAgent,
}
c.common.client = c
c.APIKeys = (*APIKeysService)(&c.common)
c.DataFeeds = (*DataFeedsService)(&c.common)
c.DataSources = (*DataSourcesService)(&c.common)
c.Jobs = (*JobsService)(&c.common)
c.Notifications = (*NotificationsService)(&c.common)
c.Records = (*RecordsService)(&c.common)
c.Settings = (*SettingsService)(&c.common)
c.Teams = (*TeamsService)(&c.common)
c.Users = (*UsersService)(&c.common)
c.Warnings = (*WarningsService)(&c.common)
c.Zones = (*ZonesService)(&c.common)
for _, option := range options {
option(c)
}
return c
}
type service struct {
client *Client
}
// SetHTTPClient sets a Client instances' httpClient.
func SetHTTPClient(httpClient Doer) func(*Client) {
return func(c *Client) { c.httpClient = httpClient }
}
// SetAPIKey sets a Client instances' APIKey.
func SetAPIKey(key string) func(*Client) {
return func(c *Client) { c.APIKey = key }
}
// SetEndpoint sets a Client instances' Endpoint.
func SetEndpoint(endpoint string) func(*Client) {
return func(c *Client) { c.Endpoint, _ = url.Parse(endpoint) }
}
// SetUserAgent sets a Client instances' user agent.
func SetUserAgent(ua string) func(*Client) {
return func(c *Client) { c.UserAgent = ua }
}
// SetRateLimitFunc sets a Client instances' RateLimitFunc.
func SetRateLimitFunc(ratefunc func(rl RateLimit)) func(*Client) {
return func(c *Client) { c.RateLimitFunc = ratefunc }
}
// Do satisfies the Doer interface.
func (c Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
err = CheckResponse(resp)
if err != nil {
return resp, err
}
rl := parseRate(resp)
c.RateLimitFunc(rl)
if v != nil {
// Try to unmarshal body into given type using streaming decoder.
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
return nil, err
}
}
return resp, err
}
// NewRequest constructs and returns a http.Request.
func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(path)
if err != nil {
return nil, err
}
uri := c.Endpoint.ResolveReference(rel)
// Encode body as json
buf := new(bytes.Buffer)
if body != nil {
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, uri.String(), buf)
if err != nil {
return nil, err
}
req.Header.Add(headerAuth, c.APIKey)
req.Header.Add("User-Agent", c.UserAgent)
return req, nil
}
// Response wraps stdlib http response.
type Response struct {
*http.Response
}
// Error contains all http responses outside the 2xx range.
type Error struct {
Resp *http.Response
Message string
}
// Satisfy std lib error interface.
func (re *Error) Error() string {
return fmt.Sprintf("%v %v: %d %v", re.Resp.Request.Method, re.Resp.Request.URL, re.Resp.StatusCode, re.Message)
}
// CheckResponse handles parsing of rest api errors. Returns nil if no error.
func CheckResponse(resp *http.Response) error {
if c := resp.StatusCode; c >= 200 && c <= 299 {
return nil
}
restErr := &Error{Resp: resp}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if len(b) == 0 {
return restErr
}
err = json.Unmarshal(b, restErr)
if err != nil {
return err
}
return restErr
}
// RateLimitFunc is rate limiting strategy for the Client instance.
type RateLimitFunc func(RateLimit)
// RateLimit stores X-Ratelimit-* headers
type RateLimit struct {
Limit int
Remaining int
Period int
}
var defaultRateLimitFunc = func(rl RateLimit) {}
// PercentageLeft returns the ratio of Remaining to Limit as a percentage
func (rl RateLimit) PercentageLeft() int {
return rl.Remaining * 100 / rl.Limit
}
// WaitTime returns the time.Duration ratio of Period to Limit
func (rl RateLimit) WaitTime() time.Duration {
return (time.Second * time.Duration(rl.Period)) / time.Duration(rl.Limit)
}
// WaitTimeRemaining returns the time.Duration ratio of Period to Remaining
func (rl RateLimit) WaitTimeRemaining() time.Duration {
return (time.Second * time.Duration(rl.Period)) / time.Duration(rl.Remaining)
}
// RateLimitStrategySleep sets RateLimitFunc to sleep by WaitTimeRemaining
func (c *Client) RateLimitStrategySleep() {
c.RateLimitFunc = func(rl RateLimit) {
remaining := rl.WaitTimeRemaining()
time.Sleep(remaining)
}
}
// parseRate parses rate related headers from http response.
func parseRate(resp *http.Response) RateLimit {
var rl RateLimit
if limit := resp.Header.Get(headerRateLimit); limit != "" {
rl.Limit, _ = strconv.Atoi(limit)
}
if remaining := resp.Header.Get(headerRateRemaining); remaining != "" {
rl.Remaining, _ = strconv.Atoi(remaining)
}
if period := resp.Header.Get(headerRatePeriod); period != "" {
rl.Period, _ = strconv.Atoi(period)
}
return rl
}
// SetTimeParam sets a url timestamp query param given the parameters name.
func SetTimeParam(key string, t time.Time) func(*url.Values) {
return func(v *url.Values) { v.Set(key, strconv.Itoa(int(t.Unix()))) }
}
// SetBoolParam sets a url boolean query param given the parameters name.
func SetBoolParam(key string, b bool) func(*url.Values) {
return func(v *url.Values) { v.Set(key, strconv.FormatBool(b)) }
}
// SetStringParam sets a url string query param given the parameters name.
func SetStringParam(key, val string) func(*url.Values) {
return func(v *url.Values) { v.Set(key, val) }
}