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)]
34#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
35pub struct ListResponse<T: Serialize> {
36    /// The items in this page.
37    pub data: Vec<T>,
38    /// Total number of matching items across all pages.
39    pub total: i64,
40    /// Maximum number of items per page (as requested).
41    pub limit: u32,
42    /// Zero-based offset of the first item in this page.
43    pub offset: u32,
44}
45
46impl<T: Serialize> IntoResponse for ListResponse<T> {
47    fn into_response(self) -> Response {
48        Json(self).into_response()
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use serde_json::json;
56
57    #[derive(Serialize)]
58    struct Item {
59        id: String,
60    }
61
62    #[test]
63    fn serializes_fields_correctly() {
64        let resp = ListResponse {
65            data: vec![Item { id: "abc".into() }],
66            total: 10,
67            limit: 25,
68            offset: 0,
69        };
70        let v = serde_json::to_value(&resp).unwrap();
71        assert_eq!(v["total"], 10);
72        assert_eq!(v["limit"], 25);
73        assert_eq!(v["offset"], 0);
74        assert_eq!(v["data"][0]["id"], "abc");
75    }
76
77    #[test]
78    fn empty_data_serializes() {
79        let resp: ListResponse<Item> = ListResponse {
80            data: vec![],
81            total: 0,
82            limit: 50,
83            offset: 0,
84        };
85        let v = serde_json::to_value(&resp).unwrap();
86        assert_eq!(v["data"], json!([]));
87        assert_eq!(v["total"], 0);
88    }
89
90    #[test]
91    fn offset_pagination_fields() {
92        let resp: ListResponse<Item> = ListResponse {
93            data: vec![],
94            total: 100,
95            limit: 10,
96            offset: 30,
97        };
98        let v = serde_json::to_value(&resp).unwrap();
99        assert_eq!(v["limit"], 10);
100        assert_eq!(v["offset"], 30);
101    }
102}