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