Skip to content

Commit 2a7f41f

Browse files
authored
Merge pull request #274 from flimzy/RetryAfter
Expose RetryAfter value in error
2 parents 80fbf5a + beecb19 commit 2a7f41f

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

spotify.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ type Error struct {
148148
Message string `json:"message"`
149149
// The HTTP status code.
150150
Status int `json:"status"`
151+
// RetryAfter contains the time before which client should not retry a
152+
// rate-limited request, calculated from the Retry-After header, when present.
153+
RetryAfter time.Time `json:"-"`
151154
}
152155

153156
func (e Error) Error() string {
@@ -196,6 +199,9 @@ func decodeError(resp *http.Response) error {
196199
e.E.Message = fmt.Sprintf("spotify: unexpected HTTP %d: %s (empty error)",
197200
resp.StatusCode, http.StatusText(resp.StatusCode))
198201
}
202+
if retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After")); retryAfter != 0 {
203+
e.E.RetryAfter = time.Now().Add(time.Duration(retryAfter) * time.Second)
204+
}
199205

200206
return e.E
201207
}

spotify_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package spotify
22

33
import (
44
"context"
5+
"errors"
56
"io"
67
"net/http"
78
"net/http/httptest"
89
"os"
10+
"strconv"
911
"strings"
1012
"testing"
1113
"time"
@@ -105,6 +107,52 @@ func TestNewReleasesRateLimitExceeded(t *testing.T) {
105107
}
106108
}
107109

110+
func TestRateLimitExceededReportsRetryAfter(t *testing.T) {
111+
t.Parallel()
112+
const retryAfter = 2
113+
114+
handlers := []http.HandlerFunc{
115+
// first attempt fails
116+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
117+
w.Header().Set("Retry-After", strconv.Itoa(retryAfter))
118+
w.WriteHeader(rateLimitExceededStatusCode)
119+
_, _ = io.WriteString(w, `{ "error": { "message": "slow down", "status": 429 } }`)
120+
}),
121+
// next attempt succeeds
122+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
123+
f, err := os.Open("test_data/new_releases.txt")
124+
if err != nil {
125+
t.Fatal(err)
126+
}
127+
defer f.Close()
128+
_, err = io.Copy(w, f)
129+
if err != nil {
130+
t.Fatal(err)
131+
}
132+
}),
133+
}
134+
135+
i := 0
136+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
137+
handlers[i](w, r)
138+
i++
139+
}))
140+
defer server.Close()
141+
142+
client := &Client{http: http.DefaultClient, baseURL: server.URL + "/"}
143+
_, err := client.NewReleases(context.Background())
144+
if err == nil {
145+
t.Fatal("expected an error")
146+
}
147+
var spotifyError Error
148+
if !errors.As(err, &spotifyError) {
149+
t.Fatalf("expected a spotify error, got %T", err)
150+
}
151+
if retryAfter*time.Second-time.Until(spotifyError.RetryAfter) > time.Second {
152+
t.Error("expected RetryAfter value")
153+
}
154+
}
155+
108156
func TestClient_Token(t *testing.T) {
109157
// oauth setup for valid test token
110158
config := oauth2.Config{

0 commit comments

Comments
 (0)