diff --git a/api/client.go b/api/client.go index db78ce2..2e6a551 100644 --- a/api/client.go +++ b/api/client.go @@ -25,6 +25,42 @@ import ( "time" ) +func NewErrorAPI(err error, warnings []string) Error { + if err == nil && warnings == nil { + return nil + } + return &ErrorAPI{err, warnings} +} + +type ErrorAPI struct { + err error + warnings []string +} + +func (w *ErrorAPI) Err() error { + return w.err +} + +func (w *ErrorAPI) Error() string { + if w.err != nil { + return w.err.Error() + } + return "Warnings: " + strings.Join(w.warnings, " , ") +} + +func (w *ErrorAPI) Warnings() []string { + return w.warnings +} + +// Error encapsulates an error + warning +type Error interface { + error + // Err returns the underlying error. + Err() error + // Warnings returns a list of warnings. + Warnings() []string +} + // DefaultRoundTripper is used if no RoundTripper is set in Config. var DefaultRoundTripper http.RoundTripper = &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -55,14 +91,14 @@ func (cfg *Config) roundTripper() http.RoundTripper { // Client is the interface for an API client. type Client interface { URL(ep string, args map[string]string) *url.URL - Do(context.Context, *http.Request) (*http.Response, []byte, error) + Do(context.Context, *http.Request) (*http.Response, []byte, Error) } // DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request. -func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, error) { +func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Error) { req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode())) if err != nil { - return nil, nil, err + return nil, nil, NewErrorAPI(err, nil) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -71,11 +107,14 @@ func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) ( u.RawQuery = args.Encode() req, err = http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return nil, nil, err + return nil, nil, NewErrorAPI(err, nil) } } else { - return resp, body, err + if err != nil { + return resp, body, NewErrorAPI(err, nil) + } + return resp, body, nil } return c.Do(ctx, req) } @@ -115,7 +154,7 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL { return &u } -func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { +func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Error) { if ctx != nil { req = req.WithContext(ctx) } @@ -127,7 +166,7 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, }() if err != nil { - return nil, nil, err + return nil, nil, NewErrorAPI(err, nil) } var body []byte @@ -147,5 +186,5 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, case <-done: } - return resp, body, err + return resp, body, NewErrorAPI(err, nil) } diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index 8394c97..decd09b 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "time" "github.com/prometheus/client_golang/api" @@ -95,13 +96,26 @@ const ( // Error is an error returned by the API. type Error struct { - Type ErrorType - Msg string - Detail string + Type ErrorType + Msg string + Detail string + warnings []string } func (e *Error) Error() string { - return fmt.Sprintf("%s: %s", e.Type, e.Msg) + if e.Type != "" || e.Msg != "" { + return fmt.Sprintf("%s: %s", e.Type, e.Msg) + } + + return "Warnings: " + strings.Join(e.warnings, " , ") +} + +func (w *Error) Err() error { + return w +} + +func (w *Error) Warnings() []string { + return w.warnings } // Range represents a sliced time range. @@ -115,32 +129,32 @@ type Range struct { // API provides bindings for Prometheus's v1 API. type API interface { // Alerts returns a list of all active alerts. - Alerts(ctx context.Context) (AlertsResult, error) + Alerts(ctx context.Context) (AlertsResult, api.Error) // AlertManagers returns an overview of the current state of the Prometheus alert manager discovery. - AlertManagers(ctx context.Context) (AlertManagersResult, error) + AlertManagers(ctx context.Context) (AlertManagersResult, api.Error) // CleanTombstones removes the deleted data from disk and cleans up the existing tombstones. - CleanTombstones(ctx context.Context) error + CleanTombstones(ctx context.Context) api.Error // Config returns the current Prometheus configuration. - Config(ctx context.Context) (ConfigResult, error) + Config(ctx context.Context) (ConfigResult, api.Error) // DeleteSeries deletes data for a selection of series in a time range. - DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error + DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error // Flags returns the flag values that Prometheus was launched with. - Flags(ctx context.Context) (FlagsResult, error) + Flags(ctx context.Context) (FlagsResult, api.Error) // LabelValues performs a query for the values of the given label. - LabelValues(ctx context.Context, label string) (model.LabelValues, error) + LabelValues(ctx context.Context, label string) (model.LabelValues, api.Error) // Query performs a query for the given time. - Query(ctx context.Context, query string, ts time.Time) (model.Value, error) + Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error) // QueryRange performs a query for the given range. - QueryRange(ctx context.Context, query string, r Range) (model.Value, error) + QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error) // Series finds series by label matchers. - Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) + Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error) // Snapshot creates a snapshot of all current data into snapshots/- // under the TSDB's data directory and returns the directory as response. - Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) + Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error) // Rules returns a list of alerting and recording rules that are currently loaded. - Rules(ctx context.Context) (RulesResult, error) + Rules(ctx context.Context) (RulesResult, api.Error) // Targets returns an overview of the current state of the Prometheus target discovery. - Targets(ctx context.Context) (TargetsResult, error) + Targets(ctx context.Context) (TargetsResult, api.Error) } // AlertsResult contains the result from querying the alerts endpoint. @@ -408,73 +422,73 @@ type httpAPI struct { client api.Client } -func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, error) { +func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, api.Error) { u := h.client.URL(epAlerts, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return AlertsResult{}, err + return AlertsResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return AlertsResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return AlertsResult{}, apiErr } var res AlertsResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) { +func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, api.Error) { u := h.client.URL(epAlertManagers, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return AlertManagersResult{}, err + return AlertManagersResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return AlertManagersResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return AlertManagersResult{}, apiErr } var res AlertManagersResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) CleanTombstones(ctx context.Context) error { +func (h *httpAPI) CleanTombstones(ctx context.Context) api.Error { u := h.client.URL(epCleanTombstones, nil) req, err := http.NewRequest(http.MethodPost, u.String(), nil) if err != nil { - return err + return api.NewErrorAPI(err, nil) } - _, _, err = h.client.Do(ctx, req) - return err + _, _, apiErr := h.client.Do(ctx, req) + return apiErr } -func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) { +func (h *httpAPI) Config(ctx context.Context) (ConfigResult, api.Error) { u := h.client.URL(epConfig, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return ConfigResult{}, err + return ConfigResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return ConfigResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return ConfigResult{}, apiErr } var res ConfigResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error { +func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error { u := h.client.URL(epDeleteSeries, nil) q := u.Query() @@ -489,47 +503,47 @@ func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime req, err := http.NewRequest(http.MethodPost, u.String(), nil) if err != nil { - return err + return api.NewErrorAPI(err, nil) } - _, _, err = h.client.Do(ctx, req) - return err + _, _, apiErr := h.client.Do(ctx, req) + return apiErr } -func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) { +func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, api.Error) { u := h.client.URL(epFlags, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return FlagsResult{}, err + return FlagsResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return FlagsResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return FlagsResult{}, apiErr } var res FlagsResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) { +func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, api.Error) { u := h.client.URL(epLabelValues, map[string]string{"name": label}) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return nil, err + return nil, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return nil, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return nil, apiErr } var labelValues model.LabelValues err = json.Unmarshal(body, &labelValues) - return labelValues, err + return labelValues, api.NewErrorAPI(err, nil) } -func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) { +func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error) { u := h.client.URL(epQuery, nil) q := u.Query() @@ -538,18 +552,16 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model. q.Set("time", ts.Format(time.RFC3339Nano)) } - _, body, err := api.DoGetFallback(h.client, ctx, u, q) - if err != nil { - return nil, err + _, body, apiErr := api.DoGetFallback(h.client, ctx, u, q) + if apiErr != nil { + return nil, apiErr } var qres queryResult - err = json.Unmarshal(body, &qres) - - return model.Value(qres.v), err + return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil) } -func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) { +func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error) { u := h.client.URL(epQueryRange, nil) q := u.Query() @@ -564,18 +576,17 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model. q.Set("end", end) q.Set("step", step) - _, body, err := api.DoGetFallback(h.client, ctx, u, q) - if err != nil { - return nil, err + _, body, apiErr := api.DoGetFallback(h.client, ctx, u, q) + if apiErr != nil { + return nil, apiErr } var qres queryResult - err = json.Unmarshal(body, &qres) - return model.Value(qres.v), err + return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil) } -func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) { +func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error) { u := h.client.URL(epSeries, nil) q := u.Query() @@ -590,20 +601,20 @@ func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.T req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return nil, err + return nil, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return nil, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return nil, apiErr } var mset []model.LabelSet err = json.Unmarshal(body, &mset) - return mset, err + return mset, api.NewErrorAPI(err, nil) } -func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) { +func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error) { u := h.client.URL(epSnapshot, nil) q := u.Query() @@ -613,53 +624,53 @@ func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, req, err := http.NewRequest(http.MethodPost, u.String(), nil) if err != nil { - return SnapshotResult{}, err + return SnapshotResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return SnapshotResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return SnapshotResult{}, apiErr } var res SnapshotResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) Rules(ctx context.Context) (RulesResult, error) { +func (h *httpAPI) Rules(ctx context.Context) (RulesResult, api.Error) { u := h.client.URL(epRules, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return RulesResult{}, err + return RulesResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return RulesResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return RulesResult{}, apiErr } var res RulesResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) { +func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, api.Error) { u := h.client.URL(epTargets, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return TargetsResult{}, err + return TargetsResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return TargetsResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return TargetsResult{}, apiErr } var res TargetsResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } // apiClient wraps a regular client and processes successful API responses. @@ -673,6 +684,7 @@ type apiResponse struct { Data json.RawMessage `json:"data"` ErrorType ErrorType `json:"errorType"` Error string `json:"error"` + Warnings []string `json:"warnings,omitempty"` } func apiError(code int) bool { @@ -690,14 +702,16 @@ func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) { return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode) } -func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { - resp, body, err := c.Client.Do(ctx, req) - if err != nil { - return resp, body, err +func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { + resp, body, apiErr := c.Client.Do(ctx, req) + if apiErr != nil { + return resp, body, apiErr } code := resp.StatusCode + var err api.Error + if code/100 != 2 && !apiError(code) { errorType, errorMsg := errorTypeAndMsgFor(resp) return resp, body, &Error{ @@ -710,27 +724,30 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [ var result apiResponse if http.StatusNoContent != code { - if err = json.Unmarshal(body, &result); err != nil { + if jsonErr := json.Unmarshal(body, &result); jsonErr != nil { return resp, body, &Error{ Type: ErrBadResponse, - Msg: err.Error(), + Msg: jsonErr.Error(), } } } if apiError(code) != (result.Status == "error") { err = &Error{ - Type: ErrBadResponse, - Msg: "inconsistent body for response code", + Type: ErrBadResponse, + Msg: "inconsistent body for response code", + warnings: result.Warnings, } } if apiError(code) && result.Status == "error" { err = &Error{ - Type: result.ErrorType, - Msg: result.Error, + Type: result.ErrorType, + Msg: result.Error, + warnings: result.Warnings, } } return resp, []byte(result.Data), err + } diff --git a/api/prometheus/v1/api_test.go b/api/prometheus/v1/api_test.go index f195f2b..2429e05 100644 --- a/api/prometheus/v1/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -25,11 +25,14 @@ import ( "testing" "time" + "github.com/prometheus/client_golang/api" "github.com/prometheus/common/model" + "github.com/prometheus/tsdb/testutil" ) type apiTest struct { - do func() (interface{}, error) + do func() (interface{}, api.Error) + inWarnings []string inErr error inStatusCode int inRes interface{} @@ -58,7 +61,7 @@ func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL { return u } -func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { +func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { test := c.curTest @@ -83,7 +86,7 @@ func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Respon resp.StatusCode = http.StatusOK } - return resp, b, test.inErr + return resp, b, api.NewErrorAPI(test.inErr, test.inWarnings) } func TestAPIs(t *testing.T) { @@ -96,74 +99,74 @@ func TestAPIs(t *testing.T) { client: client, } - doAlertManagers := func() func() (interface{}, error) { - return func() (interface{}, error) { + doAlertManagers := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.AlertManagers(context.Background()) } } - doCleanTombstones := func() func() (interface{}, error) { - return func() (interface{}, error) { + doCleanTombstones := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return nil, promAPI.CleanTombstones(context.Background()) } } - doConfig := func() func() (interface{}, error) { - return func() (interface{}, error) { + doConfig := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Config(context.Background()) } } - doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, error) { - return func() (interface{}, error) { + doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime) } } - doFlags := func() func() (interface{}, error) { - return func() (interface{}, error) { + doFlags := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Flags(context.Background()) } } - doLabelValues := func(label string) func() (interface{}, error) { - return func() (interface{}, error) { + doLabelValues := func(label string) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.LabelValues(context.Background(), label) } } - doQuery := func(q string, ts time.Time) func() (interface{}, error) { - return func() (interface{}, error) { + doQuery := func(q string, ts time.Time) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Query(context.Background(), q, ts) } } - doQueryRange := func(q string, rng Range) func() (interface{}, error) { - return func() (interface{}, error) { + doQueryRange := func(q string, rng Range) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.QueryRange(context.Background(), q, rng) } } - doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, error) { - return func() (interface{}, error) { + doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime) } } - doSnapshot := func(skipHead bool) func() (interface{}, error) { - return func() (interface{}, error) { + doSnapshot := func(skipHead bool) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Snapshot(context.Background(), skipHead) } } - doRules := func() func() (interface{}, error) { - return func() (interface{}, error) { + doRules := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Rules(context.Background()) } } - doTargets := func() func() (interface{}, error) { - return func() (interface{}, error) { + doTargets := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Targets(context.Background()) } } @@ -693,7 +696,7 @@ func (c *testClient) URL(ep string, args map[string]string) *url.URL { return nil } -func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { +func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { if ctx == nil { c.Fatalf("context was not passed down") } @@ -829,6 +832,21 @@ func TestAPIClientDo(t *testing.T) { Msg: "inconsistent body for response code", }, }, + { + code: http.StatusOK, + response: &apiResponse{ + Status: "error", + Data: json.RawMessage(`"test"`), + ErrorType: ErrTimeout, + Error: "timed out", + Warnings: []string{"a"}, + }, + expectedErr: &Error{ + Type: ErrBadResponse, + Msg: "inconsistent body for response code", + warnings: []string{"a"}, + }, + }, } tc := &testClient{ @@ -846,28 +864,20 @@ func TestAPIClientDo(t *testing.T) { _, body, err := client.Do(context.Background(), tc.req) if test.expectedErr != nil { - if err == nil { - t.Fatalf("expected error %q but got none", test.expectedErr) - } - if test.expectedErr.Error() != err.Error() { - t.Errorf("unexpected error: want %q, got %q", test.expectedErr, err) - } + testutil.NotOk(t, err) + testutil.Equals(t, test.expectedErr.Error(), err.Error()) + if test.expectedErr.Detail != "" { apiErr := err.(*Error) - if apiErr.Detail != test.expectedErr.Detail { - t.Errorf("unexpected error details: want %q, got %q", test.expectedErr.Detail, apiErr.Detail) - } + testutil.Equals(t, apiErr.Detail, test.expectedErr.Detail) } + + testutil.Equals(t, test.expectedErr.Warnings(), err.Warnings()) return } - if err != nil { - t.Fatalf("unexpeceted error %s", err) - } + testutil.Ok(t, err) - want, got := test.expectedBody, string(body) - if want != got { - t.Errorf("unexpected body: want %q, got %q", want, got) - } + testutil.Equals(t, test.expectedBody, string(body)) }) } diff --git a/go.sum b/go.sum index d998271..76c568e 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -5,7 +6,9 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= @@ -28,6 +31,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= @@ -43,8 +47,10 @@ github.com/prometheus/procfs v0.0.0-20190412120340-e22ddced7142/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=