1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Deserialize)]
7pub struct PaginationParams {
8 #[serde(default = "default_page")]
10 pub page: u32,
11
12 #[serde(default = "default_page_size")]
14 pub page_size: u32,
15
16 pub sort_by: Option<String>,
18
19 #[serde(default)]
21 pub sort_order: SortOrder,
22}
23
24fn default_page() -> u32 {
25 1
26}
27
28fn default_page_size() -> u32 {
29 20
30}
31
32#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize)]
34#[serde(rename_all = "lowercase")]
35pub enum SortOrder {
36 #[default]
37 Asc,
38 Desc,
39}
40
41impl PaginationParams {
42 pub fn limit(&self) -> u32 {
44 self.page_size.min(100) }
46
47 pub fn offset(&self) -> u32 {
49 (self.page.saturating_sub(1)) * self.limit()
50 }
51
52 pub fn validate(&self) -> Result<(), String> {
54 if self.page == 0 {
55 return Err("Page must be greater than 0".to_string());
56 }
57
58 if self.page_size == 0 {
59 return Err("Page size must be greater than 0".to_string());
60 }
61
62 if self.page_size > 100 {
63 return Err("Page size cannot exceed 100".to_string());
64 }
65
66 Ok(())
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct PaginatedResponse<T> {
73 pub data: Vec<T>,
75
76 pub pagination: PaginationMetadata,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct PaginationMetadata {
83 pub page: u32,
85
86 pub page_size: u32,
88
89 pub total_items: u64,
91
92 pub total_pages: u32,
94
95 pub has_next: bool,
97
98 pub has_prev: bool,
100}
101
102impl PaginationMetadata {
103 pub fn new(page: u32, page_size: u32, total_items: u64) -> Self {
105 let total_pages = ((total_items as f64) / (page_size as f64)).ceil() as u32;
106 let has_next = page < total_pages;
107 let has_prev = page > 1;
108
109 Self {
110 page,
111 page_size,
112 total_items,
113 total_pages,
114 has_next,
115 has_prev,
116 }
117 }
118}
119
120impl<T> PaginatedResponse<T> {
121 pub fn new(data: Vec<T>, params: &PaginationParams, total_items: u64) -> Self {
123 Self {
124 data,
125 pagination: PaginationMetadata::new(params.page, params.page_size, total_items),
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_pagination_params_defaults() {
136 let params = PaginationParams {
137 page: default_page(),
138 page_size: default_page_size(),
139 sort_by: None,
140 sort_order: SortOrder::default(),
141 };
142
143 assert_eq!(params.page, 1);
144 assert_eq!(params.page_size, 20);
145 assert_eq!(params.limit(), 20);
146 assert_eq!(params.offset(), 0);
147 }
148
149 #[test]
150 fn test_pagination_offset_calculation() {
151 let params = PaginationParams {
152 page: 3,
153 page_size: 10,
154 sort_by: None,
155 sort_order: SortOrder::Asc,
156 };
157
158 assert_eq!(params.offset(), 20); assert_eq!(params.limit(), 10);
160 }
161
162 #[test]
163 fn test_pagination_max_page_size() {
164 let params = PaginationParams {
165 page: 1,
166 page_size: 200, sort_by: None,
168 sort_order: SortOrder::Asc,
169 };
170
171 assert_eq!(params.limit(), 100); }
173
174 #[test]
175 fn test_pagination_validation() {
176 let valid_params = PaginationParams {
177 page: 1,
178 page_size: 20,
179 sort_by: None,
180 sort_order: SortOrder::Asc,
181 };
182 assert!(valid_params.validate().is_ok());
183
184 let invalid_page = PaginationParams {
185 page: 0,
186 page_size: 20,
187 sort_by: None,
188 sort_order: SortOrder::Asc,
189 };
190 assert!(invalid_page.validate().is_err());
191
192 let invalid_page_size = PaginationParams {
193 page: 1,
194 page_size: 0,
195 sort_by: None,
196 sort_order: SortOrder::Asc,
197 };
198 assert!(invalid_page_size.validate().is_err());
199 }
200
201 #[test]
202 fn test_pagination_metadata() {
203 let metadata = PaginationMetadata::new(2, 10, 45);
204
205 assert_eq!(metadata.page, 2);
206 assert_eq!(metadata.page_size, 10);
207 assert_eq!(metadata.total_items, 45);
208 assert_eq!(metadata.total_pages, 5); assert!(metadata.has_next);
210 assert!(metadata.has_prev);
211 }
212
213 #[test]
214 fn test_paginated_response_creation() {
215 let data = vec![1, 2, 3];
216 let params = PaginationParams {
217 page: 1,
218 page_size: 10,
219 sort_by: None,
220 sort_order: SortOrder::Asc,
221 };
222
223 let response = PaginatedResponse::new(data.clone(), ¶ms, 100);
224
225 assert_eq!(response.data.len(), 3);
226 assert_eq!(response.pagination.total_items, 100);
227 assert_eq!(response.pagination.total_pages, 10);
228 assert!(response.pagination.has_next);
229 assert!(!response.pagination.has_prev);
230 }
231}