client.go raw
1 package vegadns
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "net/url"
10 "strings"
11 "time"
12 )
13
14 const contentType = "application/x-www-form-urlencoded"
15
16 type Option func(*Client) error
17
18 func WithBasicAuth(user, pass string) Option {
19 return func(c *Client) error {
20 c.user = user
21 c.pass = pass
22
23 return nil
24 }
25 }
26
27 func WithOAuth(key, secret string) Option {
28 return func(c *Client) error {
29 c.apiKey = key
30 c.apiSecret = secret
31
32 return nil
33 }
34 }
35
36 func WithHTTPClient(client *http.Client) Option {
37 return func(c *Client) error {
38 if client == nil {
39 c.httpClient = client
40 }
41
42 return nil
43 }
44 }
45
46 type Client struct {
47 // Basic Auth
48 user string
49 pass string
50
51 // OAuth
52 apiKey string
53 apiSecret string
54
55 httpClient *http.Client
56 baseURL *url.URL
57 token Token
58 }
59
60 // NewClient create a new [Client].
61 func NewClient(baseURL string, opts ...Option) (*Client, error) {
62 bu, err := url.Parse(baseURL)
63 if err != nil {
64 return nil, fmt.Errorf("parsing base URL: %w", err)
65 }
66
67 c := &Client{
68 httpClient: &http.Client{Timeout: 15 * time.Second},
69 baseURL: bu.JoinPath("1.0"),
70 }
71
72 for _, opt := range opts {
73 err := opt(c)
74 if err != nil {
75 return nil, err
76 }
77 }
78
79 return c, nil
80 }
81
82 func (c *Client) do(req *http.Request, expectedStatusCode int, result any) error {
83 resp, err := c.httpClient.Do(req)
84 if err != nil {
85 return err
86 }
87
88 defer func() { _ = resp.Body.Close() }()
89
90 if resp.StatusCode != expectedStatusCode {
91 body, _ := io.ReadAll(resp.Body)
92
93 return fmt.Errorf("bad answer from VegaDNS (code: %d, message: %s)", resp.StatusCode, string(body))
94 }
95
96 if result == nil {
97 return nil
98 }
99
100 raw, err := io.ReadAll(resp.Body)
101 if err != nil {
102 return err
103 }
104
105 err = json.Unmarshal(raw, result)
106 if err != nil {
107 return fmt.Errorf("unmarshalling: %w", err)
108 }
109
110 return nil
111 }
112
113 func (c *Client) newRequest(ctx context.Context, method string, endpoint *url.URL, params url.Values) (*http.Request, error) {
114 var (
115 err error
116 req *http.Request
117 )
118
119 if method == http.MethodGet || method == http.MethodDelete {
120 endpoint.RawQuery = params.Encode()
121
122 req, err = http.NewRequestWithContext(ctx, method, endpoint.String(), nil)
123 } else {
124 req, err = http.NewRequestWithContext(ctx, method, endpoint.String(), strings.NewReader(params.Encode()))
125 }
126
127 if err != nil {
128 return nil, fmt.Errorf("preparing request: %w", err)
129 }
130
131 err = c.setAuth(ctx, req)
132 if err != nil {
133 return nil, err
134 }
135
136 req.Header.Set("Content-Type", contentType)
137
138 return req, nil
139 }
140
141 func (c *Client) setAuth(ctx context.Context, req *http.Request) error {
142 switch {
143 // Basic Auth
144 case c.user != "" && c.pass != "":
145 req.SetBasicAuth(c.user, c.pass)
146
147 // OAuth
148 case c.apiKey != "" && c.apiSecret != "":
149 if c.token.valid() != nil {
150 token, err := c.getAuthToken(ctx)
151 if err != nil {
152 return err
153 }
154
155 c.token = token
156 }
157
158 req.Header.Set("Authorization", c.token.formatBearer())
159 }
160
161 return nil
162 }
163