Skip to main content

remendo/models/
pagination.rs

1//! Generic pagination wrapper for API list responses.
2
3use serde::Deserialize;
4
5/// A paginated list response from the Sashiko API.
6///
7/// Wraps any item type with pagination metadata. Used by both
8/// `GET /api/patchsets` and `GET /api/messages`.
9///
10/// # Examples
11///
12/// ```
13/// use remendo::models::{Paginated, Patchset};
14///
15/// // An empty paginated response
16/// let empty: Paginated<Patchset> = Paginated {
17///     items: vec![],
18///     total: 0,
19///     page: 1,
20///     per_page: 50,
21/// };
22/// assert!(empty.items.is_empty());
23/// assert_eq!(empty.total, 0);
24/// ```
25#[derive(Debug, Clone, PartialEq, Deserialize)]
26pub struct Paginated<T> {
27    /// The items on this page.
28    pub items: Vec<T>,
29    /// Total number of items across all pages.
30    #[serde(default)]
31    pub total: u32,
32    /// Current page number (1-indexed).
33    #[serde(default)]
34    pub page: u32,
35    /// Items per page.
36    #[serde(default)]
37    pub per_page: u32,
38}
39
40impl<T> Paginated<T> {
41    /// Total number of pages derived from `total` and `per_page`.
42    #[must_use]
43    pub fn total_pages(&self) -> u32 {
44        if self.per_page == 0 {
45            return 1;
46        }
47        self.total.div_ceil(self.per_page)
48    }
49}
50
51#[cfg(test)]
52#[allow(clippy::expect_used)]
53mod tests {
54    use super::*;
55    use crate::models::Patchset;
56
57    #[test]
58    fn deserialize_paginated_patchsets() {
59        let json = r#"{
60            "items": [{
61                "id": 19555,
62                "subject": "[PATCH v2 0/4] iio: light: fix null pointer",
63                "status": "Pending",
64                "author": "dev@example.com",
65                "date": 1778690980,
66                "total_parts": 4,
67                "received_parts": 4,
68                "subsystems": ["LKML", "linux-iio"],
69                "findings_low": 0,
70                "findings_medium": 1,
71                "findings_high": 0,
72                "findings_critical": 0
73            }],
74            "total": 19558,
75            "page": 1,
76            "per_page": 50
77        }"#;
78        let paginated: Paginated<Patchset> =
79            serde_json::from_str(json).expect("deserialize paginated");
80        assert_eq!(paginated.items.len(), 1);
81        assert_eq!(paginated.total, 19558);
82        assert_eq!(paginated.page, 1);
83        assert_eq!(paginated.per_page, 50);
84    }
85
86    #[test]
87    fn deserialize_empty_paginated() {
88        let json = r#"{"items": [], "total": 0, "page": 1, "per_page": 50}"#;
89        let paginated: Paginated<Patchset> =
90            serde_json::from_str(json).expect("deserialize empty paginated");
91        assert!(paginated.items.is_empty());
92        assert_eq!(paginated.total, 0);
93    }
94}