1pub mod archive;
52pub mod jobs;
53pub mod queues;
54pub mod stats;
55pub mod system;
56
57use serde::{Deserialize, Serialize};
58
59#[derive(Debug, Serialize)]
61pub struct ApiResponse<T> {
62 pub success: bool,
63 pub data: Option<T>,
64 pub error: Option<String>,
65 pub timestamp: chrono::DateTime<chrono::Utc>,
66}
67
68impl<T> ApiResponse<T> {
69 pub fn success(data: T) -> Self {
70 Self {
71 success: true,
72 data: Some(data),
73 error: None,
74 timestamp: chrono::Utc::now(),
75 }
76 }
77
78 pub fn error(message: String) -> Self {
79 Self {
80 success: false,
81 data: None,
82 error: Some(message),
83 timestamp: chrono::Utc::now(),
84 }
85 }
86}
87
88#[derive(Debug, Deserialize)]
90pub struct PaginationParams {
91 pub page: Option<u32>,
92 pub limit: Option<u32>,
93 pub offset: Option<u32>,
94}
95
96impl Default for PaginationParams {
97 fn default() -> Self {
98 Self {
99 page: Some(1),
100 limit: Some(50),
101 offset: None,
102 }
103 }
104}
105
106impl PaginationParams {
107 pub fn get_offset(&self) -> u32 {
108 if let Some(offset) = self.offset {
109 offset
110 } else {
111 let page = self.page.unwrap_or(1);
112 let limit = self.limit.unwrap_or(50);
113 (page.saturating_sub(1)) * limit
114 }
115 }
116
117 pub fn get_limit(&self) -> u32 {
118 self.limit.unwrap_or(50).min(1000) }
120}
121
122#[derive(Debug, Serialize)]
124pub struct PaginationMeta {
125 pub page: u32,
126 pub limit: u32,
127 pub offset: u32,
128 pub total: u64,
129 pub total_pages: u32,
130 pub has_next: bool,
131 pub has_prev: bool,
132}
133
134impl PaginationMeta {
135 pub fn new(params: &PaginationParams, total: u64) -> Self {
136 let limit = params.get_limit();
137 let offset = params.get_offset();
138 let page = params.page.unwrap_or(1);
139 let total_pages = ((total as f64) / (limit as f64)).ceil() as u32;
140
141 Self {
142 page,
143 limit,
144 offset,
145 total,
146 total_pages,
147 has_next: page < total_pages,
148 has_prev: page > 1,
149 }
150 }
151}
152
153#[derive(Debug, Serialize)]
155pub struct PaginatedResponse<T> {
156 pub items: Vec<T>,
157 pub pagination: PaginationMeta,
158}
159
160#[derive(Debug, Deserialize, Default)]
162pub struct FilterParams {
163 pub status: Option<String>,
164 pub priority: Option<String>,
165 pub queue: Option<String>,
166 pub created_after: Option<chrono::DateTime<chrono::Utc>>,
167 pub created_before: Option<chrono::DateTime<chrono::Utc>>,
168 pub search: Option<String>,
169}
170
171#[derive(Debug, Deserialize, Default)]
173pub struct SortParams {
174 pub sort_by: Option<String>,
175 pub sort_order: Option<String>,
176}
177
178impl SortParams {
179 pub fn get_order_by(&self) -> (String, String) {
180 let field = self.sort_by.as_deref().unwrap_or("created_at").to_string();
181 let direction = match self.sort_order.as_deref() {
182 Some("asc") | Some("ASC") => "ASC".to_string(),
183 _ => "DESC".to_string(),
184 };
185 (field, direction)
186 }
187}
188
189pub async fn handle_api_error(
191 err: warp::Rejection,
192) -> Result<impl warp::Reply, std::convert::Infallible> {
193 let response = if err.is_not_found() {
194 ApiResponse::<()>::error("Resource not found".to_string())
195 } else if err
196 .find::<warp::filters::body::BodyDeserializeError>()
197 .is_some()
198 {
199 ApiResponse::<()>::error("Invalid request body".to_string())
200 } else if err.find::<warp::reject::InvalidQuery>().is_some() {
201 ApiResponse::<()>::error("Invalid query parameters".to_string())
202 } else {
203 ApiResponse::<()>::error("Internal server error".to_string())
204 };
205
206 let status = if err.is_not_found() {
207 warp::http::StatusCode::NOT_FOUND
208 } else if err
209 .find::<warp::filters::body::BodyDeserializeError>()
210 .is_some()
211 || err.find::<warp::reject::InvalidQuery>().is_some()
212 {
213 warp::http::StatusCode::BAD_REQUEST
214 } else {
215 warp::http::StatusCode::INTERNAL_SERVER_ERROR
216 };
217
218 Ok(warp::reply::with_status(
219 warp::reply::json(&response),
220 status,
221 ))
222}
223
224pub fn with_pagination()
226-> impl warp::Filter<Extract = (PaginationParams,), Error = warp::Rejection> + Clone {
227 warp::query::<PaginationParams>()
228}
229
230pub fn with_filters()
232-> impl warp::Filter<Extract = (FilterParams,), Error = warp::Rejection> + Clone {
233 warp::query::<FilterParams>()
234}
235
236pub fn with_sort() -> impl warp::Filter<Extract = (SortParams,), Error = warp::Rejection> + Clone {
238 warp::query::<SortParams>()
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_api_response_success() {
247 let response = ApiResponse::success("test data");
248 assert!(response.success);
249 assert_eq!(response.data, Some("test data"));
250 assert!(response.error.is_none());
251 }
252
253 #[test]
254 fn test_api_response_error() {
255 let response: ApiResponse<()> = ApiResponse::error("Something went wrong".to_string());
256 assert!(!response.success);
257 assert!(response.data.is_none());
258 assert_eq!(response.error, Some("Something went wrong".to_string()));
259 }
260
261 #[test]
262 fn test_pagination_params_defaults() {
263 let params = PaginationParams::default();
264 assert_eq!(params.get_limit(), 50);
265 assert_eq!(params.get_offset(), 0);
266 }
267
268 #[test]
269 fn test_pagination_params_calculation() {
270 let params = PaginationParams {
271 page: Some(3),
272 limit: Some(20),
273 offset: None,
274 };
275 assert_eq!(params.get_limit(), 20);
276 assert_eq!(params.get_offset(), 40); }
278
279 #[test]
280 fn test_pagination_meta() {
281 let params = PaginationParams {
282 page: Some(2),
283 limit: Some(10),
284 offset: None,
285 };
286 let meta = PaginationMeta::new(¶ms, 45);
287
288 assert_eq!(meta.page, 2);
289 assert_eq!(meta.limit, 10);
290 assert_eq!(meta.total, 45);
291 assert_eq!(meta.total_pages, 5);
292 assert!(meta.has_next);
293 assert!(meta.has_prev);
294 }
295
296 #[test]
297 fn test_sort_params_defaults() {
298 let params = SortParams {
299 sort_by: None,
300 sort_order: None,
301 };
302 let (field, direction) = params.get_order_by();
303 assert_eq!(field, "created_at");
304 assert_eq!(direction, "DESC");
305 }
306
307 #[test]
308 fn test_sort_params_custom() {
309 let params = SortParams {
310 sort_by: Some("name".to_string()),
311 sort_order: Some("asc".to_string()),
312 };
313 let (field, direction) = params.get_order_by();
314 assert_eq!(field, "name");
315 assert_eq!(direction, "ASC");
316 }
317}