-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
172 lines (148 loc) · 4.5 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
package gotinyclient
import (
"fmt"
"io/ioutil"
"net/http"
"path"
gotiny "github.com/chrisvdg/gotiny/backend"
"github.com/pkg/errors"
)
var (
// ErrEntryNotFound represents an error where a tiny url entry could not be found
ErrEntryNotFound = errors.New("tiny URL entry not found")
// ErrUnauthorized represents an error where a request failed to authorize
ErrUnauthorized = errors.New("request unauthorized")
// ErrReadUnauthorized represents an error where a request with read permissions failed to authorize
ErrReadUnauthorized = errors.New("unauthorized, read access token may be invalid")
// ErrWriteUnauthorized represents an error where a request with write permissions failed to authorize
ErrWriteUnauthorized = errors.New("unauthorized, write access token may be invalid")
// ErrBadRequest represents an error where a request was made with bad parameters
ErrBadRequest = errors.New("bad request")
// UnauthorizedErrors is a list of errors that represent unauthorzied errors
UnauthorizedErrors = []error{ErrUnauthorized, ErrReadUnauthorized, ErrWriteUnauthorized}
// ErrUnexpectedCode represents an error where a request returned an unexpected error
ErrUnexpectedCode = errors.New("recieved unexpected http code")
)
const (
authHeader = "Authorization"
bearerPrefix = "bearer"
apiPath = "api"
)
var (
successCodes = []int{
http.StatusOK,
http.StatusCreated,
http.StatusNoContent,
}
)
// TinyURL represents a tiny url entry
type TinyURL gotiny.TinyURL
// JSONTime represents a json parsable time struct
type JSONTime gotiny.JSONTime
// New returns a new gotiny client
func New(baseURL, readToken, writeToken string) (*Client, error) {
return &Client{
baseURL: baseURL,
readToken: readToken,
writeToken: writeToken,
http: &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
},
}, nil
}
// Client represents a gotiny client
type Client struct {
baseURL string
readToken string
writeToken string
http *http.Client
}
// getReqURL returns the full API call address using the base URL
// and appending the provided path
func (c *Client) getReqURL(resourcePath string) string {
return fmt.Sprintf("%s/%s", c.baseURL, path.Join(apiPath, resourcePath))
}
func (c *Client) getReqURLWithID(resourcePath, resourceID string) string {
return c.getReqURL(path.Join(resourcePath, resourceID))
}
// do executed the provided http request and returns the response with read body
func (c *Client) do(req *http.Request) (*response, error) {
if req.Method == "POST" {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
}
resp, err := c.http.Do(req)
if err != nil {
return nil, errors.Wrap(err, "failed to send request to gotiny server")
}
defer resp.Body.Close()
r := &response{}
r.body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "failed to read response body")
}
r.httpResp = resp
return r, nil
}
// checkDefaultErrors default response error check
func (c *Client) checkDefaultErrors(res *response, req *http.Request, expectedCodes []int, isResource bool) error {
switch res.httpResp.StatusCode {
case http.StatusUnauthorized:
return ErrUnauthorized
case http.StatusNotFound:
if isResource {
return ErrEntryNotFound
}
return errors.Errorf("Path %s not found", req.URL.String())
case http.StatusBadRequest:
return errors.Wrap(ErrBadRequest, string(res.body))
default:
if len(expectedCodes) == 0 {
expectedCodes = successCodes
}
if !isExpectedCode(expectedCodes, res.httpResp.StatusCode) {
return errors.Wrap(ErrUnexpectedCode, string(res.body))
}
}
return nil
}
// addAuthHeader adds authorization header if configured
func (c *Client) addAuthHeader(auth authType, req *http.Request) {
switch auth {
case authRead:
if c.readToken != "" {
req.Header.Add(authHeader, fmt.Sprintf("%s %s", bearerPrefix, c.readToken))
}
case authWrite:
if c.writeToken != "" {
req.Header.Add(authHeader, fmt.Sprintf("%s %s", bearerPrefix, c.writeToken))
}
}
}
type authType int
const (
authRead authType = iota + 1
authWrite
)
type response struct {
httpResp *http.Response
body []byte
}
// IsUnauthorized checks wether provided error is an authorized error
func IsUnauthorized(err error) bool {
for _, r := range UnauthorizedErrors {
if errors.Cause(err) == r {
return true
}
}
return false
}
func isExpectedCode(expCodes []int, code int) bool {
for _, c := range expCodes {
if c == code {
return true
}
}
return false
}