1use std::fmt::{Debug, Display};
2
3#[cfg(feature = "serde")]
4use serde::{
5 Deserialize, Serialize,
6 de::{Deserialize as DeDeserialize, Deserializer as DeDeserializer, Error as DeError},
7};
8#[cfg(feature = "utoipa")]
9use utoipa::ToSchema;
10
11use crate::{ErrorKind, PaginationError, PaginationResult};
12
13#[derive(Clone, Debug)]
24#[cfg_attr(feature = "serde", derive(Serialize))]
25#[cfg_attr(feature = "utoipa", derive(ToSchema))]
26pub struct Page<E> {
27 items: Vec<E>,
28 page: usize,
29 size: usize,
30 total: usize,
31 pages: usize,
32 previous_page: Option<usize>,
33 next_page: Option<usize>,
34}
35
36impl<E> Page<E> {
37 pub fn get_items(&self) -> &Vec<E> {
39 &self.items
40 }
41
42 pub fn get_page(&self) -> usize {
44 self.page
45 }
46
47 pub fn get_size(&self) -> usize {
49 self.size
50 }
51
52 pub fn get_total(&self) -> usize {
54 self.total
55 }
56
57 pub fn get_pages(&self) -> usize {
59 self.pages
60 }
61
62 pub fn get_previous_page(&self) -> Option<usize> {
64 self.previous_page
65 }
66
67 pub fn get_next_page(&self) -> Option<usize> {
69 self.next_page
70 }
71
72 fn verify_fields(&self) -> PaginationResult<()> {
88 let items_length: usize = self.get_items().len();
89
90 let expected_pages: usize = match self.get_size().eq(&0) {
92 true => 1,
93 false => self.get_total().div_ceil(self.get_size()).max(1),
94 };
95 if expected_pages.ne(&self.get_pages()) {
96 return Err(PaginationError::from(ErrorKind::InvalidValue(format!(
97 "Total pages error: expected '{}', found '{}'",
98 expected_pages,
99 self.get_pages(),
100 ))));
101 }
102
103 if self.get_page().gt(&(self.get_pages() - 1)) {
105 return Err(PaginationError::from(ErrorKind::InvalidValue(format!(
106 "Page index '{}' exceeds total pages '{}'",
107 self.get_page(),
108 self.get_pages(),
109 ))));
110 }
111
112 if self.get_page().lt(&(self.get_pages() - 1)) && items_length.ne(&self.get_size()) {
114 return Err(PaginationError::from(ErrorKind::InvalidValue(format!(
115 "Items length '{}' is not equal to page size '{}' for an intermediate page '{}'",
116 &items_length,
117 self.get_size(),
118 self.get_page(),
119 ))));
120 }
121
122 if self.get_page().eq(&(self.get_pages() - 1))
124 && self
125 .get_total()
126 .ne(&((self.get_pages() - 1) * self.get_size() + items_length))
127 {
128 return Err(PaginationError::from(ErrorKind::InvalidValue(format!(
129 "Total elements error: expected '{}', found '{}'",
130 (self.get_pages() - 1) * self.get_size() + items_length,
131 self.get_total(),
132 ))));
133 }
134
135 let expected_previous_page: Option<usize> = match self.get_page().eq(&0) {
137 true => None,
138 false => Some(self.get_page() - 1),
139 };
140
141 if expected_previous_page.ne(&self.get_previous_page()) {
142 return Err(PaginationError::from(ErrorKind::InvalidValue(format!(
143 "Previous page index error: expected '{:?}', found '{:?}'",
144 expected_previous_page,
145 self.get_previous_page(),
146 ))));
147 }
148
149 let expected_next_page: Option<usize> = match self.get_page().eq(&(self.get_pages() - 1)) {
151 true => None,
152 false => Some(self.get_page() + 1),
153 };
154
155 if expected_next_page.ne(&self.get_next_page()) {
156 return Err(PaginationError::from(ErrorKind::InvalidValue(format!(
157 "Next page index error: expected '{:?}', found '{:?}'",
158 expected_next_page,
159 self.get_next_page(),
160 ))));
161 }
162
163 Ok(())
164 }
165
166 pub fn new(items: &Vec<E>, page: usize, size: usize, total: usize) -> PaginationResult<Page<E>>
199 where
200 E: Clone,
201 {
202 let pages: usize = match size.eq(&0) {
203 true => 1,
204 false => total.div_ceil(size).max(1),
205 };
206
207 let page: Page<E> = Page {
208 items: items.to_owned(),
209 page,
210 size,
211 total,
212 pages,
213 previous_page: match page.eq(&0) {
214 true => None,
215 false => Some(page - 1),
216 },
217 next_page: match page.eq(&(pages - 1)) {
218 true => None,
219 false => Some(page + 1),
220 },
221 };
222 page.verify_fields()?;
223
224 Ok(page)
225 }
226}
227
228impl<E> Default for Page<E> {
230 fn default() -> Self {
231 Self {
232 items: Vec::new(),
233 page: 0,
234 size: 0,
235 total: 0,
236 pages: 1,
237 previous_page: None,
238 next_page: None,
239 }
240 }
241}
242
243impl<E> Display for Page<E>
245where
246 E: Debug,
247{
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 write!(
250 f,
251 "Page {{ items: {:?}, page: {}, size: {}, total: {}, pages: {}, previous_page: {:?}, next_page: {:?} }}",
252 self.items,
253 self.page,
254 self.size,
255 self.total,
256 self.pages,
257 self.previous_page,
258 self.next_page
259 )
260 }
261}
262
263impl<E> IntoIterator for Page<E> {
265 type Item = E;
266 type IntoIter = std::vec::IntoIter<Self::Item>;
267
268 fn into_iter(self) -> Self::IntoIter {
269 self.items.into_iter()
270 }
271}
272
273#[cfg(feature = "serde")]
275impl<'de, E> DeDeserialize<'de> for Page<E>
276where
277 E: Deserialize<'de>,
278{
279 fn deserialize<D>(deserializer: D) -> Result<Page<E>, D::Error>
280 where
281 D: DeDeserializer<'de>,
282 {
283 #[derive(Deserialize)]
284 struct PageModel<E> {
285 items: Vec<E>,
286 page: usize,
287 size: usize,
288 total: usize,
289 pages: usize,
290 previous_page: Option<usize>,
291 next_page: Option<usize>,
292 }
293
294 let page_model: PageModel<E> = DeDeserialize::deserialize(deserializer)?;
295
296 let page: Page<E> = Page {
297 items: page_model.items,
298 page: page_model.page,
299 size: page_model.size,
300 total: page_model.total,
301 pages: page_model.pages,
302 previous_page: page_model.previous_page,
303 next_page: page_model.next_page,
304 };
305
306 page.verify_fields().map_err(DeError::custom)?;
307
308 Ok(page)
309 }
310}
311
312#[cfg(test)]
313mod test_page_model {
314 use crate::*;
315 use std::vec::IntoIter;
316
317 #[test]
319 fn test_page_model_constructor() {
320 let items: Vec<u32> = vec![2, 3];
321 let page: usize = 1;
322 let size: usize = 2;
323 let total_elements: usize = 5;
324
325 let expected_total_pages: usize = 3;
326 let expected_previous_page: Option<usize> = Some(0);
327 let expected_next_page: Option<usize> = Some(2);
328
329 let pagination_result: PaginationResult<Page<u32>> =
330 Page::new(&items, page, size, total_elements);
331 assert!(pagination_result.is_ok());
332
333 let page_model: Page<u32> = pagination_result.unwrap();
334 assert_eq!(page_model.get_items(), &items);
335 assert_eq!(page_model.get_page(), page);
336 assert_eq!(page_model.get_size(), size);
337 assert_eq!(page_model.get_total(), total_elements);
338 assert_eq!(page_model.get_pages(), expected_total_pages);
339 assert_eq!(page_model.get_previous_page(), expected_previous_page);
340 assert_eq!(page_model.get_next_page(), expected_next_page);
341 }
342
343 #[test]
345 fn test_page_index_exceeds_total_pages() {
346 let items: Vec<u32> = vec![1, 2];
347 let page: usize = 3;
348 let size: usize = 2;
349 let total_elements: usize = 5;
350
351 let pagination_result: PaginationResult<Page<u32>> =
352 Page::new(&items, page, size, total_elements);
353 assert!(pagination_result.is_err());
354
355 let pagination_error: PaginationError = pagination_result.unwrap_err();
356 assert!(
357 pagination_error
358 .to_string()
359 .eq("INVALID VALUE ERROR- Page index '3' exceeds total pages '3'")
360 );
361 }
362
363 #[test]
365 fn test_items_length_not_equal_to_size_for_not_last_page() {
366 let items: Vec<u32> = vec![1, 2, 3, 4];
367 let page: usize = 0;
368 let size: usize = 2;
369 let total_elements: usize = 3;
370
371 let pagination_result: PaginationResult<Page<u32>> =
372 Page::new(&items, page, size, total_elements);
373 assert!(pagination_result.is_err());
374
375 let pagination_error: PaginationError = pagination_result.unwrap_err();
376 assert!(pagination_error
377 .to_string()
378 .eq("INVALID VALUE ERROR- Items length '4' is not equal to page size '2' for an intermediate page '0'",));
379 }
380
381 #[test]
383 fn test_item_length_error_for_intermediate_page_index() {
384 let items: Vec<u32> = vec![1];
385 let page: usize = 0;
386 let size: usize = 2;
387 let total_elements: usize = 3;
388
389 let pagination_result: PaginationResult<Page<u32>> =
390 Page::new(&items, page, size, total_elements);
391 assert!(pagination_result.is_err());
392
393 let pagination_error: PaginationError = pagination_result.unwrap_err();
394 assert!(pagination_error
395 .to_string()
396 .eq("INVALID VALUE ERROR- Items length '1' is not equal to page size '2' for an intermediate page '0'"));
397 }
398
399 #[test]
401 fn test_page_model_into_iter() {
402 let items: Vec<u32> = vec![1, 2];
403 let page: usize = 0;
404 let size: usize = 2;
405 let total_elements: usize = 5;
406
407 let pagination_result: PaginationResult<Page<u32>> =
408 Page::new(&items, page, size, total_elements);
409 assert!(pagination_result.is_ok());
410
411 let page_model: Page<u32> = pagination_result.unwrap();
412 let mut iter: IntoIter<u32> = page_model.into_iter();
413 assert_eq!(iter.next(), Some(1));
414 assert_eq!(iter.next(), Some(2));
415 assert_eq!(iter.next(), None);
416 }
417
418 #[test]
420 fn test_page_model_from_size_equals_to_0() {
421 let items: Vec<u32> = vec![1, 2];
422 let page: usize = 0;
423 let size: usize = 0;
424 let total_elements: usize = 5;
425
426 let pagination_result: PaginationResult<Page<u32>> =
427 Page::new(&items, page, size, total_elements);
428 assert!(pagination_result.is_err());
429
430 let pagination_error: PaginationError = pagination_result.unwrap_err();
431 assert!(
432 pagination_error
433 .to_string()
434 .eq("INVALID VALUE ERROR- Total elements error: expected '2', found '5'")
435 );
436 }
437
438 #[test]
440 fn test_default_page_model_constructor() {
441 let expected_items: Vec<u32> = vec![];
442 let expected_page: usize = 0;
443 let expected_size: usize = 0;
444 let expected_total_elements: usize = 0;
445 let expected_total_pages: usize = 1;
446 let expected_previous_page: Option<usize> = None;
447 let expected_next_page: Option<usize> = None;
448
449 let page_model: Page<u32> = Page::default();
450
451 assert_eq!(page_model.get_items(), &expected_items);
452 assert_eq!(page_model.get_page(), expected_page);
453 assert_eq!(page_model.get_size(), expected_size);
454 assert_eq!(page_model.get_total(), expected_total_elements);
455 assert_eq!(page_model.get_pages(), expected_total_pages);
456 assert_eq!(page_model.get_previous_page(), expected_previous_page);
457 assert_eq!(page_model.get_next_page(), expected_next_page);
458 }
459
460 #[test]
462 fn test_page_model_display() {
463 let items: Vec<u32> = vec![1, 2];
464 let page: usize = 0;
465 let size: usize = 2;
466 let total_elements: usize = 5;
467
468 let pagination_result: PaginationResult<Page<u32>> =
469 Page::new(&items, page, size, total_elements);
470 assert!(pagination_result.is_ok());
471
472 let page_model: Page<u32> = pagination_result.unwrap();
473 let page_model_display: String = format!("{}", page_model);
474 assert!(page_model_display.eq("Page { items: [1, 2], page: 0, size: 2, total: 5, pages: 3, previous_page: None, next_page: Some(1) }"));
475 }
476
477 #[test]
479 fn test_page_model_debug() {
480 let items: Vec<u32> = vec![1, 2];
481 let page: usize = 0;
482 let size: usize = 2;
483 let total_elements: usize = 5;
484
485 let pagination_result: PaginationResult<Page<u32>> =
486 Page::new(&items, page, size, total_elements);
487 assert!(pagination_result.is_ok());
488
489 let page_model: Page<u32> = pagination_result.unwrap();
490 let page_model_debug: String = format!("{:?}", page_model);
491 assert!(page_model_debug.eq("Page { items: [1, 2], page: 0, size: 2, total: 5, pages: 3, previous_page: None, next_page: Some(1) }"));
492 }
493
494 #[test]
496 fn test_page_model_clone() {
497 let items: Vec<u32> = vec![5];
498 let page: usize = 2;
499 let size: usize = 2;
500 let total_elements: usize = 5;
501
502 let pagination_result: PaginationResult<Page<u32>> =
503 Page::new(&items, page, size, total_elements);
504 assert!(pagination_result.is_ok());
505
506 let page_model: Page<u32> = pagination_result.unwrap();
507 let cloned_page_model: Page<u32> = page_model.clone();
508 assert_eq!(cloned_page_model.get_items(), page_model.get_items());
509 assert_eq!(cloned_page_model.get_page(), page_model.get_page());
510 assert_eq!(cloned_page_model.get_size(), page_model.get_size());
511 assert_eq!(cloned_page_model.get_total(), page_model.get_total());
512 assert_eq!(cloned_page_model.get_pages(), page_model.get_pages());
513 assert_eq!(
514 cloned_page_model.get_previous_page(),
515 page_model.get_previous_page()
516 );
517 assert_eq!(
518 cloned_page_model.get_next_page(),
519 page_model.get_next_page()
520 );
521 }
522
523 #[cfg(feature = "serde")]
525 #[test]
526 fn test_page_model_serialization_and_deserialization() {
527 use serde::{Deserialize, Serialize};
528
529 #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
530 struct Person {
531 name: String,
532 age: u8,
533 }
534
535 let items: Vec<Person> = vec![
536 Person {
537 name: "John".to_string(),
538 age: 20,
539 },
540 Person {
541 name: "Jane".to_string(),
542 age: 25,
543 },
544 ];
545 let page: usize = 0;
546 let size: usize = 2;
547 let total_elements: usize = 5;
548 let total_pages: usize = 3;
549 let previous_page: Option<usize> = None;
550 let next_page: Option<usize> = Some(1);
551
552 let pagination_result: PaginationResult<Page<Person>> =
553 Page::new(&items, page, size, total_elements);
554 assert!(pagination_result.is_ok());
555
556 let page_model: Page<Person> = pagination_result.unwrap();
557
558 let serialized: String = serde_json::to_string(&page_model).unwrap();
559 assert!(serialized.eq("{\"items\":[{\"name\":\"John\",\"age\":20},{\"name\":\"Jane\",\"age\":25}],\"page\":0,\"size\":2,\"total\":5,\"pages\":3,\"previous_page\":null,\"next_page\":1}"));
560
561 let deserialized: Page<Person> = serde_json::from_str(&serialized).unwrap();
562 assert_eq!(deserialized.get_items(), &items);
563 assert_eq!(deserialized.get_page(), page);
564 assert_eq!(deserialized.get_size(), size);
565 assert_eq!(deserialized.get_total(), total_elements);
566 assert_eq!(deserialized.get_pages(), total_pages);
567 assert_eq!(deserialized.get_previous_page(), previous_page);
568 assert_eq!(deserialized.get_next_page(), next_page);
569 }
570
571 #[cfg(feature = "serde")]
573 #[test]
574 fn test_page_model_deserialization_with_invalid_pages() {
575 use serde::{Deserialize, Serialize};
576 use serde_json::Error as SerdeJsonError;
577
578 #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
579 struct Person {
580 name: String,
581 age: u8,
582 }
583
584 let serialized: String = "{\"items\":[{\"name\":\"John\",\"age\":20},{\"name\":\"Jane\",\"age\":25}],\"page\":0,\"size\":2,\"total\":5,\"pages\":0,\"previous_page\":null,\"next_page\":1}".to_string();
585 let deserialized: Result<Page<Person>, serde_json::Error> =
586 serde_json::from_str(&serialized);
587
588 assert!(deserialized.is_err());
589
590 let error: SerdeJsonError = deserialized.unwrap_err();
591 assert_eq!(
592 error.to_string(),
593 "INVALID VALUE ERROR- Total pages error: expected '3', found '0'"
594 );
595 }
596
597 #[cfg(feature = "serde")]
599 #[test]
600 fn test_page_model_deserialization_with_invalid_previous_page() {
601 use serde::{Deserialize, Serialize};
602 use serde_json::Error as SerdeJsonError;
603
604 #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
605 struct Person {
606 name: String,
607 age: u8,
608 }
609
610 let serialized: String = "{\"items\":[{\"name\":\"John\",\"age\":20},{\"name\":\"Jane\",\"age\":25}],\"page\":0,\"size\":2,\"total\":5,\"pages\":3,\"previous_page\":2,\"next_page\":1}".to_string();
611 let deserialized: Result<Page<Person>, serde_json::Error> =
612 serde_json::from_str(&serialized);
613
614 assert!(deserialized.is_err());
615
616 let error: SerdeJsonError = deserialized.unwrap_err();
617 assert_eq!(
618 error.to_string(),
619 "INVALID VALUE ERROR- Previous page index error: expected 'None', found 'Some(2)'"
620 );
621 }
622
623 #[cfg(feature = "serde")]
625 #[test]
626 fn test_page_model_deserialization_with_invalid_next_page() {
627 use serde::{Deserialize, Serialize};
628 use serde_json::Error as SerdeJsonError;
629
630 #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
631 struct Person {
632 name: String,
633 age: u8,
634 }
635
636 let serialized: String = "{\"items\":[{\"name\":\"John\",\"age\":20},{\"name\":\"Jane\",\"age\":25}],\"page\":0,\"size\":2,\"total\":5,\"pages\":3,\"previous_page\":null,\"next_page\":2}".to_string();
637 let deserialized: Result<Page<Person>, serde_json::Error> =
638 serde_json::from_str(&serialized);
639
640 assert!(deserialized.is_err());
641
642 let error: SerdeJsonError = deserialized.unwrap_err();
643 assert_eq!(
644 error.to_string(),
645 "INVALID VALUE ERROR- Next page index error: expected 'Some(1)', found 'Some(2)'"
646 );
647 }
648
649 #[cfg(feature = "serde")]
651 #[test]
652 fn test_page_deserialization_error() {
653 use serde_json::json;
654
655 let invalid_json = json!({
656 "items": ["item1", "item2"],
657 "page": "invalid_page",
658 "size": 2,
659 "total": 2,
660 "pages": 1,
661 "previous_page": null,
662 "next_page": null
663 });
664
665 let result: Result<Page<String>, _> = serde_json::from_value(invalid_json);
666
667 assert!(result.is_err());
668 }
669
670 #[cfg(feature = "utoipa")]
672 #[test]
673 fn test_page_to_schema() {
674 use utoipa::{
675 PartialSchema, ToSchema,
676 openapi::{RefOr, Schema},
677 };
678
679 #[derive(Clone, ToSchema)]
680 #[allow(dead_code)]
681 struct Record {
682 number: u8,
683 }
684
685 let schema_object: RefOr<Schema> = Page::<Record>::schema();
686
687 let json_string: String = serde_json::to_string(&schema_object).unwrap();
688 assert_eq!(
689 json_string,
690 "{\"type\":\"object\",\"description\":\"Model to represent paginated items.\\n\\n#### Fields:\\n- **items**: Represents the items in a [`Page`] as a [`Vec`] of `E`.\\n- **page**: Represents the page index in a [`Page`]. It starts from 0 to ***pages*** - 1.\\n- **size**: Represents the maximum number of elements per [`Page`]. ***items*** length must be equal to ***size*** for all pages except the last page, when ***items*** length could be less than or equal to ***size***.\\n- **total**: Represents the total number of records used for pagination.\\n- **pages**: Represents the total number of pages required for paginate the items.\\n- **previous_page**: Represents the previous page index in a [`Page`]. If there is no previous page, it will be [`None`].\\n- **next_page**: Represents the next page index in a [`Page`]. If there is no next page, it will be [`None`].\",\"required\":[\"items\",\"page\",\"size\",\"total\",\"pages\"],\"properties\":{\"items\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Record\"}},\"next_page\":{\"type\":[\"integer\",\"null\"],\"minimum\":0},\"page\":{\"type\":\"integer\",\"minimum\":0},\"pages\":{\"type\":\"integer\",\"minimum\":0},\"previous_page\":{\"type\":[\"integer\",\"null\"],\"minimum\":0},\"size\":{\"type\":\"integer\",\"minimum\":0},\"total\":{\"type\":\"integer\",\"minimum\":0}}}"
691 );
692 }
693}