client.go raw

   1  package auroradns
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"io"
   7  	"net/http"
   8  	"net/url"
   9  )
  10  
  11  const defaultBaseURL = "https://api.auroradns.eu"
  12  
  13  const (
  14  	contentTypeHeader = "Content-Type"
  15  	contentTypeJSON   = "application/json"
  16  )
  17  
  18  // ErrorResponse A representation of an API error message.
  19  // Deprecated: use ResponseError instead.
  20  type ErrorResponse = ResponseError
  21  
  22  // ResponseError A representation of an API error message.
  23  type ResponseError struct {
  24  	ErrorCode string `json:"error"`
  25  	Message   string `json:"errormsg"`
  26  }
  27  
  28  func (e *ResponseError) Error() string {
  29  	return fmt.Sprintf("%s - %s", e.ErrorCode, e.Message)
  30  }
  31  
  32  // Option Type of a client option.
  33  type Option func(*Client) error
  34  
  35  // Client The API client.
  36  type Client struct {
  37  	baseURL    *url.URL
  38  	UserAgent  string
  39  	httpClient *http.Client
  40  }
  41  
  42  // NewClient Creates a new client.
  43  func NewClient(httpClient *http.Client, opts ...Option) (*Client, error) {
  44  	if httpClient == nil {
  45  		httpClient = http.DefaultClient
  46  	}
  47  
  48  	baseURL, _ := url.Parse(defaultBaseURL)
  49  
  50  	client := &Client{
  51  		baseURL:    baseURL,
  52  		httpClient: httpClient,
  53  	}
  54  
  55  	for _, opt := range opts {
  56  		err := opt(client)
  57  		if err != nil {
  58  			return nil, err
  59  		}
  60  	}
  61  
  62  	return client, nil
  63  }
  64  
  65  func (c *Client) newRequest(method, resource string, body io.Reader) (*http.Request, error) {
  66  	u, err := c.baseURL.Parse(resource)
  67  	if err != nil {
  68  		return nil, err
  69  	}
  70  
  71  	req, err := http.NewRequest(method, u.String(), body)
  72  	if err != nil {
  73  		return nil, err
  74  	}
  75  
  76  	req.Header.Set(contentTypeHeader, contentTypeJSON)
  77  
  78  	if c.UserAgent != "" {
  79  		req.Header.Set("User-Agent", c.UserAgent)
  80  	}
  81  
  82  	return req, nil
  83  }
  84  
  85  func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) {
  86  	resp, err := c.httpClient.Do(req)
  87  	if err != nil {
  88  		return nil, err
  89  	}
  90  	defer func() { _ = resp.Body.Close() }()
  91  
  92  	if err = checkResponse(resp); err != nil {
  93  		return resp, err
  94  	}
  95  
  96  	if v == nil {
  97  		return resp, nil
  98  	}
  99  
 100  	raw, err := io.ReadAll(resp.Body)
 101  	if err != nil {
 102  		return resp, fmt.Errorf("failed to read body: %w", err)
 103  	}
 104  
 105  	if err = json.Unmarshal(raw, v); err != nil {
 106  		return resp, fmt.Errorf("unmarshaling %T error: %w: %s", v, err, string(raw))
 107  	}
 108  
 109  	return resp, nil
 110  }
 111  
 112  func checkResponse(resp *http.Response) error {
 113  	if c := resp.StatusCode; 200 <= c && c <= 299 {
 114  		return nil
 115  	}
 116  
 117  	data, err := io.ReadAll(resp.Body)
 118  	if err == nil && data != nil {
 119  		errorResponse := new(ResponseError)
 120  		err = json.Unmarshal(data, errorResponse)
 121  		if err != nil {
 122  			return fmt.Errorf("unmarshaling ErrorResponse error: %w: %s", err, string(data))
 123  		}
 124  
 125  		return errorResponse
 126  	}
 127  
 128  	return fmt.Errorf("status code: %d %s", resp.StatusCode, resp.Status)
 129  }
 130  
 131  // WithBaseURL Allows to define a custom base URL.
 132  func WithBaseURL(rawBaseURL string) func(*Client) error {
 133  	return func(client *Client) error {
 134  		if rawBaseURL == "" {
 135  			return nil
 136  		}
 137  
 138  		baseURL, err := url.Parse(rawBaseURL)
 139  		if err != nil {
 140  			return err
 141  		}
 142  
 143  		client.baseURL = baseURL
 144  
 145  		return nil
 146  	}
 147  }
 148