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