Skip to main content

axum_api_kit/
list.rs

1use axum::{
2    response::{IntoResponse, Response},
3    Json,
4};
5use serde::Serialize;
6
7/// A generic paginated collection response.
8///
9/// Serializes as:
10/// ```json
11/// { "data": [...], "total": 42, "limit": 50, "offset": 0 }
12/// ```
13///
14/// # Example
15///
16/// ```rust
17/// use axum::response::IntoResponse;
18/// use axum_api_kit::ListResponse;
19/// use serde::Serialize;
20///
21/// #[derive(Serialize)]
22/// struct Item { id: String }
23///
24/// async fn list_items() -> impl IntoResponse {
25///     ListResponse {
26///         data: vec![Item { id: "1".into() }],
27///         total: 1,
28///         limit: 50,
29///         offset: 0,
30///     }
31/// }
32/// ```
33#[derive(Debug, Clone, Serialize)]
34pub struct ListResponse<T: Serialize> {
35    /// The items in this page.
36    pub data: Vec<T>,
37    /// Total number of matching items across all pages.
38    pub total: i64,
39    /// Maximum number of items per page (as requested).
40    pub limit: u32,
41    /// Zero-based offset of the first item in this page.
42    pub offset: u32,
43}
44
45impl<T: Serialize> IntoResponse for ListResponse<T> {
46    fn into_response(self) -> Response {
47        Json(self).into_response()
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use serde_json::json;
55
56    #[derive(Serialize)]
57    struct Item {
58        id: String,
59    }
60
61    #[test]
62    fn serializes_fields_correctly() {
63        let resp = ListResponse {
64            data: vec![Item { id: "abc".into() }],
65            total: 10,
66            limit: 25,
67            offset: 0,
68        };
69        let v = serde_json::to_value(&resp).unwrap();
70        assert_eq!(v["total"], 10);
71        assert_eq!(v["limit"], 25);
72        assert_eq!(v["offset"], 0);
73        assert_eq!(v["data"][0]["id"], "abc");
74    }
75
76    #[test]
77    fn empty_data_serializes() {
78        let resp: ListResponse<Item> = ListResponse {
79            data: vec![],
80            total: 0,
81            limit: 50,
82            offset: 0,
83        };
84        let v = serde_json::to_value(&resp).unwrap();
85        assert_eq!(v["data"], json!([]));
86        assert_eq!(v["total"], 0);
87    }
88
89    #[test]
90    fn offset_pagination_fields() {
91        let resp: ListResponse<Item> = ListResponse {
92            data: vec![],
93            total: 100,
94            limit: 10,
95            offset: 30,
96        };
97        let v = serde_json::to_value(&resp).unwrap();
98        assert_eq!(v["limit"], 10);
99        assert_eq!(v["offset"], 30);
100    }
101}