client.go

 1package catwalk
 2
 3import (
 4	"cmp"
 5	"context"
 6	"encoding/json"
 7	"fmt"
 8	"net/http"
 9	"os"
10	"time"
11
12	xetag "github.com/charmbracelet/x/etag"
13)
14
15const defaultURL = "http://localhost:8080"
16
17// Client represents a client for the catwalk service.
18type Client struct {
19	baseURL    string
20	httpClient *http.Client
21}
22
23// New creates a new client instance
24// Uses CATWALK_URL environment variable or falls back to localhost:8080.
25func New() *Client {
26	return NewWithURL(cmp.Or(os.Getenv("CATWALK_URL"), defaultURL))
27}
28
29// NewWithURL creates a new client with a specific URL.
30func NewWithURL(url string) *Client {
31	return &Client{
32		baseURL: url,
33		httpClient: &http.Client{
34			Timeout: 30 * time.Second,
35		},
36	}
37}
38
39// ErrNotModified happens when the given ETag matches the server, so no update
40// is needed.
41var ErrNotModified = fmt.Errorf("not modified")
42
43// Etag returns the ETag for the given data.
44func Etag(data []byte) string { return xetag.Of(data) }
45
46// GetProviders retrieves all available providers from the service.
47func (c *Client) GetProviders(ctx context.Context, etag string) ([]Provider, error) {
48	req, err := http.NewRequestWithContext(
49		ctx,
50		http.MethodGet,
51		fmt.Sprintf("%s/v2/providers", c.baseURL),
52		nil,
53	)
54	if err != nil {
55		return nil, fmt.Errorf("could not create request: %w", err)
56	}
57	xetag.Request(req, etag)
58
59	resp, err := c.httpClient.Do(req)
60	if err != nil {
61		return nil, fmt.Errorf("failed to make request: %w", err)
62	}
63	defer resp.Body.Close() //nolint:errcheck
64
65	if resp.StatusCode == http.StatusNotModified {
66		return nil, ErrNotModified
67	}
68
69	if resp.StatusCode != http.StatusOK {
70		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
71	}
72
73	var providers []Provider
74	if err := json.NewDecoder(resp.Body).Decode(&providers); err != nil {
75		return nil, fmt.Errorf("failed to decode response: %w", err)
76	}
77
78	return providers, nil
79}