diff --git a/client.go b/client.go index 6bcdb5f..53aefeb 100644 --- a/client.go +++ b/client.go @@ -31,6 +31,7 @@ type Client struct { ctxAnalytics context.Context log Logger offlineHandler OfflineHandler + errorHandler func(handler *FlagsmithAPIError) } // NewClient creates instance of Client with given configuration. @@ -147,7 +148,8 @@ func (c *Client) GetIdentitySegments(identifier string, traits []*Trait) ([]*seg // NOTE: This method only works with Edge API endpoint. func (c *Client) BulkIdentify(ctx context.Context, batch []*IdentityTraits) error { if len(batch) > bulkIdentifyMaxCount { - return &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: batch size must be less than %d", bulkIdentifyMaxCount)} + msg := fmt.Sprintf("flagsmith: batch size must be less than %d", bulkIdentifyMaxCount) + return &FlagsmithAPIError{Msg: msg} } body := struct { @@ -160,13 +162,16 @@ func (c *Client) BulkIdentify(ctx context.Context, batch []*IdentityTraits) erro ForceContentType("application/json"). Post(c.config.baseURL + "bulk-identities/") if resp.StatusCode() == 404 { - return &FlagsmithAPIError{msg: "flagsmith: Bulk identify endpoint not found; Please make sure you are using Edge API endpoint"} + msg := "flagsmith: Bulk identify endpoint not found; Please make sure you are using Edge API endpoint" + return &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} } if err != nil { - return &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err)} + msg := fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err) + return &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} } if !resp.IsSuccess() { - return &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: unexpected response from Flagsmith API: %s", resp.Status())} + msg := fmt.Sprintf("flagsmith: unexpected response from Flagsmith API: %s", resp.Status()) + return &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} } return nil } @@ -179,10 +184,12 @@ func (c *Client) GetEnvironmentFlagsFromAPI(ctx context.Context) (Flags, error) ForceContentType("application/json"). Get(c.config.baseURL + "flags/") if err != nil { - return Flags{}, &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err)} + msg := fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err) + return Flags{}, &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} } if !resp.IsSuccess() { - return Flags{}, &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: unexpected response from Flagsmith API: %s", resp.Status())} + msg := fmt.Sprintf("flagsmith: unexpected response from Flagsmith API: %s", resp.Status()) + return Flags{}, &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} } return makeFlagsFromAPIFlags(resp.Body(), c.analyticsProcessor, c.defaultFlagHandler) } @@ -200,10 +207,12 @@ func (c *Client) GetIdentityFlagsFromAPI(ctx context.Context, identifier string, ForceContentType("application/json"). Post(c.config.baseURL + "identities/") if err != nil { - return Flags{}, &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err)} + msg := fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err) + return Flags{}, &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} } if !resp.IsSuccess() { - return Flags{}, &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: unexpected response from Flagsmith API: %s", resp.Status())} + msg := fmt.Sprintf("flagsmith: unexpected response from Flagsmith API: %s", resp.Status()) + return Flags{}, &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} } return makeFlagsfromIdentityAPIJson(resp.Body(), c.analyticsProcessor, c.defaultFlagHandler) } @@ -267,10 +276,20 @@ func (c *Client) UpdateEnvironment(ctx context.Context) error { Get(c.config.baseURL + "environment-document/") if err != nil { - return &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err)} + msg := fmt.Sprintf("flagsmith: error performing request to Flagsmith API: %s", err) + f := &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} + if c.errorHandler != nil { + c.errorHandler(f) + } + return f } if resp.StatusCode() != 200 { - return &FlagsmithAPIError{msg: fmt.Sprintf("flagsmith: unexpected response from Flagsmith API: %s", resp.Status())} + msg := fmt.Sprintf("flagsmith: unexpected response from Flagsmith API: %s", resp.Status()) + f := &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()} + if c.errorHandler != nil { + c.errorHandler(f) + } + return f } c.environment.Store(&env) identitiesWithOverrides := make(map[string]identities.IdentityModel) diff --git a/client_test.go b/client_test.go index 4a7739c..cb732ad 100644 --- a/client_test.go +++ b/client_test.go @@ -675,3 +675,34 @@ func TestOfflineHandlerIsUsedWhenRequestFails(t *testing.T) { assert.Equal(t, fixtures.Feature1ID, allFlags[0].FeatureID) assert.Equal(t, fixtures.Feature1Value, allFlags[0].Value) } + +func TestPollErrorHandlerIsUsedWhenPollFails(t *testing.T) { + // Given + ctx := context.Background() + var capturedError error + var statusCode int + var status string + + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + + // When + client := flagsmith.NewClient(fixtures.EnvironmentAPIKey, + flagsmith.WithBaseURL(server.URL+"/api/v1/"), + flagsmith.WithErrorHandler(func(handler *flagsmith.FlagsmithAPIError) { + capturedError = handler.Err + statusCode = handler.ResponseStatusCode + status = handler.ResponseStatus + }), + ) + + // when + _ = client.UpdateEnvironment(ctx) + + // Then + assert.Equal(t, capturedError, nil) + assert.Equal(t, statusCode, 500) + assert.Equal(t, status, "500 Internal Server Error") +} diff --git a/errors.go b/errors.go index dc15538..794c062 100644 --- a/errors.go +++ b/errors.go @@ -5,7 +5,10 @@ type FlagsmithClientError struct { } type FlagsmithAPIError struct { - msg string + Msg string + Err error + ResponseStatusCode int + ResponseStatus string } func (e FlagsmithClientError) Error() string { @@ -13,5 +16,5 @@ func (e FlagsmithClientError) Error() string { } func (e FlagsmithAPIError) Error() string { - return e.msg + return e.Msg } diff --git a/options.go b/options.go index 13660df..c49084b 100644 --- a/options.go +++ b/options.go @@ -117,3 +117,10 @@ func WithOfflineMode() Option { c.config.offlineMode = true } } + +// WithErrorHandler provides a way to handle errors that occur during update of an environment. +func WithErrorHandler(handler func(handler *FlagsmithAPIError)) Option { + return func(c *Client) { + c.errorHandler = handler + } +}