-
Notifications
You must be signed in to change notification settings - Fork 247
/
errors.go
114 lines (99 loc) · 3.13 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
package anaconda
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"time"
)
const (
//Error code defintions match the Twitter documentation
//https://developer.twitter.com/en/docs/basics/response-codes
TwitterErrorCouldNotAuthenticate = 32
TwitterErrorDoesNotExist = 34
TwitterErrorAccountSuspended = 64
TwitterErrorApi1Deprecation = 68 //This should never be needed
TwitterErrorRateLimitExceeded = 88
TwitterErrorInvalidToken = 89
TwitterErrorOverCapacity = 130
TwitterErrorInternalError = 131
TwitterErrorCouldNotAuthenticateYou = 135
TwitterErrorStatusIsADuplicate = 187
TwitterErrorBadAuthenticationData = 215
TwitterErrorUserMustVerifyLogin = 231
// Undocumented by Twitter, but may be returned instead of 34
TwitterErrorDoesNotExist2 = 144
)
type ApiError struct {
StatusCode int
Header http.Header
Body string
Decoded TwitterErrorResponse
URL *url.URL
}
func newApiError(resp *http.Response) *ApiError {
// TODO don't ignore this error
// TODO don't use ReadAll
p, _ := ioutil.ReadAll(resp.Body)
var twitterErrorResp TwitterErrorResponse
_ = json.Unmarshal(p, &twitterErrorResp)
return &ApiError{
StatusCode: resp.StatusCode,
Header: resp.Header,
Body: string(p),
Decoded: twitterErrorResp,
URL: resp.Request.URL,
}
}
// ApiError supports the error interface
func (aerr ApiError) Error() string {
return fmt.Sprintf("Get %s returned status %d, %s", aerr.URL, aerr.StatusCode, aerr.Body)
}
// Check to see if an error is a Rate Limiting error. If so, find the next available window in the header.
// Use like so:
//
// if aerr, ok := err.(*ApiError); ok {
// if isRateLimitError, nextWindow := aerr.RateLimitCheck(); isRateLimitError {
// <-time.After(nextWindow.Sub(time.Now()))
// }
// }
//
func (aerr *ApiError) RateLimitCheck() (isRateLimitError bool, nextWindow time.Time) {
// TODO check for error code 130, which also signifies a rate limit
if aerr.StatusCode == 429 {
if reset := aerr.Header.Get("X-Rate-Limit-Reset"); reset != "" {
if resetUnix, err := strconv.ParseInt(reset, 10, 64); err == nil {
resetTime := time.Unix(resetUnix, 0)
// Reject any time greater than an hour away
if resetTime.Sub(time.Now()) > time.Hour {
return true, time.Now().Add(15 * time.Minute)
}
return true, resetTime
}
}
}
return false, time.Time{}
}
//TwitterErrorResponse has an array of Twitter error messages
//It satisfies the "error" interface
//For the most part, Twitter seems to return only a single error message
//Currently, we assume that this always contains exactly one error message
type TwitterErrorResponse struct {
Errors []TwitterError `json:"errors"`
}
func (tr TwitterErrorResponse) First() error {
return tr.Errors[0]
}
func (tr TwitterErrorResponse) Error() string {
return tr.Errors[0].Message
}
//TwitterError represents a single Twitter error messages/code pair
type TwitterError struct {
Message string `json:"message"`
Code int `json:"code"`
}
func (te TwitterError) Error() string {
return te.Message
}