diff --git a/api/client.go b/api/client.go index 1c8c1eaa2..6880609bb 100644 --- a/api/client.go +++ b/api/client.go @@ -157,6 +157,17 @@ func (gr GraphQLErrorResponse) Error() string { return fmt.Sprintf("graphql error: '%s'", strings.Join(errorMessages, ", ")) } +// HTTPError is an error returned by a failed API call +type HTTPError struct { + Code int + URL string + Message string +} + +func (e HTTPError) Error() string { + return fmt.Sprintf("http error, '%s' failed (%d): '%s'", e.URL, e.Code, e.Message) +} + // Returns whether or not scopes are present, appID, and error func (c Client) HasScopes(wantedScopes ...string) (bool, string, error) { url := "https://api.github.com/user" @@ -298,7 +309,11 @@ func handleHTTPError(resp *http.Response) error { message = parsedBody.Message } - return fmt.Errorf("http error, '%s' failed (%d): '%s'", resp.Request.URL, resp.StatusCode, message) + return HTTPError{ + Code: resp.StatusCode, + URL: resp.Request.URL.String(), + Message: message, + } } var jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`) diff --git a/api/client_test.go b/api/client_test.go index 4c81bf315..9c908be12 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "errors" "io/ioutil" "reflect" "testing" @@ -66,3 +67,16 @@ func TestRESTGetDelete(t *testing.T) { err := client.REST("DELETE", "applications/CLIENTID/grant", r, nil) eq(t, err, nil) } + +func TestRESTError(t *testing.T) { + http := &httpmock.Registry{} + client := NewClient(ReplaceTripper(http)) + + http.StubResponse(422, bytes.NewBufferString(`{"message": "OH NO"}`)) + + var httpErr HTTPError + err := client.REST("DELETE", "/repos/branch", nil, nil) + if err == nil || !errors.As(err, &httpErr) || httpErr.Code != 422 { + t.Fatalf("got %q", err.Error()) + } +}