errors.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. package anaconda
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "net/url"
  8. "strconv"
  9. "time"
  10. )
  11. const (
  12. //Error code defintions match the Twitter documentation
  13. //https://developer.twitter.com/en/docs/basics/response-codes
  14. TwitterErrorCouldNotAuthenticate = 32
  15. TwitterErrorDoesNotExist = 34
  16. TwitterErrorAccountSuspended = 64
  17. TwitterErrorApi1Deprecation = 68 //This should never be needed
  18. TwitterErrorRateLimitExceeded = 88
  19. TwitterErrorInvalidToken = 89
  20. TwitterErrorOverCapacity = 130
  21. TwitterErrorInternalError = 131
  22. TwitterErrorCouldNotAuthenticateYou = 135
  23. TwitterErrorStatusIsADuplicate = 187
  24. TwitterErrorBadAuthenticationData = 215
  25. TwitterErrorUserMustVerifyLogin = 231
  26. // Undocumented by Twitter, but may be returned instead of 34
  27. TwitterErrorDoesNotExist2 = 144
  28. )
  29. type ApiError struct {
  30. StatusCode int
  31. Header http.Header
  32. Body string
  33. Decoded TwitterErrorResponse
  34. URL *url.URL
  35. }
  36. func newApiError(resp *http.Response) *ApiError {
  37. // TODO don't ignore this error
  38. // TODO don't use ReadAll
  39. p, _ := ioutil.ReadAll(resp.Body)
  40. var twitterErrorResp TwitterErrorResponse
  41. _ = json.Unmarshal(p, &twitterErrorResp)
  42. return &ApiError{
  43. StatusCode: resp.StatusCode,
  44. Header: resp.Header,
  45. Body: string(p),
  46. Decoded: twitterErrorResp,
  47. URL: resp.Request.URL,
  48. }
  49. }
  50. // ApiError supports the error interface
  51. func (aerr ApiError) Error() string {
  52. return fmt.Sprintf("Get %s returned status %d, %s", aerr.URL, aerr.StatusCode, aerr.Body)
  53. }
  54. // Check to see if an error is a Rate Limiting error. If so, find the next available window in the header.
  55. // Use like so:
  56. //
  57. // if aerr, ok := err.(*ApiError); ok {
  58. // if isRateLimitError, nextWindow := aerr.RateLimitCheck(); isRateLimitError {
  59. // <-time.After(nextWindow.Sub(time.Now()))
  60. // }
  61. // }
  62. //
  63. func (aerr *ApiError) RateLimitCheck() (isRateLimitError bool, nextWindow time.Time) {
  64. // TODO check for error code 130, which also signifies a rate limit
  65. if aerr.StatusCode == 429 {
  66. if reset := aerr.Header.Get("X-Rate-Limit-Reset"); reset != "" {
  67. if resetUnix, err := strconv.ParseInt(reset, 10, 64); err == nil {
  68. resetTime := time.Unix(resetUnix, 0)
  69. // Reject any time greater than an hour away
  70. if resetTime.Sub(time.Now()) > time.Hour {
  71. return true, time.Now().Add(15 * time.Minute)
  72. }
  73. return true, resetTime
  74. }
  75. }
  76. }
  77. return false, time.Time{}
  78. }
  79. //TwitterErrorResponse has an array of Twitter error messages
  80. //It satisfies the "error" interface
  81. //For the most part, Twitter seems to return only a single error message
  82. //Currently, we assume that this always contains exactly one error message
  83. type TwitterErrorResponse struct {
  84. Errors []TwitterError `json:"errors"`
  85. }
  86. func (tr TwitterErrorResponse) First() error {
  87. return tr.Errors[0]
  88. }
  89. func (tr TwitterErrorResponse) Error() string {
  90. return tr.Errors[0].Message
  91. }
  92. //TwitterError represents a single Twitter error messages/code pair
  93. type TwitterError struct {
  94. Message string `json:"message"`
  95. Code int `json:"code"`
  96. }
  97. func (te TwitterError) Error() string {
  98. return te.Message
  99. }