elif_http/controller/
base.rs

1//! Controller System for Route Organization
2//!
3//! Provides both service-oriented controllers and a new ElifController system
4//! for automatic route registration and organization.
5
6use 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/// Query parameters for pagination and filtering
20#[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/// Pagination metadata for API responses
42#[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/// Base controller providing HTTP utilities (no infrastructure dependencies)
52#[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    /// Validate and normalize pagination parameters
67    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    /// Create standardized success response
75    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    /// Create standardized created response
81    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    /// Create paginated response with metadata
87    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    /// Create standardized delete response
100    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
118/// Send-safe controller trait for HTTP request handling
119/// Controllers delegate business logic to injected services
120pub trait Controller: Send + Sync {
121    /// List resources with pagination
122    fn index(
123        &self,
124        container: ElifState<Arc<IocContainer>>,
125        params: ElifQuery<QueryParams>,
126    ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
127
128    /// Get single resource by ID
129    fn show(
130        &self,
131        container: ElifState<Arc<IocContainer>>,
132        id: ElifPath<String>,
133    ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
134
135    /// Create new resource
136    fn create(
137        &self,
138        container: ElifState<Arc<IocContainer>>,
139        data: ElifJson<Value>,
140    ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
141
142    /// Update existing resource
143    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    /// Delete resource
151    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/// Route parameter definition for controllers
159#[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/// Controller route definition
190#[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/// Main trait for controllers with automatic route registration
227#[async_trait]
228pub trait ElifController: Send + Sync + 'static {
229    /// Controller name for identification
230    fn name(&self) -> &str;
231
232    /// Base path for all routes in this controller
233    fn base_path(&self) -> &str;
234
235    /// Route definitions for this controller
236    fn routes(&self) -> Vec<ControllerRoute>;
237
238    /// Dependencies required by this controller (optional)
239    fn dependencies(&self) -> Vec<String> {
240        vec![]
241    }
242
243    /// Handle a request by dispatching to the appropriate method
244    /// Uses Arc<Self> for thread-safe concurrent access to controller instance
245    async fn handle_request(
246        self: Arc<Self>,
247        method_name: String,
248        request: ElifRequest,
249    ) -> HttpResult<ElifResponse>;
250    
251    /// Dynamic dispatch method for trait objects
252    /// This method allows calling controller methods through trait objects
253    /// by providing a default implementation that performs reflection-like dispatch
254    async fn handle_request_dyn(
255        &self,
256        method_name: String,
257        request: ElifRequest,
258    ) -> HttpResult<ElifResponse> {
259        // Default implementation returns a helpful error message
260        // Concrete controllers can override this for better dynamic dispatch
261        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 to help implement controller method dispatch
273/// Updated to work with Arc<Self> for thread-safe controller access
274#[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(&params);
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(&params);
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}