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

     1  package encoder
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/go-playground/form/v4"
    10  	"github.com/go-playground/validator/v10"
    11  	"github.com/polygon-io/client-go/rest/models"
    12  )
    13  
    14  // Encoder defines a path and query param encoder that plays nicely with the Polygon REST API.
    15  type Encoder struct {
    16  	validate     *validator.Validate
    17  	pathEncoder  *form.Encoder
    18  	queryEncoder *form.Encoder
    19  }
    20  
    21  // New returns a new path and query param encoder.
    22  func New() *Encoder {
    23  	return &Encoder{
    24  		validate:     validator.New(),
    25  		pathEncoder:  newEncoder("path"),
    26  		queryEncoder: newEncoder("query"),
    27  	}
    28  }
    29  
    30  // EncodeParams encodes path and query params and returns a valid request URI.
    31  func (e *Encoder) EncodeParams(path string, params any) (string, error) {
    32  	if err := e.validateParams(params); err != nil {
    33  		return "", err
    34  	}
    35  
    36  	uri, err := e.encodePath(path, params)
    37  	if err != nil {
    38  		return "", err
    39  	}
    40  
    41  	query, err := e.encodeQuery(params)
    42  	if err != nil {
    43  		return "", err
    44  	} else if query != "" {
    45  		uri += "?" + query
    46  	}
    47  
    48  	return uri, nil
    49  }
    50  
    51  func (e *Encoder) validateParams(params any) error {
    52  	if err := e.validate.Struct(params); err != nil {
    53  		return fmt.Errorf("invalid request params: %w", err)
    54  	}
    55  	return nil
    56  }
    57  
    58  func (e *Encoder) encodePath(uri string, params any) (string, error) {
    59  	val, err := e.pathEncoder.Encode(&params)
    60  	if err != nil {
    61  		return "", fmt.Errorf("error encoding path params: %w", err)
    62  	}
    63  
    64  	pathParams := map[string]string{}
    65  	for k, v := range val {
    66  		pathParams[k] = v[0] // only accept the first one for a given key
    67  	}
    68  
    69  	for k, v := range pathParams {
    70  		uri = strings.ReplaceAll(uri, fmt.Sprintf("{%s}", k), url.PathEscape(v))
    71  	}
    72  
    73  	return uri, nil
    74  }
    75  
    76  func (e *Encoder) encodeQuery(params any) (string, error) {
    77  	query, err := e.queryEncoder.Encode(&params)
    78  	if err != nil {
    79  		return "", fmt.Errorf("error encoding query params: %w", err)
    80  	}
    81  	return query.Encode(), nil
    82  }
    83  
    84  func newEncoder(tag string) *form.Encoder {
    85  	e := form.NewEncoder()
    86  	e.SetMode(form.ModeExplicit)
    87  	e.SetTagName(tag)
    88  
    89  	e.RegisterCustomTypeFunc(func(x any) ([]string, error) {
    90  		return []string{fmt.Sprint(time.Time(x.(models.Time)).Format("2006-01-02T15:04:05.000Z"))}, nil
    91  	}, models.Time{})
    92  	e.RegisterCustomTypeFunc(func(x any) ([]string, error) {
    93  		return []string{fmt.Sprint(time.Time(x.(models.Date)).Format("2006-01-02"))}, nil
    94  	}, models.Date{})
    95  	e.RegisterCustomTypeFunc(func(x any) ([]string, error) {
    96  		return []string{fmt.Sprint(time.Time(x.(models.Millis)).UnixMilli())}, nil
    97  	}, models.Millis{})
    98  	e.RegisterCustomTypeFunc(func(x any) ([]string, error) {
    99  		if isDay(time.Time(x.(models.Nanos))) {
   100  			// endpoints that have nanosecond timestamp query parameters are expected to
   101  			// also work with date strings if a user wants all data from a specific day
   102  			return []string{fmt.Sprint(time.Time(x.(models.Nanos)).Format("2006-01-02"))}, nil
   103  		}
   104  		return []string{fmt.Sprint(time.Time(x.(models.Nanos)).UnixNano())}, nil
   105  	}, models.Nanos{})
   106  
   107  	return e
   108  }
   109  
   110  func isDay(t time.Time) bool {
   111  	if t.Hour() != 0 || t.Minute() != 0 || t.Second() != 0 || t.Nanosecond() != 0 {
   112  		return false
   113  	}
   114  	return true
   115  }