package retry import ( "context" "fmt" "log/slog" "time" ) type Retrier struct { maxRetries int backoff []time.Duration } func NewRetrier(maxRetries int, backoff []time.Duration) *Retrier { return &Retrier{maxRetries: maxRetries, backoff: backoff} } // DefaultRetrier returns a retrier with 3 attempts, exponential backoff: 1s, 5s, 30s. func DefaultRetrier() *Retrier { return NewRetrier(3, []time.Duration{1 * time.Second, 5 * time.Second, 30 * time.Second}) } func (r *Retrier) Do(ctx context.Context, fn func() error) error { var lastErr error for attempt := 0; attempt <= r.maxRetries; attempt++ { if attempt > 0 { delay := r.backoff[attempt-1] slog.Info("retrying", "attempt", attempt, "delay", delay) select { case <-ctx.Done(): return ctx.Err() case <-time.After(delay): } } err := fn() if err == nil { return nil } lastErr = err slog.Warn("attempt failed", "attempt", attempt, "error", err) } return fmt.Errorf("all %d attempts failed, last error: %w", r.maxRetries+1, lastErr) }