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}