github.com/polygon-io/client-go@v1.16.4/rest/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"time"
     8  
     9  	"github.com/go-resty/resty/v2"
    10  	"github.com/polygon-io/client-go/rest/encoder"
    11  	"github.com/polygon-io/client-go/rest/models"
    12  )
    13  
    14  const clientVersion = "v1.16.0"
    15  
    16  const (
    17  	APIURL            = "https://api.polygon.io"
    18  	DefaultRetryCount = 3
    19  )
    20  
    21  // Client defines an HTTP client for the Polygon REST API.
    22  type Client struct {
    23  	HTTP    *resty.Client
    24  	encoder *encoder.Encoder
    25  }
    26  
    27  // New returns a new client with the specified API key and default settings.
    28  func New(apiKey string) Client {
    29  	return newClient(apiKey, nil)
    30  }
    31  
    32  // NewWithClient returns a new client with the specified API key and a custom HTTP client.
    33  func NewWithClient(apiKey string, hc *http.Client) Client {
    34  	return newClient(apiKey, hc)
    35  }
    36  
    37  func newClient(apiKey string, hc *http.Client) Client {
    38  	var c *resty.Client
    39  	if hc == nil {
    40  		c = resty.New()
    41  	} else {
    42  		c = resty.NewWithClient(hc)
    43  	}
    44  
    45  	c.SetBaseURL(APIURL)
    46  	c.SetAuthToken(apiKey)
    47  	c.SetRetryCount(DefaultRetryCount)
    48  	c.SetTimeout(10 * time.Second)
    49  	c.SetHeader("User-Agent", fmt.Sprintf("Polygon.io GoClient/%v", clientVersion))
    50  	c.SetHeader("Accept-Encoding", "gzip")
    51  
    52  	return Client{
    53  		HTTP:    c,
    54  		encoder: encoder.New(),
    55  	}
    56  }
    57  
    58  // Call makes an API call based on the request params and options. The response is automatically unmarshaled.
    59  func (c *Client) Call(ctx context.Context, method, path string, params, response any, opts ...models.RequestOption) error {
    60  	uri, err := c.encoder.EncodeParams(path, params)
    61  	if err != nil {
    62  		return err
    63  	}
    64  	return c.CallURL(ctx, method, uri, response, opts...)
    65  }
    66  
    67  // CallURL makes an API call based on a request URI and options. The response is automatically unmarshaled.
    68  func (c *Client) CallURL(ctx context.Context, method, uri string, response any, opts ...models.RequestOption) error {
    69  	options := mergeOptions(opts...)
    70  
    71  	req := c.HTTP.R().SetContext(ctx)
    72  	if options.APIKey != nil {
    73  		req.SetAuthToken(*options.APIKey)
    74  	}
    75  	req.SetQueryParamsFromValues(options.QueryParams)
    76  	req.SetHeaderMultiValues(options.Headers)
    77  	req.SetResult(response).SetError(&models.ErrorResponse{})
    78  
    79  	res, err := req.Execute(method, uri)
    80  	if err != nil {
    81  		return fmt.Errorf("failed to execute request: %w", err)
    82  	} else if res.IsError() {
    83  		errRes := res.Error().(*models.ErrorResponse)
    84  		errRes.StatusCode = res.StatusCode()
    85  		if errRes.RequestID == "" {
    86  			errRes.RequestID = res.Header().Get("X-Request-ID")
    87  		}
    88  		return errRes
    89  	}
    90  
    91  	if options.Trace {
    92  		fmt.Printf("Request URL: %s\n", uri)
    93  		sanitizedHeaders := req.Header
    94  		for k := range sanitizedHeaders {
    95  			if k == "Authorization" {
    96  				sanitizedHeaders[k] = []string{"REDACTED"}
    97  			}
    98  		}
    99  		fmt.Printf("Request Headers: %s\n", sanitizedHeaders)
   100  		fmt.Printf("Response Headers: %+v\n", res.Header())
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  func mergeOptions(opts ...models.RequestOption) *models.RequestOptions {
   107  	options := &models.RequestOptions{}
   108  	for _, o := range opts {
   109  		o(options)
   110  	}
   111  
   112  	return options
   113  }