use crate::controller::Controller;
use axum::Router;
#[cfg(test)]
use mockall::automock;
use springtime_di::component_registry::conditional::unregistered_component;
use springtime_di::instance_provider::{ComponentInstancePtr, ErrorPtr};
use springtime_di::{component_alias, injectable, Component};
use tracing::debug;
#[injectable]
#[cfg_attr(test, automock)]
pub trait RouterConfigure {
fn configure(&self, router: Router) -> Result<Router, ErrorPtr>;
}
#[injectable]
pub trait RouterBootstrap {
fn bootstrap_router(&self, server_name: &str) -> Result<Router, ErrorPtr>;
}
#[derive(Component)]
#[component(priority = -128, condition = "unregistered_component::<dyn RouterBootstrap + Send + Sync>")]
struct ControllerRouterBootstrap {
controllers: Vec<ComponentInstancePtr<dyn Controller + Send + Sync>>,
configure_components: Vec<ComponentInstancePtr<dyn RouterConfigure + Send + Sync>>,
}
#[component_alias]
impl RouterBootstrap for ControllerRouterBootstrap {
fn bootstrap_router(&self, server_name: &str) -> Result<Router, ErrorPtr> {
self.controllers
.iter()
.filter(|controller| {
controller
.server_names()
.map(|server_names| server_names.contains(server_name))
.unwrap_or(true)
})
.try_fold(Router::new(), |router, controller| {
let path = controller.path().unwrap_or_else(|| "/".to_string());
let inner_router = controller.create_router()?;
debug!(path, "Registering new controller routes.");
controller
.configure_router(inner_router, controller.clone())
.and_then(|inner_router| controller.post_configure_router(inner_router))
.map(|inner_router| {
if path.is_empty() || path == "/" {
router.merge(inner_router)
} else {
router.nest(&path, inner_router)
}
})
})
.and_then(|router| {
self.configure_components
.iter()
.try_fold(router, |router, configure| configure.configure(router))
})
}
}
#[cfg(test)]
mod tests {
use crate::controller::MockController;
use crate::router::{ControllerRouterBootstrap, MockRouterConfigure, RouterBootstrap};
use axum::Router;
use rustc_hash::FxHashSet;
use springtime_di::instance_provider::ComponentInstancePtr;
#[test]
fn should_configure_router_with_filtering() {
let mut controller = MockController::new();
controller
.expect_configure_router()
.times(1)
.return_const(Ok(Router::new()));
controller.expect_server_names().times(1).return_const(
["1".to_string(), "2".to_string()]
.into_iter()
.collect::<FxHashSet<_>>(),
);
controller.expect_path().return_const(None);
controller
.expect_create_router()
.return_const(Ok(Router::new()));
controller
.expect_post_configure_router()
.returning(|router| Ok(router));
let bootstrap = ControllerRouterBootstrap {
controllers: vec![ComponentInstancePtr::new(controller)],
configure_components: vec![],
};
assert!(bootstrap.bootstrap_router("1").is_ok());
}
#[test]
fn should_not_configure_router_with_filtering() {
let mut controller = MockController::new();
controller
.expect_configure_router()
.times(0)
.return_const(Ok(Router::new()));
controller.expect_server_names().times(1).return_const(
["1".to_string(), "2".to_string()]
.into_iter()
.collect::<FxHashSet<_>>(),
);
let bootstrap = ControllerRouterBootstrap {
controllers: vec![ComponentInstancePtr::new(controller)],
configure_components: vec![],
};
assert!(bootstrap.bootstrap_router("3").is_ok());
}
#[test]
fn should_pass_existing_router_for_configuration() {
let mut configure = MockRouterConfigure::new();
configure
.expect_configure()
.times(1)
.returning(|router| Ok(router));
let bootstrap = ControllerRouterBootstrap {
controllers: vec![],
configure_components: vec![ComponentInstancePtr::new(configure)],
};
assert!(bootstrap.bootstrap_router("1").is_ok());
}
}