use axum::Router;
use tokio::net::TcpListener;
use crate::{Error, Result};
pub async fn serve(router: Router, host: &str, port: u16) -> Result<()> {
let listener = TcpListener::bind((host, port))
.await
.map_err(|source| Error::Bind {
addr: format!("{host}:{port}"),
source,
})?;
let addr = listener.local_addr().map_err(Error::Server)?;
tracing::info!("Server running on http://{addr}");
axum::serve(listener, router)
.with_graceful_shutdown(shutdown_signal())
.await
.map_err(Error::Server)?;
tracing::info!("Server stopped");
Ok(())
}
pub async fn shutdown_signal() {
let ctrl_c = async {
if tokio::signal::ctrl_c().await.is_err() {
std::future::pending::<()>().await;
}
};
#[cfg(unix)]
let terminate = async {
use tokio::signal::unix::{SignalKind, signal};
match signal(SignalKind::terminate()) {
Ok(mut sigterm) => {
sigterm.recv().await;
}
Err(_) => std::future::pending::<()>().await,
}
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {},
() = terminate => {},
}
tracing::info!("Shutting down gracefully...");
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn reports_bind_errors_with_address() {
let taken = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let port = taken.local_addr().unwrap().port();
let err = serve(Router::new(), "127.0.0.1", port).await.unwrap_err();
assert!(matches!(err, Error::Bind { .. }), "{err}");
assert!(err.to_string().contains(&port.to_string()));
}
}