diff --git a/twitch/http.go b/twitch/http.go new file mode 100644 index 0000000..4ba6c8a --- /dev/null +++ b/twitch/http.go @@ -0,0 +1,47 @@ +package twitch + +import ( + "fmt" +) + +type httpError struct { + body []byte + code int + err error +} + +var errAnyHTTPError = newHTTPError(0, nil, nil) + +func newHTTPError(status int, body []byte, wrappedErr error) httpError { + return httpError{ + body: body, + code: status, + err: wrappedErr, + } +} + +func (h httpError) Error() string { + selfE := fmt.Sprintf("unexpected status %d", h.code) + if h.body != nil { + selfE = fmt.Sprintf("%s (%s)", selfE, h.body) + } + + if h.err == nil { + return selfE + } + + return fmt.Sprintf("%s: %s", selfE, h.err) +} + +func (h httpError) Is(target error) bool { + ht, ok := target.(httpError) + if !ok { + return false + } + + return ht.code == 0 || ht.code == h.code +} + +func (h httpError) Unwrap() error { + return h.err +} diff --git a/twitch/twitch.go b/twitch/twitch.go index 748dd36..21bc704 100644 --- a/twitch/twitch.go +++ b/twitch/twitch.go @@ -529,22 +529,30 @@ func (c *Client) RefreshToken() error { var resp OAuthTokenResponse - if err := c.request(clientRequestOpts{ + err := c.request(clientRequestOpts{ AuthType: authTypeUnauthorized, Context: context.Background(), Method: http.MethodPost, OKStatus: http.StatusOK, Out: &resp, URL: fmt.Sprintf("https://id.twitch.tv/oauth2/token?%s", params.Encode()), - }); err != nil { + }) + switch { + case err == nil: + // That's fine, just continue + + case errors.Is(err, errAnyHTTPError): // Retried refresh failed, wipe tokens + log.WithError(err).Warning("resetting tokens after refresh-failure") c.UpdateToken("", "") if c.tokenUpdateHook != nil { if herr := c.tokenUpdateHook("", ""); herr != nil { log.WithError(err).Error("Unable to store token wipe after refresh failure") } } + return errors.Wrap(err, "executing request") + default: return errors.Wrap(err, "executing request") } @@ -793,9 +801,9 @@ func (c *Client) request(opts clientRequestOpts) error { if opts.OKStatus != 0 && resp.StatusCode != opts.OKStatus { body, err := ioutil.ReadAll(resp.Body) if err != nil { - return errors.Wrapf(err, "unexpected status %d and cannot read body", resp.StatusCode) + return newHTTPError(resp.StatusCode, nil, err) } - return errors.Errorf("unexpected status %d: %s", resp.StatusCode, body) + return newHTTPError(resp.StatusCode, body, nil) } if opts.Out == nil {