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 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/// 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 BaseController {
56    pub fn new() -> Self {
57        Self
58    }
59
60    /// Validate and normalize pagination parameters
61    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    /// Create standardized success response
69    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    /// Create standardized created response
75    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    /// Create paginated response with metadata
81    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    /// Create standardized delete response
90    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
104/// Send-safe controller trait for HTTP request handling
105/// Controllers delegate business logic to injected services
106pub trait Controller: Send + Sync {
107    /// List resources with pagination
108    fn index(
109        &self,
110        container: ElifState<Arc<IocContainer>>,
111        params: ElifQuery<QueryParams>,
112    ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
113
114    /// Get single resource by ID
115    fn show(
116        &self,
117        container: ElifState<Arc<IocContainer>>,
118        id: ElifPath<String>,
119    ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
120
121    /// Create new resource
122    fn create(
123        &self,
124        container: ElifState<Arc<IocContainer>>,
125        data: ElifJson<Value>,
126    ) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>;
127
128    /// Update existing resource
129    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    /// Delete resource
137    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/// Route parameter definition for controllers
145#[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/// Controller route definition
176#[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/// Main trait for controllers with automatic route registration
213#[async_trait]
214pub trait ElifController: Send + Sync + 'static {
215    /// Controller name for identification
216    fn name(&self) -> &str;
217    
218    /// Base path for all routes in this controller
219    fn base_path(&self) -> &str;
220    
221    /// Route definitions for this controller
222    fn routes(&self) -> Vec<ControllerRoute>;
223    
224    /// Dependencies required by this controller (optional)
225    fn dependencies(&self) -> Vec<String> { 
226        vec![] 
227    }
228    
229    /// Handle a request by dispatching to the appropriate method
230    /// Uses Arc<Self> for thread-safe concurrent access to controller instance
231    async fn handle_request(
232        self: Arc<Self>,
233        method_name: String,
234        request: ElifRequest,
235    ) -> HttpResult<ElifResponse>;
236}
237
238/// Macro to help implement controller method dispatch
239/// Updated to work with Arc<Self> for thread-safe controller access
240#[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(&params);
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(&params);
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}