package client import ( "bytes" "encoding/json" "fmt" "git.db.org.ai/dborg/internal/config" "io" "net/http" "net/url" "time" ) type Client struct { config *config.Config httpClient *http.Client } func New(cfg *config.Config) (*Client, error) { if err := cfg.Validate(); err != nil { return nil, err } return &Client{ config: cfg, httpClient: &http.Client{ Timeout: cfg.Timeout, }, }, nil } func (c *Client) doRequest(method, path string, params url.Values, body interface{}) ([]byte, error) { fullURL := c.config.BaseURL + path if params != nil && len(params) > 0 { fullURL += "?" + params.Encode() } var reqBody io.Reader if body != nil { jsonData, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("failed to marshal request body: %w", err) } reqBody = bytes.NewBuffer(jsonData) } req, err := http.NewRequest(method, fullURL, reqBody) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("X-API-Key", c.config.APIKey) req.Header.Set("User-Agent", c.config.UserAgent) if body != nil { req.Header.Set("Content-Type", "application/json") } var resp *http.Response var lastErr error for attempt := 0; attempt <= c.config.MaxRetries; attempt++ { if attempt > 0 { time.Sleep(time.Duration(attempt) * time.Second) } resp, err = c.httpClient.Do(req) if err != nil { lastErr = err continue } defer resp.Body.Close() if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated { return io.ReadAll(resp.Body) } bodyBytes, _ := io.ReadAll(resp.Body) switch resp.StatusCode { case http.StatusForbidden: lastErr = fmt.Errorf("access denied (403): %s - This endpoint requires premium access", string(bodyBytes)) case http.StatusUnauthorized: lastErr = fmt.Errorf("unauthorized (401): %s - Check your API key", string(bodyBytes)) case http.StatusTooManyRequests: lastErr = fmt.Errorf("rate limit exceeded (429): %s", string(bodyBytes)) case http.StatusBadRequest: lastErr = fmt.Errorf("bad request (400): %s", string(bodyBytes)) default: lastErr = fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(bodyBytes)) } if resp.StatusCode != http.StatusTooManyRequests && resp.StatusCode < 500 { break } } return nil, lastErr } func (c *Client) Get(path string, params url.Values) ([]byte, error) { return c.doRequest(http.MethodGet, path, params, nil) } func (c *Client) Post(path string, body interface{}) ([]byte, error) { return c.doRequest(http.MethodPost, path, nil, body) } func (c *Client) Delete(path string) ([]byte, error) { return c.doRequest(http.MethodDelete, path, nil, nil) } func (c *Client) Patch(path string, body interface{}) ([]byte, error) { return c.doRequest(http.MethodPatch, path, nil, body) } func (c *Client) Put(path string, body interface{}) ([]byte, error) { return c.doRequest(http.MethodPut, path, nil, body) }