Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a configurable error handler for environment updates. Return response codes on HTTP errors #140

Merged
merged 4 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down
31 changes: 31 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
7 changes: 5 additions & 2 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ type FlagsmithClientError struct {
}

type FlagsmithAPIError struct {
msg string
Msg string
Err error
ResponseStatusCode int
ResponseStatus string
}

func (e FlagsmithClientError) Error() string {
return e.msg
}

func (e FlagsmithAPIError) Error() string {
return e.msg
return e.Msg
}
7 changes: 7 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Loading