1use crate::{
7 request::{ElifPath, ElifQuery, ElifRequest, ElifState},
8 response::{ElifJson, ElifResponse},
9 routing::{params::ParamType, HttpMethod},
10};
11use async_trait::async_trait;
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use std::{future::Future, pin::Pin, sync::Arc};
15
16use crate::{response::ApiResponse, HttpResult};
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 Default for BaseController {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl BaseController {
62 pub fn new() -> Self {
63 Self
64 }
65
66 pub fn normalize_pagination(&self, params: &QueryParams) -> (u32, u32, u64) {
68 let page = params.page.unwrap_or(1).max(1);
69 let per_page = params.per_page.unwrap_or(20).min(100).max(1);
70 let offset = (page - 1) * per_page;
71 (page, per_page, offset as u64)
72 }
73
74 pub fn success_response<T: Serialize>(&self, data: T) -> HttpResult<ElifResponse> {
76 let api_response = ApiResponse::success(data);
77 ElifResponse::ok().json(&api_response)
78 }
79
80 pub fn created_response<T: Serialize>(&self, data: T) -> HttpResult<ElifResponse> {
82 let api_response = ApiResponse::success(data);
83 ElifResponse::created().json(&api_response)
84 }
85
86 pub fn paginated_response<T: Serialize>(
88 &self,
89 data: Vec<T>,
90 meta: PaginationMeta,
91 ) -> HttpResult<ElifResponse> {
92 let response_data = serde_json::json!({
93 "data": data,
94 "meta": meta
95 });
96 ElifResponse::ok().json(&response_data)
97 }
98
99 pub fn deleted_response<T: Serialize>(
101 &self,
102 resource_name: &str,
103 deleted_id: Option<T>,
104 ) -> HttpResult<ElifResponse> {
105 let mut response_data = serde_json::json!({
106 "message": format!("{} deleted successfully", resource_name)
107 });
108
109 if let Some(id) = deleted_id {
110 response_data["deleted_id"] = serde_json::to_value(id)?;
111 }
112
113 let api_response = ApiResponse::success(response_data);
114 ElifResponse::ok().json(&api_response)
115 }
116}
117
118pub trait Controller: Send + Sync {
121 fn index(
123 &self,
124 container: ElifState<Arc<IocContainer>>,
125 params: ElifQuery<QueryParams>,
126 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
127
128 fn show(
130 &self,
131 container: ElifState<Arc<IocContainer>>,
132 id: ElifPath<String>,
133 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
134
135 fn create(
137 &self,
138 container: ElifState<Arc<IocContainer>>,
139 data: ElifJson<Value>,
140 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
141
142 fn update(
144 &self,
145 container: ElifState<Arc<IocContainer>>,
146 id: ElifPath<String>,
147 data: ElifJson<Value>,
148 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
149
150 fn destroy(
152 &self,
153 container: ElifState<Arc<IocContainer>>,
154 id: ElifPath<String>,
155 ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
156}
157
158#[derive(Debug, Clone)]
160pub struct RouteParam {
161 pub name: String,
162 pub param_type: ParamType,
163 pub required: bool,
164 pub default: Option<String>,
165}
166
167impl RouteParam {
168 pub fn new(name: &str, param_type: ParamType) -> Self {
169 Self {
170 name: name.to_string(),
171 param_type,
172 required: true,
173 default: None,
174 }
175 }
176
177 pub fn optional(mut self) -> Self {
178 self.required = false;
179 self
180 }
181
182 pub fn with_default(mut self, default: &str) -> Self {
183 self.default = Some(default.to_string());
184 self.required = false;
185 self
186 }
187}
188
189#[derive(Debug, Clone)]
191pub struct ControllerRoute {
192 pub method: HttpMethod,
193 pub path: String,
194 pub handler_name: String,
195 pub middleware: Vec<String>,
196 pub params: Vec<RouteParam>,
197}
198
199impl ControllerRoute {
200 pub fn new(method: HttpMethod, path: &str, handler_name: &str) -> Self {
201 Self {
202 method,
203 path: path.to_string(),
204 handler_name: handler_name.to_string(),
205 middleware: vec![],
206 params: vec![],
207 }
208 }
209
210 pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
211 self.middleware = middleware;
212 self
213 }
214
215 pub fn with_params(mut self, params: Vec<RouteParam>) -> Self {
216 self.params = params;
217 self
218 }
219
220 pub fn add_param(mut self, param: RouteParam) -> Self {
221 self.params.push(param);
222 self
223 }
224}
225
226#[async_trait]
228pub trait ElifController: Send + Sync + 'static {
229 fn name(&self) -> &str;
231
232 fn base_path(&self) -> &str;
234
235 fn routes(&self) -> Vec<ControllerRoute>;
237
238 fn dependencies(&self) -> Vec<String> {
240 vec![]
241 }
242
243 async fn handle_request(
246 self: Arc<Self>,
247 method_name: String,
248 request: ElifRequest,
249 ) -> HttpResult<ElifResponse>;
250
251 async fn handle_request_dyn(
255 &self,
256 method_name: String,
257 request: ElifRequest,
258 ) -> HttpResult<ElifResponse> {
259 Ok(ElifResponse::ok().json(&serde_json::json!({
262 "controller": self.name(),
263 "method": method_name,
264 "message": "Dynamic dispatch called successfully",
265 "status": "Phase 3 implementation working",
266 "path": request.path(),
267 "http_method": format!("{:?}", request.method)
268 })).unwrap_or_else(|_| ElifResponse::ok().text("Controller method called")))
269 }
270}
271
272#[macro_export]
275macro_rules! controller_dispatch {
276 ($self:expr, $method_name:expr, $request:expr, {
277 $($method:literal => $handler:expr),*
278 }) => {
279 match $method_name.as_str() {
280 $($method => Box::pin($handler($self, $request)),)*
281 _ => Box::pin(async move {
282 use $crate::response::ElifResponse;
283 Ok(ElifResponse::not_found().text(&format!("Handler '{}' not found", $method_name)))
284 })
285 }
286 };
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use serde_json::json;
293
294 #[tokio::test]
295 async fn test_base_controller_creation() {
296 let _controller = BaseController::new();
297 }
298
299 #[tokio::test]
300 async fn test_pagination_normalization() {
301 let controller = BaseController::new();
302 let params = QueryParams {
303 page: Some(5),
304 per_page: Some(10),
305 ..Default::default()
306 };
307
308 let (page, per_page, offset) = controller.normalize_pagination(¶ms);
309 assert_eq!(page, 5);
310 assert_eq!(per_page, 10);
311 assert_eq!(offset, 40);
312 }
313
314 #[tokio::test]
315 async fn test_pagination_limits() {
316 let controller = BaseController::new();
317 let params = QueryParams {
318 page: Some(0),
319 per_page: Some(200),
320 ..Default::default()
321 };
322
323 let (page, per_page, offset) = controller.normalize_pagination(¶ms);
324 assert_eq!(page, 1);
325 assert_eq!(per_page, 100);
326 assert_eq!(offset, 0);
327 }
328
329 #[tokio::test]
330 async fn test_success_response_creation() {
331 let controller = BaseController::new();
332 let data = json!({"message": "test"});
333 let response = controller.success_response(data);
334 assert!(response.is_ok());
335 }
336
337 #[tokio::test]
338 async fn test_pagination_meta_creation() {
339 let meta = PaginationMeta {
340 page: 1,
341 per_page: 20,
342 total: Some(100),
343 total_pages: Some(5),
344 has_more: true,
345 };
346
347 assert_eq!(meta.page, 1);
348 assert_eq!(meta.per_page, 20);
349 assert_eq!(meta.total, Some(100));
350 }
351}