use axum::Router;
use once_cell::sync::Lazy;
use portpicker::{pick_unused_port, Port};
use springtime::application;
use springtime::future::{BoxFuture, FutureExt};
use springtime_di::instance_provider::ErrorPtr;
use springtime_di::{component_alias, Component};
use springtime_web_axum::axum::extract::Path;
use springtime_web_axum::config::{ServerConfig, WebConfig, WebConfigProvider};
use springtime_web_axum::controller;
use springtime_web_axum::server::{ShutdownSignalSender, ShutdownSignalSource};
use std::sync::Mutex;
use tokio::sync::Barrier;
#[derive(Component)]
struct TestController;
#[controller(path = "/test", server_names = ["default", "test"])]
impl TestController {
#[get("/{user_id}")]
async fn hello_world(&self, Path(user_id): Path<u32>) -> String {
format!("Hello {user_id}!")
}
#[post("/")]
async fn post_something(&self) -> &'static str {
"Posted!"
}
#[fallback]
async fn fallback(&self) -> &'static str {
"fallback"
}
#[router_source]
fn create_router(&self) -> Result<Router, ErrorPtr> {
Ok(Router::new())
}
#[router_post_configure]
fn post_configure_router(&self, router: Router) -> Result<Router, ErrorPtr> {
Ok(router)
}
}
#[derive(Component)]
#[component(constructor = "TestWebConfigProvider::new")]
struct TestWebConfigProvider {
#[component(ignore)]
config: WebConfig,
}
#[component_alias]
impl WebConfigProvider for TestWebConfigProvider {
fn config(&self) -> BoxFuture<'_, Result<&WebConfig, ErrorPtr>> {
async { Ok(&self.config) }.boxed()
}
}
impl TestWebConfigProvider {
fn new() -> BoxFuture<'static, Result<Self, ErrorPtr>> {
async {
let mut server_config = ServerConfig::default();
server_config.listen_address = format!("127.0.0.1:{}", *PORT);
let mut config = WebConfig::default();
config.servers = [("test".to_string(), server_config)].into_iter().collect();
Ok(Self { config })
}
.boxed()
}
}
static SHUTDOWN_SIGNAL: Lazy<Mutex<Option<ShutdownSignalSender>>> = Lazy::new(Default::default);
static START_BARRIER: Lazy<Barrier> = Lazy::new(|| Barrier::new(2));
static PORT: Lazy<Port> = Lazy::new(|| pick_unused_port().unwrap());
#[derive(Component)]
struct TestShutdownSignalSource;
#[component_alias]
impl ShutdownSignalSource for TestShutdownSignalSource {
fn register_shutdown(&self, shutdown_sender: ShutdownSignalSender) -> Result<(), ErrorPtr> {
SHUTDOWN_SIGNAL.lock().unwrap().replace(shutdown_sender);
tokio::spawn(async {
START_BARRIER.wait().await;
});
Ok(())
}
}
#[tokio::test]
async fn should_register_controller() {
let handle = tokio::spawn(async {
let mut application = application::create_default().unwrap();
application.run().await.unwrap();
});
let body = reqwest::get(format!("http://localhost:{}/test/42", *PORT))
.await
.unwrap()
.text()
.await
.unwrap();
assert_eq!(body, "Hello 42!");
let body = reqwest::get(format!("http://localhost:{}/test/invalid/route", *PORT))
.await
.unwrap()
.text()
.await
.unwrap();
assert_eq!(body, "fallback");
START_BARRIER.wait().await;
SHUTDOWN_SIGNAL
.lock()
.unwrap()
.as_ref()
.unwrap()
.send(())
.unwrap();
handle.await.unwrap();
}