pub fn compute_new_cursor(
prev_since: Option<&str>,
exported_max: Option<&str>,
applied_max: Option<&str>,
round_start_at: &str,
) -> Option<String> {
let candidate = match (exported_max, applied_max) {
(Some(a), Some(b)) => Some(if a >= b { a } else { b }),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
};
let capped = candidate.map(|c| {
if c < round_start_at {
c.to_string()
} else {
round_start_at.to_string()
}
});
let advanced = capped.or_else(|| prev_since.map(String::from));
match (advanced, prev_since) {
(Some(a), Some(p)) if a.as_str() < p => Some(p.to_string()),
(a, _) => a,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cursor_advances_to_exported_max_not_wallclock() {
let c = compute_new_cursor(
None,
Some("2026-04-21 08:00:00"),
None,
"2026-04-21 12:00:00",
);
assert_eq!(c.as_deref(), Some("2026-04-21 08:00:00"));
}
#[test]
fn cursor_takes_max_of_exported_and_applied() {
let c = compute_new_cursor(
None,
Some("2026-04-21 08:00:00"),
Some("2026-04-21 09:00:00"),
"2026-04-21 12:00:00",
);
assert_eq!(c.as_deref(), Some("2026-04-21 09:00:00"));
}
#[test]
fn cursor_capped_at_round_start_for_future_skew() {
let c = compute_new_cursor(
None,
Some("2099-01-01 00:00:00"),
None,
"2026-04-21 12:00:00",
);
assert_eq!(c.as_deref(), Some("2026-04-21 12:00:00"));
}
#[test]
fn cursor_stays_put_when_nothing_exchanged() {
let c = compute_new_cursor(
Some("2026-04-21 10:00:00"),
None,
None,
"2026-04-21 12:00:00",
);
assert_eq!(c.as_deref(), Some("2026-04-21 10:00:00"));
}
#[test]
fn cursor_never_moves_backwards() {
let c = compute_new_cursor(
Some("2026-04-21 11:00:00"),
Some("2026-04-21 08:00:00"),
None,
"2026-04-21 12:00:00",
);
assert_eq!(c.as_deref(), Some("2026-04-21 11:00:00"));
}
#[test]
fn cursor_no_previous_no_exchange_returns_none() {
let c = compute_new_cursor(None, None, None, "2026-04-21 12:00:00");
assert!(c.is_none());
}
}