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(
160 column: impl Into<String>,
161 value: CursorValue,
162 direction: CursorDirection,
163 ) -> Self {
164 Self {
165 column: column.into(),
166 value,
167 direction,
168 }
169 }
170
171 pub fn after(column: impl Into<String>, value: impl Into<CursorValue>) -> Self {
173 Self::new(column, value.into(), CursorDirection::After)
174 }
175
176 pub fn before(column: impl Into<String>, value: impl Into<CursorValue>) -> Self {
178 Self::new(column, value.into(), CursorDirection::Before)
179 }
180
181 pub fn to_sql_condition(&self) -> String {
185 let mut sql = String::with_capacity(self.column.len() + 12);
187 sql.push_str(&self.column);
188 sql.push(' ');
189 sql.push_str(match self.direction {
190 CursorDirection::After => "> $cursor",
191 CursorDirection::Before => "< $cursor",
192 });
193 sql
194 }
195
196 #[inline]
198 pub fn write_sql_condition(&self, buffer: &mut String) {
199 buffer.push_str(&self.column);
200 buffer.push(' ');
201 buffer.push_str(match self.direction {
202 CursorDirection::After => "> $cursor",
203 CursorDirection::Before => "< $cursor",
204 });
205 }
206
207 #[inline]
209 pub const fn operator(&self) -> &'static str {
210 match self.direction {
211 CursorDirection::After => ">",
212 CursorDirection::Before => "<",
213 }
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
219pub enum CursorValue {
220 Int(i64),
222 String(String),
224}
225
226impl From<i32> for CursorValue {
227 fn from(v: i32) -> Self {
228 Self::Int(v as i64)
229 }
230}
231
232impl From<i64> for CursorValue {
233 fn from(v: i64) -> Self {
234 Self::Int(v)
235 }
236}
237
238impl From<String> for CursorValue {
239 fn from(v: String) -> Self {
240 Self::String(v)
241 }
242}
243
244impl From<&str> for CursorValue {
245 fn from(v: &str) -> Self {
246 Self::String(v.to_string())
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
252pub enum CursorDirection {
253 After,
255 Before,
257}
258
259#[derive(Debug, Clone)]
261pub struct PaginatedResult<T> {
262 pub data: Vec<T>,
264 pub has_next: bool,
266 pub has_previous: bool,
268 pub next_cursor: Option<CursorValue>,
270 pub previous_cursor: Option<CursorValue>,
272 pub total_count: Option<u64>,
274}
275
276impl<T> PaginatedResult<T> {
277 pub fn new(data: Vec<T>) -> Self {
279 Self {
280 data,
281 has_next: false,
282 has_previous: false,
283 next_cursor: None,
284 previous_cursor: None,
285 total_count: None,
286 }
287 }
288
289 pub fn with_pagination(
291 mut self,
292 has_next: bool,
293 has_previous: bool,
294 ) -> Self {
295 self.has_next = has_next;
296 self.has_previous = has_previous;
297 self
298 }
299
300 pub fn with_total(mut self, total: u64) -> Self {
302 self.total_count = Some(total);
303 self
304 }
305
306 pub fn with_cursors(
308 mut self,
309 next: Option<CursorValue>,
310 previous: Option<CursorValue>,
311 ) -> Self {
312 self.next_cursor = next;
313 self.previous_cursor = previous;
314 self
315 }
316
317 pub fn len(&self) -> usize {
319 self.data.len()
320 }
321
322 pub fn is_empty(&self) -> bool {
324 self.data.is_empty()
325 }
326}
327
328impl<T> IntoIterator for PaginatedResult<T> {
329 type Item = T;
330 type IntoIter = std::vec::IntoIter<T>;
331
332 fn into_iter(self) -> Self::IntoIter {
333 self.data.into_iter()
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn test_pagination_skip_take() {
343 let pagination = Pagination::new().skip(10).take(20);
344 assert_eq!(pagination.to_sql(), "LIMIT 20 OFFSET 10");
345 }
346
347 #[test]
348 fn test_pagination_page() {
349 let pagination = Pagination::page(3, 10);
350 assert_eq!(pagination.skip, Some(20));
351 assert_eq!(pagination.take, Some(10));
352 }
353
354 #[test]
355 fn test_cursor_after() {
356 let cursor = Cursor::after("id", 100i64);
357 assert_eq!(cursor.to_sql_condition(), "id > $cursor");
358 }
359
360 #[test]
361 fn test_cursor_before() {
362 let cursor = Cursor::before("id", 100i64);
363 assert_eq!(cursor.to_sql_condition(), "id < $cursor");
364 }
365
366 #[test]
367 fn test_paginated_result() {
368 let result = PaginatedResult::new(vec![1, 2, 3])
369 .with_pagination(true, false)
370 .with_total(100);
371
372 assert_eq!(result.len(), 3);
373 assert!(result.has_next);
374 assert!(!result.has_previous);
375 assert_eq!(result.total_count, Some(100));
376 }
377}
378