juspay/hyperswitch · error

grace > 1 => Some

Error message

grace > 1 => Some

What it means

Test-time expect() in crates/router/src/workflows/revenue_recovery.rs:2078: compute_mathmodel_retry_time(&stats, 3, grace=14, DEFAULT_RETRY_HOUR) returned None (or errored) for a case the test asserts must always produce a value when grace > 1. The function picks the next retry datetime within a grace window derived from StatsDocument slot weights; a None means the scheduler failed to find any candidate retry slot despite a non-trivial grace period.

Solutions

  1. Run the test and inspect why the picker returned None: add temporary logging inside compute_mathmodel_retry_time for the computed window bounds.
  2. Check the grace-window day arithmetic: with grace=14 the candidate window must always contain at least one future day (before+1 .. before+grace+1).
  3. Verify StatsDocument::default() (all-zero weights) falls back to a uniform/valid slot selection rather than early-returning None.
  4. Confirm DEFAULT_RETRY_HOUR is a valid hour (0-23) that survives slot filtering; if the hour is filtered, the fallback must still yield a datetime.
  5. Fix the regression in the window/slot logic rather than relaxing the test assertion.

Example fix

// before
let dt = compute_mathmodel_retry_time(&stats, 3, grace, DEFAULT_RETRY_HOUR)
    .expect("grace > 1 => Some");
// after (in the function, guarantee the invariant)
compute_uniform_slot(&stats, max_retries, grace, retry_hour)
    .or_else(|| fallback_next_day(retry_hour, grace)) // never None when grace >= 2
// before(production-shape fix): return None on empty window
// after: clamp window end to at least start + 1 day so Some is guaranteed for grace > 1
Defensive patterns

Strategy: fallback

Validate before calling

// caller-side guard before relying on the result:
let dt = compute_mathmodel_retry_time(&stats, retries, grace, retry_hour)
    .unwrap_or_else(|| default_retry_datetime(grace, retry_hour));

Try / catch

match compute_mathmodel_retry_time(&stats, 3, grace, DEFAULT_RETRY_HOUR) {
    Some(dt) => dt,
    None => fallback_next_day_retry(DEFAULT_RETRY_HOUR, grace),
}

Prevention

When it happens

Trigger: Calling compute_mathmodel_retry_time with grace > 1 on either a populated sample() StatsDocument or the all-defaults StatsDocument::default() and receiving None — e.g. the candidate-window computation excludes all days/hours so no retry slot is produced, an internal regression in the mathmodel picker.

Common situations: A code change to compute_mathmodel_retry_time (window arithmetic, slot filtering, DEFAULT_RETRY_HOUR handling) that makes it return None for zero/sparse stats; changing DEFAULT_RETRY_HOUR to an hour that gets filtered out; off-by-one in day-grace arithmetic so the window is empty.

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/faa076f77d75c53c. Report an issue: GitHub.

Appendix: source

Thrown at crates/router/src/workflows/revenue_recovery.rs:2078

            assert!(pick_hour(&doc.hod, DEFAULT_RETRY_HOUR) < 24);
        }
        assert_eq!(
            pick_hour(&[SlotCounter::default(); 24], DEFAULT_RETRY_HOUR),
            DEFAULT_RETRY_HOUR
        );
    }

    #[test]
    fn result_is_always_in_window() {
        // Invariant for EVERY random outcome: result date ∈ [tomorrow, today+grace], hour ∈ 0..23,
        // never panics — for both rich and empty stats. (Window starts on the NEXT day; the bounds
        // carry a 1-day slack so a midnight tick between captures can't flake it.)
        let grace: u32 = 14;
        for stats in [sample(), StatsDocument::default()] {
            for _ in 0..200 {
                let before = common_utils::date_time::now().assume_utc();
                let dt = compute_mathmodel_retry_time(&stats, 3, grace, DEFAULT_RETRY_HOUR)
                    .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");

View on GitHub (pinned to afd9284352)