1use std::collections::HashMap;
19use std::sync::Arc;
20use std::pin::Pin;
21use std::future::Future;
22use elif_core::modules::CompileTimeModuleMetadata;
23use elif_core::container::IocContainer;
24use crate::controller::ControllerRoute;
25use crate::routing::{ElifRouter, HttpMethod};
26use crate::bootstrap::{BootstrapError, RouteConflict, RouteInfo, ConflictType, ConflictResolution, ParamDef};
27
28#[derive(Debug, Clone)]
30pub struct ControllerMetadata {
31 pub name: String,
33 pub base_path: String,
35 pub routes: Vec<RouteMetadata>,
37 pub middleware: Vec<String>,
39 pub dependencies: Vec<String>,
41}
42
43#[derive(Debug, Clone)]
45pub struct RouteMetadata {
46 pub method: HttpMethod,
48 pub path: String,
50 pub handler_name: String,
52 pub middleware: Vec<String>,
54 pub params: Vec<ParamMetadata>,
56}
57
58#[derive(Debug, Clone)]
60pub struct ParamMetadata {
61 pub name: String,
63 pub param_type: String,
65 pub required: bool,
67 pub default: Option<String>,
69}
70
71#[derive(Debug)]
73pub struct ControllerRegistry {
74 controllers: HashMap<String, ControllerMetadata>,
76 #[allow(dead_code)]
78 container: Arc<IocContainer>,
79}
80
81impl ControllerRegistry {
82 pub fn new(container: Arc<IocContainer>) -> Self {
84 Self {
85 controllers: HashMap::new(),
86 container,
87 }
88 }
89
90 pub fn from_modules(modules: &[CompileTimeModuleMetadata], container: Arc<IocContainer>) -> Result<Self, BootstrapError> {
92 let mut registry = Self::new(container);
93
94 let mut controller_names = std::collections::HashSet::new();
96 for module in modules {
97 for controller_name in &module.controllers {
98 controller_names.insert(controller_name.clone());
99 }
100 }
101
102 for controller_name in controller_names {
104 let metadata = registry.build_controller_metadata(&controller_name)?;
105 registry.controllers.insert(controller_name.clone(), metadata);
106 }
107
108 Ok(registry)
109 }
110
111 fn build_controller_metadata(&self, controller_name: &str) -> Result<ControllerMetadata, BootstrapError> {
113 let controller = super::controller_registry::create_controller(controller_name)?;
115
116 let routes = controller.routes()
118 .into_iter()
119 .map(|route| RouteMetadata::from(route))
120 .collect();
121
122 let dependencies = controller.dependencies();
123
124 Ok(ControllerMetadata {
125 name: controller.name().to_string(),
126 base_path: controller.base_path().to_string(),
127 routes,
128 middleware: vec![], dependencies,
130 })
131 }
132
133 pub fn register_all_routes(&self, mut router: ElifRouter) -> Result<ElifRouter, BootstrapError> {
135 for (controller_name, metadata) in &self.controllers {
136 router = self.register_controller_routes(router, controller_name, metadata)?;
137 }
138 Ok(router)
139 }
140
141 fn register_controller_routes(
143 &self,
144 mut router: ElifRouter,
145 controller_name: &str,
146 metadata: &ControllerMetadata
147 ) -> Result<ElifRouter, BootstrapError> {
148 tracing::info!(
149 "Bootstrap: Registering controller '{}' with {} routes at base path '{}'",
150 controller_name,
151 metadata.routes.len(),
152 metadata.base_path
153 );
154
155 let controller = super::controller_registry::create_controller(controller_name)?;
157 let controller_arc = std::sync::Arc::new(controller);
158
159 for route in &metadata.routes {
161 let full_path = self.combine_paths(&metadata.base_path, &route.path);
162
163 tracing::debug!(
164 "Registering route: {} {} -> {}::{}",
165 route.method,
166 full_path,
167 controller_name,
168 route.handler_name
169 );
170
171 let controller_clone = std::sync::Arc::clone(&controller_arc);
173 let method_name = route.handler_name.clone();
174 let handler = move |request: crate::request::ElifRequest| {
175 let controller_for_request = std::sync::Arc::clone(&controller_clone);
176 let method_for_request = method_name.clone();
177 Box::pin(async move {
178 controller_for_request.handle_request_dyn(method_for_request, request).await
179 }) as Pin<Box<dyn Future<Output = crate::errors::HttpResult<crate::response::ElifResponse>> + Send>>
180 };
181
182 router = match route.method {
184 HttpMethod::GET => router.get(&full_path, handler),
185 HttpMethod::POST => router.post(&full_path, handler),
186 HttpMethod::PUT => router.put(&full_path, handler),
187 HttpMethod::DELETE => router.delete(&full_path, handler),
188 HttpMethod::PATCH => router.patch(&full_path, handler),
189 HttpMethod::HEAD => {
190 tracing::warn!("HEAD method not yet supported for route: {}", full_path);
191 continue;
192 },
193 HttpMethod::OPTIONS => {
194 tracing::warn!("OPTIONS method not yet supported for route: {}", full_path);
195 continue;
196 },
197 HttpMethod::TRACE => {
198 tracing::warn!("TRACE method not yet supported for route: {}", full_path);
199 continue;
200 },
201 };
202 }
203
204 tracing::info!(
205 "Bootstrap: Successfully registered controller '{}' with {} HTTP routes",
206 controller_name,
207 metadata.routes.len()
208 );
209
210 Ok(router)
211 }
212
213 pub fn validate_routes(&self) -> Result<(), Vec<RouteConflict>> {
215 let mut conflicts = Vec::new();
216 let mut route_map: HashMap<String, Vec<(String, &RouteMetadata)>> = HashMap::new();
217
218 for (controller_name, metadata) in &self.controllers {
220 for route in &metadata.routes {
221 let full_path = format!("{}{}", metadata.base_path, route.path);
222 let key = format!("{} {}", route.method, full_path);
223
224 route_map.entry(key).or_default().push((controller_name.clone(), route));
225 }
226 }
227
228 for (_route_key, controllers) in route_map {
230 if controllers.len() > 1 {
231 let (first_controller, first_route) = &controllers[0];
233 let (second_controller, second_route) = &controllers[1];
234
235 let route1 = RouteInfo {
236 method: first_route.method.clone(),
237 path: format!("{}{}",
238 self.get_controller_base_path(first_controller).unwrap_or_default(),
239 first_route.path
240 ),
241 controller: first_controller.clone(),
242 handler: first_route.handler_name.clone(),
243 middleware: first_route.middleware.clone(),
244 parameters: first_route.params.iter().map(|p| ParamDef {
245 name: p.name.clone(),
246 param_type: p.param_type.clone(),
247 required: p.required,
248 constraints: vec![], }).collect(),
250 };
251
252 let route2 = RouteInfo {
253 method: second_route.method.clone(),
254 path: format!("{}{}",
255 self.get_controller_base_path(second_controller).unwrap_or_default(),
256 second_route.path
257 ),
258 controller: second_controller.clone(),
259 handler: second_route.handler_name.clone(),
260 middleware: second_route.middleware.clone(),
261 parameters: second_route.params.iter().map(|p| ParamDef {
262 name: p.name.clone(),
263 param_type: p.param_type.clone(),
264 required: p.required,
265 constraints: vec![],
266 }).collect(),
267 };
268
269 conflicts.push(RouteConflict {
270 route1,
271 route2,
272 conflict_type: ConflictType::Exact,
273 resolution_suggestions: vec![
274 ConflictResolution::DifferentControllerPaths {
275 suggestion: format!("Consider using different base paths for {} and {}",
276 first_controller, second_controller)
277 }
278 ],
279 });
280 }
281 }
282
283 if conflicts.is_empty() {
284 Ok(())
285 } else {
286 Err(conflicts)
287 }
288 }
289
290 pub fn get_controller_metadata(&self, name: &str) -> Option<&ControllerMetadata> {
292 self.controllers.get(name)
293 }
294
295 pub fn get_controller_names(&self) -> Vec<String> {
297 self.controllers.keys().cloned().collect()
298 }
299
300 pub fn total_routes(&self) -> usize {
302 self.controllers.values()
303 .map(|metadata| metadata.routes.len())
304 .sum()
305 }
306
307 fn get_controller_base_path(&self, controller_name: &str) -> Option<String> {
309 self.controllers.get(controller_name)
310 .map(|metadata| metadata.base_path.clone())
311 }
312
313 fn combine_paths(&self, base: &str, route: &str) -> String {
315 let base = base.trim_end_matches('/');
316 let route = route.trim_start_matches('/');
317
318 let path = if route.is_empty() {
319 base.to_string()
320 } else if base.is_empty() {
321 format!("/{}", route)
322 } else {
323 format!("{}/{}", base, route)
324 };
325
326 if path.is_empty() {
328 "/".to_string()
329 } else {
330 path
331 }
332 }
333
334}
335
336impl From<ControllerRoute> for RouteMetadata {
338 fn from(route: ControllerRoute) -> Self {
339 Self {
340 method: route.method,
341 path: route.path,
342 handler_name: route.handler_name,
343 middleware: route.middleware,
344 params: route.params.into_iter().map(|p| ParamMetadata {
345 name: p.name,
346 param_type: format!("{:?}", p.param_type), required: p.required,
348 default: p.default,
349 }).collect(),
350 }
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_controller_registry_creation() {
360 let container = Arc::new(IocContainer::new());
361 let registry = ControllerRegistry::new(container);
362
363 assert_eq!(registry.get_controller_names().len(), 0);
364 assert_eq!(registry.total_routes(), 0);
365 }
366
367 #[test]
368 fn test_route_conflict_detection() {
369 let container = Arc::new(IocContainer::new());
370 let registry = ControllerRegistry::new(container);
371
372 assert!(registry.validate_routes().is_ok());
374 }
375
376 #[test]
377 fn test_controller_metadata_conversion() {
378 use crate::controller::{ControllerRoute, RouteParam};
379 use crate::routing::params::ParamType;
380
381 let controller_route = ControllerRoute {
382 method: HttpMethod::GET,
383 path: "/test".to_string(),
384 handler_name: "test_handler".to_string(),
385 middleware: vec!["auth".to_string()],
386 params: vec![RouteParam {
387 name: "id".to_string(),
388 param_type: ParamType::Integer,
389 required: true,
390 default: None,
391 }],
392 };
393
394 let route_metadata: RouteMetadata = controller_route.into();
395
396 assert_eq!(route_metadata.method, HttpMethod::GET);
397 assert_eq!(route_metadata.path, "/test");
398 assert_eq!(route_metadata.handler_name, "test_handler");
399 assert_eq!(route_metadata.middleware.len(), 1);
400 assert_eq!(route_metadata.params.len(), 1);
401 assert_eq!(route_metadata.params[0].name, "id");
402 assert_eq!(route_metadata.params[0].required, true);
403 }
404}