client.go raw

   1  package v2
   2  
   3  import (
   4  	"context"
   5  	"encoding/json"
   6  	"fmt"
   7  	"io"
   8  	"net/http"
   9  	"strings"
  10  )
  11  
  12  type (
  13  	QueryParam map[string]string
  14  	Client     struct {
  15  		httpClient     *http.Client
  16  		defaultHeaders http.Header
  17  		BaseURL        string
  18  	}
  19  	ReturnTypes interface {
  20  		Zone | List[Zone] | RRSet | List[RRSet]
  21  	}
  22  )
  23  
  24  //nolint:exhaustruct
  25  var _ DNSClient[Zone, RRSet] = &Client{}
  26  
  27  func NewClient(apiURL string, httpClient *http.Client, defaultHeaders http.Header) DNSClient[Zone, RRSet] {
  28  	return &Client{
  29  		httpClient:     httpClient,
  30  		defaultHeaders: defaultHeaders,
  31  		BaseURL:        apiURL,
  32  	}
  33  }
  34  
  35  // WithHeaders returns reference to a copy of the initial client
  36  // with extra headers passed in params. Conflicting headers are replaced
  37  // with new ones.
  38  func (c *Client) WithHeaders(headers http.Header) DNSClient[Zone, RRSet] {
  39  	temporaryClient := *c
  40  	temporaryClient.defaultHeaders = temporaryClient.defaultHeaders.Clone()
  41  	if temporaryClient.defaultHeaders == nil {
  42  		temporaryClient.defaultHeaders = http.Header{}
  43  	}
  44  	for k, v := range headers {
  45  		temporaryClient.defaultHeaders.Del(k)
  46  		for _, value := range v {
  47  			temporaryClient.defaultHeaders.Add(k, value)
  48  		}
  49  	}
  50  
  51  	return &temporaryClient
  52  }
  53  
  54  // prepareRequest prepares request with default headers and additional content.
  55  func (c *Client) prepareRequest(
  56  	ctx context.Context,
  57  	method, path string,
  58  	body io.Reader,
  59  	//nolint: unparam
  60  	params, extraHeaders *map[string]string,
  61  ) (*http.Request, error) {
  62  	path = fmt.Sprintf("%s%s", c.BaseURL, path)
  63  	request, err := http.NewRequestWithContext(ctx, method, path, body)
  64  	if err != nil {
  65  		return nil, fmt.Errorf("prepare request: %w", err)
  66  	}
  67  
  68  	if c.defaultHeaders != nil {
  69  		request.Header = c.defaultHeaders.Clone()
  70  	}
  71  
  72  	if extraHeaders != nil {
  73  		for key, value := range *extraHeaders {
  74  			request.Header.Add(key, value)
  75  		}
  76  	}
  77  
  78  	addedParamsToHTTPQuery(request, params)
  79  
  80  	return request.WithContext(ctx), nil
  81  }
  82  
  83  func addedParamsToHTTPQuery(request *http.Request, params *map[string]string) {
  84  	if params == nil {
  85  		return
  86  	}
  87  	urlQuery := request.URL.Query()
  88  	for key, value := range *params {
  89  		for _, val := range strings.Split(value, ",") {
  90  			urlQuery.Add(key, val)
  91  		}
  92  	}
  93  	request.URL.RawQuery = urlQuery.Encode()
  94  }
  95  
  96  func processRequest[RT ReturnTypes](client *http.Client, request *http.Request, err error) (*RT, error) {
  97  	if err != nil {
  98  		return nil, ErrInvalidRequestObj
  99  	}
 100  	response, err := client.Do(request)
 101  	if err != nil {
 102  		return nil, fmt.Errorf("processing request: %w", err)
 103  	}
 104  	body, err := io.ReadAll(response.Body)
 105  	defer response.Body.Close()
 106  	if err != nil {
 107  		return nil, fmt.Errorf("processing response: %w", err)
 108  	}
 109  	resp, err := checkProccessResult[RT](response.StatusCode, body)
 110  
 111  	return resp, err
 112  }
 113  
 114  func checkProccessResult[RT ReturnTypes](statusCode int, body []byte) (*RT, error) {
 115  	switch {
 116  	case statusCode == http.StatusNoContent && len(body) == 0:
 117  		//nolint: nilnil
 118  		return nil, nil
 119  	case statusCode == http.StatusNotFound:
 120  		return nil, ErrNotFound
 121  	case statusCode < http.StatusBadRequest:
 122  		var result RT
 123  		if err := json.Unmarshal(body, &result); err != nil {
 124  			return nil, fmt.Errorf("processing good response: %w", err)
 125  		}
 126  
 127  		return &result, nil
 128  	default:
 129  		var result BadResponseError
 130  		if err := json.Unmarshal(body, &result); err != nil {
 131  			return nil, fmt.Errorf("processing error response: %w", err)
 132  		}
 133  		result.Code = statusCode
 134  
 135  		return nil, &result
 136  	}
 137  }
 138