-
Notifications
You must be signed in to change notification settings - Fork 4
/
errors.go
235 lines (187 loc) · 6.41 KB
/
errors.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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package todoist
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/pkg/errors"
)
// BaseError reports an error caused by a Todoist (sync) API request. BaseError
// will report on any status code response, therefore it is used for non-200 (OK)
// status code responses.
type BaseError struct {
Response *http.Response `json:"-"` // HTTP response that caused this error
Tag string `json:"error_tag"` // error tag
Code int `json:"error_code"` // error code
Message string `json:"error"` // error message
HTTPCode int `json:"http_code"` // error HTTP code
ErrorExtra map[string]interface{} `json:"error_extra"` // more detail on errors
}
func (e BaseError) Error() string {
return fmt.Sprintf("(%d) %s: %s", e.HTTPCode, e.Tag, e.Message)
}
// BadRequestError is used if the request was incorrect.
type BadRequestError struct {
BaseError
}
func (e BadRequestError) Error() string {
return fmt.Sprintf("(%d) %s: %s", e.HTTPCode, e.Tag, e.Message)
}
// UnauthorizedError is used if authentication is required, and has failed, or has not yet been provided.
type UnauthorizedError struct {
BaseError
}
func (e UnauthorizedError) Error() string {
return fmt.Sprintf("(%d) %s: %s", e.HTTPCode, e.Tag, e.Message)
}
// ForbiddenError is used if the request was valid, but for something that is forbidden.
type ForbiddenError struct {
BaseError
}
func (e ForbiddenError) Error() string {
return fmt.Sprintf("(%d) %s: %s", e.HTTPCode, e.Tag, e.Message)
}
// NotFoundError is used if the requested resource could not be found.
type NotFoundError struct {
BaseError
}
func (e NotFoundError) Error() string {
return fmt.Sprintf("(%d) %s: %s", e.HTTPCode, e.Tag, e.Message)
}
// TooManyRequestsError is used if the user has sent too many requests in a given amount of time.
type TooManyRequestsError struct {
BaseError
}
func (e TooManyRequestsError) Error() string {
return fmt.Sprintf("(%d) %s: %s", e.HTTPCode, e.Tag, e.Message)
}
// InternalServerError is used if the request failed due to a server error.
type InternalServerError struct {
BaseError
}
func (e InternalServerError) Error() string {
return fmt.Sprintf("(%d) %s: %s", e.HTTPCode, e.Tag, e.Message)
}
// ServiceUnavailableError is used if the server is currently unable to handle the request.
type ServiceUnavailableError struct {
BaseError
}
func (e ServiceUnavailableError) Error() string {
return fmt.Sprintf("(%d) %s: %s", e.HTTPCode, e.Tag, e.Message)
}
// SyncError reports an error caused by a Todoist (sync) API request
// with a 200 (OK) status code response, and contains an embedded BaseError.
// Todoist API docs: https://developer.todoist.com/sync/v8/?shell#response-error
type SyncError struct {
BaseError // embedded original error
ID string `json:"-"` // original command UUID
}
// checkResponseForErrors checks the API response for an error, and returns it if
// present. A response is considered an error if it has a status code not equal
// to 200 OK, or it has values in the `sync_status` field that are not equal
// to "ok".
//
// API error responses are expected to have response bodies, and a JSON response
// body that maps to SyncError.
func checkResponseForErrors(r *http.Response, v interface{}) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
// TODO: handle this nicer
return err
}
if body == nil {
return errors.New("response body is nil")
}
switch c := r.StatusCode; c {
// The request was processed successfully.
// In the Todoist API, a 200 (OK) status code means that the response is at least
// partially correct. If the response is a CommandResponse, there might be errors
// in the sync_status field, so we still need to check that field for any errors.
case http.StatusOK:
if err = json.Unmarshal(body, &v); err != nil {
// TODO: handle this nicer
return err
}
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
switch vType := v.(type) {
case CommandResponse:
cr := vType
// Range through each of the sync_status values, and map
// each non "ok" value to a SyncError struct
for cmdID, cmdResult := range cr.SyncStatus {
if cmdResult != "ok" {
// Serialize the command result back into an "unmarshallable" string
cmdResultBytes, _ := json.Marshal(cmdResult)
var syncErr SyncError
if err = json.Unmarshal(cmdResultBytes, &syncErr); err != nil {
return err
}
syncErr.ID = cmdID
return syncErr
}
}
return nil
default:
return nil
}
// The request was incorrect.
case http.StatusBadRequest:
var badRequestError BadRequestError
if err := json.Unmarshal(body, &badRequestError); err != nil {
return err
}
return badRequestError
// Authentication is required, and has failed, or has not yet been provided.
case http.StatusUnauthorized:
var unauthorizedError UnauthorizedError
if err := json.Unmarshal(body, &unauthorizedError); err != nil {
return err
}
return unauthorizedError
// The request was valid, but for something that is forbidden.
case http.StatusForbidden:
var forbiddenError ForbiddenError
if err := json.Unmarshal(body, &forbiddenError); err != nil {
return err
}
return forbiddenError
// The requested resource could not be found.
case http.StatusNotFound:
var notFoundError NotFoundError
if err := json.Unmarshal(body, ¬FoundError); err != nil {
return err
}
return notFoundError
// The user has sent too many requests in a given amount of time.
case http.StatusTooManyRequests:
var tooManyRequestsError TooManyRequestsError
if err := json.Unmarshal(body, &tooManyRequestsError); err != nil {
return err
}
return tooManyRequestsError
// The request failed due to a server error.
case http.StatusInternalServerError:
var internalServerError InternalServerError
if err := json.Unmarshal(body, &internalServerError); err != nil {
return err
}
return internalServerError
// The server is currently unable to handle the request.
case http.StatusServiceUnavailable:
var serviceUnavailableError ServiceUnavailableError
if err := json.Unmarshal(body, &serviceUnavailableError); err != nil {
return err
}
return serviceUnavailableError
default:
unknownError := BaseError{
Response: r,
Tag: "UNKNOWN_ERROR",
Message: "Unknown error occured.",
HTTPCode: r.StatusCode,
ErrorExtra: map[string]interface{}{},
}
return unknownError
}
}