122 lines
2.8 KiB
Go
122 lines
2.8 KiB
Go
package fetcher
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/go-errors/errors"
|
|
)
|
|
|
|
type Fetcher struct {
|
|
server string
|
|
client *http.Client
|
|
editors []RequestEditor
|
|
status []int
|
|
}
|
|
|
|
type FetcherOption func(*Fetcher)
|
|
|
|
func NewFetcher(server string, opts ...FetcherOption) *Fetcher {
|
|
api := &Fetcher{
|
|
server: server,
|
|
client: http.DefaultClient,
|
|
}
|
|
for _, apply := range opts {
|
|
apply(api)
|
|
}
|
|
return api
|
|
}
|
|
|
|
func WithHTTPClient(client *http.Client) FetcherOption {
|
|
return func(s *Fetcher) {
|
|
s.client = client
|
|
}
|
|
}
|
|
|
|
func WithExpectedStatus(statusCode ...int) FetcherOption {
|
|
return func(s *Fetcher) {
|
|
s.status = statusCode
|
|
}
|
|
}
|
|
|
|
func WithBearerToken(token string) FetcherOption {
|
|
addHeader := func(req *http.Request) {
|
|
req.Header.Add("Authorization", "Bearer "+token)
|
|
}
|
|
return WithRequestEditor(addHeader)
|
|
}
|
|
|
|
func WithUserAgent(agent string) FetcherOption {
|
|
addHeader := func(req *http.Request) {
|
|
req.Header.Add("User-Agent", agent)
|
|
}
|
|
return WithRequestEditor(addHeader)
|
|
}
|
|
|
|
func WithRequestEditor(fn RequestEditor) FetcherOption {
|
|
return func(s *Fetcher) {
|
|
s.editors = append(s.editors, fn)
|
|
}
|
|
}
|
|
|
|
type RequestEditor func(req *http.Request)
|
|
|
|
func (s *Fetcher) Send(ctx context.Context, method, path string, reqBody any, reqEditors ...RequestEditor) (*http.Response, error) {
|
|
body, ok := reqBody.(io.Reader)
|
|
if !ok && reqBody != nil {
|
|
var buf bytes.Buffer
|
|
enc := json.NewEncoder(&buf)
|
|
if err := enc.Encode(reqBody); err != nil {
|
|
return nil, errors.Errorf("failed to encode request body: %w", err)
|
|
}
|
|
reqEditors = append(reqEditors, func(req *http.Request) {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
})
|
|
body = &buf
|
|
}
|
|
// Creates request
|
|
req, err := http.NewRequestWithContext(ctx, method, s.server+path, body)
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to initialise http request: %w", err)
|
|
}
|
|
for _, apply := range s.editors {
|
|
apply(req)
|
|
}
|
|
for _, apply := range reqEditors {
|
|
apply(req)
|
|
}
|
|
// Sends request
|
|
resp, err := s.client.Do(req)
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to execute http request: %w", err)
|
|
}
|
|
for _, expected := range s.status {
|
|
if resp.StatusCode == expected {
|
|
return resp, nil
|
|
}
|
|
}
|
|
// Reject unexpected status codes as error
|
|
if len(s.status) > 0 || resp.StatusCode >= http.StatusBadRequest {
|
|
defer resp.Body.Close()
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return resp, errors.Errorf("Error status %d: %w", resp.StatusCode, err)
|
|
}
|
|
return resp, errors.Errorf("Error status %d: %s", resp.StatusCode, data)
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func ParseJSON[T any](r io.ReadCloser) (T, error) {
|
|
defer r.Close()
|
|
var data T
|
|
dec := json.NewDecoder(r)
|
|
if err := dec.Decode(&data); err != nil {
|
|
return data, errors.Errorf("failed to parse response body: %w", err)
|
|
}
|
|
return data, nil
|
|
}
|