-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdesec.go
156 lines (123 loc) · 3.53 KB
/
desec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package desec
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"github.com/hashicorp/go-retryablehttp"
)
const defaultBaseURL = "https://desec.io/api/v1/"
type httpDoer interface {
Do(req *http.Request) (*http.Response, error)
}
type service struct {
client *Client
}
// ClientOptions the options of the Client.
type ClientOptions struct {
// HTTPClient HTTP client used to communicate with the API.
HTTPClient *http.Client
// Maximum number of retries
RetryMax int
// Customer logger instance. Can be either Logger or LeveledLogger
Logger interface{}
}
// NewDefaultClientOptions creates a new ClientOptions with default values.
func NewDefaultClientOptions() ClientOptions {
return ClientOptions{
HTTPClient: http.DefaultClient,
RetryMax: 5,
Logger: nil,
}
}
// Client deSEC API client.
type Client struct {
// Base URL for API requests.
BaseURL string
httpClient httpDoer
token string
common service // Reuse a single struct instead of allocating one for each service on the heap.
// Services used for talking to different parts of the deSEC API.
Account *AccountService
Tokens *TokensService
TokenPolicies *TokenPoliciesService
Records *RecordsService
Domains *DomainsService
}
// New creates a new Client.
func New(token string, opts ClientOptions) *Client {
// https://github.com/desec-io/desec-stack/blob/main/docs/rate-limits.rst
retryClient := retryablehttp.NewClient()
retryClient.RetryMax = opts.RetryMax
retryClient.HTTPClient = opts.HTTPClient
retryClient.Logger = opts.Logger
client := &Client{
httpClient: retryClient.StandardClient(),
BaseURL: defaultBaseURL,
token: token,
}
client.common.client = client
client.Account = (*AccountService)(&client.common)
client.Tokens = (*TokensService)(&client.common)
client.TokenPolicies = (*TokenPoliciesService)(&client.common)
client.Records = (*RecordsService)(&client.common)
client.Domains = (*DomainsService)(&client.common)
return client
}
func (c *Client) newRequest(ctx context.Context, method string, endpoint fmt.Stringer, reqBody interface{}) (*http.Request, error) {
buf := new(bytes.Buffer)
if reqBody != nil {
err := json.NewEncoder(buf).Encode(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
}
req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if c.token != "" {
req.Header.Set("Authorization", fmt.Sprintf("Token %s", c.token))
}
return req, nil
}
func (c *Client) createEndpoint(parts ...string) (*url.URL, error) {
base, err := url.Parse(c.BaseURL)
if err != nil {
return nil, err
}
endpoint := base.JoinPath(parts...)
endpoint.Path += "/"
return endpoint, nil
}
func handleResponse(resp *http.Response, respData interface{}) error {
body, err := io.ReadAll(resp.Body)
if err != nil {
return &APIError{
StatusCode: resp.StatusCode,
err: fmt.Errorf("failed to read response body: %w", err),
}
}
if len(body) == 0 {
return nil
}
err = json.Unmarshal(body, respData)
if err != nil {
return fmt.Errorf("failed to umarshal response body: %w", err)
}
return nil
}
func handleError(resp *http.Response) error {
switch resp.StatusCode {
case http.StatusNotFound:
return readError(resp, &NotFoundError{})
default:
return readRawError(resp)
}
}
// Pointer creates pointer of string.
func Pointer[T string](v T) *T { return &v }