Skip to content

Commit f0bfdaf

Browse files
authored
fix: Preserve HTTP Response in URL Errors (#3369)
Fixes: #3362.
1 parent cea0bba commit f0bfdaf

File tree

2 files changed

+53
-5
lines changed

2 files changed

+53
-5
lines changed

github/github.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -841,28 +841,31 @@ func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, erro
841841
}
842842

843843
resp, err := c.client.Do(req)
844+
var response *Response
845+
if resp != nil {
846+
response = newResponse(resp)
847+
}
848+
844849
if err != nil {
845850
// If we got an error, and the context has been canceled,
846851
// the context's error is probably more useful.
847852
select {
848853
case <-ctx.Done():
849-
return nil, ctx.Err()
854+
return response, ctx.Err()
850855
default:
851856
}
852857

853858
// If the error type is *url.Error, sanitize its URL before returning.
854859
if e, ok := err.(*url.Error); ok {
855860
if url, err := url.Parse(e.URL); err == nil {
856861
e.URL = sanitizeURL(url).String()
857-
return nil, e
862+
return response, e
858863
}
859864
}
860865

861-
return nil, err
866+
return response, err
862867
}
863868

864-
response := newResponse(resp)
865-
866869
// Don't update the rate limits if this was a cached response.
867870
// X-From-Cache is set by https://github.com/gregjones/httpcache
868871
if response.Header.Get("X-From-Cache") == "" {

github/github_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,51 @@ func TestDo_redirectLoop(t *testing.T) {
11061106
}
11071107
}
11081108

1109+
func TestDo_preservesResponseInHTTPError(t *testing.T) {
1110+
t.Parallel()
1111+
client, mux, _ := setup(t)
1112+
1113+
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
1114+
w.Header().Set("Content-Type", "application/json")
1115+
w.WriteHeader(http.StatusNotFound)
1116+
fmt.Fprintf(w, `{
1117+
"message": "Resource not found",
1118+
"documentation_url": "https://docs.github.com/rest/reference/repos#get-a-repository"
1119+
}`)
1120+
})
1121+
1122+
req, _ := client.NewRequest("GET", ".", nil)
1123+
var resp *Response
1124+
var data interface{}
1125+
resp, err := client.Do(context.Background(), req, &data)
1126+
1127+
if err == nil {
1128+
t.Fatal("Expected error response")
1129+
}
1130+
1131+
// Verify error type and access to status code
1132+
errResp, ok := err.(*ErrorResponse)
1133+
if !ok {
1134+
t.Fatalf("Expected *ErrorResponse error, got %T", err)
1135+
}
1136+
1137+
// Verify status code is accessible from both Response and ErrorResponse
1138+
if resp == nil {
1139+
t.Fatal("Expected response to be returned even with error")
1140+
}
1141+
if got, want := resp.StatusCode, http.StatusNotFound; got != want {
1142+
t.Errorf("Response status = %d, want %d", got, want)
1143+
}
1144+
if got, want := errResp.Response.StatusCode, http.StatusNotFound; got != want {
1145+
t.Errorf("Error response status = %d, want %d", got, want)
1146+
}
1147+
1148+
// Verify error contains proper message
1149+
if !strings.Contains(errResp.Message, "Resource not found") {
1150+
t.Errorf("Error message = %q, want to contain 'Resource not found'", errResp.Message)
1151+
}
1152+
}
1153+
11091154
// Test that an error caused by the internal http client's Do() function
11101155
// does not leak the client secret.
11111156
func TestDo_sanitizeURL(t *testing.T) {

0 commit comments

Comments
 (0)