use axum::{Extension, Router};
use http::Method;
use nidus_config::Config;
use nidus_core::{
Container, LifecycleHook, LifecycleRunner, Module, ModuleDefinition, Nidus, RequestScope,
Result,
};
use nidus_http::middleware::request_scope_layer;
use std::sync::Arc;
use crate::request::TestRequest;
#[derive(Clone)]
pub struct TestApp {
router: Router,
container: Arc<Container>,
config: Config,
lifecycle: Arc<LifecycleRunner>,
}
impl TestApp {
pub fn bootstrap<M>() -> Result<TestAppBuilder>
where
M: Module,
{
Self::bootstrap_with_router::<M>(Router::new())
}
pub fn bootstrap_with_router<M>(router: Router) -> Result<TestAppBuilder>
where
M: Module,
{
Nidus::bootstrap::<M>()?;
Ok(Self::builder(router))
}
pub fn bootstrap_with_modules<M, I>(modules: I) -> Result<TestAppBuilder>
where
M: Module,
I: IntoIterator<Item = ModuleDefinition>,
{
Self::bootstrap_with_modules_and_router::<M, I>(modules, Router::new())
}
pub fn bootstrap_with_modules_and_router<M, I>(
modules: I,
router: Router,
) -> Result<TestAppBuilder>
where
M: Module,
I: IntoIterator<Item = ModuleDefinition>,
{
Nidus::bootstrap_with_modules::<M, I>(modules)?;
Ok(Self::builder(router))
}
pub fn from_router(router: Router) -> Self {
let container = Arc::new(Container::new());
Self {
router: router.layer(Extension(Arc::clone(&container))),
container,
config: Config::new(),
lifecycle: Arc::new(LifecycleRunner::new()),
}
}
pub fn builder(router: Router) -> TestAppBuilder {
TestAppBuilder {
router,
container: Container::new(),
config: Config::new(),
lifecycle: LifecycleRunner::new(),
request_scope: false,
}
}
pub fn get(&self, path: impl Into<String>) -> TestRequest {
self.request(Method::GET, path)
}
pub fn post(&self, path: impl Into<String>) -> TestRequest {
self.request(Method::POST, path)
}
pub fn put(&self, path: impl Into<String>) -> TestRequest {
self.request(Method::PUT, path)
}
pub fn patch(&self, path: impl Into<String>) -> TestRequest {
self.request(Method::PATCH, path)
}
pub fn delete(&self, path: impl Into<String>) -> TestRequest {
self.request(Method::DELETE, path)
}
pub fn request(&self, method: Method, path: impl Into<String>) -> TestRequest {
TestRequest::new(self.router.clone(), method, path.into())
}
pub fn resolve<T>(&self) -> Result<Arc<T>>
where
T: Send + Sync + 'static,
{
self.container.resolve::<T>()
}
pub fn request_scope(&self) -> RequestScope<'_> {
self.container.request_scope()
}
pub fn config(&self) -> &Config {
&self.config
}
pub async fn shutdown(&self) -> Result<()> {
self.lifecycle.shutdown().await
}
}
pub struct TestAppBuilder {
router: Router,
container: Container,
config: Config,
lifecycle: LifecycleRunner,
request_scope: bool,
}
impl TestAppBuilder {
pub fn provider<T>(mut self, value: T) -> Result<Self>
where
T: Send + Sync + 'static,
{
self.container.register_singleton(value)?;
Ok(self)
}
pub fn transient_provider<T, F>(mut self, factory: F) -> Result<Self>
where
T: Send + Sync + 'static,
F: Fn(&Container) -> Result<T> + Send + Sync + 'static,
{
self.container.register_transient::<T, F>(factory)?;
Ok(self)
}
pub fn request_provider<T, F>(mut self, factory: F) -> Result<Self>
where
T: Send + Sync + 'static,
F: Fn(&Container) -> Result<T> + Send + Sync + 'static,
{
self.container.register_request::<T, F>(factory)?;
Ok(self)
}
pub fn request_scoped_provider<T, F>(mut self, factory: F) -> Result<Self>
where
T: Send + Sync + 'static,
F: for<'scope> Fn(&RequestScope<'scope>) -> Result<T> + Send + Sync + 'static,
{
self.container.register_request_scoped::<T, F>(factory)?;
Ok(self)
}
pub fn with_request_scope(mut self) -> Self {
self.request_scope = true;
self
}
pub fn override_provider<T>(mut self, value: T) -> Result<Self>
where
T: Send + Sync + 'static,
{
self.container.override_singleton(value)?;
Ok(self)
}
pub fn config(mut self, config: Config) -> Self {
self.config = config;
self
}
pub fn lifecycle_hook<H>(mut self, hook: H) -> Self
where
H: LifecycleHook,
{
self.lifecycle = self.lifecycle.hook(hook);
self
}
pub fn build(self) -> TestApp {
let container = Arc::new(self.container);
let mut router = self.router.layer(Extension(Arc::clone(&container)));
if self.request_scope {
router = router.layer(request_scope_layer(Arc::clone(&container)));
}
TestApp {
router,
container,
config: self.config,
lifecycle: Arc::new(self.lifecycle),
}
}
pub async fn build_started(self) -> Result<TestApp> {
self.lifecycle.startup().await?;
Ok(self.build())
}
}