1use std::{sync::Arc, pin::Pin, future::Future};
7use crate::{
8 request::{ElifState, ElifPath, ElifQuery, ElifRequest},
9 response::{ElifJson, ElifResponse},
10 routing::{HttpMethod, params::ParamType},
11};
12use serde::{Serialize, Deserialize};
13use serde_json::Value;
14use async_trait::async_trait;
15
16use crate::{HttpResult, response::ApiResponse};
17use elif_core::container::IocContainer;
18
19#[derive(Debug, Serialize, Deserialize)]
21pub struct QueryParams {
22 pub page: Option<u32>,
23 pub per_page: Option<u32>,
24 pub sort: Option<String>,
25 pub order: Option<String>,
26 pub filter: Option<String>,
27}
28
29impl Default for QueryParams {
30 fn default() -> Self {
31 Self {
32 page: Some(1),
33 per_page: Some(20),
34 sort: Some("id".to_string()),
35 order: Some("asc".to_string()),
36 filter: None,
37 }
38 }
39}
40
41#[derive(Debug, Serialize, Deserialize)]
43pub struct PaginationMeta {
44 pub page: u32,
45 pub per_page: u32,
46 pub total: Option<u64>,
47 pub total_pages: Option<u32>,
48 pub has_more: bool,
49}
50
51#[derive(Clone)]
53pub struct BaseController;
54
55impl BaseController {
56 pub fn new() -> Self {
57 Self
58 }
59
60 pub fn normalize_pagination(&self, params: &QueryParams) -> (u32, u32, u64) {
62 let page = params.page.unwrap_or(1).max(1);
63 let per_page = params.per_page.unwrap_or(20).min(100).max(1);
64 let offset = (page - 1) * per_page;
65 (page, per_page, offset as u64)
66 }
67
68 pub fn success_response<T: Serialize>(&self, data: T) -> HttpResult<ElifResponse> {
70 let api_response = ApiResponse::success(data);
71 Ok(ElifResponse::ok().json(&api_response)?)
72 }
73
74 pub fn created_response<T: Serialize>(&self, data: T) -> HttpResult<ElifResponse> {
76 let api_response = ApiResponse::success(data);
77 Ok(ElifResponse::created().json(&api_response)?)
78 }
79
80 pub fn paginated_response<T: Serialize>(&self, data: Vec<T>, meta: PaginationMeta) -> HttpResult<ElifResponse> {
82 let response_data = serde_json::json!({
83 "data": data,
84 "meta": meta
85 });
86 Ok(ElifResponse::ok().json(&response_data)?)
87 }
88
89 pub fn deleted_response<T: Serialize>(&self, resource_name: &str, deleted_id: Option<T>) -> HttpResult<ElifResponse> {
91 let mut response_data = serde_json::json!({
92 "message": format!("{} deleted successfully", resource_name)
93 });
94
95 if let Some(id) = deleted_id {
96 response_data["deleted_id"] = serde_json::to_value(id)?;
97 }
98
99 let api_response = ApiResponse::success(response_data);
100 Ok(ElifResponse::ok().json(&api_response)?)
101 }
102}
103
104pub trait Controller: Send + Sync {
107 fn index(
109 &self,
110 container: ElifState<Arc<IocContainer>>,
111 params: ElifQuery<QueryParams>,
112 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
113
114 fn show(
116 &self,
117 container: ElifState<Arc<IocContainer>>,
118 id: ElifPath<String>,
119 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
120
121 fn create(
123 &self,
124 container: ElifState<Arc<IocContainer>>,
125 data: ElifJson<Value>,
126 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
127
128 fn update(
130 &self,
131 container: ElifState<Arc<IocContainer>>,
132 id: ElifPath<String>,
133 data: ElifJson<Value>,
134 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
135
136 fn destroy(
138 &self,
139 container: ElifState<Arc<IocContainer>>,
140 id: ElifPath<String>,
141 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
142}
143
144#[derive(Debug, Clone)]
146pub struct RouteParam {
147 pub name: String,
148 pub param_type: ParamType,
149 pub required: bool,
150 pub default: Option<String>,
151}
152
153impl RouteParam {
154 pub fn new(name: &str, param_type: ParamType) -> Self {
155 Self {
156 name: name.to_string(),
157 param_type,
158 required: true,
159 default: None,
160 }
161 }
162
163 pub fn optional(mut self) -> Self {
164 self.required = false;
165 self
166 }
167
168 pub fn with_default(mut self, default: &str) -> Self {
169 self.default = Some(default.to_string());
170 self.required = false;
171 self
172 }
173}
174
175#[derive(Debug, Clone)]
177pub struct ControllerRoute {
178 pub method: HttpMethod,
179 pub path: String,
180 pub handler_name: String,
181 pub middleware: Vec<String>,
182 pub params: Vec<RouteParam>,
183}
184
185impl ControllerRoute {
186 pub fn new(method: HttpMethod, path: &str, handler_name: &str) -> Self {
187 Self {
188 method,
189 path: path.to_string(),
190 handler_name: handler_name.to_string(),
191 middleware: vec![],
192 params: vec![],
193 }
194 }
195
196 pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
197 self.middleware = middleware;
198 self
199 }
200
201 pub fn with_params(mut self, params: Vec<RouteParam>) -> Self {
202 self.params = params;
203 self
204 }
205
206 pub fn add_param(mut self, param: RouteParam) -> Self {
207 self.params.push(param);
208 self
209 }
210}
211
212#[async_trait]
214pub trait ElifController: Send + Sync + 'static {
215 fn name(&self) -> &str;
217
218 fn base_path(&self) -> &str;
220
221 fn routes(&self) -> Vec<ControllerRoute>;
223
224 fn dependencies(&self) -> Vec<String> {
226 vec![]
227 }
228
229 async fn handle_request(
232 self: Arc<Self>,
233 method_name: String,
234 request: ElifRequest,
235 ) -> HttpResult<ElifResponse>;
236}
237
238#[macro_export]
241macro_rules! controller_dispatch {
242 ($self:expr, $method_name:expr, $request:expr, {
243 $($method:literal => $handler:expr),*
244 }) => {
245 match $method_name.as_str() {
246 $($method => Box::pin($handler($self, $request)),)*
247 _ => Box::pin(async move {
248 use crate::response::ElifResponse;
249 Ok(ElifResponse::not_found().text(&format!("Handler '{}' not found", $method_name)))
250 })
251 }
252 };
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use serde_json::json;
259
260 #[tokio::test]
261 async fn test_base_controller_creation() {
262 let _controller = BaseController::new();
263 }
264
265 #[tokio::test]
266 async fn test_pagination_normalization() {
267 let controller = BaseController::new();
268 let params = QueryParams {
269 page: Some(5),
270 per_page: Some(10),
271 ..Default::default()
272 };
273
274 let (page, per_page, offset) = controller.normalize_pagination(¶ms);
275 assert_eq!(page, 5);
276 assert_eq!(per_page, 10);
277 assert_eq!(offset, 40);
278 }
279
280 #[tokio::test]
281 async fn test_pagination_limits() {
282 let controller = BaseController::new();
283 let params = QueryParams {
284 page: Some(0),
285 per_page: Some(200),
286 ..Default::default()
287 };
288
289 let (page, per_page, offset) = controller.normalize_pagination(¶ms);
290 assert_eq!(page, 1);
291 assert_eq!(per_page, 100);
292 assert_eq!(offset, 0);
293 }
294
295 #[tokio::test]
296 async fn test_success_response_creation() {
297 let controller = BaseController::new();
298 let data = json!({"message": "test"});
299 let response = controller.success_response(data);
300 assert!(response.is_ok());
301 }
302
303 #[tokio::test]
304 async fn test_pagination_meta_creation() {
305 let meta = PaginationMeta {
306 page: 1,
307 per_page: 20,
308 total: Some(100),
309 total_pages: Some(5),
310 has_more: true,
311 };
312
313 assert_eq!(meta.page, 1);
314 assert_eq!(meta.per_page, 20);
315 assert_eq!(meta.total, Some(100));
316 }
317}