Skip to content

Commit e8d7be7

Browse files
refactor(strutil): move formatResetAt to strutil.FormatResetAt
The formatResetAt helper in server/circuit_breaker.go already delegates to strutil.FormatDuration for the relative portion of the output. Moving it to strutil keeps all time-formatting utilities together, improves discoverability, and removes a private function from a domain-specific file. - Add FormatResetAt(t time.Time) string to internal/strutil/format_duration.go - Add TestFormatResetAt table-driven tests - Replace all four call sites in circuit_breaker.go with strutil.FormatResetAt - Remove the now-unused private formatResetAt function Addresses: #5032 (Finding 4) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d4ac02b commit e8d7be7

3 files changed

Lines changed: 29 additions & 11 deletions

File tree

internal/server/circuit_breaker.go

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,12 @@ func (cb *circuitBreaker) RecordRateLimit(resetAt time.Time) {
195195
cb.openedAt = cb.nowFunc()
196196
logger.LogError("backend",
197197
"circuit breaker for server %q OPENED after %d consecutive rate-limit errors; resets at %s",
198-
cb.serverID, cb.consecutiveErrors, formatResetAt(cb.resetAt))
198+
cb.serverID, cb.consecutiveErrors, strutil.FormatResetAt(cb.resetAt))
199199
logCircuitBreaker.Printf("server %q circuit breaker CLOSED → OPEN (errors=%d)", cb.serverID, cb.consecutiveErrors)
200200
} else {
201201
logger.LogWarn("backend",
202202
"rate-limit error for server %q (consecutive=%d/%d); resets at %s",
203-
cb.serverID, cb.consecutiveErrors, cb.threshold, formatResetAt(cb.resetAt))
203+
cb.serverID, cb.consecutiveErrors, cb.threshold, strutil.FormatResetAt(cb.resetAt))
204204
}
205205

206206
case circuitHalfOpen:
@@ -209,13 +209,13 @@ func (cb *circuitBreaker) RecordRateLimit(resetAt time.Time) {
209209
cb.openedAt = cb.nowFunc()
210210
logger.LogError("backend",
211211
"circuit breaker for server %q re-OPENED after probe was rate-limited; resets at %s",
212-
cb.serverID, formatResetAt(cb.resetAt))
212+
cb.serverID, strutil.FormatResetAt(cb.resetAt))
213213
logCircuitBreaker.Printf("server %q circuit breaker HALF-OPEN → OPEN (probe rate-limited)", cb.serverID)
214214

215215
case circuitOpen:
216216
// Already open — update reset time.
217217
logger.LogWarn("backend", "server %q circuit breaker still OPEN; resets at %s",
218-
cb.serverID, formatResetAt(cb.resetAt))
218+
cb.serverID, strutil.FormatResetAt(cb.resetAt))
219219
}
220220
}
221221

@@ -226,13 +226,6 @@ func (cb *circuitBreaker) State() circuitBreakerState {
226226
return cb.state
227227
}
228228

229-
// formatResetAt returns a human-readable representation of a reset time.
230-
func formatResetAt(t time.Time) string {
231-
if t.IsZero() {
232-
return "unknown"
233-
}
234-
return fmt.Sprintf("%s (in %s)", t.UTC().Format(time.RFC3339), strutil.FormatDuration(time.Until(t).Round(time.Second)))
235-
}
236229

237230
// buildCircuitBreakers creates per-backend circuit breakers from the configuration.
238231
func buildCircuitBreakers(cfg *config.Config) map[string]*circuitBreaker {

internal/strutil/format_duration.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ import (
55
"time"
66
)
77

8+
// FormatResetAt returns a human-readable representation of a reset time,
9+
// combining an RFC3339 timestamp with a relative countdown (e.g. "2026-05-03T12:00:00Z (in 5.0m)").
10+
// Returns "unknown" when t is the zero value.
11+
func FormatResetAt(t time.Time) string {
12+
if t.IsZero() {
13+
return "unknown"
14+
}
15+
return fmt.Sprintf("%s (in %s)", t.UTC().Format(time.RFC3339), FormatDuration(time.Until(t).Round(time.Second)))
16+
}
17+
818
// FormatDuration formats a duration for display like the debug npm package.
919
// It provides granular formatting from nanoseconds to hours.
1020
func FormatDuration(d time.Duration) string {

internal/strutil/format_duration_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,18 @@ func TestFormatDuration(t *testing.T) {
164164
})
165165
}
166166
}
167+
168+
func TestFormatResetAt(t *testing.T) {
169+
t.Run("zero time returns unknown", func(t *testing.T) {
170+
result := FormatResetAt(time.Time{})
171+
assert.Equal(t, "unknown", result)
172+
})
173+
174+
t.Run("non-zero time includes RFC3339 timestamp and relative countdown", func(t *testing.T) {
175+
// Use a fixed future time so the test is deterministic.
176+
future := time.Date(2030, 1, 1, 12, 0, 0, 0, time.UTC)
177+
result := FormatResetAt(future)
178+
assert.Contains(t, result, "2030-01-01T12:00:00Z")
179+
assert.Contains(t, result, " (in ")
180+
})
181+
}

0 commit comments

Comments
 (0)