-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathhttpClient.go
141 lines (109 loc) · 2.9 KB
/
httpClient.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
package api
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/url"
"github.com/lucassabreu/clockify-cli/api/dto"
"github.com/pkg/errors"
)
// QueryAppender an interface to identify if the parameters should be sent through the query or body
type QueryAppender interface {
AppendToQuery(*url.URL) *url.URL
}
// ErrorNotFound Not Found
var ErrorNotFound = dto.Error{Message: "Nothing was found", Code: 404}
// ErrorForbidden Forbidden
var ErrorForbidden = dto.Error{Message: "Forbidden", Code: 403}
// ErrorTooManyRequests Too Many Requests
var ErrorTooManyRequests = dto.Error{Message: "Too Many Requests", Code: 429}
type transport struct {
apiKey string
next http.RoundTripper
}
func (t transport) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Set("X-Api-Key", t.apiKey)
return t.next.RoundTrip(r)
}
// NewRequest to be used in Client
func (c *client) NewRequest(method, uri string, body interface{}) (*http.Request, error) {
u, err := c.baseURL.Parse(c.baseURL.Path + "/" + uri)
if err != nil {
return nil, err
}
if qa, ok := body.(QueryAppender); ok {
u = qa.AppendToQuery(u)
}
if method == "GET" {
body = nil
}
var buf io.ReadWriter
if body != nil {
buf = new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
c.infof("request body: %s", buf.(*bytes.Buffer))
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Accept", "application/json")
return req, nil
}
// Do executes a http.Request inside the Clockify's Client
func (c *client) Do(
req *http.Request, v interface{}, name string) (*http.Response, error) {
<-c.requestTickets
r, err := c.Client.Do(req)
if err != nil {
return r, err
}
defer r.Body.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, r.Body)
if err != nil {
return nil, errors.WithStack(err)
}
if c.debugLogger != nil {
c.debugf("name: %s, method: %s, url: %s, status: %d, response: \"%s\"",
name, req.Method, req.URL.String(), r.StatusCode, buf)
} else {
c.infof("name: %s, method: %s, url: %s, status: %d",
name, req.Method, req.URL.String(), r.StatusCode)
}
decoder := json.NewDecoder(buf)
if r.StatusCode < 200 || r.StatusCode > 300 {
var apiErr dto.Error
err = decoder.Decode(&apiErr)
if err != nil && err != io.EOF {
return r, errors.WithStack(err)
}
if r.StatusCode == 404 && apiErr.Message == "" {
apiErr = ErrorNotFound
}
if r.StatusCode == 403 && apiErr.Message == "" {
apiErr = ErrorForbidden
}
if r.StatusCode == 429 && apiErr.Message == "" {
apiErr = ErrorTooManyRequests
}
if apiErr.Message == "" {
apiErr.Message = "No response"
}
return r, errors.WithStack(apiErr)
}
if v == nil {
return r, nil
}
if buf.Len() == 0 {
return r, nil
}
return r, errors.WithStack(decoder.Decode(v))
}