From 5f92f5d07f9907ce411399286d777471a5f1e803 Mon Sep 17 00:00:00 2001 From: Will Norris Date: Wed, 3 Jul 2013 15:30:32 -0700 Subject: [PATCH] add support for GitHub's new rate reset Because the reset date is expressed as a Unix timestamp (rather than an ISO 8601 timstamp), we can't directly unmarshal the JSON response. Instead we have to do a little weird indirection to unmarshal it as an int first, and then convert it into a proper Time object. --- github/github.go | 31 +++++++++++++++++++++++++++---- github/github_test.go | 24 +++++++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/github/github.go b/github/github.go index 98f93d5..a6b994d 100644 --- a/github/github.go +++ b/github/github.go @@ -53,6 +53,7 @@ import ( "net/http" "net/url" "strconv" + "time" ) const ( @@ -62,6 +63,7 @@ const ( headerRateLimit = "X-RateLimit-Limit" headerRateRemaining = "X-RateLimit-Remaining" + headerRateReset = "X-RateLimit-Reset" ) // A Client manages communication with the GitHub API. @@ -168,6 +170,11 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { if remaining := resp.Header.Get(headerRateRemaining); remaining != "" { c.Rate.Remaining, _ = strconv.Atoi(remaining) } + if reset := resp.Header.Get(headerRateReset); reset != "" { + if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 { + c.Rate.Reset = time.Unix(v, 0) + } + } err = CheckResponse(resp) if err != nil { @@ -261,7 +268,11 @@ func parseBoolResponse(err error) (bool, error) { // API response wrapper to a rate limit request. type rateResponse struct { - Rate *Rate `json:rate` + Rate struct { + Limit int `json:limit` + Remaining int `json:remaining` + Reset int64 `json:reset` + } `json:rate` } // Rate represents the rate limit for the current client. Unauthenticated @@ -269,10 +280,13 @@ type rateResponse struct { // 5,000 per hour. type Rate struct { // The number of requests per hour the client is currently limited to. - Limit int `json:limit` + Limit int // The number of remaining requests the client can make this hour. - Remaining int `json:remaining` + Remaining int + + // The time at which the current rate limit will reset. + Reset time.Time } // RateLimit returns the rate limit for the current client. @@ -284,7 +298,16 @@ func (c *Client) RateLimit() (*Rate, error) { response := new(rateResponse) _, err = c.Do(req, response) - return response.Rate, err + if err != nil { + return nil, err + } + + rate := &Rate{ + Limit: response.Rate.Limit, + Remaining: response.Rate.Remaining, + Reset: time.Unix(response.Rate.Reset, 0), + } + return rate, err } /* diff --git a/github/github_test.go b/github/github_test.go index bbe72eb..5cb3fe1 100644 --- a/github/github_test.go +++ b/github/github_test.go @@ -15,6 +15,7 @@ import ( "reflect" "strings" "testing" + "time" ) var ( @@ -199,6 +200,7 @@ func TestDo_rateLimit(t *testing.T) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Add(headerRateLimit, "60") w.Header().Add(headerRateRemaining, "59") + w.Header().Add(headerRateReset, "1372700873") }) var want int @@ -207,7 +209,10 @@ func TestDo_rateLimit(t *testing.T) { t.Errorf("Client rate limit = %v, want %v", client.Rate.Limit, want) } if want = 0; client.Rate.Limit != want { - t.Errorf("Client rate remaining, got %v", client.Rate.Remaining, want) + t.Errorf("Client rate remaining = %v, got %v", client.Rate.Remaining, want) + } + if !client.Rate.Reset.IsZero() { + t.Errorf("Client rate reset not initialized to zero value") } req, _ := client.NewRequest("GET", "/", nil) @@ -219,6 +224,10 @@ func TestDo_rateLimit(t *testing.T) { if want = 59; client.Rate.Remaining != want { t.Errorf("Client rate remaining = %v, want %v", client.Rate.Remaining, want) } + reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC) + if client.Rate.Reset.UTC() != reset { + t.Errorf("Client rate reset = %v, want %v", client.Rate.Reset, reset) + } } func TestDo_rateLimit_errorResponse(t *testing.T) { @@ -228,6 +237,7 @@ func TestDo_rateLimit_errorResponse(t *testing.T) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Add(headerRateLimit, "60") w.Header().Add(headerRateRemaining, "59") + w.Header().Add(headerRateReset, "1372700873") http.Error(w, "Bad Request", 400) }) @@ -242,6 +252,10 @@ func TestDo_rateLimit_errorResponse(t *testing.T) { if want = 59; client.Rate.Remaining != want { t.Errorf("Client rate remaining = %v, want %v", client.Rate.Remaining, want) } + reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC) + if client.Rate.Reset.UTC() != reset { + t.Errorf("Client rate reset = %v, want %v", client.Rate.Reset, reset) + } } func TestCheckResponse(t *testing.T) { @@ -350,7 +364,7 @@ func TestRateLimit(t *testing.T) { if m := "GET"; m != r.Method { t.Errorf("Request method = %v, want %v", r.Method, m) } - fmt.Fprint(w, `{"rate":{"limit":2,"remaining":1}}`) + fmt.Fprint(w, `{"rate":{"limit":2,"remaining":1,"reset":1372700873}}`) }) rate, err := client.RateLimit() @@ -358,7 +372,11 @@ func TestRateLimit(t *testing.T) { t.Errorf("Rate limit returned error: %v", err) } - want := &Rate{Limit: 2, Remaining: 1} + want := &Rate{ + Limit: 2, + Remaining: 1, + Reset: time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC).Local(), + } if !reflect.DeepEqual(rate, want) { t.Errorf("RateLimit returned %+v, want %+v", rate, want) }