artifacts/api/api.go

176 lines
4.2 KiB
Go
Raw Normal View History

2024-07-31 21:09:11 +00:00
// Package api holds functions to interact with the artifacts API
package api
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"slices"
)
// custom error codes
var (
ErrTokenNotProvided = errors.New("make sure to provide a token via the api.SetToken() function, or set it via env variable ARTIFACTS_TOKEN")
)
const (
baseURL = "https://api.artifactsmmo.com"
)
var token string // the token must be provided for the client to work
type apiResponse struct {
Data json.RawMessage `json:"data"`
Total int `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
Pages int `json:"pages"`
}
// SetToken can be used to set the token, which must be provided before calling any other function.
// It can be provided using this function or via environment variable 'ARTIFACTS_TOKEN'.
func SetToken(t string) {
token = t
}
func meta[T any](endpoint string, method string, body any, responseCodes map[int]string) (*T, error) {
// make it possible to set a token via env variable
if token == "" && os.Getenv("ARTIFACTS_TOKEN") != "" {
SetToken(os.Getenv("ARTIFACTS_TOKEN"))
}
// check for token
if token == "" {
return nil, ErrTokenNotProvided
}
bodyBytes, err := json.Marshal(body)
if err != nil {
return nil, err
}
// create a new request to retrieve characters
request, err := http.NewRequest(method, baseURL+endpoint, bytes.NewReader(bodyBytes))
if err != nil {
return nil, err
}
// add auth header to the request
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
request.Header.Set("Content-Type", "application/json")
// execute this request
response, err := http.DefaultClient.Do(request)
if err != nil {
return nil, err
}
// check all additional status codes first
for code, message := range responseCodes {
if response.StatusCode != code {
continue
}
// exit the function if a statuscode was found and return it's message
return nil, fmt.Errorf("%d: %s", code, message)
}
// basic error handling
if response.StatusCode != 200 {
return nil, fmt.Errorf("%s", response.Status)
}
// read in the body of the response
b, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
// close the body when we are finished
defer response.Body.Close()
// convert it to a native go type
var ar apiResponse
if err := json.Unmarshal(b, &ar); err != nil {
return nil, err
}
if ar.Page < ar.Pages {
// parse endpoint
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
urlValues := u.Query()
urlValues.Set("page", fmt.Sprintf("%d", ar.Page+1))
endpoint = u.Path
nextPage, err := meta[T](fmt.Sprintf("%s?%s", endpoint, urlValues.Encode()), method, body, responseCodes)
if err != nil {
return nil, err
}
b1, err := ar.Data.MarshalJSON()
if err != nil {
return nil, err
}
b2, err := json.Marshal(nextPage)
if err != nil {
return nil, err
}
bCombined := slices.Concat(b1[0:len(b1)-1], []byte(","), b2[1:])
// copy combined dataset into ar.Data
ar.Data.UnmarshalJSON(bCombined)
// combine both things
}
d, err := ar.Data.MarshalJSON()
if err != nil {
return nil, err
}
// no error occurred
var r T
if err := json.Unmarshal(d, &r); err != nil {
return nil, err
}
return &r, nil
}
// Post is used to call an endpoint using a POST request, responseCodes contains all possible
// error codes that the API can respond with other than 200 OK
func Post[T any](endpoint string, body any, responseCodes map[int]string) (output T, err error) {
// use meta function to get a response
response, err := meta[T](endpoint, http.MethodPost, body, responseCodes)
if err != nil {
return output, err
}
// return the data alongside any error that might have occurred
return *response, err
}
// Get is used to call an endpoint using a GET request, responseCodes contains all possible
// error codes that the API can respond with other than 200 OK
func Get[T any](endpoint string, responseCodes map[int]string) (output T, err error) {
// use meta function to get a response
response, err := meta[T](endpoint, http.MethodGet, nil, responseCodes)
if err != nil {
return output, err
}
// return the data alongside any error that might have occurred
return *response, err
}