Skip to main content

rs_zero/rest/
response.rs

1use axum::{Json, http::StatusCode, response::IntoResponse};
2use serde::Serialize;
3
4/// Uniform API response used by rs-zero REST services.
5#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
6pub struct ApiResponse<T>
7where
8    T: Serialize,
9{
10    pub success: bool,
11    pub err_code: String,
12    pub err_message: String,
13    pub data: Option<T>,
14}
15
16impl<T> ApiResponse<T>
17where
18    T: Serialize,
19{
20    /// Creates a successful response.
21    pub fn success(data: T) -> Self {
22        Self {
23            success: true,
24            err_code: "0".to_string(),
25            err_message: String::new(),
26            data: Some(data),
27        }
28    }
29
30    /// Creates a successful response without data.
31    pub fn success_no_data() -> Self {
32        Self {
33            success: true,
34            err_code: "0".to_string(),
35            err_message: String::new(),
36            data: None,
37        }
38    }
39
40    /// Creates a failed response.
41    pub fn fail(code: impl Into<String>, message: impl Into<String>) -> Self {
42        Self {
43            success: false,
44            err_code: code.into(),
45            err_message: message.into(),
46            data: None,
47        }
48    }
49}
50
51impl<T> IntoResponse for ApiResponse<T>
52where
53    T: Serialize,
54{
55    fn into_response(self) -> axum::response::Response {
56        (StatusCode::OK, Json(self)).into_response()
57    }
58}
59
60/// Uniform paginated response.
61#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
62pub struct PageResponse<T>
63where
64    T: Serialize,
65{
66    pub success: bool,
67    pub err_code: String,
68    pub err_message: String,
69    pub data: Vec<T>,
70    pub total: i64,
71    pub page_size: i64,
72    pub has_more: bool,
73    pub current_page: i64,
74    pub total_pages: i64,
75}
76
77impl<T> PageResponse<T>
78where
79    T: Serialize,
80{
81    /// Creates a successful paginated response.
82    pub fn success(data: Vec<T>, total: i64, page_size: i64, current_page: i64) -> Self {
83        Self {
84            success: true,
85            err_code: "0".to_string(),
86            err_message: String::new(),
87            data,
88            total,
89            page_size,
90            has_more: total > page_size * current_page,
91            current_page,
92            total_pages: if page_size <= 0 {
93                0
94            } else {
95                (total + page_size - 1) / page_size
96            },
97        }
98    }
99
100    /// Creates an empty successful page.
101    pub fn empty() -> Self {
102        Self {
103            success: true,
104            err_code: "0".to_string(),
105            err_message: String::new(),
106            data: Vec::new(),
107            total: 0,
108            page_size: 0,
109            has_more: false,
110            current_page: 0,
111            total_pages: 0,
112        }
113    }
114}
115
116impl<T> IntoResponse for PageResponse<T>
117where
118    T: Serialize,
119{
120    fn into_response(self) -> axum::response::Response {
121        (StatusCode::OK, Json(self)).into_response()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::{ApiResponse, PageResponse};
128
129    #[test]
130    fn response_success_uses_zero_error_code() {
131        let response = ApiResponse::success("ok");
132
133        assert!(response.success);
134        assert_eq!(response.err_code, "0");
135        assert_eq!(response.data, Some("ok"));
136    }
137
138    #[test]
139    fn page_response_calculates_total_pages() {
140        let response = PageResponse::success(vec![1, 2], 11, 5, 2);
141
142        assert!(response.has_more);
143        assert_eq!(response.total_pages, 3);
144    }
145}