feat(client): implement exponential backoff retry for network errors
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -15,6 +16,7 @@ type config struct {
|
||||
ZarinHubUsername string
|
||||
ZarinHubPassword string
|
||||
ZarinHubTimeout time.Duration
|
||||
ZarinHubRetries int
|
||||
DatabasePath string
|
||||
}
|
||||
|
||||
@@ -44,6 +46,18 @@ func readConfig(getenv func(string) string) (config, error) {
|
||||
)
|
||||
}
|
||||
|
||||
retriesText := valueOrDefault(
|
||||
getenv("ZARINHUB_RETRIES"),
|
||||
"2",
|
||||
)
|
||||
|
||||
retries, err := strconv.Atoi(retriesText)
|
||||
if err != nil || retries < 0 {
|
||||
return config{}, fmt.Errorf(
|
||||
"ZARINHUB_RETRIES must be a non-negative integer",
|
||||
)
|
||||
}
|
||||
|
||||
return config{
|
||||
ServerAddr: valueOrDefault(
|
||||
getenv("SERVER_ADDR"),
|
||||
@@ -64,6 +78,7 @@ func readConfig(getenv func(string) string) (config, error) {
|
||||
ZarinHubUsername: username,
|
||||
ZarinHubPassword: password,
|
||||
ZarinHubTimeout: timeout,
|
||||
ZarinHubRetries: retries,
|
||||
DatabasePath: valueOrDefault(
|
||||
getenv("DATABASE_PATH"),
|
||||
"./card-to-iban.db",
|
||||
|
||||
@@ -31,6 +31,9 @@ func TestReadConfigUsesDefaults(t *testing.T) {
|
||||
if config.ZarinHubTimeout != 5*time.Second {
|
||||
t.Errorf("ZarinHubTimeout = %v", config.ZarinHubTimeout)
|
||||
}
|
||||
if config.ZarinHubRetries != 2 {
|
||||
t.Errorf("ZarinHubRetries = %d, want 2", config.ZarinHubRetries)
|
||||
}
|
||||
if config.DatabasePath != "./card-to-iban.db" {
|
||||
t.Errorf("DatabasePath = %q", config.DatabasePath)
|
||||
}
|
||||
@@ -70,3 +73,22 @@ func TestReadConfigRejectsInvalidTimeout(t *testing.T) {
|
||||
t.Fatalf("readConfig() error = %v, want timeout error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadConfigRejectsInvalidRetries(t *testing.T) {
|
||||
testCases := []string{"-1", "invalid", "2.5"}
|
||||
|
||||
for _, tc := range testCases {
|
||||
values := map[string]string{
|
||||
"ZARINHUB_API_USERNAME": "test-username",
|
||||
"ZARINHUB_API_PASSWORD": "test-password",
|
||||
"ZARINHUB_RETRIES": tc,
|
||||
}
|
||||
|
||||
_, err := readConfig(func(key string) string {
|
||||
return values[key]
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "ZARINHUB_RETRIES") {
|
||||
t.Errorf("retries=%q: want error mentioning ZARINHUB_RETRIES, got %v", tc, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ func run(logger *slog.Logger) error {
|
||||
httpClient,
|
||||
config.ZarinHubEndpoint,
|
||||
tokenManager,
|
||||
config.ZarinHubRetries,
|
||||
)
|
||||
cardService := service.NewCardService(zarinHubClient)
|
||||
auditRepository := repository.NewAuditRepository(db)
|
||||
|
||||
Reference in New Issue
Block a user