lmrc_http_common/
response.rs1use axum::{
4 http::StatusCode,
5 response::{IntoResponse, Response},
6 Json,
7};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct SuccessResponse<T> {
13 pub success: bool,
15 pub data: T,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub meta: Option<serde_json::Value>,
20}
21
22impl<T> SuccessResponse<T> {
23 pub fn new(data: T) -> Self {
24 Self {
25 success: true,
26 data,
27 meta: None,
28 }
29 }
30
31 pub fn with_meta(mut self, meta: serde_json::Value) -> Self {
32 self.meta = Some(meta);
33 self
34 }
35}
36
37impl<T> IntoResponse for SuccessResponse<T>
38where
39 T: Serialize,
40{
41 fn into_response(self) -> Response {
42 Json(self).into_response()
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct PaginatedResponse<T> {
49 pub success: bool,
51 pub data: Vec<T>,
53 pub pagination: PaginationMeta,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct PaginationMeta {
59 pub page: u64,
61 pub per_page: u64,
63 pub total: u64,
65 pub total_pages: u64,
67 pub has_next: bool,
69 pub has_prev: bool,
71}
72
73impl PaginationMeta {
74 pub fn new(page: u64, per_page: u64, total: u64) -> Self {
75 let total_pages = (total + per_page - 1) / per_page.max(1);
76 Self {
77 page,
78 per_page,
79 total,
80 total_pages,
81 has_next: page < total_pages,
82 has_prev: page > 1,
83 }
84 }
85}
86
87impl<T> PaginatedResponse<T> {
88 pub fn new(data: Vec<T>, page: u64, per_page: u64, total: u64) -> Self {
89 Self {
90 success: true,
91 data,
92 pagination: PaginationMeta::new(page, per_page, total),
93 }
94 }
95}
96
97impl<T> IntoResponse for PaginatedResponse<T>
98where
99 T: Serialize,
100{
101 fn into_response(self) -> Response {
102 Json(self).into_response()
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct EmptyResponse {
109 pub success: bool,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub message: Option<String>,
112}
113
114impl EmptyResponse {
115 pub fn new() -> Self {
116 Self {
117 success: true,
118 message: None,
119 }
120 }
121
122 pub fn with_message(mut self, message: impl Into<String>) -> Self {
123 self.message = Some(message.into());
124 self
125 }
126}
127
128impl Default for EmptyResponse {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134impl IntoResponse for EmptyResponse {
135 fn into_response(self) -> Response {
136 Json(self).into_response()
137 }
138}
139
140pub struct CreatedResponse<T> {
142 pub data: T,
143 pub location: Option<String>,
144}
145
146impl<T> CreatedResponse<T> {
147 pub fn new(data: T) -> Self {
148 Self {
149 data,
150 location: None,
151 }
152 }
153
154 pub fn with_location(mut self, location: impl Into<String>) -> Self {
155 self.location = Some(location.into());
156 self
157 }
158}
159
160impl<T> IntoResponse for CreatedResponse<T>
161where
162 T: Serialize,
163{
164 fn into_response(self) -> Response {
165 let mut response = (StatusCode::CREATED, Json(SuccessResponse::new(self.data))).into_response();
166
167 if let Some(location) = self.location
168 && let Ok(header_value) = location.parse() {
169 response.headers_mut().insert("Location", header_value);
170 }
171
172 response
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_pagination_meta() {
182 let meta = PaginationMeta::new(2, 10, 45);
183 assert_eq!(meta.page, 2);
184 assert_eq!(meta.per_page, 10);
185 assert_eq!(meta.total, 45);
186 assert_eq!(meta.total_pages, 5);
187 assert!(meta.has_next);
188 assert!(meta.has_prev);
189
190 let first_page = PaginationMeta::new(1, 10, 45);
191 assert!(!first_page.has_prev);
192 assert!(first_page.has_next);
193
194 let last_page = PaginationMeta::new(5, 10, 45);
195 assert!(last_page.has_prev);
196 assert!(!last_page.has_next);
197 }
198
199 #[test]
200 fn test_empty_response() {
201 let resp = EmptyResponse::new();
202 assert!(resp.success);
203 assert!(resp.message.is_none());
204
205 let resp = EmptyResponse::new().with_message("Resource deleted");
206 assert_eq!(resp.message, Some("Resource deleted".to_string()));
207 }
208}