package api import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" ) // BaseClient provides common HTTP functionality for API clients type BaseClient struct { HTTPClient *http.Client BaseURL string } // NewBaseClient creates a new BaseClient with default settings func NewBaseClient(baseURL string) BaseClient { return BaseClient{ HTTPClient: &http.Client{Timeout: 15 * time.Second}, BaseURL: baseURL, } } // Get performs a GET request and decodes the JSON response func (c *BaseClient) Get(ctx context.Context, path string, headers map[string]string, result interface{}) error { req, err := http.NewRequestWithContext(ctx, "GET", c.BaseURL+path, nil) if err != nil { return fmt.Errorf("failed to create request: %w", err) } for k, v := range headers { req.Header.Set(k, v) } return c.doJSON(req, result) } // Post performs a POST request with JSON body and decodes the response func (c *BaseClient) Post(ctx context.Context, path string, headers map[string]string, body interface{}, result interface{}) error { var bodyReader io.Reader if body != nil { jsonData, err := json.Marshal(body) if err != nil { return fmt.Errorf("failed to marshal request body: %w", err) } bodyReader = bytes.NewBuffer(jsonData) } req, err := http.NewRequestWithContext(ctx, "POST", c.BaseURL+path, bodyReader) if err != nil { return fmt.Errorf("failed to create request: %w", err) } if body != nil { req.Header.Set("Content-Type", "application/json") } for k, v := range headers { req.Header.Set(k, v) } return c.doJSON(req, result) } // PostForm performs a POST request with form-encoded body func (c *BaseClient) PostForm(ctx context.Context, path string, headers map[string]string, formData string, result interface{}) error { req, err := http.NewRequestWithContext(ctx, "POST", c.BaseURL+path, bytes.NewBufferString(formData)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") for k, v := range headers { req.Header.Set(k, v) } return c.doJSON(req, result) } // PostEmpty performs a POST request with no body and expects no response body func (c *BaseClient) PostEmpty(ctx context.Context, path string, headers map[string]string) error { req, err := http.NewRequestWithContext(ctx, "POST", c.BaseURL+path, nil) if err != nil { return fmt.Errorf("failed to create request: %w", err) } for k, v := range headers { req.Header.Set(k, v) } resp, err := c.HTTPClient.Do(req) if err != nil { return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body)) } return nil } // Put performs a PUT request with form-encoded body func (c *BaseClient) Put(ctx context.Context, path string, headers map[string]string, formData string, result interface{}) error { req, err := http.NewRequestWithContext(ctx, "PUT", c.BaseURL+path, bytes.NewBufferString(formData)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") for k, v := range headers { req.Header.Set(k, v) } return c.doJSON(req, result) } // doJSON executes the request and decodes JSON response func (c *BaseClient) doJSON(req *http.Request, result interface{}) error { resp, err := c.HTTPClient.Do(req) if err != nil { return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body)) } if result != nil { if err := json.NewDecoder(resp.Body).Decode(result); err != nil { return fmt.Errorf("failed to decode response: %w", err) } } return nil }