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