package cloudflare import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "time" ) const apiBase = "https://api.cloudflare.com/client/v4" // Client handles Cloudflare API calls. type Client struct { apiToken string httpClient *http.Client logger *log.Logger debug bool } // New creates a Cloudflare API client. func New(apiToken string, logger *log.Logger, debug bool) *Client { return &Client{ apiToken: apiToken, httpClient: &http.Client{Timeout: 15 * time.Second}, logger: logger, debug: debug, } } // IsConfigured returns true if a CF API token is set. func (c *Client) IsConfigured() bool { return c.apiToken != "" } // apiResponse is the generic Cloudflare API response wrapper. type apiResponse struct { Success bool `json:"success"` Errors []apiError `json:"errors"` Messages []apiMessage `json:"messages"` Result json.RawMessage `json:"result"` } type apiError struct { Code int `json:"code"` Message string `json:"message"` } type apiMessage struct { Code int `json:"code"` Message string `json:"message"` } // do performs an HTTP request to the Cloudflare API and decodes the response. func (c *Client) do(method, path string, body interface{}) (*apiResponse, error) { var bodyReader io.Reader if body != nil { data, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("marshal request: %w", err) } bodyReader = bytes.NewReader(data) if c.debug { c.logger.Printf("[CF-DEBUG] %s %s body=%s", method, path, string(data)) } } else if c.debug { c.logger.Printf("[CF-DEBUG] %s %s", method, path) } url := apiBase + path req, err := http.NewRequest(method, url, bodyReader) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.apiToken) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("http request: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response: %w", err) } if c.debug { c.logger.Printf("[CF-DEBUG] Response %d: %s", resp.StatusCode, string(respBody)) } var apiResp apiResponse if err := json.Unmarshal(respBody, &apiResp); err != nil { return nil, fmt.Errorf("decode response (status %d): %w", resp.StatusCode, err) } if !apiResp.Success { msg := "unknown error" if len(apiResp.Errors) > 0 { msg = apiResp.Errors[0].Message for _, e := range apiResp.Errors[1:] { msg += "; " + e.Message } } return &apiResp, fmt.Errorf("cloudflare API error (status %d): %s", resp.StatusCode, msg) } return &apiResp, nil }