Skip to content

Commit

Permalink
Merge pull request #140 from ajinkyasurya/ajinkyasurya/pollErrorHandler
Browse files Browse the repository at this point in the history
feat: Add a configurable error handler for environment updates. Return response codes on HTTP errors
  • Loading branch information
rolodato authored Sep 30, 2024
2 parents c3512cf + 880cf36 commit a6faf98
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 12 deletions.
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
}
}

0 comments on commit a6faf98

Please sign in to comment.