-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
195 lines (176 loc) · 4.72 KB
/
client.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
)
const (
defaultHeaderToken = "Authorization"
defaultTokenPrefix = "Bearer"
)
// Client is a way to connect to 3rd party API servers.
type Client struct {
apiEndPoint string
apiToken string
headerToken string // What header should we use to send the token (eg, "Authorization")
tokenPrefix string // What to send before the token (eg, "Bearer", "Basic"...)
paramToken string // What query parameter should we use to send the token (eg, "private_token")
disallowUnknownFields bool
unixSocket string
}
// NewClient creates a new Client ready to use.
func NewClient(apiEndPoint string) *Client {
return &Client{apiEndPoint: apiEndPoint}
}
// WithToken adds a token to a Client.
func (c *Client) WithToken(tk string) *Client {
c2 := new(Client)
*c2 = *c
c2.apiToken = tk
return c2
}
// WithHeaderToken specifies which Header line to use when sending a token.
func (c *Client) WithHeaderToken(ht string) *Client {
c2 := new(Client)
*c2 = *c
c2.headerToken = ht
return c2
}
// WithTokenPrefix adds an optional prefix to the token in the Header line.
func (c *Client) WithTokenPrefix(tp string) *Client {
c2 := new(Client)
*c2 = *c
c2.tokenPrefix = tp
return c2
}
// WithParamToken specifies which query parameter to use when sending a token.
func (c *Client) WithParamToken(pt string) *Client {
c2 := new(Client)
*c2 = *c
c2.paramToken = pt
return c2
}
// DisallowUnknownFields causes the JSON decoder to return an error when the
// destination is a struct and the input contains object keys which do not
// match any non-ignored, exported fields in the destination.
func (c *Client) DisallowUnknownFields() *Client {
c2 := new(Client)
*c2 = *c
c2.disallowUnknownFields = true
return c2
}
// WithUnixSocket causes the client to connect through this Unix domain socket,
// instead of using the network.
func (c *Client) WithUnixSocket(socket string) *Client {
c2 := new(Client)
*c2 = *c
c2.unixSocket = socket
return c2
}
// Request makes a HTTP request to the API.
// If data is not a []byte, it will be encoding as a JSON object.
func (c *Client) Request(method, URL string, data any, dest any) error {
var err error
var body io.Reader
if data != nil {
var b []byte
switch d := data.(type) {
case []byte:
b = d
default:
b, err = json.Marshal(data)
if err != nil {
return err
}
}
body = bytes.NewBuffer(b)
}
// make headerToken and tokenPrefix the default values if needed, but only for this call.
headerToken, tokenPrefix := c.headerToken, c.tokenPrefix
if c.apiToken != "" && headerToken == "" && c.paramToken == "" {
headerToken = defaultHeaderToken
if tokenPrefix == "" {
tokenPrefix = defaultTokenPrefix
}
}
u, err := url.Parse(c.apiEndPoint)
if err != nil {
return err
}
u = u.JoinPath(URL)
if c.apiToken != "" && c.paramToken != "" {
v, err := url.ParseQuery(u.RawQuery)
if err != nil {
return err
}
v.Add(c.paramToken, c.apiToken)
u.RawQuery = v.Encode()
}
req, err := http.NewRequest(method, u.String(), body)
if err != nil {
return err
}
if c.apiToken != "" && headerToken != "" {
token := c.apiToken
if tokenPrefix != "" {
token = tokenPrefix + " " + token
}
req.Header.Set(headerToken, token)
}
client := &http.Client{}
if c.unixSocket != "" {
client.Transport = &http.Transport{
Dial: func(proto, addr string) (conn net.Conn, err error) {
return net.Dial("unix", c.unixSocket)
},
}
}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("api: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
var foo struct {
Error string
}
decoder := json.NewDecoder(resp.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&foo); err != nil {
return fmt.Errorf("%s", resp.Status)
}
return fmt.Errorf("%s: %s", resp.Status, foo.Error)
}
if dest == nil {
var foo any
dest = &foo
}
decoder := json.NewDecoder(resp.Body)
if c.disallowUnknownFields {
decoder.DisallowUnknownFields()
}
if err := decoder.Decode(dest); err != nil {
return err
}
return nil
}
// Get makes a HTTP GET request to the API.
func (c *Client) Get(url string, dest any) error {
return c.Request("GET", url, nil, dest)
}
// Post makes a HTTP POST request to the API.
func (c *Client) Post(url string, data any, dest any) error {
return c.Request("POST", url, data, dest)
}
// Put makes a HTTP PUT request to the API.
func (c *Client) Put(url string, data any, dest any) error {
return c.Request("PUT", url, data, dest)
}
// Delete makes a HTTP DELETE request to the API.
func (c *Client) Delete(url string, dest any) error {
return c.Request("DELETE", url, []byte(nil), dest)
}