mas_storage/
pagination.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7//! Utilities to manage paginated queries.
8
9use thiserror::Error;
10use ulid::Ulid;
11
12/// An error returned when invalid pagination parameters are provided
13#[derive(Debug, Error)]
14#[error("Either 'first' or 'last' must be specified")]
15pub struct InvalidPagination;
16
17/// Pagination parameters
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct Pagination {
20    /// The cursor to start from
21    pub before: Option<Ulid>,
22
23    /// The cursor to end at
24    pub after: Option<Ulid>,
25
26    /// The maximum number of items to return
27    pub count: usize,
28
29    /// In which direction to paginate
30    pub direction: PaginationDirection,
31}
32
33/// The direction to paginate
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum PaginationDirection {
36    /// Paginate forward
37    Forward,
38
39    /// Paginate backward
40    Backward,
41}
42
43impl Pagination {
44    /// Creates a new [`Pagination`] from user-provided parameters.
45    ///
46    /// # Errors
47    ///
48    /// Either `first` or `last` must be provided, else this function will
49    /// return an [`InvalidPagination`] error.
50    pub const fn try_new(
51        before: Option<Ulid>,
52        after: Option<Ulid>,
53        first: Option<usize>,
54        last: Option<usize>,
55    ) -> Result<Self, InvalidPagination> {
56        let (direction, count) = match (first, last) {
57            (Some(first), _) => (PaginationDirection::Forward, first),
58            (_, Some(last)) => (PaginationDirection::Backward, last),
59            (None, None) => return Err(InvalidPagination),
60        };
61
62        Ok(Self {
63            before,
64            after,
65            count,
66            direction,
67        })
68    }
69
70    /// Creates a [`Pagination`] which gets the first N items
71    #[must_use]
72    pub const fn first(first: usize) -> Self {
73        Self {
74            before: None,
75            after: None,
76            count: first,
77            direction: PaginationDirection::Forward,
78        }
79    }
80
81    /// Creates a [`Pagination`] which gets the last N items
82    #[must_use]
83    pub const fn last(last: usize) -> Self {
84        Self {
85            before: None,
86            after: None,
87            count: last,
88            direction: PaginationDirection::Backward,
89        }
90    }
91
92    /// Get items before the given cursor
93    #[must_use]
94    pub const fn before(mut self, id: Ulid) -> Self {
95        self.before = Some(id);
96        self
97    }
98
99    /// Clear the before cursor
100    #[must_use]
101    pub const fn clear_before(mut self) -> Self {
102        self.before = None;
103        self
104    }
105
106    /// Get items after the given cursor
107    #[must_use]
108    pub const fn after(mut self, id: Ulid) -> Self {
109        self.after = Some(id);
110        self
111    }
112
113    /// Clear the after cursor
114    #[must_use]
115    pub const fn clear_after(mut self) -> Self {
116        self.after = None;
117        self
118    }
119
120    /// Process a page returned by a paginated query
121    #[must_use]
122    pub fn process<T>(&self, mut edges: Vec<T>) -> Page<T> {
123        let is_full = edges.len() == (self.count + 1);
124        if is_full {
125            edges.pop();
126        }
127
128        let (has_previous_page, has_next_page) = match self.direction {
129            PaginationDirection::Forward => (false, is_full),
130            PaginationDirection::Backward => {
131                // 6. If the last argument is provided, I reverse the order of the results
132                edges.reverse();
133                (is_full, false)
134            }
135        };
136
137        Page {
138            has_next_page,
139            has_previous_page,
140            edges,
141        }
142    }
143}
144
145/// A page of results returned by a paginated query
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct Page<T> {
148    /// When paginating forwards, this is true if there are more items after
149    pub has_next_page: bool,
150
151    /// When paginating backwards, this is true if there are more items before
152    pub has_previous_page: bool,
153
154    /// The items in the page
155    pub edges: Vec<T>,
156}
157
158impl<T> Page<T> {
159    /// Map the items in this page with the given function
160    ///
161    /// # Parameters
162    ///
163    /// * `f`: The function to map the items with
164    #[must_use]
165    pub fn map<F, T2>(self, f: F) -> Page<T2>
166    where
167        F: FnMut(T) -> T2,
168    {
169        let edges = self.edges.into_iter().map(f).collect();
170        Page {
171            has_next_page: self.has_next_page,
172            has_previous_page: self.has_previous_page,
173            edges,
174        }
175    }
176
177    /// Try to map the items in this page with the given fallible function
178    ///
179    /// # Parameters
180    ///
181    /// * `f`: The fallible function to map the items with
182    ///
183    /// # Errors
184    ///
185    /// Returns the first error encountered while mapping the items
186    pub fn try_map<F, E, T2>(self, f: F) -> Result<Page<T2>, E>
187    where
188        F: FnMut(T) -> Result<T2, E>,
189    {
190        let edges: Result<Vec<T2>, E> = self.edges.into_iter().map(f).collect();
191        Ok(Page {
192            has_next_page: self.has_next_page,
193            has_previous_page: self.has_previous_page,
194            edges: edges?,
195        })
196    }
197}