juspay/hyperswitch · error
grace 2 => Some
Error message
grace 2 => Some
What it means
Test-time expect() in window_starts_next_day (crates/router/src/workflows/revenue_recovery.rs:2096): compute_mathmodel_retry_time(&sample(), 3, grace=2, DEFAULT_RETRY_HOUR) returned None where the test requires Some for grace 2. The semantic contract is that the failure day is excluded and grace 2 yields exactly tomorrow, so a None means the next-day window computation failed to produce a candidate.
Solutions
- Check the day-window construction for grace=2: it must include exactly the next calendar day; fix any off-by-one that turns the window empty.
- Ensure hour-slot selection always accepts DEFAULT_RETRY_HOUR when only one day is in the window (no filtering that can drop the sole candidate).
- Test around midnight locally — if flaky only at boundaries, normalize the 'today' anchor date inside the function before windowing.
- Keep the contract documented: grace counts today, so Some must be guaranteed for grace >= 2; fix the function, not the test.
Example fix
// before
let dt = compute_mathmodel_retry_time(&sample(), 3, 2, DEFAULT_RETRY_HOUR)
.expect("grace 2 => Some");
// after (in the function)
let start = today + 1; // failure day excluded
let end = today + grace; // inclusive: grace 2 => [tomorrow]
assert!(start <= end, "window empty for grace {grace}"); // fail loudly in dev, not None in tests
// and select from [start..=end] falling back to DEFAULT_RETRY_HOUR on the chosen day Defensive patterns
Strategy: fallback
Validate before calling
// assert the window is non-empty before selection:
let start = today + 1;
let end = today + grace; // inclusive
assert!(start <= end, "empty retry window for grace {grace}"); Try / catch
let dt = compute_mathmodel_retry_time(&sample(), 3, 2, DEFAULT_RETRY_HOUR)
.unwrap_or_else(|| tomorrow_at(DEFAULT_RETRY_HOUR)); Prevention
- Keep the documented contract: grace counts today, failure day excluded, Some guaranteed for grace >= 2
- Add midnight-boundary tests so date arithmetic can't silently empty the window
- Don't filter the sole candidate day/hour without a fallback
- Fix the function, never relax this assertion
When it happens
Trigger: Calling compute_mathmodel_retry_time with grace=2 on the sample StatsDocument and getting None — the day-window [tomorrow] came back empty because the function excluded the single eligible future day (off-by-one in inclusive/exclusive grace counting or hour-slot filtering dropped the only day).
Common situations: Refactors that changed whether grace counts today (inclusive) versus starts tomorrow; DEFAULT_RETRY_HOUR hour-slot filtering removing the only candidate day/hour; timezone/date arithmetic changes shifting the 'before' date boundary (midnight ticks).
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
AI-assisted analysis of juspay/hyperswitch@afd9284352 (2026-09-18).
Data as JSON: /api/errors/96499ad4949dbb47.
Report an issue: GitHub.
Appendix: source
Thrown at crates/router/src/workflows/revenue_recovery.rs:2096
.expect("grace > 1 => Some");
let last = (before + time::Duration::days(i64::from(grace) + 1)).date();
assert!(
dt.date() > before.date() && dt.date() <= last,
"date {} out of window",
dt.date()
);
assert!(dt.hour() < 24);
}
}
}
#[test]
fn window_starts_next_day() {
// Failure day is excluded: the earliest candidate is tomorrow. grace COUNTS today, so grace 2
// = today + 1 future day (tomorrow) — assert the pick is that next day, not the failure day.
let before = common_utils::date_time::now().assume_utc();
let dt = compute_mathmodel_retry_time(&sample(), 3, 2, DEFAULT_RETRY_HOUR)
.expect("grace 2 => Some");
assert!(
dt.date() > before.date(),
"expected next day, got {} (today {})",
dt.date(),
before.date()
);
assert!(dt.date() <= (before + time::Duration::days(2)).date());
}
#[test]
fn grace_zero_and_one_return_none() {
// grace COUNTS today; grace 0 = no grace, grace 1 = today only -> no future day -> None (v1).
assert!(compute_mathmodel_retry_time(&sample(), 3, 0, DEFAULT_RETRY_HOUR).is_none());
assert!(compute_mathmodel_retry_time(&sample(), 3, 1, DEFAULT_RETRY_HOUR).is_none());
}
#[test]
fn zero_budget_returns_none() {View on GitHub (pinned to afd9284352)