forked from deta/deta-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
200 lines (176 loc) · 4.04 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
196
197
198
199
200
package deta
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
)
var (
// ErrBadRequest bad request
ErrBadRequest = errors.New("bad request")
// ErrUnauthorized aunauthorized
ErrUnauthorized = errors.New("unauthorized")
// ErrNotFound not found
ErrNotFound = errors.New("not found")
// ErrConflict conflict
ErrConflict = errors.New("conflict")
// ErrInternalServerError internal server error
ErrInternalServerError = errors.New("internal server error")
// internal error
// invalid auth type
errInvalidAuthType = errors.New("invalid auth type")
)
// auth info for requests
type authInfo struct {
authType string // auth type
headerKey string // header key
headerValue string // header value
}
// client that talks with deta apis
type detaClient struct {
rootEndpoint string
client *http.Client
authInfo *authInfo
}
// returns a pointer to a new deta client
func newDetaClient(rootEndpoint string, ai *authInfo) *detaClient {
// only api keys auth for now
/*
if i.Auth.Type != "api-key" {
return nil, errInvalidAuthType
}
*/
return &detaClient{
rootEndpoint: rootEndpoint,
authInfo: ai,
client: &http.Client{},
}
}
// error response
type errorResp struct {
StatusCode int `json:"-"`
Errors []string `json:"errors"`
}
// returns appropriate errors from the error response
func (c *detaClient) errorRespToErr(e *errorResp) error {
var errorMsg string
if len(e.Errors) >= 1 {
errorMsg = e.Errors[0]
}
switch e.StatusCode {
case 400:
return fmt.Errorf("%w: %s", ErrBadRequest, errorMsg)
case 401:
// does not require wrapping
return ErrUnauthorized
case 404:
// does not require wrapping
return ErrNotFound
case 409:
return fmt.Errorf("%w: %s", ErrConflict, errorMsg)
default:
// default internal server error for other error status codes
// does not require wrapping
return ErrInternalServerError
}
}
// input to request method
type requestInput struct {
Path string
Method string
Headers map[string]string
QueryParams map[string]string
Body interface{}
RawBody []byte
ContentType string
Read bool
}
// output of request function
type requestOutput struct {
Status int
Body []byte
RawBody io.ReadCloser
Header http.Header
Error *errorResp
}
func (c *detaClient) request(i *requestInput) (*requestOutput, error) {
marshalled := []byte("")
if i.Body != nil {
// set default content-type to application/json
if i.ContentType == "" {
i.ContentType = "application/json"
}
var err error
marshalled, err = json.Marshal(&i.Body)
if err != nil {
return nil, err
}
}
if i.RawBody != nil {
marshalled = i.RawBody
}
url := fmt.Sprintf("%s%s", c.rootEndpoint, i.Path)
req, err := http.NewRequest(i.Method, url, bytes.NewBuffer(marshalled))
if err != nil {
return nil, err
}
// headers
if i.ContentType != "" {
req.Header.Set("Content-type", i.ContentType)
}
for k, v := range i.Headers {
req.Header.Set(k, v)
}
// auth
if c.authInfo != nil {
// set auth value in specified header key in the request headers
req.Header.Set(c.authInfo.headerKey, c.authInfo.headerValue)
}
// query params
q := req.URL.Query()
for k, v := range i.QueryParams {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
// send the request
res, err := c.client.Do(req)
if err != nil {
return nil, err
}
// request output
o := &requestOutput{
Status: res.StatusCode,
Header: res.Header,
}
// errors
var er errorResp
if i.Read {
if res.StatusCode >= 200 && res.StatusCode <= 299 {
o.RawBody = res.Body
return o, nil
}
}
if !i.Read {
defer res.Body.Close()
b, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if res.StatusCode >= 200 && res.StatusCode <= 299 {
o.Body = b
return o, nil
}
// json unmarshal json error responses
if strings.Contains(res.Header.Get("Content-Type"), "application/json") {
if err = json.Unmarshal(b, &er); err != nil {
return nil, err
}
}
}
er.StatusCode = res.StatusCode
return nil, c.errorRespToErr(&er)
}