use super::{HttpMethod, RouteInfo, RouteRegistry};
use crate::controller::{factory::IocControllable, ElifController};
use crate::errors::HttpResult;
use crate::handlers::elif_handler;
use crate::middleware::v2::{Middleware, MiddlewarePipelineV2};
use crate::request::ElifRequest;
use crate::response::{ElifResponse, IntoElifResponse};
use axum::{
routing::{delete, get, patch, post, put},
Router as AxumRouter,
};
use elif_core::container::IocContainer;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub struct Router<S = ()>
where
S: Clone + Send + Sync + 'static,
{
axum_router: AxumRouter<S>,
registry: Arc<Mutex<RouteRegistry>>,
route_counter: Arc<Mutex<usize>>,
middleware_stack: MiddlewarePipelineV2,
middleware_groups: HashMap<String, MiddlewarePipelineV2>,
route_middleware: HashMap<String, Vec<String>>, controller_registry: Arc<Mutex<ControllerRegistry>>,
ioc_container: Option<Arc<IocContainer>>,
}
impl<S> Router<S>
where
S: Clone + Send + Sync + 'static,
{
pub fn new() -> Self {
Self {
axum_router: AxumRouter::new(),
registry: Arc::new(Mutex::new(RouteRegistry::new())),
route_counter: Arc::new(Mutex::new(0)),
middleware_stack: MiddlewarePipelineV2::new(),
middleware_groups: HashMap::new(),
route_middleware: HashMap::new(),
controller_registry: Arc::new(Mutex::new(ControllerRegistry::new())),
ioc_container: None,
}
}
pub fn with_state(state: S) -> Self {
Self {
axum_router: AxumRouter::new().with_state(state),
registry: Arc::new(Mutex::new(RouteRegistry::new())),
route_counter: Arc::new(Mutex::new(0)),
middleware_stack: MiddlewarePipelineV2::new(),
middleware_groups: HashMap::new(),
route_middleware: HashMap::new(),
controller_registry: Arc::new(Mutex::new(ControllerRegistry::new())),
ioc_container: None,
}
}
fn next_route_id(&self) -> String {
let mut counter = self.route_counter.lock().unwrap();
*counter += 1;
format!("route_{}", counter)
}
fn register_route(&self, method: HttpMethod, path: &str, name: Option<String>) -> String {
let route_id = self.next_route_id();
let params = self.extract_param_names(path);
let route_info = RouteInfo {
name: name.clone(),
path: path.to_string(),
method,
params,
group: None, };
self.registry
.lock()
.unwrap()
.register(route_id.clone(), route_info);
route_id
}
fn extract_param_names(&self, path: &str) -> Vec<String> {
path.split('/')
.filter_map(|segment| {
if segment.starts_with('{') && segment.ends_with('}') {
Some(segment[1..segment.len() - 1].to_string())
} else {
None
}
})
.collect()
}
pub fn use_middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
self.middleware_stack = self.middleware_stack.add(middleware);
self
}
pub fn extend_middleware(mut self, external_middleware: MiddlewarePipelineV2) -> Self {
self.middleware_stack = external_middleware.extend(self.middleware_stack);
self
}
pub fn middleware_group(mut self, name: &str, middleware: Vec<Arc<dyn Middleware>>) -> Self {
let pipeline = MiddlewarePipelineV2::from(middleware);
self.middleware_groups.insert(name.to_string(), pipeline);
self
}
pub fn route(self, path: &str) -> RouteBuilder<S> {
RouteBuilder::new(self, path.to_string())
}
fn add_route<F, Fut, R, M>(
mut self,
method: HttpMethod,
path: &str,
handler: F,
method_router_fn: M,
) -> Self
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
M: FnOnce(
crate::handlers::handler::ElifHandlerWrapper<F, Fut, R>,
) -> axum::routing::MethodRouter<S>,
{
self.register_route(method, path, None);
let method_router = method_router_fn(elif_handler(handler));
self.axum_router = self.axum_router.route(path, method_router);
self
}
pub fn get<F, Fut, R>(self, path: &str, handler: F) -> Self
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_route(HttpMethod::GET, path, handler, get)
}
pub fn post<F, Fut, R>(self, path: &str, handler: F) -> Self
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_route(HttpMethod::POST, path, handler, post)
}
pub fn put<F, Fut, R>(self, path: &str, handler: F) -> Self
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_route(HttpMethod::PUT, path, handler, put)
}
pub fn delete<F, Fut, R>(self, path: &str, handler: F) -> Self
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_route(HttpMethod::DELETE, path, handler, delete)
}
pub fn patch<F, Fut, R>(self, path: &str, handler: F) -> Self
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_route(HttpMethod::PATCH, path, handler, patch)
}
pub fn controller<C>(mut self, controller: C) -> Self
where
C: ElifController + 'static,
{
let base_path = controller.base_path().to_string();
let controller_name = controller.name().to_string();
let controller_arc = Arc::new(controller);
for route in controller_arc.routes() {
let full_path = self.combine_paths(&base_path, &route.path);
let handler =
controller_handler(Arc::clone(&controller_arc), route.handler_name.clone());
self = match route.method {
HttpMethod::GET => self.get(&full_path, handler),
HttpMethod::POST => self.post(&full_path, handler),
HttpMethod::PUT => self.put(&full_path, handler),
HttpMethod::DELETE => self.delete(&full_path, handler),
HttpMethod::PATCH => self.patch(&full_path, handler),
_ => {
continue;
}
};
}
if let Ok(mut registry) = self.controller_registry.lock() {
registry.register(controller_name, controller_arc as Arc<dyn ElifController>);
}
self
}
pub fn with_ioc_container(mut self, container: Arc<IocContainer>) -> Self {
self.ioc_container = Some(container);
self
}
pub fn controller_from_container<C>(self) -> Self
where
C: ElifController + IocControllable + 'static,
{
let container = self.ioc_container.as_ref()
.expect("IoC container must be set before registering IoC controllers. Use .with_ioc_container() first");
let controller_arc = match C::from_ioc_container(container, None) {
Ok(controller) => Arc::new(controller),
Err(err) => {
eprintln!(
"Warning: Failed to create singleton controller instance: {}",
err
);
return self;
}
};
let base_path = controller_arc.base_path().to_string();
let controller_name = controller_arc.name().to_string();
let mut router = self;
for route in controller_arc.routes() {
let full_path = router.combine_paths(&base_path, &route.path);
let handler_controller_arc = Arc::clone(&controller_arc);
let method_name = route.handler_name.clone();
let handler = move |request: ElifRequest| {
let controller = Arc::clone(&handler_controller_arc);
let method_name = method_name.clone();
Box::pin(async move { controller.handle_request(method_name, request).await })
};
router = match route.method {
HttpMethod::GET => router.get(&full_path, handler),
HttpMethod::POST => router.post(&full_path, handler),
HttpMethod::PUT => router.put(&full_path, handler),
HttpMethod::DELETE => router.delete(&full_path, handler),
HttpMethod::PATCH => router.patch(&full_path, handler),
_ => {
continue;
}
};
}
if let Ok(mut registry) = router.controller_registry.lock() {
registry.register(controller_name, controller_arc as Arc<dyn ElifController>);
}
router
}
pub fn scoped_controller_from_container<C>(self) -> Self
where
C: ElifController + IocControllable + 'static,
{
let container = self.ioc_container.as_ref()
.expect("IoC container must be set before registering IoC controllers. Use .with_ioc_container() first");
let container_arc = Arc::clone(container);
let temp_controller = match C::from_ioc_container(container, None) {
Ok(controller) => controller,
Err(err) => {
eprintln!(
"Warning: Failed to create controller for route registration: {}",
err
);
return self;
}
};
let base_path = temp_controller.base_path().to_string();
let controller_name = temp_controller.name().to_string();
let mut router = self;
for route in temp_controller.routes() {
let full_path = router.combine_paths(&base_path, &route.path);
let handler = scoped_ioc_controller_handler::<C>(
Arc::clone(&container_arc),
route.handler_name.clone(),
);
router = match route.method {
HttpMethod::GET => router.get(&full_path, handler),
HttpMethod::POST => router.post(&full_path, handler),
HttpMethod::PUT => router.put(&full_path, handler),
HttpMethod::DELETE => router.delete(&full_path, handler),
HttpMethod::PATCH => router.patch(&full_path, handler),
_ => {
continue;
}
};
}
if let Ok(mut registry) = router.controller_registry.lock() {
let controller_arc = Arc::new(temp_controller);
registry.register(controller_name, controller_arc as Arc<dyn ElifController>);
}
router
}
fn combine_paths(&self, base: &str, route: &str) -> String {
let base = base.trim_end_matches('/');
let route = route.trim_start_matches('/');
let path = if route.is_empty() {
base.to_string()
} else if base.is_empty() {
format!("/{}", route)
} else {
format!("{}/{}", base, route)
};
if path.is_empty() {
"/".to_string()
} else {
path
}
}
pub fn merge(mut self, other: Router<S>) -> Self {
if let (Ok(mut self_registry), Ok(other_registry)) =
(self.registry.lock(), other.registry.lock())
{
for route_info in other_registry.all_routes().values() {
let new_id = self.next_route_id();
self_registry.register(new_id, route_info.clone());
}
}
self.middleware_groups.extend(other.middleware_groups);
self.route_middleware.extend(other.route_middleware);
if let Ok(other_controller_registry) = other.controller_registry.lock() {
let controllers_to_merge: Vec<_> = other_controller_registry
.all_controllers()
.map(|(name, controller)| (name.clone(), Arc::clone(controller)))
.collect();
drop(other_controller_registry);
if let Ok(mut self_controller_registry) = self.controller_registry.lock() {
for (name, controller) in controllers_to_merge {
self_controller_registry.register(name, controller);
}
}
}
if self.ioc_container.is_none() && other.ioc_container.is_some() {
self.ioc_container = other.ioc_container;
}
self.middleware_stack = self.middleware_stack.extend(other.middleware_stack);
self.axum_router = self.axum_router.merge(other.axum_router);
self
}
pub(crate) fn merge_axum(mut self, other: AxumRouter<S>) -> Self {
self.axum_router = self.axum_router.merge(other);
self
}
pub fn nest(mut self, path: &str, router: Router<S>) -> Self {
if let (Ok(mut self_registry), Ok(router_registry)) =
(self.registry.lock(), router.registry.lock())
{
for route_info in router_registry.all_routes().values() {
let new_id = self.next_route_id();
self_registry.register(new_id, route_info.clone());
}
}
self.middleware_groups.extend(router.middleware_groups);
self.route_middleware.extend(router.route_middleware);
let has_nested_middleware = !router.middleware_stack.is_empty();
let nested_middleware = router.middleware_stack.clone();
let nested_axum_router = router.axum_router;
let nested_axum_router = if has_nested_middleware {
use axum::extract::Request;
use axum::middleware::from_fn;
use axum::middleware::Next;
nested_axum_router.layer(from_fn(move |req: Request, next: Next| {
let pipeline = nested_middleware.clone();
async move {
let elif_req = crate::request::ElifRequest::from_axum_request(req).await;
let response = pipeline
.execute(elif_req, |req| {
Box::pin(async move {
let axum_req = req.into_axum_request();
let axum_response = next.run(axum_req).await;
crate::response::ElifResponse::from_axum_response(axum_response)
.await
})
})
.await;
response.into_axum_response()
}
}))
} else {
nested_axum_router
};
self.axum_router = self.axum_router.nest(path, nested_axum_router);
self
}
pub fn into_axum_router(self) -> AxumRouter<S> {
self.axum_router
}
pub fn registry(&self) -> Arc<Mutex<RouteRegistry>> {
Arc::clone(&self.registry)
}
pub fn url_for(&self, name: &str, params: &HashMap<String, String>) -> Option<String> {
let registry = self.registry.lock().unwrap();
if let Some(route) = registry.get_by_name(name) {
let mut url = route.path.clone();
for (key, value) in params {
url = url.replace(&format!("{{{}}}", key), value);
}
Some(url)
} else {
None
}
}
pub fn middleware_pipeline(&self) -> &MiddlewarePipelineV2 {
&self.middleware_stack
}
pub fn middleware_groups(&self) -> &HashMap<String, MiddlewarePipelineV2> {
&self.middleware_groups
}
pub fn route_middleware(&self) -> &HashMap<String, Vec<String>> {
&self.route_middleware
}
pub fn controller_registry(&self) -> Arc<Mutex<ControllerRegistry>> {
Arc::clone(&self.controller_registry)
}
pub fn ioc_container(&self) -> Option<&Arc<IocContainer>> {
self.ioc_container.as_ref()
}
pub(crate) fn add_axum_route(
mut self,
path: &str,
method_router: axum::routing::MethodRouter<S>,
) -> Self {
self.axum_router = self.axum_router.route(path, method_router);
self
}
}
impl<S> Default for Router<S>
where
S: Clone + Send + Sync + 'static,
{
fn default() -> Self {
Self::new()
}
}
pub struct RouteBuilder<S = ()>
where
S: Clone + Send + Sync + 'static,
{
router: Router<S>,
path: String,
middleware_groups: Vec<String>,
name: Option<String>,
}
impl<S> RouteBuilder<S>
where
S: Clone + Send + Sync + 'static,
{
pub fn new(router: Router<S>, path: String) -> Self {
Self {
router,
path,
middleware_groups: Vec::new(),
name: None,
}
}
pub fn use_group(mut self, group_name: &str) -> Self {
self.middleware_groups.push(group_name.to_string());
self
}
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
fn add_method_route<F, Fut, R, M>(
mut self,
method: HttpMethod,
handler: F,
method_router_fn: M,
) -> Router<S>
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
M: FnOnce(
crate::handlers::handler::ElifHandlerWrapper<F, Fut, R>,
) -> axum::routing::MethodRouter<S>,
{
let route_id = self
.router
.register_route(method, &self.path, self.name.clone());
self.router
.route_middleware
.insert(route_id, self.middleware_groups);
let method_router = method_router_fn(elif_handler(handler));
self.router.axum_router = self.router.axum_router.route(&self.path, method_router);
self.router
}
pub fn get<F, Fut, R>(self, handler: F) -> Router<S>
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_method_route(HttpMethod::GET, handler, get)
}
pub fn post<F, Fut, R>(self, handler: F) -> Router<S>
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_method_route(HttpMethod::POST, handler, post)
}
pub fn put<F, Fut, R>(self, handler: F) -> Router<S>
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_method_route(HttpMethod::PUT, handler, put)
}
pub fn delete<F, Fut, R>(self, handler: F) -> Router<S>
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_method_route(HttpMethod::DELETE, handler, delete)
}
pub fn patch<F, Fut, R>(self, handler: F) -> Router<S>
where
F: Fn(ElifRequest) -> Fut + Send + Clone + 'static,
Fut: Future<Output = HttpResult<R>> + Send + 'static,
R: IntoElifResponse + Send + 'static,
{
self.add_method_route(HttpMethod::PATCH, handler, patch)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::HttpResult;
use crate::request::ElifRequest;
use crate::response::ElifResponse;
async fn elif_handler(_req: ElifRequest) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().text("Hello, World!"))
}
#[test]
fn test_router_creation() {
let router = Router::<()>::new()
.get("/", elif_handler)
.post("/users", elif_handler)
.get("/users/{id}", elif_handler);
let registry = router.registry();
let reg = registry.lock().unwrap();
assert_eq!(reg.all_routes().len(), 3);
}
#[test]
fn test_param_extraction() {
let router = Router::<()>::new();
let params = router.extract_param_names("/users/{id}/posts/{slug}");
assert_eq!(params, vec!["id", "slug"]);
}
#[test]
fn test_url_generation() {
let router = Router::<()>::new().get("/users/{id}/posts/{slug}", elif_handler);
{
let mut registry = router.registry.lock().unwrap();
let route_info = RouteInfo {
name: Some("user.posts.show".to_string()),
path: "/users/{id}/posts/{slug}".to_string(),
method: HttpMethod::GET,
params: vec!["id".to_string(), "slug".to_string()],
group: None,
};
registry.register("test_route".to_string(), route_info);
}
let mut params = HashMap::new();
params.insert("id".to_string(), "123".to_string());
params.insert("slug".to_string(), "hello-world".to_string());
let url = router.url_for("user.posts.show", ¶ms);
assert_eq!(url, Some("/users/123/posts/hello-world".to_string()));
}
#[test]
fn test_middleware_integration() {
use crate::middleware::v2::LoggingMiddleware;
let router = Router::<()>::new()
.use_middleware(LoggingMiddleware)
.get("/", elif_handler);
assert_eq!(router.middleware_pipeline().len(), 1);
assert_eq!(
router.middleware_pipeline().names(),
vec!["LoggingMiddleware"]
);
}
#[test]
fn test_middleware_groups() {
use crate::middleware::v2::LoggingMiddleware;
use std::sync::Arc;
let router = Router::<()>::new()
.middleware_group("api", vec![Arc::new(LoggingMiddleware)])
.get("/", elif_handler);
assert!(router.middleware_groups().contains_key("api"));
let api_group = router.middleware_groups().get("api").unwrap();
assert_eq!(api_group.len(), 1);
assert_eq!(api_group.names(), vec!["LoggingMiddleware"]);
}
#[test]
fn test_router_merge_with_middleware() {
use crate::middleware::v2::{LoggingMiddleware, SimpleAuthMiddleware};
use std::sync::Arc;
let router1 = Router::<()>::new()
.use_middleware(LoggingMiddleware)
.middleware_group(
"auth",
vec![Arc::new(SimpleAuthMiddleware::new("secret".to_string()))],
);
let router2 = Router::<()>::new()
.use_middleware(SimpleAuthMiddleware::new("token".to_string()))
.middleware_group("api", vec![Arc::new(LoggingMiddleware)]);
let merged = router1.merge(router2);
assert!(merged.middleware_groups().contains_key("auth"));
assert!(merged.middleware_groups().contains_key("api"));
let auth_group = merged.middleware_groups().get("auth").unwrap();
assert_eq!(auth_group.len(), 1);
assert_eq!(auth_group.names(), vec!["SimpleAuthMiddleware"]);
let api_group = merged.middleware_groups().get("api").unwrap();
assert_eq!(api_group.len(), 1);
assert_eq!(api_group.names(), vec!["LoggingMiddleware"]);
assert_eq!(merged.middleware_pipeline().len(), 2);
assert_eq!(
merged.middleware_pipeline().names(),
vec!["LoggingMiddleware", "SimpleAuthMiddleware"]
);
}
#[test]
fn test_middleware_group_with_multiple_middleware() {
use crate::middleware::v2::{LoggingMiddleware, SimpleAuthMiddleware};
use std::sync::Arc;
let router = Router::<()>::new()
.middleware_group(
"api",
vec![
Arc::new(LoggingMiddleware),
Arc::new(SimpleAuthMiddleware::new("secret".to_string())),
],
)
.get("/", elif_handler);
let api_group = router.middleware_groups().get("api").unwrap();
assert_eq!(api_group.len(), 2);
assert_eq!(
api_group.names(),
vec!["LoggingMiddleware", "SimpleAuthMiddleware"]
);
}
#[test]
fn test_middleware_merge_preserves_global_middleware() {
use crate::middleware::v2::{LoggingMiddleware, SimpleAuthMiddleware};
let router1 = Router::<()>::new()
.use_middleware(LoggingMiddleware)
.use_middleware(SimpleAuthMiddleware::new("router1".to_string()));
let router2 = Router::<()>::new()
.use_middleware(SimpleAuthMiddleware::new("router2".to_string()))
.use_middleware(LoggingMiddleware);
let merged = router1.merge(router2);
assert_eq!(merged.middleware_pipeline().len(), 4);
assert_eq!(
merged.middleware_pipeline().names(),
vec![
"LoggingMiddleware",
"SimpleAuthMiddleware",
"SimpleAuthMiddleware",
"LoggingMiddleware"
]
);
}
#[test]
fn test_nested_router_middleware_scoping() {
use crate::middleware::v2::{LoggingMiddleware, SimpleAuthMiddleware};
let parent_router = Router::<()>::new()
.use_middleware(LoggingMiddleware)
.get("/parent", elif_handler);
let nested_router = Router::<()>::new()
.use_middleware(SimpleAuthMiddleware::new("nested_secret".to_string()))
.get("/nested", elif_handler);
let composed_router = parent_router.nest("/api", nested_router);
assert_eq!(composed_router.middleware_pipeline().len(), 1);
assert_eq!(
composed_router.middleware_pipeline().names(),
vec!["LoggingMiddleware"]
);
let registry = composed_router.registry();
let reg = registry.lock().unwrap();
let route_count = reg.all_routes().len();
assert_eq!(
route_count, 2,
"Expected 2 routes after nesting (parent + nested)"
);
}
#[test]
fn test_nested_router_middleware_groups_merged() {
use crate::middleware::v2::LoggingMiddleware;
use std::sync::Arc;
let parent_router = Router::<()>::new()
.middleware_group("parent_group", vec![Arc::new(LoggingMiddleware)])
.get("/parent", elif_handler);
let nested_router = Router::<()>::new()
.middleware_group("nested_group", vec![Arc::new(LoggingMiddleware)])
.get("/nested", elif_handler);
let composed_router = parent_router.nest("/api", nested_router);
assert!(composed_router
.middleware_groups()
.contains_key("parent_group"));
assert!(composed_router
.middleware_groups()
.contains_key("nested_group"));
let parent_group = composed_router
.middleware_groups()
.get("parent_group")
.unwrap();
assert_eq!(parent_group.len(), 1);
assert_eq!(parent_group.names(), vec!["LoggingMiddleware"]);
let nested_group = composed_router
.middleware_groups()
.get("nested_group")
.unwrap();
assert_eq!(nested_group.len(), 1);
assert_eq!(nested_group.names(), vec!["LoggingMiddleware"]);
}
#[test]
fn test_nested_router_empty_middleware_optimization() {
use crate::middleware::v2::LoggingMiddleware;
let parent_router = Router::<()>::new()
.use_middleware(LoggingMiddleware)
.get("/parent", elif_handler);
let nested_router = Router::<()>::new().get("/nested", elif_handler);
assert_eq!(nested_router.middleware_pipeline().len(), 0);
assert!(nested_router.middleware_pipeline().is_empty());
let composed_router = parent_router.nest("/api", nested_router);
assert_eq!(composed_router.middleware_pipeline().len(), 1);
assert_eq!(
composed_router.middleware_pipeline().names(),
vec!["LoggingMiddleware"]
);
}
#[test]
fn test_controller_registration() {
use crate::controller::{ControllerRoute, ElifController, RouteParam};
use crate::routing::params::ParamType;
struct TestController;
#[async_trait::async_trait]
impl ElifController for TestController {
fn name(&self) -> &str {
"TestController"
}
fn base_path(&self) -> &str {
"/test"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![
ControllerRoute::new(HttpMethod::GET, "", "list"),
ControllerRoute::new(HttpMethod::GET, "/{id}", "show")
.add_param(RouteParam::new("id", ParamType::Integer)),
ControllerRoute::new(HttpMethod::POST, "", "create"),
]
}
async fn handle_request(
self: std::sync::Arc<Self>,
method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
match method_name.as_str() {
"list" => Ok(ElifResponse::ok().text("List method")),
"show" => Ok(ElifResponse::ok().text("Show method")),
"create" => Ok(ElifResponse::ok().text("Create method")),
_ => Ok(ElifResponse::not_found().text("Method not found")),
}
}
}
let controller = TestController;
let router = Router::<()>::new().controller(controller);
let registry = router.registry();
let reg = registry.lock().unwrap();
assert_eq!(reg.all_routes().len(), 3);
let controller_registry = router.controller_registry();
let ctrl_reg = controller_registry.lock().unwrap();
assert!(ctrl_reg.get_controller("TestController").is_some());
}
#[test]
fn test_combine_paths_edge_cases() {
let router = Router::<()>::new();
assert_eq!(router.combine_paths("/users", "/posts"), "/users/posts");
assert_eq!(router.combine_paths("/users", "posts"), "/users/posts");
assert_eq!(router.combine_paths("users", "/posts"), "users/posts");
assert_eq!(router.combine_paths("users", "posts"), "users/posts");
assert_eq!(router.combine_paths("", ""), "/"); assert_eq!(router.combine_paths("/", ""), "/"); assert_eq!(router.combine_paths("", "/"), "/"); assert_eq!(router.combine_paths("/", "/"), "/");
assert_eq!(router.combine_paths("/users/", ""), "/users");
assert_eq!(router.combine_paths("/users/", "/posts"), "/users/posts");
assert_eq!(router.combine_paths("/", "posts"), "/posts");
assert_eq!(router.combine_paths("users", "/"), "users");
}
#[test]
fn test_controller_with_root_base_path() {
use crate::controller::{ControllerRoute, ElifController};
struct RootController;
#[async_trait::async_trait]
impl ElifController for RootController {
fn name(&self) -> &str {
"RootController"
}
fn base_path(&self) -> &str {
"/"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![
ControllerRoute::new(HttpMethod::GET, "", "home"), ControllerRoute::new(HttpMethod::GET, "/health", "health"), ]
}
async fn handle_request(
self: std::sync::Arc<Self>,
method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
match method_name.as_str() {
"home" => Ok(ElifResponse::ok().text("Home")),
"health" => Ok(ElifResponse::ok().text("Health")),
_ => Ok(ElifResponse::not_found().text("Not found")),
}
}
}
let controller = RootController;
let router = Router::<()>::new().controller(controller);
let registry = router.registry();
let reg = registry.lock().unwrap();
let routes = reg.all_routes();
assert_eq!(routes.len(), 2);
let paths: Vec<&String> = routes.values().map(|route| &route.path).collect();
assert!(paths.contains(&&"/".to_string()));
assert!(paths.contains(&&"/health".to_string()));
}
#[test]
fn test_controller_with_empty_base_path() {
use crate::controller::{ControllerRoute, ElifController};
struct EmptyBaseController;
#[async_trait::async_trait]
impl ElifController for EmptyBaseController {
fn name(&self) -> &str {
"EmptyBaseController"
}
fn base_path(&self) -> &str {
""
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![
ControllerRoute::new(HttpMethod::GET, "", "root"), ControllerRoute::new(HttpMethod::GET, "/api", "api"), ]
}
async fn handle_request(
self: std::sync::Arc<Self>,
method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
match method_name.as_str() {
"root" => Ok(ElifResponse::ok().text("Root")),
"api" => Ok(ElifResponse::ok().text("API")),
_ => Ok(ElifResponse::not_found().text("Not found")),
}
}
}
let controller = EmptyBaseController;
let router = Router::<()>::new().controller(controller);
let registry = router.registry();
let reg = registry.lock().unwrap();
let routes = reg.all_routes();
assert_eq!(routes.len(), 2);
let paths: Vec<&String> = routes.values().map(|route| &route.path).collect();
assert!(paths.contains(&&"/".to_string()));
assert!(paths.contains(&&"/api".to_string()));
}
#[tokio::test]
async fn test_concurrent_router_merge_no_deadlock() {
use crate::controller::{ControllerRoute, ElifController};
use tokio::time::{timeout, Duration};
struct TestControllerA;
#[async_trait::async_trait]
impl ElifController for TestControllerA {
fn name(&self) -> &str {
"TestControllerA"
}
fn base_path(&self) -> &str {
"/a"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![ControllerRoute::new(HttpMethod::GET, "", "test")]
}
async fn handle_request(
self: std::sync::Arc<Self>,
_method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().text("A"))
}
}
struct TestControllerB;
#[async_trait::async_trait]
impl ElifController for TestControllerB {
fn name(&self) -> &str {
"TestControllerB"
}
fn base_path(&self) -> &str {
"/b"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![ControllerRoute::new(HttpMethod::GET, "", "test")]
}
async fn handle_request(
self: std::sync::Arc<Self>,
_method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().text("B"))
}
}
let task1 = tokio::spawn(async move {
let a_copy = Router::<()>::new().controller(TestControllerA);
let b_copy = Router::<()>::new().controller(TestControllerB);
let _merged = a_copy.merge(b_copy);
});
let task2 = tokio::spawn(async move {
let a_copy = Router::<()>::new().controller(TestControllerA);
let b_copy = Router::<()>::new().controller(TestControllerB);
let _merged = b_copy.merge(a_copy);
});
let result = timeout(Duration::from_millis(100), async {
let _ = tokio::try_join!(task1, task2);
})
.await;
assert!(
result.is_ok(),
"Router merge operations should not deadlock"
);
}
#[test]
fn test_controller_merge_preserves_all_controllers() {
use crate::controller::{ControllerRoute, ElifController};
struct ControllerA;
#[async_trait::async_trait]
impl ElifController for ControllerA {
fn name(&self) -> &str {
"ControllerA"
}
fn base_path(&self) -> &str {
"/a"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![ControllerRoute::new(HttpMethod::GET, "", "test")]
}
async fn handle_request(
self: std::sync::Arc<Self>,
_method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().text("A"))
}
}
struct ControllerB;
#[async_trait::async_trait]
impl ElifController for ControllerB {
fn name(&self) -> &str {
"ControllerB"
}
fn base_path(&self) -> &str {
"/b"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![ControllerRoute::new(HttpMethod::GET, "", "test")]
}
async fn handle_request(
self: std::sync::Arc<Self>,
_method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().text("B"))
}
}
let router_a = Router::<()>::new().controller(ControllerA);
let router_b = Router::<()>::new().controller(ControllerB);
let merged_router = router_a.merge(router_b);
let controller_registry = merged_router.controller_registry();
let registry = controller_registry.lock().unwrap();
assert!(registry.get_controller("ControllerA").is_some());
assert!(registry.get_controller("ControllerB").is_some());
let names = registry.controller_names();
assert_eq!(names.len(), 2);
assert!(names.contains(&&"ControllerA".to_string()));
assert!(names.contains(&&"ControllerB".to_string()));
}
#[test]
fn test_simple_nest_route_registration() {
let parent_router = Router::<()>::new().get("/parent", elif_handler);
let nested_router = Router::<()>::new().get("/nested", elif_handler);
assert_eq!(
parent_router.registry().lock().unwrap().all_routes().len(),
1
);
assert_eq!(
nested_router.registry().lock().unwrap().all_routes().len(),
1
);
let composed_router = parent_router.nest("/api", nested_router);
let route_count = composed_router
.registry()
.lock()
.unwrap()
.all_routes()
.len();
assert_eq!(
route_count, 2,
"Expected both parent and nested routes to be registered, got: {}",
route_count
);
}
#[test]
fn test_route_builder_with_middleware_groups() {
use crate::middleware::v2::LoggingMiddleware;
use std::sync::Arc;
let router = Router::<()>::new()
.middleware_group("api", vec![Arc::new(LoggingMiddleware)])
.middleware_group("auth", vec![Arc::new(LoggingMiddleware)])
.route("/api/users")
.use_group("api")
.use_group("auth")
.get(elif_handler);
assert!(router.middleware_groups().contains_key("api"));
assert!(router.middleware_groups().contains_key("auth"));
assert_eq!(router.route_middleware().len(), 1);
let route_middleware = router.route_middleware().values().next().unwrap();
assert_eq!(route_middleware.len(), 2);
assert!(route_middleware.contains(&"api".to_string()));
assert!(route_middleware.contains(&"auth".to_string()));
}
#[test]
fn test_route_builder_chaining() {
use crate::middleware::v2::LoggingMiddleware;
use std::sync::Arc;
let router = Router::<()>::new()
.middleware_group("api", vec![Arc::new(LoggingMiddleware)])
.route("/api/users")
.use_group("api")
.name("users.index")
.get(elif_handler);
let binding = router.registry();
let registry = binding.lock().unwrap();
let routes: Vec<_> = registry.all_routes().values().collect();
assert_eq!(routes.len(), 1);
let route = routes[0];
assert_eq!(route.path, "/api/users");
assert_eq!(route.name, Some("users.index".to_string()));
assert_eq!(route.method, HttpMethod::GET);
}
#[test]
fn test_multiple_routes_with_different_middleware() {
use crate::middleware::v2::{LoggingMiddleware, SimpleAuthMiddleware};
use std::sync::Arc;
let router = Router::<()>::new()
.middleware_group("api", vec![Arc::new(LoggingMiddleware)])
.middleware_group(
"auth",
vec![Arc::new(SimpleAuthMiddleware::new("secret".to_string()))],
)
.route("/api/public")
.use_group("api")
.get(elif_handler)
.route("/api/protected")
.use_group("api")
.use_group("auth")
.get(elif_handler)
.route("/api/admin")
.use_group("auth")
.get(elif_handler);
assert_eq!(router.registry().lock().unwrap().all_routes().len(), 3);
let route_middleware = router.route_middleware();
assert_eq!(route_middleware.len(), 3);
let middleware_counts: Vec<usize> = route_middleware.values().map(|v| v.len()).collect();
middleware_counts
.iter()
.for_each(|&count| assert!(count > 0));
let total_middleware_assignments: usize = middleware_counts.iter().sum();
assert_eq!(total_middleware_assignments, 4); }
#[test]
fn test_ioc_controller_registration() {
use crate::controller::{factory::IocControllable, ControllerRoute, ElifController};
use elif_core::container::IocContainer;
use elif_core::ServiceBinder;
use std::sync::Arc;
#[derive(Default)]
struct TestService {
#[allow(dead_code)]
name: String,
}
unsafe impl Send for TestService {}
unsafe impl Sync for TestService {}
struct IocTestController {
#[allow(dead_code)]
service: Arc<TestService>,
}
#[async_trait::async_trait]
impl ElifController for IocTestController {
fn name(&self) -> &str {
"IocTestController"
}
fn base_path(&self) -> &str {
"/ioc-test"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![
ControllerRoute::new(HttpMethod::GET, "", "list"),
ControllerRoute::new(HttpMethod::GET, "/{id}", "show"),
]
}
async fn handle_request(
self: Arc<Self>,
method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
match method_name.as_str() {
"list" => Ok(ElifResponse::ok().text("IoC List")),
"show" => Ok(ElifResponse::ok().text("IoC Show")),
_ => Ok(ElifResponse::not_found().text("Method not found")),
}
}
}
impl IocControllable for IocTestController {
fn from_ioc_container(
container: &IocContainer,
_scope: Option<&elif_core::container::ScopeId>,
) -> Result<Self, String> {
let service = container
.resolve::<TestService>()
.map_err(|e| format!("Failed to resolve TestService: {}", e))?;
Ok(Self { service })
}
}
let mut container = IocContainer::new();
container.bind::<TestService, TestService>();
container.build().expect("Container build failed");
let container_arc = Arc::new(container);
let router = Router::<()>::new()
.with_ioc_container(Arc::clone(&container_arc))
.controller_from_container::<IocTestController>();
let registry = router.registry();
let reg = registry.lock().unwrap();
assert_eq!(reg.all_routes().len(), 2);
let controller_registry = router.controller_registry();
let ctrl_reg = controller_registry.lock().unwrap();
assert!(ctrl_reg.get_controller("IocTestController").is_some());
}
#[test]
#[should_panic(expected = "IoC container must be set before registering IoC controllers")]
fn test_ioc_controller_without_container() {
use crate::controller::{factory::IocControllable, ControllerRoute, ElifController};
use elif_core::container::IocContainer;
struct TestController;
#[async_trait::async_trait]
impl ElifController for TestController {
fn name(&self) -> &str {
"TestController"
}
fn base_path(&self) -> &str {
"/test"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![]
}
async fn handle_request(
self: std::sync::Arc<Self>,
_method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().text("test"))
}
}
impl IocControllable for TestController {
fn from_ioc_container(
_container: &IocContainer,
_scope: Option<&elif_core::container::ScopeId>,
) -> Result<Self, String> {
Ok(Self)
}
}
Router::<()>::new().controller_from_container::<TestController>();
}
#[test]
fn test_router_with_ioc_container() {
use elif_core::container::IocContainer;
use std::sync::Arc;
let container = Arc::new(IocContainer::new());
let router = Router::<()>::new().with_ioc_container(Arc::clone(&container));
assert!(router.ioc_container().is_some());
assert!(Arc::ptr_eq(router.ioc_container().unwrap(), &container));
}
#[test]
fn test_merge_preserves_ioc_container() {
use elif_core::container::IocContainer;
use std::sync::Arc;
let container = Arc::new(IocContainer::new());
let router1 = Router::<()>::new().with_ioc_container(Arc::clone(&container));
let router2 = Router::<()>::new();
let merged = router1.merge(router2);
assert!(merged.ioc_container().is_some());
assert!(Arc::ptr_eq(merged.ioc_container().unwrap(), &container));
}
#[test]
fn test_merge_prefers_first_container() {
use elif_core::container::IocContainer;
use std::sync::Arc;
let container1 = Arc::new(IocContainer::new());
let container2 = Arc::new(IocContainer::new());
let router1 = Router::<()>::new().with_ioc_container(Arc::clone(&container1));
let router2 = Router::<()>::new().with_ioc_container(Arc::clone(&container2));
let merged = router1.merge(router2);
assert!(merged.ioc_container().is_some());
assert!(Arc::ptr_eq(merged.ioc_container().unwrap(), &container1));
}
#[test]
fn test_scoped_controller_registration() {
use crate::controller::{factory::IocControllable, ControllerRoute, ElifController};
use elif_core::container::IocContainer;
use elif_core::ServiceBinder;
use std::sync::Arc;
#[derive(Default)]
struct ScopedService {
#[allow(dead_code)]
id: String,
}
unsafe impl Send for ScopedService {}
unsafe impl Sync for ScopedService {}
struct ScopedController {
#[allow(dead_code)]
service: Arc<ScopedService>,
}
#[async_trait::async_trait]
impl ElifController for ScopedController {
fn name(&self) -> &str {
"ScopedController"
}
fn base_path(&self) -> &str {
"/scoped"
}
fn routes(&self) -> Vec<ControllerRoute> {
vec![ControllerRoute::new(HttpMethod::GET, "/test", "test")]
}
async fn handle_request(
self: Arc<Self>,
_method_name: String,
_request: ElifRequest,
) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().text("Scoped"))
}
}
impl IocControllable for ScopedController {
fn from_ioc_container(
container: &IocContainer,
_scope: Option<&elif_core::container::ScopeId>,
) -> Result<Self, String> {
let service = container
.resolve::<ScopedService>()
.map_err(|e| format!("Failed to resolve ScopedService: {}", e))?;
Ok(Self { service })
}
}
let mut container = IocContainer::new();
container.bind::<ScopedService, ScopedService>();
container.build().expect("Container build failed");
let container_arc = Arc::new(container);
let router = Router::<()>::new()
.with_ioc_container(Arc::clone(&container_arc))
.scoped_controller_from_container::<ScopedController>();
let registry = router.registry();
let reg = registry.lock().unwrap();
assert_eq!(reg.all_routes().len(), 1);
let controller_registry = router.controller_registry();
let ctrl_reg = controller_registry.lock().unwrap();
assert!(ctrl_reg.get_controller("ScopedController").is_some());
}
#[test]
fn test_route_without_middleware_groups() {
let router = Router::<()>::new().route("/simple").get(elif_handler);
let binding = router.registry();
let registry = binding.lock().unwrap();
assert_eq!(registry.all_routes().len(), 1);
let route_middleware = router.route_middleware();
assert_eq!(route_middleware.len(), 1);
let middleware_groups = route_middleware.values().next().unwrap();
assert_eq!(middleware_groups.len(), 0);
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
use crate::middleware::v2::{Middleware, Next, NextFuture};
use crate::response::ElifResponse;
use serde_json::json;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
struct HeaderTestMiddleware {
name: String,
counter: Arc<Mutex<usize>>,
}
impl HeaderTestMiddleware {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
counter: Arc::new(Mutex::new(0)),
}
}
}
impl Middleware for HeaderTestMiddleware {
fn handle(&self, mut request: ElifRequest, next: Next) -> NextFuture<'static> {
let name = self.name.clone();
let counter = self.counter.clone();
Box::pin(async move {
{
let mut count = counter.lock().unwrap();
*count += 1;
}
let header_name = crate::response::headers::ElifHeaderName::from_str(&format!(
"x-middleware-{}",
name.to_lowercase()
))
.unwrap();
let header_value =
crate::response::headers::ElifHeaderValue::from_str("executed").unwrap();
request.headers.insert(header_name, header_value);
let response = next.run(request).await;
let response_header = format!("x-response-{}", name.to_lowercase());
response
.header(&response_header, "executed")
.unwrap_or_else(|_| {
ElifResponse::ok().text("Middleware executed")
})
})
}
fn name(&self) -> &'static str {
match self.name.as_str() {
"Parent" => "Parent",
"Nested" => "Nested",
"Global" => "Global",
"First" => "First",
"Second" => "Second",
"Third" => "Third",
"Router1" => "Router1",
"Router2" => "Router2",
_ => "TestMiddleware",
}
}
}
async fn test_handler(request: ElifRequest) -> HttpResult<ElifResponse> {
let mut response_headers = HashMap::new();
for (key, value) in request.headers.iter() {
let key_str = key.as_str().to_string();
if let Ok(value_str) = value.to_str() {
response_headers.insert(key_str, value_str.to_string());
}
}
Ok(ElifResponse::ok().json(&json!({
"message": "Hello from handler",
"request_headers": response_headers,
"path": request.path()
}))?)
}
async fn nested_handler(request: ElifRequest) -> HttpResult<ElifResponse> {
Ok(ElifResponse::ok().json(&json!({
"message": "Hello from nested handler",
"path": request.path()
}))?)
}
#[tokio::test]
async fn test_global_middleware_execution() {
let test_middleware = HeaderTestMiddleware::new("Global");
let middleware_counter = test_middleware.counter.clone();
let router = Router::<()>::new()
.use_middleware(test_middleware)
.get("/test", test_handler);
assert_eq!(router.middleware_pipeline().len(), 1);
assert_eq!(router.middleware_pipeline().names(), vec!["Global"]);
assert_eq!(middleware_counter.lock().unwrap().clone(), 0);
let request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/test".parse().unwrap(),
crate::response::headers::ElifHeaderMap::new(),
);
let response = router
.middleware_pipeline()
.execute(request, |req| {
Box::pin(async move {
let header_name =
crate::response::headers::ElifHeaderName::from_str("x-middleware-global")
.unwrap();
assert!(req.headers.contains_key(&header_name));
ElifResponse::ok().text("Pipeline test response")
})
})
.await;
assert_eq!(middleware_counter.lock().unwrap().clone(), 1);
assert_eq!(
response.status_code(),
crate::response::status::ElifStatusCode::OK
);
}
#[tokio::test]
async fn test_nested_router_middleware_isolation() {
let parent_middleware = HeaderTestMiddleware::new("Parent");
let nested_middleware = HeaderTestMiddleware::new("Nested");
let parent_counter = parent_middleware.counter.clone();
let nested_counter = nested_middleware.counter.clone();
let parent_router = Router::<()>::new()
.use_middleware(parent_middleware)
.get("/parent", test_handler);
let nested_router = Router::<()>::new()
.use_middleware(nested_middleware)
.get("/nested", nested_handler);
let composed_router = parent_router.nest("/api", nested_router);
assert_eq!(composed_router.middleware_pipeline().len(), 1); assert_eq!(
composed_router.middleware_pipeline().names(),
vec!["Parent"]
);
assert_eq!(parent_counter.lock().unwrap().clone(), 0);
assert_eq!(nested_counter.lock().unwrap().clone(), 0);
let parent_request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/parent".parse().unwrap(),
crate::response::headers::ElifHeaderMap::new(),
);
let parent_response = composed_router
.middleware_pipeline()
.execute(parent_request, |req| {
Box::pin(async move {
let parent_header =
crate::response::headers::ElifHeaderName::from_str("x-middleware-parent")
.unwrap();
let nested_header =
crate::response::headers::ElifHeaderName::from_str("x-middleware-nested")
.unwrap();
assert!(req.headers.contains_key(&parent_header));
assert!(!req.headers.contains_key(&nested_header));
ElifResponse::ok().text("Parent response")
})
})
.await;
assert_eq!(
parent_response.status_code(),
crate::response::status::ElifStatusCode::OK
);
assert_eq!(parent_counter.lock().unwrap().clone(), 1);
assert_eq!(nested_counter.lock().unwrap().clone(), 0);
}
#[tokio::test]
async fn test_middleware_execution_order() {
let first_middleware = HeaderTestMiddleware::new("First");
let second_middleware = HeaderTestMiddleware::new("Second");
let third_middleware = HeaderTestMiddleware::new("Third");
let first_counter = first_middleware.counter.clone();
let second_counter = second_middleware.counter.clone();
let third_counter = third_middleware.counter.clone();
let router = Router::<()>::new()
.use_middleware(first_middleware)
.use_middleware(second_middleware)
.use_middleware(third_middleware)
.get("/test", test_handler);
assert_eq!(router.middleware_pipeline().len(), 3);
assert_eq!(
router.middleware_pipeline().names(),
vec!["First", "Second", "Third"]
);
let request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/test".parse().unwrap(),
crate::response::headers::ElifHeaderMap::new(),
);
let response = router
.middleware_pipeline()
.execute(request, |req| {
Box::pin(async move {
let first_header =
crate::response::headers::ElifHeaderName::from_str("x-middleware-first")
.unwrap();
let second_header =
crate::response::headers::ElifHeaderName::from_str("x-middleware-second")
.unwrap();
let third_header =
crate::response::headers::ElifHeaderName::from_str("x-middleware-third")
.unwrap();
assert!(req.headers.contains_key(&first_header));
assert!(req.headers.contains_key(&second_header));
assert!(req.headers.contains_key(&third_header));
ElifResponse::ok().text("Handler executed after all middleware")
})
})
.await;
assert_eq!(first_counter.lock().unwrap().clone(), 1);
assert_eq!(second_counter.lock().unwrap().clone(), 1);
assert_eq!(third_counter.lock().unwrap().clone(), 1);
assert_eq!(
response.status_code(),
crate::response::status::ElifStatusCode::OK
);
}
#[tokio::test]
async fn test_router_merge_middleware_execution() {
let router1_middleware = HeaderTestMiddleware::new("Router1");
let router2_middleware = HeaderTestMiddleware::new("Router2");
let router1_counter = router1_middleware.counter.clone();
let router2_counter = router2_middleware.counter.clone();
let router1 = Router::<()>::new()
.use_middleware(router1_middleware)
.get("/router1", test_handler);
let router2 = Router::<()>::new()
.use_middleware(router2_middleware)
.get("/router2", test_handler);
let merged_router = router1.merge(router2);
assert_eq!(merged_router.middleware_pipeline().len(), 2);
assert_eq!(
merged_router.middleware_pipeline().names(),
vec!["Router1", "Router2"]
);
let request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/test".parse().unwrap(),
crate::response::headers::ElifHeaderMap::new(),
);
let response = merged_router
.middleware_pipeline()
.execute(request, |req| {
Box::pin(async move {
let router1_header =
crate::response::headers::ElifHeaderName::from_str("x-middleware-router1")
.unwrap();
let router2_header =
crate::response::headers::ElifHeaderName::from_str("x-middleware-router2")
.unwrap();
assert!(req.headers.contains_key(&router1_header));
assert!(req.headers.contains_key(&router2_header));
ElifResponse::ok().text("Merged router response")
})
})
.await;
assert_eq!(router1_counter.lock().unwrap().clone(), 1);
assert_eq!(router2_counter.lock().unwrap().clone(), 1);
assert_eq!(
response.status_code(),
crate::response::status::ElifStatusCode::OK
);
}
#[tokio::test]
async fn test_middleware_with_early_return() {
#[derive(Debug)]
struct AuthMiddleware {
required_token: String,
}
impl AuthMiddleware {
fn new(token: String) -> Self {
Self {
required_token: token,
}
}
}
impl Middleware for AuthMiddleware {
fn handle(&self, request: ElifRequest, next: Next) -> NextFuture<'static> {
let required_token = self.required_token.clone();
Box::pin(async move {
let auth_header = request
.header("authorization")
.and_then(|h| h.to_str().ok());
match auth_header {
Some(header) if header.starts_with("Bearer ") => {
let token = &header[7..];
if token == required_token {
next.run(request).await
} else {
ElifResponse::unauthorized().json_value(json!({
"error": {
"code": "invalid_token",
"message": "Invalid authorization token"
}
}))
}
}
_ => {
ElifResponse::unauthorized().json_value(json!({
"error": {
"code": "missing_token",
"message": "Authorization header required"
}
}))
}
}
})
}
fn name(&self) -> &'static str {
"AuthMiddleware"
}
}
let router = Router::<()>::new()
.use_middleware(AuthMiddleware::new("secret123".to_string()))
.get("/protected", test_handler);
let request_no_auth = ElifRequest::new(
crate::request::ElifMethod::GET,
"/protected".parse().unwrap(),
crate::response::headers::ElifHeaderMap::new(),
);
let response_no_auth = router
.middleware_pipeline()
.execute(request_no_auth, |_req| {
Box::pin(async move {
panic!("Handler should not be called when auth fails");
})
})
.await;
assert_eq!(
response_no_auth.status_code(),
crate::response::status::ElifStatusCode::UNAUTHORIZED
);
let mut headers = crate::response::headers::ElifHeaderMap::new();
let auth_header =
crate::response::headers::ElifHeaderName::from_str("authorization").unwrap();
let auth_value =
crate::response::headers::ElifHeaderValue::from_str("Bearer secret123").unwrap();
headers.insert(auth_header, auth_value);
let request_valid_auth = ElifRequest::new(
crate::request::ElifMethod::GET,
"/protected".parse().unwrap(),
headers,
);
let response_valid_auth = router
.middleware_pipeline()
.execute(request_valid_auth, |req| {
Box::pin(async move {
assert!(req.header("authorization").is_some());
ElifResponse::ok().text("Protected content accessed")
})
})
.await;
assert_eq!(
response_valid_auth.status_code(),
crate::response::status::ElifStatusCode::OK
);
let mut headers = crate::response::headers::ElifHeaderMap::new();
let auth_header =
crate::response::headers::ElifHeaderName::from_str("authorization").unwrap();
let auth_value =
crate::response::headers::ElifHeaderValue::from_str("Bearer invalid").unwrap();
headers.insert(auth_header, auth_value);
let request_invalid_auth = ElifRequest::new(
crate::request::ElifMethod::GET,
"/protected".parse().unwrap(),
headers,
);
let response_invalid_auth = router
.middleware_pipeline()
.execute(request_invalid_auth, |_req| {
Box::pin(async move {
panic!("Handler should not be called when auth token is invalid");
})
})
.await;
assert_eq!(
response_invalid_auth.status_code(),
crate::response::status::ElifStatusCode::UNAUTHORIZED
);
}
}
pub fn controller_handler<C>(
controller: Arc<C>,
method_name: String,
) -> impl Fn(ElifRequest) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>
+ Clone
+ Send
+ Sync
+ 'static
where
C: ElifController + 'static,
{
move |request: ElifRequest| {
let controller = Arc::clone(&controller);
let method_name = method_name.clone();
Box::pin(async move { controller.handle_request(method_name, request).await })
}
}
pub struct ControllerRegistry {
controllers: HashMap<String, Arc<dyn ElifController>>,
}
impl std::fmt::Debug for ControllerRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ControllerRegistry")
.field(
"controllers",
&format!("{} controllers", self.controllers.len()),
)
.finish()
}
}
impl Default for ControllerRegistry {
fn default() -> Self {
Self::new()
}
}
impl ControllerRegistry {
pub fn new() -> Self {
Self {
controllers: HashMap::new(),
}
}
pub fn register(&mut self, name: String, controller: Arc<dyn ElifController>) {
self.controllers.insert(name, controller);
}
pub fn get_controller(&self, name: &str) -> Option<&Arc<dyn ElifController>> {
self.controllers.get(name)
}
pub fn all_controllers(&self) -> impl Iterator<Item = (&String, &Arc<dyn ElifController>)> {
self.controllers.iter()
}
pub fn controller_names(&self) -> Vec<&String> {
self.controllers.keys().collect()
}
}
pub fn scoped_ioc_controller_handler<C>(
container: Arc<IocContainer>,
method_name: String,
) -> impl Fn(ElifRequest) -> Pin<Box<dyn Future<Output = HttpResult<ElifResponse>> + Send>>
+ Clone
+ Send
+ Sync
+ 'static
where
C: ElifController + IocControllable + 'static,
{
move |request: ElifRequest| {
let container = Arc::clone(&container);
let method_name = method_name.clone();
Box::pin(async move {
let scope_id =
container
.create_scope()
.map_err(|e| crate::errors::HttpError::InternalError {
message: format!("Failed to create request scope: {}", e),
})?;
let controller = C::from_ioc_container(&container, Some(&scope_id)).map_err(|e| {
crate::errors::HttpError::InternalError {
message: format!("Failed to resolve scoped controller: {}", e),
}
})?;
let controller_arc = Arc::new(controller);
let result = controller_arc.handle_request(method_name, request).await;
let container_clone = Arc::clone(&container);
tokio::spawn(async move {
if let Err(e) = container_clone.dispose_scope(&scope_id).await {
eprintln!("Failed to dispose request scope: {}", e);
}
});
result
})
}
}