1package internal 2 3import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 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 := ioutil.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 := ioutil.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