package slapshotapi import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "github.com/pkg/errors" ) type endpoint interface { path() string method() string } // bodyEndpoint is an optional interface for endpoints that send a JSON body type bodyEndpoint interface { endpoint body() ([]byte, error) } func (c *SlapAPI) request( ctx context.Context, ep endpoint, ) ([]byte, error) { baseurl := fmt.Sprintf("https://%s.slapshot.gg%s", c.env, ep.path()) var bodyReader io.Reader if bep, ok := ep.(bodyEndpoint); ok { data, err := bep.body() if err != nil { return nil, errors.Wrap(err, "endpoint.body") } bodyReader = bytes.NewReader(data) } req, err := http.NewRequestWithContext(ctx, ep.method(), baseurl, bodyReader) if err != nil { return nil, errors.Wrap(err, "http.NewRequestWithContext") } req.Header.Add("Accept", "application/json") req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.key)) if bodyReader != nil { req.Header.Add("Content-Type", "application/json") } res, err := c.do(ctx, req) if err != nil { return nil, errors.Wrap(err, "c.do") } defer func() { _ = res.Body.Close() }() if res.StatusCode != http.StatusOK { return nil, errors.New(fmt.Sprintf("API request failed with status %d", res.StatusCode)) } body, err := io.ReadAll(res.Body) if err != nil { return nil, errors.Wrap(err, "io.ReadAll") } return body, nil } // requestRaw performs an API request and returns the raw status code and body. // This is used for endpoints where non-200 status codes carry meaningful data // (e.g. DELETE returning a plain text response). func (c *SlapAPI) requestRaw( ctx context.Context, ep endpoint, ) (int, []byte, error) { baseurl := fmt.Sprintf("https://%s.slapshot.gg%s", c.env, ep.path()) var bodyReader io.Reader if bep, ok := ep.(bodyEndpoint); ok { data, err := bep.body() if err != nil { return 0, nil, errors.Wrap(err, "endpoint.body") } bodyReader = bytes.NewReader(data) } req, err := http.NewRequestWithContext(ctx, ep.method(), baseurl, bodyReader) if err != nil { return 0, nil, errors.Wrap(err, "http.NewRequestWithContext") } req.Header.Add("Accept", "application/json") req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.key)) if bodyReader != nil { req.Header.Add("Content-Type", "application/json") } res, err := c.do(ctx, req) if err != nil { return 0, nil, errors.Wrap(err, "c.do") } defer func() { _ = res.Body.Close() }() body, err := io.ReadAll(res.Body) if err != nil { return 0, nil, errors.Wrap(err, "io.ReadAll") } return res.StatusCode, body, nil } // unmarshal is a helper that unmarshals JSON response data into a target struct func unmarshal[T any](data []byte) (*T, error) { var result T err := json.Unmarshal(data, &result) if err != nil { return nil, errors.Wrap(err, "json.Unmarshal") } return &result, nil }