client.go raw

   1  package internal
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"io"
   7  	"net/http"
   8  	"net/url"
   9  	"time"
  10  
  11  	"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
  12  )
  13  
  14  const (
  15  	defaultBaseURL = "https://api.dns.constellix.com"
  16  	defaultVersion = "v1"
  17  )
  18  
  19  // Client the Constellix client.
  20  type Client struct {
  21  	BaseURL    string
  22  	HTTPClient *http.Client
  23  
  24  	common service // Reuse a single struct instead of allocating one for each service on the heap.
  25  
  26  	// Services used for communicating with the API
  27  	Domains    *DomainService
  28  	TxtRecords *TxtRecordService
  29  }
  30  
  31  // NewClient Creates a Constellix client.
  32  func NewClient(httpClient *http.Client) *Client {
  33  	if httpClient == nil {
  34  		httpClient = &http.Client{Timeout: 5 * time.Second}
  35  	}
  36  
  37  	client := &Client{
  38  		BaseURL:    defaultBaseURL,
  39  		HTTPClient: httpClient,
  40  	}
  41  
  42  	client.common.client = client
  43  	client.Domains = (*DomainService)(&client.common)
  44  	client.TxtRecords = (*TxtRecordService)(&client.common)
  45  
  46  	return client
  47  }
  48  
  49  type service struct {
  50  	client *Client
  51  }
  52  
  53  // do sends an API request and returns the API response.
  54  func (c *Client) do(req *http.Request, result any) error {
  55  	req.Header.Set("Accept", "application/json")
  56  	req.Header.Set("Content-Type", "application/json")
  57  
  58  	resp, err := c.HTTPClient.Do(req)
  59  	if err != nil {
  60  		return errutils.NewHTTPDoError(req, err)
  61  	}
  62  
  63  	defer func() { _ = resp.Body.Close() }()
  64  
  65  	err = checkResponse(resp)
  66  	if err != nil {
  67  		return err
  68  	}
  69  
  70  	raw, err := io.ReadAll(resp.Body)
  71  	if err != nil {
  72  		return errutils.NewReadResponseError(req, resp.StatusCode, err)
  73  	}
  74  
  75  	if err = json.Unmarshal(raw, result); err != nil {
  76  		return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
  77  	}
  78  
  79  	return nil
  80  }
  81  
  82  func (c *Client) createEndpoint(fragment ...string) (string, error) {
  83  	return url.JoinPath(c.BaseURL, fragment...)
  84  }
  85  
  86  func checkResponse(resp *http.Response) error {
  87  	if resp.StatusCode == http.StatusOK {
  88  		return nil
  89  	}
  90  
  91  	raw, err := io.ReadAll(resp.Body)
  92  	if err == nil && raw != nil {
  93  		errAPI := &APIError{StatusCode: resp.StatusCode}
  94  
  95  		if json.Unmarshal(raw, errAPI) != nil {
  96  			return fmt.Errorf("API error: status code: %d: %v", resp.StatusCode, string(raw))
  97  		}
  98  
  99  		switch resp.StatusCode {
 100  		case http.StatusNotFound:
 101  			return &NotFound{APIError: errAPI}
 102  		case http.StatusBadRequest:
 103  			return &BadRequest{APIError: errAPI}
 104  		default:
 105  			return errAPI
 106  		}
 107  	}
 108  
 109  	return fmt.Errorf("API error, status code: %d", resp.StatusCode)
 110  }
 111