Skip to main content

agentic_forge_core/query/
pagination.rs

1//! Cursor-based pagination.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct CursorPage<T> {
7    pub items: Vec<T>,
8    pub cursor: Option<String>,
9    pub has_more: bool,
10    pub total: Option<usize>,
11}
12
13impl<T> CursorPage<T> {
14    pub fn empty() -> Self {
15        Self {
16            items: Vec::new(),
17            cursor: None,
18            has_more: false,
19            total: Some(0),
20        }
21    }
22
23    pub fn from_slice(all: Vec<T>, cursor: Option<&str>, limit: usize) -> Self
24    where
25        T: Clone,
26    {
27        let start = cursor.and_then(|c| c.parse::<usize>().ok()).unwrap_or(0);
28        let total = all.len();
29
30        if start >= total {
31            return Self {
32                items: Vec::new(),
33                cursor: None,
34                has_more: false,
35                total: Some(total),
36            };
37        }
38
39        let end = (start + limit).min(total);
40        let items = all[start..end].to_vec();
41        let has_more = end < total;
42        let next_cursor = if has_more {
43            Some(end.to_string())
44        } else {
45            None
46        };
47
48        Self {
49            items,
50            cursor: next_cursor,
51            has_more,
52            total: Some(total),
53        }
54    }
55
56    pub fn len(&self) -> usize {
57        self.items.len()
58    }
59
60    pub fn is_empty(&self) -> bool {
61        self.items.is_empty()
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_cursor_page_first() {
71        let data: Vec<i32> = (0..20).collect();
72        let page = CursorPage::from_slice(data, None, 5);
73        assert_eq!(page.len(), 5);
74        assert!(page.has_more);
75        assert_eq!(page.cursor, Some("5".into()));
76        assert_eq!(page.total, Some(20));
77    }
78
79    #[test]
80    fn test_cursor_page_second() {
81        let data: Vec<i32> = (0..20).collect();
82        let page = CursorPage::from_slice(data, Some("5"), 5);
83        assert_eq!(page.items, vec![5, 6, 7, 8, 9]);
84        assert!(page.has_more);
85    }
86
87    #[test]
88    fn test_cursor_page_last() {
89        let data: Vec<i32> = (0..20).collect();
90        let page = CursorPage::from_slice(data, Some("15"), 5);
91        assert_eq!(page.len(), 5);
92        assert!(!page.has_more);
93        assert_eq!(page.cursor, None);
94    }
95
96    #[test]
97    fn test_cursor_page_past_end() {
98        let data: Vec<i32> = (0..5).collect();
99        let page = CursorPage::from_slice(data, Some("100"), 5);
100        assert!(page.is_empty());
101        assert!(!page.has_more);
102    }
103
104    #[test]
105    fn test_cursor_page_empty() {
106        let page: CursorPage<i32> = CursorPage::empty();
107        assert!(page.is_empty());
108        assert_eq!(page.total, Some(0));
109    }
110
111    #[test]
112    fn test_cursor_page_exact_boundary() {
113        let data: Vec<i32> = (0..10).collect();
114        let page = CursorPage::from_slice(data, None, 10);
115        assert_eq!(page.len(), 10);
116        assert!(!page.has_more);
117    }
118
119    #[test]
120    fn test_cursor_page_limit_larger_than_data() {
121        let data: Vec<i32> = (0..3).collect();
122        let page = CursorPage::from_slice(data, None, 100);
123        assert_eq!(page.len(), 3);
124        assert!(!page.has_more);
125    }
126}