Skip to content

Commit

Permalink
Feedback: change log, configurable retries, and wrap error
Browse files Browse the repository at this point in the history
  • Loading branch information
amishas157 committed Dec 2, 2024
1 parent 0112a1d commit f5792cd
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 28 deletions.
2 changes: 1 addition & 1 deletion support/http/httptest/client_expectation.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (ce *ClientExpectation) ReturnMultipleResults(responseSets []ResponseData)
ce.URL,
func(req *http.Request) (*http.Response, error) {
if responseIndex >= len(allResponses) {
panic(fmt.Sprintf("no responses available"))
panic(fmt.Errorf("no responses available"))
}

resp := allResponses[responseIndex]
Expand Down
29 changes: 21 additions & 8 deletions utils/apiclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
"time"

"github.com/pkg/errors"
"github.com/stellar/go/support/log"
)

const (
maxRetries = 5
initialBackoff = 1 * time.Second
defaultMaxRetries = 5
defaultInitialBackoffTime = 1 * time.Second
)

func isRetryableStatusCode(statusCode int) bool {
Expand All @@ -33,6 +34,18 @@ func (c *APIClient) CallAPI(reqParams RequestParams) (interface{}, error) {
reqParams.Headers = map[string]interface{}{}
}

if c.MaxRetries == 0 {
c.MaxRetries = defaultMaxRetries
}

if c.InitialBackoffTime == 0 {
c.InitialBackoffTime = defaultInitialBackoffTime
}

if reqParams.Endpoint == "" {
return nil, fmt.Errorf("Please set endpoint to query")
}

url := c.GetURL(reqParams.Endpoint, reqParams.QueryParams)
reqBody, err := CreateRequestBody(reqParams.RequestType, url)
if err != nil {
Expand All @@ -49,7 +62,7 @@ func (c *APIClient) CallAPI(reqParams RequestParams) (interface{}, error) {
var result interface{}
retries := 0

for retries <= maxRetries {
for retries <= c.MaxRetries {
resp, err := client.Do(reqBody)
if err != nil {
return nil, errors.Wrap(err, "http request failed")
Expand All @@ -59,19 +72,19 @@ func (c *APIClient) CallAPI(reqParams RequestParams) (interface{}, error) {
if resp.StatusCode == http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
return nil, errors.Wrap(err, "failed to read response body")
}

if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
return nil, errors.Wrap(err, "failed to unmarshal JSON")
}

return result, nil
} else if isRetryableStatusCode(resp.StatusCode) {
retries++
backoffDuration := initialBackoff * time.Duration(1<<retries)
if retries <= maxRetries {
fmt.Printf("Received retryable status %d. Retrying in %v...\n", resp.StatusCode, backoffDuration)
backoffDuration := c.InitialBackoffTime * time.Duration(1<<retries)
if retries <= c.MaxRetries {
log.Debugf("Received retryable status %d. Retrying in %v...\n", resp.StatusCode, backoffDuration)
time.Sleep(backoffDuration)
} else {
return nil, fmt.Errorf("maximum retries reached after receiving status %d", resp.StatusCode)
Expand Down
25 changes: 11 additions & 14 deletions utils/apiclient/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ func TestGetURL(t *testing.T) {
assert.Equal(t, "https://stellar.org/federation?acct=2382376&federation_type=bank_account&swift=BOPBPHMM&type=forward", furl)
}

type testCase struct {
name string
mockResponses []httptest.ResponseData
expected interface{}
expectedError string
}

func TestCallAPI(t *testing.T) {
testCases := []struct {
name string
mockResponses []httptest.ResponseData
expected interface{}
expectedError string
}{
testCases := []testCase{
{
name: "status 200 - Success",
mockResponses: []httptest.ResponseData{
Expand Down Expand Up @@ -90,14 +92,9 @@ func TestCallAPI(t *testing.T) {
result, err := c.CallAPI(reqParams)

if tc.expectedError != "" {
if err == nil {
t.Fatalf("expected error %q, got nil", tc.expectedError)
}
if err.Error() != tc.expectedError {
t.Fatalf("expected error %q, got %q", tc.expectedError, err.Error())
}
} else if err != nil {
t.Fatal(err)
assert.EqualError(t, err, tc.expectedError)
} else {
assert.NoError(t, err)
}

if tc.expected != nil {
Expand Down
11 changes: 7 additions & 4 deletions utils/apiclient/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package apiclient
import (
"net/http"
"net/url"
"time"
)

type HTTP interface {
Expand All @@ -12,10 +13,12 @@ type HTTP interface {
}

type APIClient struct {
BaseURL string
HTTP HTTP
AuthType string
AuthHeaders map[string]interface{}
BaseURL string
HTTP HTTP
AuthType string
AuthHeaders map[string]interface{}
MaxRetries int
InitialBackoffTime time.Duration
}

type RequestParams struct {
Expand Down
3 changes: 2 additions & 1 deletion utils/apiclient/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

"github.com/pkg/errors"
"github.com/stellar/go/support/log"
)

func CreateRequestBody(requestType string, url string) (*http.Request, error) {
Expand All @@ -20,7 +21,7 @@ func SetHeaders(req *http.Request, args map[string]interface{}) {
for key, value := range args {
strValue, ok := value.(string)
if !ok {
fmt.Printf("Skipping non-string value for header %s\n", key)
log.Debugf("Skipping non-string value for header %s\n", key)
continue
}

Expand Down

0 comments on commit f5792cd

Please sign in to comment.