1use thiserror::Error;
10use ulid::Ulid;
11
12#[derive(Debug, Error)]
14#[error("Either 'first' or 'last' must be specified")]
15pub struct InvalidPagination;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct Pagination<Cursor = Ulid> {
20 pub before: Option<Cursor>,
22
23 pub after: Option<Cursor>,
25
26 pub count: usize,
28
29 pub direction: PaginationDirection,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum PaginationDirection {
36 Forward,
38
39 Backward,
41}
42
43pub trait Node<C = Ulid> {
45 fn cursor(&self) -> C;
47}
48
49impl<C> Pagination<C> {
50 pub fn try_new(
57 before: Option<C>,
58 after: Option<C>,
59 first: Option<usize>,
60 last: Option<usize>,
61 ) -> Result<Self, InvalidPagination> {
62 let (direction, count) = match (first, last) {
63 (Some(first), _) => (PaginationDirection::Forward, first),
64 (_, Some(last)) => (PaginationDirection::Backward, last),
65 (None, None) => return Err(InvalidPagination),
66 };
67
68 Ok(Self {
69 before,
70 after,
71 count,
72 direction,
73 })
74 }
75
76 #[must_use]
78 pub const fn first(first: usize) -> Self {
79 Self {
80 before: None,
81 after: None,
82 count: first,
83 direction: PaginationDirection::Forward,
84 }
85 }
86
87 #[must_use]
89 pub const fn last(last: usize) -> Self {
90 Self {
91 before: None,
92 after: None,
93 count: last,
94 direction: PaginationDirection::Backward,
95 }
96 }
97
98 #[must_use]
100 pub fn before(mut self, cursor: C) -> Self {
101 self.before = Some(cursor);
102 self
103 }
104
105 #[must_use]
107 pub fn clear_before(mut self) -> Self {
108 self.before = None;
109 self
110 }
111
112 #[must_use]
114 pub fn after(mut self, cursor: C) -> Self {
115 self.after = Some(cursor);
116 self
117 }
118
119 #[must_use]
121 pub fn clear_after(mut self) -> Self {
122 self.after = None;
123 self
124 }
125
126 #[must_use]
128 pub fn process<T: Node<C>>(&self, mut nodes: Vec<T>) -> Page<T, C> {
129 let is_full = nodes.len() == (self.count + 1);
130 if is_full {
131 nodes.pop();
132 }
133
134 let (has_previous_page, has_next_page) = match self.direction {
135 PaginationDirection::Forward => (false, is_full),
136 PaginationDirection::Backward => {
137 nodes.reverse();
139 (is_full, false)
140 }
141 };
142
143 let edges = nodes
144 .into_iter()
145 .map(|node| Edge {
146 cursor: node.cursor(),
147 node,
148 })
149 .collect();
150
151 Page {
152 has_next_page,
153 has_previous_page,
154 edges,
155 }
156 }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct Edge<T, C = Ulid> {
162 pub cursor: C,
164 pub node: T,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct Page<T, C = Ulid> {
171 pub has_next_page: bool,
173
174 pub has_previous_page: bool,
176
177 pub edges: Vec<Edge<T, C>>,
179}
180
181impl<T, C> Page<T, C> {
182 #[must_use]
188 pub fn map<F, T2>(self, mut f: F) -> Page<T2, C>
189 where
190 F: FnMut(T) -> T2,
191 {
192 let edges = self
193 .edges
194 .into_iter()
195 .map(|edge| Edge {
196 cursor: edge.cursor,
197 node: f(edge.node),
198 })
199 .collect();
200 Page {
201 has_next_page: self.has_next_page,
202 has_previous_page: self.has_previous_page,
203 edges,
204 }
205 }
206
207 pub fn try_map<F, E, T2>(self, mut f: F) -> Result<Page<T2, C>, E>
217 where
218 F: FnMut(T) -> Result<T2, E>,
219 {
220 let edges: Result<Vec<Edge<T2, C>>, E> = self
221 .edges
222 .into_iter()
223 .map(|edge| {
224 Ok(Edge {
225 cursor: edge.cursor,
226 node: f(edge.node)?,
227 })
228 })
229 .collect();
230
231 Ok(Page {
232 has_next_page: self.has_next_page,
233 has_previous_page: self.has_previous_page,
234 edges: edges?,
235 })
236 }
237}