Skip to content

Commit

Permalink
feat(error): include http response in api errors (#320)
Browse files Browse the repository at this point in the history
This allows us to show more useful information in debug logs in
downstream projects.

---------

Co-authored-by: Jonas Lammler <[email protected]>
(cherry picked from commit 9558239)
  • Loading branch information
apricote authored and github-actions[bot] committed Oct 20, 2023
1 parent a9fa72b commit 03e1f8c
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 4 deletions.
11 changes: 7 additions & 4 deletions hcloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
return response, fmt.Errorf("hcloud: error reading response meta data: %s", err)
}

if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
err = errorFromResponse(resp, body)
if response.StatusCode >= 400 && response.StatusCode <= 599 {
err = errorFromResponse(response, body)
if err == nil {
err = fmt.Errorf("hcloud: server responded with status code %d", resp.StatusCode)
} else if IsError(err, ErrorCodeConflict) {
Expand Down Expand Up @@ -359,7 +359,7 @@ func dumpRequest(r *http.Request) ([]byte, error) {
return dumpReq, nil
}

func errorFromResponse(resp *http.Response, body []byte) error {
func errorFromResponse(resp *Response, body []byte) error {
if !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
return nil
}
Expand All @@ -371,7 +371,10 @@ func errorFromResponse(resp *http.Response, body []byte) error {
if respBody.Error.Code == "" && respBody.Error.Message == "" {
return nil
}
return ErrorFromSchema(respBody.Error)

hcErr := ErrorFromSchema(respBody.Error)
hcErr.response = resp
return hcErr
}

// Response represents a response from the API. It embeds http.Response.
Expand Down
3 changes: 3 additions & 0 deletions hcloud/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ func TestClientError(t *testing.T) {
if apiError.Message != "An error occurred" {
t.Errorf("unexpected error message: %q", apiError.Message)
}
if apiError.Response().StatusCode != http.StatusUnprocessableEntity {
t.Errorf("unexpected http status code: %q", apiError.Response().StatusCode)
}
}

func TestClientInvalidToken(t *testing.T) {
Expand Down
7 changes: 7 additions & 0 deletions hcloud/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,19 @@ type Error struct {
Code ErrorCode
Message string
Details interface{}

response *Response
}

func (e Error) Error() string {
return fmt.Sprintf("%s (%s)", e.Message, e.Code)
}

// Response returns the [Response] that contained the error if available.
func (e Error) Response() *Response {
return e.response
}

// ErrorDetailsInvalidInput contains the details of an 'invalid_input' error.
type ErrorDetailsInvalidInput struct {
Fields []ErrorDetailsInvalidInputField
Expand Down

0 comments on commit 03e1f8c

Please sign in to comment.