1use serde::{Deserialize, Serialize};
44use std::fmt::Write;
45
46#[derive(Debug, Clone, Default, PartialEq, Eq)]
48pub struct Pagination {
49 pub skip: Option<u64>,
51 pub take: Option<u64>,
53 pub cursor: Option<Cursor>,
55}
56
57impl Pagination {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn skip(mut self, skip: u64) -> Self {
65 self.skip = Some(skip);
66 self
67 }
68
69 pub fn take(mut self, take: u64) -> Self {
71 self.take = Some(take);
72 self
73 }
74
75 pub fn cursor(mut self, cursor: Cursor) -> Self {
77 self.cursor = Some(cursor);
78 self
79 }
80
81 pub fn is_empty(&self) -> bool {
83 self.skip.is_none() && self.take.is_none() && self.cursor.is_none()
84 }
85
86 pub fn to_sql(&self) -> String {
90 let mut sql = String::with_capacity(54);
92
93 if let Some(take) = self.take {
94 let _ = write!(sql, "LIMIT {}", take);
95 }
96
97 if let Some(skip) = self.skip {
98 if !sql.is_empty() {
99 sql.push(' ');
100 }
101 let _ = write!(sql, "OFFSET {}", skip);
102 }
103
104 sql
105 }
106
107 #[inline]
121 pub fn write_sql(&self, buffer: &mut String) {
122 if let Some(take) = self.take {
123 let _ = write!(buffer, "LIMIT {}", take);
124 }
125
126 if let Some(skip) = self.skip {
127 if self.take.is_some() {
128 buffer.push(' ');
129 }
130 let _ = write!(buffer, "OFFSET {}", skip);
131 }
132 }
133
134 pub fn first(n: u64) -> Self {
136 Self::new().take(n)
137 }
138
139 pub fn page(page: u64, page_size: u64) -> Self {
141 let skip = (page.saturating_sub(1)) * page_size;
142 Self::new().skip(skip).take(page_size)
143 }
144}
145
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
148pub struct Cursor {
149 pub column: String,
151 pub value: CursorValue,
153 pub direction: CursorDirection,
155}
156
157impl Cursor {
158 pub fn new(column: impl Into<String>, value: CursorValue, direction: CursorDirection) -> Self {
160 Self {
161 column: column.into(),
162 value,
163 direction,
164 }
165 }
166
167 pub fn after(column: impl Into<String>, value: impl Into<CursorValue>) -> Self {
169 Self::new(column, value.into(), CursorDirection::After)
170 }
171
172 pub fn before(column: impl Into<String>, value: impl Into<CursorValue>) -> Self {
174 Self::new(column, value.into(), CursorDirection::Before)
175 }
176
177 pub fn to_sql_condition(&self) -> String {
181 let mut sql = String::with_capacity(self.column.len() + 12);
183 sql.push_str(&self.column);
184 sql.push(' ');
185 sql.push_str(match self.direction {
186 CursorDirection::After => "> $cursor",
187 CursorDirection::Before => "< $cursor",
188 });
189 sql
190 }
191
192 #[inline]
194 pub fn write_sql_condition(&self, buffer: &mut String) {
195 buffer.push_str(&self.column);
196 buffer.push(' ');
197 buffer.push_str(match self.direction {
198 CursorDirection::After => "> $cursor",
199 CursorDirection::Before => "< $cursor",
200 });
201 }
202
203 #[inline]
205 pub const fn operator(&self) -> &'static str {
206 match self.direction {
207 CursorDirection::After => ">",
208 CursorDirection::Before => "<",
209 }
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
215pub enum CursorValue {
216 Int(i64),
218 String(String),
220}
221
222impl From<i32> for CursorValue {
223 fn from(v: i32) -> Self {
224 Self::Int(v as i64)
225 }
226}
227
228impl From<i64> for CursorValue {
229 fn from(v: i64) -> Self {
230 Self::Int(v)
231 }
232}
233
234impl From<String> for CursorValue {
235 fn from(v: String) -> Self {
236 Self::String(v)
237 }
238}
239
240impl From<&str> for CursorValue {
241 fn from(v: &str) -> Self {
242 Self::String(v.to_string())
243 }
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
248pub enum CursorDirection {
249 After,
251 Before,
253}
254
255#[derive(Debug, Clone)]
257pub struct PaginatedResult<T> {
258 pub data: Vec<T>,
260 pub has_next: bool,
262 pub has_previous: bool,
264 pub next_cursor: Option<CursorValue>,
266 pub previous_cursor: Option<CursorValue>,
268 pub total_count: Option<u64>,
270}
271
272impl<T> PaginatedResult<T> {
273 pub fn new(data: Vec<T>) -> Self {
275 Self {
276 data,
277 has_next: false,
278 has_previous: false,
279 next_cursor: None,
280 previous_cursor: None,
281 total_count: None,
282 }
283 }
284
285 pub fn with_pagination(mut self, has_next: bool, has_previous: bool) -> Self {
287 self.has_next = has_next;
288 self.has_previous = has_previous;
289 self
290 }
291
292 pub fn with_total(mut self, total: u64) -> Self {
294 self.total_count = Some(total);
295 self
296 }
297
298 pub fn with_cursors(
300 mut self,
301 next: Option<CursorValue>,
302 previous: Option<CursorValue>,
303 ) -> Self {
304 self.next_cursor = next;
305 self.previous_cursor = previous;
306 self
307 }
308
309 pub fn len(&self) -> usize {
311 self.data.len()
312 }
313
314 pub fn is_empty(&self) -> bool {
316 self.data.is_empty()
317 }
318}
319
320impl<T> IntoIterator for PaginatedResult<T> {
321 type Item = T;
322 type IntoIter = std::vec::IntoIter<T>;
323
324 fn into_iter(self) -> Self::IntoIter {
325 self.data.into_iter()
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_pagination_skip_take() {
335 let pagination = Pagination::new().skip(10).take(20);
336 assert_eq!(pagination.to_sql(), "LIMIT 20 OFFSET 10");
337 }
338
339 #[test]
340 fn test_pagination_page() {
341 let pagination = Pagination::page(3, 10);
342 assert_eq!(pagination.skip, Some(20));
343 assert_eq!(pagination.take, Some(10));
344 }
345
346 #[test]
347 fn test_cursor_after() {
348 let cursor = Cursor::after("id", 100i64);
349 assert_eq!(cursor.to_sql_condition(), "id > $cursor");
350 }
351
352 #[test]
353 fn test_cursor_before() {
354 let cursor = Cursor::before("id", 100i64);
355 assert_eq!(cursor.to_sql_condition(), "id < $cursor");
356 }
357
358 #[test]
359 fn test_paginated_result() {
360 let result = PaginatedResult::new(vec![1, 2, 3])
361 .with_pagination(true, false)
362 .with_total(100);
363
364 assert_eq!(result.len(), 3);
365 assert!(result.has_next);
366 assert!(!result.has_previous);
367 assert_eq!(result.total_count, Some(100));
368 }
369}