supabase-cli/pkg/fetcher/http.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
}