diff --git a/github/github.go b/github/github.go index 957ac0c7ff8..0a32a53c447 100644 --- a/github/github.go +++ b/github/github.go @@ -512,16 +512,15 @@ func parseRate(r *http.Response) Rate { return rate } -// Do sends an API request and returns the API response. The API response is -// JSON decoded and stored in the value pointed to by v, or returned as an -// error if an API error has occurred. If v implements the io.Writer -// interface, the raw response body will be written to v, without attempting to -// first decode it. If rate limit is exceeded and reset time is in the future, -// Do returns *RateLimitError immediately without making a network API call. +// BareDo sends an API request and lets you handle the api response. If an error +// or API Error occurs, the error will contain more information. Otherwise you +// are supposed to read and close the response's Body. If rate limit is exceeded +// and reset time is in the future, BareDo returns *RateLimitError immediately +// without making a network API call. // -// The provided ctx must be non-nil, if it is nil an error is returned. If it is canceled or times out, -// ctx.Err() will be returned. -func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) { +// The provided ctx must be non-nil, if it is nil an error is returned. If it is +// canceled or times out, ctx.Err() will be returned. +func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, error) { if ctx == nil { return nil, errors.New("context must be non-nil") } @@ -558,8 +557,6 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res return nil, err } - defer resp.Body.Close() - response := newResponse(resp) c.rateMu.Lock() @@ -568,6 +565,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res err = CheckResponse(resp) if err != nil { + defer resp.Body.Close() // Special case for AcceptedErrors. If an AcceptedError // has been encountered, the response's payload will be // added to the AcceptedError and returned. @@ -581,27 +579,43 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res } aerr.Raw = b - return response, aerr + err = aerr } + } + return response, err +} - return response, err +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. If v implements the io.Writer interface, +// the raw response body will be written to v, without attempting to first +// decode it. If v is nil, and no error hapens, the response is returned as is. +// If rate limit is exceeded and reset time is in the future, Do returns +// *RateLimitError immediately without making a network API call. +// +// The provided ctx must be non-nil, if it is nil an error is returned. If it +// is canceled or times out, ctx.Err() will be returned. +func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) { + resp, err := c.BareDo(ctx, req) + if err != nil { + return resp, err } + defer resp.Body.Close() - if v != nil { - if w, ok := v.(io.Writer); ok { - io.Copy(w, resp.Body) - } else { - decErr := json.NewDecoder(resp.Body).Decode(v) - if decErr == io.EOF { - decErr = nil // ignore EOF errors caused by empty response body - } - if decErr != nil { - err = decErr - } + switch v := v.(type) { + case nil: + case io.Writer: + _, err = io.Copy(v, resp.Body) + default: + decErr := json.NewDecoder(resp.Body).Decode(v) + if decErr == io.EOF { + decErr = nil // ignore EOF errors caused by empty response body + } + if decErr != nil { + err = decErr } } - - return response, err + return resp, err } // checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from diff --git a/github/github_test.go b/github/github_test.go index e95c2d00b00..db50b465555 100644 --- a/github/github_test.go +++ b/github/github_test.go @@ -1560,3 +1560,38 @@ func TestAddOptions_QueryValues(t *testing.T) { t.Error("addOptions err = nil, want error") } } + +func TestBareDo_returnsOpenBody(t *testing.T) { + + client, mux, _, teardown := setup() + defer teardown() + + expectedBody := "Hello from the other side !" + + mux.HandleFunc("/test-url", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, expectedBody) + }) + + ctx := context.Background() + req, err := client.NewRequest("GET", "test-url", nil) + if err != nil { + t.Fatalf("client.NewRequest returned error: %v", err) + } + + resp, err := client.BareDo(ctx, req) + if err != nil { + t.Fatalf("client.BareDo returned error: %v", err) + } + + got, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("ioutil.ReadAll returned error: %v", err) + } + if string(got) != expectedBody { + t.Fatalf("Expected %q, got %q", expectedBody, string(got)) + } + if err := resp.Body.Close(); err != nil { + t.Fatalf("resp.Body.Close() returned error: %v", err) + } +}