Skip to main content

sqlx_data_params/
response.rs

1#[cfg(feature = "json")]
2use serde::{Deserialize, Serialize};
3
4///This is Result<T,sqlx::Error>
5#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
6pub type Result<T, E = sqlx_data_integration::Error> = ::std::result::Result<T, E>;
7
8///This is Result<T,Cursor::Error> - just to justificate any feature
9#[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))]
10pub type Result<T, E = crate::cursor::CursorError> = ::std::result::Result<T, E>;
11
12/// Serial pagination response - classic page-based pagination with total count
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
15pub struct Serial<T> {
16    pub data: Vec<T>,
17    pub page: u32,
18    pub size: u32,
19    pub total_items: i64,
20    pub total_pages: u32,
21}
22
23/// Slice pagination response - efficient pagination without total count
24#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
26pub struct Slice<T> {
27    pub data: Vec<T>,
28    pub page: u32,
29    pub size: u32,
30    pub has_next: bool,
31    pub has_previous: bool,
32    pub total_items: Option<i64>, // Optional total count
33}
34
35/// Cursor pagination response - cursor-based pagination for large datasets
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
38pub struct Cursor<T> {
39    pub data: Vec<T>,
40    pub per_page: u32,
41    pub has_next: bool,
42    pub has_prev: bool,
43    pub next_cursor: Option<String>,
44    pub prev_cursor: Option<String>,
45}
46
47impl<T> Serial<T> {
48    const DEFAULT_LIMIT: u32 = 20;
49
50    pub fn new(data: Vec<T>, params: &crate::Params, total_elements: i64) -> Self {
51        let Some(crate::pagination::Pagination::Serial(serial)) = &params.pagination else {
52            let default_size = Self::page_size(params);
53            // Will never reach here...
54            return Self {
55                data,
56                page: 1,
57                size: default_size,
58                total_items: total_elements,
59                total_pages: 1,
60            };
61        };
62
63        let size = serial.limit().max(1);
64        let total_pages = Self::calculate_total_pages(total_elements, size);
65
66        Self {
67            data,
68            page: serial.page(),
69            size,
70            total_items: total_elements,
71            total_pages,
72        }
73    }
74
75    #[inline]
76    fn calculate_total_pages(total_items: i64, size: u32) -> u32 {
77        let size = size as i64;
78
79        total_items
80            .checked_add(size - 1)
81            .and_then(|sum| sum.checked_div(size))
82            .and_then(|pages| u32::try_from(pages.max(0)).ok())
83            .unwrap_or(1)
84    }
85
86    #[inline]
87    fn page_size(params: &crate::Params) -> u32 {
88        params
89            .limit
90            .as_ref()
91            .map(|l| l.0)
92            .unwrap_or(Self::DEFAULT_LIMIT)
93            .max(1)
94    }
95}
96
97impl<T> Slice<T> {
98    const DEFAULT_LIMIT: u32 = 20;
99
100    pub fn new(mut data: Vec<T>, params: &crate::Params, total_elements: i64) -> Self {
101        let size = Self::page_size(params);
102        let has_next = Self::trim_to_page(&mut data, size);
103
104        if let Some(crate::pagination::Pagination::Slice(slice)) = &params.pagination {
105            let page = slice.page();
106
107            return Self {
108                data,
109                page,
110                size,
111                has_next,
112                has_previous: page > 1,
113                total_items: (!slice.disable_total_count()).then_some(total_elements),
114            };
115        }
116
117        // Fallback: offset/limit
118        let page = Self::page_offset(params, size);
119
120        Self {
121            data,
122            page,
123            size,
124            has_next,
125            has_previous: page > 1,
126            total_items: Some(total_elements),
127        }
128    }
129
130    #[inline]
131    fn page_size(params: &crate::Params) -> u32 {
132        params
133            .limit
134            .as_ref()
135            .map(|l| l.0)
136            .unwrap_or(Self::DEFAULT_LIMIT)
137            .max(1)
138    }
139
140    #[inline]
141    fn page_offset(params: &crate::Params, size: u32) -> u32 {
142        params
143            .offset
144            .as_ref()
145            .map(|o| o.0 as i64)
146            .and_then(|offset| offset.checked_div(size as i64))
147            .and_then(|p| p.checked_add(1))
148            .unwrap_or(1) as u32
149    }
150
151    #[inline]
152    fn trim_to_page(data: &mut Vec<T>, size: u32) -> bool {
153        let size = size as usize;
154
155        if data.len() > size {
156            data.truncate(size);
157            return true;
158        }
159        false
160    }
161}
162
163impl<T: crate::CursorSecureExtract> Cursor<T> {
164    const DEFAULT_LIMIT: u32 = 20;
165
166    pub fn new(mut data: Vec<T>, params: &crate::Params) -> Result<Self> {
167        // Extract sorting params - cursor pagination requires ORDER BY
168        let sort = params.sort_by.as_ref().ok_or_else(|| {
169            crate::cursor::CursorError::decode_error(
170                "Cursor pagination requires ORDER BY (sort_by)",
171            )
172        })?;
173
174        let Some(crate::pagination::Pagination::Cursor(cursor)) = &params.pagination else {
175            #[allow(clippy::useless_conversion)]
176            return Err(crate::cursor::CursorError::decode_error("Cursor params is not present").into());
177        };
178
179        let is_backward = cursor.direction == Some(crate::cursor::CursorDirection::Before);
180        let requested_limit = Self::page_size(params);
181        
182        let has_more = data.len() > requested_limit as usize;
183        
184        if has_more {
185            data.truncate(requested_limit as usize);
186        }
187        
188        let had_prev = !cursor.is_empty(); //had previous cursor
189
190        let (next_cursor, prev_cursor) =
191            Self::generate_cursors(cursor, &data, has_more, had_prev, sort)?;
192
193        if is_backward {
194            data.reverse();
195        }
196
197        Ok(Self {
198            data,
199            per_page: requested_limit,
200            has_next: if is_backward { had_prev } else { has_more },
201            has_prev: if is_backward { has_more } else { had_prev },
202            next_cursor,
203            prev_cursor,
204        })
205    }
206
207    #[inline]
208    fn page_size(params: &crate::Params) -> u32 {
209        params
210            .limit
211            .as_ref()
212            .map(|l| l.0)
213            .unwrap_or(Self::DEFAULT_LIMIT)
214            .max(1)
215    }
216
217    #[inline]
218    fn generate_cursors(
219        cursor: &crate::pagination::CursorParams,
220        data: &[T],
221        has_more_pages: bool,
222        had_previous_cursor: bool,
223        sorting_params: &crate::sort::SortingParams,
224    ) -> Result<(Option<String>, Option<String>)> {
225        if data.is_empty() {
226            return Ok((None, None));
227        }
228
229        let next = if has_more_pages {
230            cursor.generate_next_cursor(data, has_more_pages, sorting_params)?
231        } else {
232            None
233        };
234
235        let prev = if had_previous_cursor {
236            cursor.generate_prev_cursor(data, had_previous_cursor, sorting_params)?
237        } else {
238            None
239        };
240
241        Ok((next, prev))
242    }
243}