diff --git a/pkg/api/error.go b/pkg/api/errors.go similarity index 50% rename from pkg/api/error.go rename to pkg/api/errors.go index 6bf7a21..a21050f 100644 --- a/pkg/api/error.go +++ b/pkg/api/errors.go @@ -11,6 +11,13 @@ type Error struct { } `json:"source"` } -func (e *Error) Error() string { - return fmt.Sprintf("%s: %s", e.Title, e.Detail) +type Errors struct { + Errors []Error `json:"errors"` +} + +func (e *Errors) Error() error { + if len(e.Errors) > 0 { + return fmt.Errorf("%d: %s", e.Errors[0].Status, e.Errors[0].Detail) + } + return fmt.Errorf("No errors") } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 6a3ec3c..5a84d41 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "testing" + "github.com/liamg/hackerone/pkg/api" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -68,6 +69,32 @@ func Test_Get(t *testing.T) { assert.Equal(t, expected, actual) } +func Test_Get_404(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + defer server.Close() + + returned := api.Errors{ + Errors: []api.Error{ + { + Status: 404, + Detail: "Not Found", + }, + }, + } + + mux.HandleFunc("/404", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusNotFound) + _ = json.NewEncoder(w).Encode(returned) + }) + + client := New("", "", OptionWithBaseURL(server.URL)) + var actual example + err := client.Get(context.TODO(), "/404", &actual) + require.Error(t, err) +} + func Test_Post(t *testing.T) { mux := http.NewServeMux() server := httptest.NewServer(mux) diff --git a/pkg/client/transport.go b/pkg/client/transport.go index c970f1f..360b706 100644 --- a/pkg/client/transport.go +++ b/pkg/client/transport.go @@ -17,35 +17,37 @@ type transport struct { apiKey string } -func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { - var response *http.Response +func (t *transport) RoundTrip(req *http.Request) (response *http.Response, err error) { req.SetBasicAuth(t.username, t.apiKey) options := []retry.Option{ retry.Delay(time.Millisecond * 100), retry.DelayType(retry.BackOffDelay), - retry.Attempts(10), + retry.Attempts(5), retry.LastErrorOnly(true), } - if err := retry.Do(func() error { + err = retry.Do(func() error { resp, err := t.underlying.RoundTrip(req) if err != nil { return err } if resp.StatusCode >= 400 { - defer func() { _ = resp.Body.Close() }() - var apiError api.Error - if err := json.NewDecoder(resp.Body).Decode(&apiError); err != nil || apiError.Status == 0 { - return fmt.Errorf("server error: status %d", resp.StatusCode) + defer resp.Body.Close() + var apiErrors api.Errors + err = json.NewDecoder(resp.Body).Decode(&apiErrors) + if err != nil { + err = fmt.Errorf("server error: status %d, %s", resp.StatusCode, resp.Body) + } else { + err = apiErrors.Error() } if resp.StatusCode < 500 { - return retry.Unrecoverable(&apiError) + // Client error + return retry.Unrecoverable(err) } - return &apiError + // Server error + return err } response = resp return nil - }, options...); err != nil { - return nil, err - } - return response, nil + }, options...) + return }