Skip to main content

agent_uri_dht/
page.rs

1//! Paginated lookup results.
2
3/// An opaque position in a paginated lookup.
4///
5/// A cursor is only meaningful to the backend that produced it and to a
6/// subsequent lookup for the same query. Do not persist one across backends.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub struct Cursor(u32);
9
10impl Cursor {
11    /// Creates a cursor at the given position.
12    #[must_use]
13    pub const fn at(position: u32) -> Self {
14        Self(position)
15    }
16
17    /// Returns the position this cursor resumes from.
18    ///
19    /// What a position indexes is the backend's business: an offset into a
20    /// result set for the simulator, a key shard for a distributed backend.
21    #[must_use]
22    pub const fn position(self) -> u32 {
23        self.0
24    }
25}
26
27/// One page of lookup results.
28///
29/// A capability path can hold more registrations than a single DHT read can
30/// return, so lookups page. `next` is `Some` only when the backend knows more
31/// results remain; an empty page with `next: None` means the query is
32/// exhausted.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct Page<T> {
35    items: Vec<T>,
36    next: Option<Cursor>,
37}
38
39impl<T> Page<T> {
40    /// Creates a page that completes the query.
41    #[must_use]
42    pub const fn complete(items: Vec<T>) -> Self {
43        Self { items, next: None }
44    }
45
46    /// Creates a page with more results available from `next`.
47    #[must_use]
48    pub const fn partial(items: Vec<T>, next: Cursor) -> Self {
49        Self {
50            items,
51            next: Some(next),
52        }
53    }
54
55    /// Returns the results in this page.
56    #[must_use]
57    pub fn items(&self) -> &[T] {
58        &self.items
59    }
60
61    /// Returns the cursor for the next page, if more results remain.
62    #[must_use]
63    pub const fn next_cursor(&self) -> Option<Cursor> {
64        self.next
65    }
66
67    /// Returns true if more results remain beyond this page.
68    #[must_use]
69    pub const fn has_more(&self) -> bool {
70        self.next.is_some()
71    }
72
73    /// Returns the number of results in this page.
74    #[must_use]
75    pub fn len(&self) -> usize {
76        self.items.len()
77    }
78
79    /// Returns true if this page carries no results.
80    #[must_use]
81    pub fn is_empty(&self) -> bool {
82        self.items.is_empty()
83    }
84
85    /// Consumes the page and returns its results.
86    #[must_use]
87    pub fn into_items(self) -> Vec<T> {
88        self.items
89    }
90}
91
92impl<T> IntoIterator for Page<T> {
93    type Item = T;
94    type IntoIter = std::vec::IntoIter<T>;
95
96    fn into_iter(self) -> Self::IntoIter {
97        self.items.into_iter()
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn complete_page_has_no_cursor() {
107        let page = Page::complete(vec![1, 2, 3]);
108        assert_eq!(page.len(), 3);
109        assert!(!page.has_more());
110        assert!(page.next_cursor().is_none());
111    }
112
113    #[test]
114    fn partial_page_carries_a_cursor() {
115        let page = Page::partial(vec![1], Cursor::at(4));
116        assert!(page.has_more());
117        assert_eq!(page.next_cursor().unwrap().position(), 4);
118    }
119
120    #[test]
121    fn empty_complete_page_is_exhausted_not_truncated() {
122        // The distinction matters: an empty page that still has a cursor means
123        // "nothing here, keep going", not "no such agents".
124        let page: Page<u8> = Page::complete(vec![]);
125        assert!(page.is_empty());
126        assert!(!page.has_more());
127    }
128
129    #[test]
130    fn empty_partial_page_still_reports_more() {
131        let page: Page<u8> = Page::partial(vec![], Cursor::at(1));
132        assert!(page.is_empty());
133        assert!(page.has_more());
134    }
135
136    #[test]
137    fn into_items_returns_the_results() {
138        let page = Page::complete(vec![7, 8]);
139        assert_eq!(page.into_items(), vec![7, 8]);
140    }
141
142    #[test]
143    fn page_iterates() {
144        let page = Page::complete(vec![1, 2, 3]);
145        assert_eq!(page.into_iter().sum::<i32>(), 6);
146    }
147}