canic/ops/model/memory/directory/
mod.rs

1mod app;
2mod subnet;
3
4pub use app::*;
5pub use subnet::*;
6
7pub use crate::model::memory::directory::DirectoryView;
8
9use candid::CandidType;
10use serde::{Deserialize, Serialize};
11
12///
13/// DirectoryPageDto
14///
15
16#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
17pub struct DirectoryPageDto {
18    pub entries: DirectoryView,
19    pub total: u64,
20    pub offset: u64,
21    pub limit: u64,
22}
23
24///
25/// Pagination
26///
27
28#[must_use]
29pub(crate) fn paginate(view: DirectoryView, offset: u64, limit: u64) -> DirectoryPageDto {
30    let total = view.len() as u64;
31    let (start, end) = pagination_bounds(total, offset, limit);
32
33    let entries = view.into_iter().skip(start).take(end - start).collect();
34
35    DirectoryPageDto {
36        entries,
37        total,
38        offset,
39        limit,
40    }
41}
42
43#[allow(clippy::cast_possible_truncation)]
44fn pagination_bounds(total: u64, offset: u64, limit: u64) -> (usize, usize) {
45    let start = offset.min(total);
46    let end = (offset + limit).min(total);
47
48    let start = start as usize;
49    let end = end as usize;
50
51    (start, end)
52}
53
54///
55/// TESTS
56///
57
58#[cfg(test)]
59mod tests {
60    use super::{DirectoryView, paginate};
61    use crate::{
62        model::memory::directory::PrincipalList,
63        types::{CanisterType, Principal},
64    };
65
66    fn p(id: u8) -> Principal {
67        Principal::from_slice(&[id; 29])
68    }
69
70    fn sample_view() -> DirectoryView {
71        vec![
72            ("a".into(), PrincipalList(vec![p(1)])),
73            ("b".into(), PrincipalList(vec![p(2), p(3)])),
74            ("c".into(), PrincipalList(vec![p(4)])),
75        ]
76    }
77
78    #[test]
79    fn paginate_within_bounds() {
80        let page = paginate(sample_view(), 1, 1);
81
82        assert_eq!(page.total, 3);
83        assert_eq!(page.offset, 1);
84        assert_eq!(page.limit, 1);
85        assert_eq!(page.entries.len(), 1);
86        assert_eq!(page.entries[0].0, CanisterType::from("b"));
87    }
88
89    #[test]
90    fn paginate_truncates_at_total() {
91        let page = paginate(sample_view(), 2, 5);
92
93        assert_eq!(page.total, 3);
94        assert_eq!(page.entries.len(), 1);
95        assert_eq!(page.entries[0].0, CanisterType::from("c"));
96    }
97
98    #[test]
99    fn paginate_handles_offset_beyond_range() {
100        let page = paginate(sample_view(), 10, 5);
101
102        assert_eq!(page.total, 3);
103        assert!(page.entries.is_empty());
104        assert_eq!(page.offset, 10);
105        assert_eq!(page.limit, 5);
106    }
107}