// Package client provides the HTTP client wrapper for HarborForge API calls. package client import ( "encoding/json" "fmt" "io" "net/http" "strings" "time" ) // Client is a simple HarborForge API client. type Client struct { BaseURL string Token string APIKey string HTTPClient *http.Client } // New creates a Client with the given base URL and optional auth token. func New(baseURL, token string) *Client { return &Client{ BaseURL: strings.TrimRight(baseURL, "/"), Token: token, HTTPClient: &http.Client{ Timeout: 30 * time.Second, }, } } // NewWithAPIKey creates a Client that authenticates using X-API-Key. func NewWithAPIKey(baseURL, apiKey string) *Client { return &Client{ BaseURL: strings.TrimRight(baseURL, "/"), APIKey: apiKey, HTTPClient: &http.Client{ Timeout: 30 * time.Second, }, } } // RequestError represents a non-2xx HTTP response. type RequestError struct { StatusCode int Body string } func (e *RequestError) Error() string { return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Body) } // Do executes an HTTP request and returns the response body bytes. func (c *Client) Do(method, path string, body io.Reader) ([]byte, error) { url := c.BaseURL + path req, err := http.NewRequest(method, url, body) if err != nil { return nil, fmt.Errorf("cannot create request: %w", err) } if c.APIKey != "" { req.Header.Set("X-API-Key", c.APIKey) } else if c.Token != "" { req.Header.Set("Authorization", "Bearer "+c.Token) } if body != nil { req.Header.Set("Content-Type", "application/json") } resp, err := c.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("cannot read response: %w", err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, &RequestError{StatusCode: resp.StatusCode, Body: string(data)} } return data, nil } // Get performs a GET request. func (c *Client) Get(path string) ([]byte, error) { return c.Do("GET", path, nil) } // Post performs a POST request with a JSON body. func (c *Client) Post(path string, body io.Reader) ([]byte, error) { return c.Do("POST", path, body) } // Put performs a PUT request with a JSON body. func (c *Client) Put(path string, body io.Reader) ([]byte, error) { return c.Do("PUT", path, body) } // Patch performs a PATCH request with a JSON body. func (c *Client) Patch(path string, body io.Reader) ([]byte, error) { return c.Do("PATCH", path, body) } // Delete performs a DELETE request. func (c *Client) Delete(path string) ([]byte, error) { return c.Do("DELETE", path, nil) } // Health checks the API health endpoint and returns the response. func (c *Client) Health() (map[string]interface{}, error) { data, err := c.Get("/health") if err != nil { return nil, err } var result map[string]interface{} if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("cannot parse health response: %w", err) } return result, nil }