pub const MAX_PAGES: usize = 1_000;
#[derive(Debug, Clone)]
pub struct PaginationCursor {
cursor: Option<String>,
pages_fetched: usize,
}
impl PaginationCursor {
#[must_use]
pub const fn new() -> Self {
Self {
cursor: None,
pages_fetched: 0,
}
}
#[must_use]
pub fn current(&self) -> Option<&str> {
self.cursor.as_deref()
}
#[must_use]
pub const fn has_more(&self) -> bool {
self.pages_fetched < MAX_PAGES
}
#[must_use]
pub const fn pages_fetched(&self) -> usize {
self.pages_fetched
}
#[must_use]
pub fn advance(&mut self, next_cursor: Option<String>) -> bool {
self.pages_fetched += 1;
self.cursor = next_cursor;
self.cursor.is_some() && self.has_more()
}
}
impl Default for PaginationCursor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn starts_with_no_cursor() {
let cursor = PaginationCursor::new();
assert!(cursor.current().is_none());
assert!(cursor.has_more());
assert_eq!(cursor.pages_fetched(), 0);
}
#[test]
fn advances_through_pages() {
let mut cursor = PaginationCursor::new();
assert!(cursor.advance(Some("page2".into())));
assert_eq!(cursor.current(), Some("page2"));
assert_eq!(cursor.pages_fetched(), 1);
assert!(!cursor.advance(None));
assert!(cursor.current().is_none());
assert_eq!(cursor.pages_fetched(), 2);
}
#[test]
fn enforces_max_pages_cap() {
let mut cursor = PaginationCursor::new();
for i in 0..MAX_PAGES {
let has_more = cursor.advance(Some(format!("page{i}")));
if i == MAX_PAGES - 1 {
assert!(!has_more, "should stop at page cap");
}
}
assert!(!cursor.has_more());
}
}