diff --git a/src/main.rs b/src/main.rs index 6a49f8e..3347383 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,6 @@ -use sqlx::PgPool; -use tokio::net::TcpListener; use zero2prod::{ configuration::get_configuration, - email_client::EmailClient, - startup::run, + startup::Application, telemetry::{get_subscriber, init_subscriber}, }; @@ -13,26 +10,7 @@ async fn main() -> Result<(), std::io::Error> { init_subscriber(subscriber); let config = get_configuration().expect("Failed to read configuration"); - let address = format!("{}:{}", config.application.host, config.application.port); + let app = Application::build(config).await; - let listener = TcpListener::bind(address) - .await - .expect("Failed to open listener"); - - let pool = PgPool::connect_lazy_with(config.database.with_db()); - - let sender_email = config - .email_client - .sender() - .expect("Invalid sender email address"); - let timeout = config.email_client.timeout(); - - let email_client = EmailClient::new( - config.email_client.base_url, - sender_email, - config.email_client.authorization_token, - timeout, - ); - - run(listener, pool, email_client).await + app.run_until_stopped().await } diff --git a/src/startup.rs b/src/startup.rs index 8a5ab69..ce91c89 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -1,12 +1,14 @@ use crate::{ app_state::AppState, + configuration::{DatabaseSettings, Settings}, email_client::EmailClient, request_id::RequestUuid, routes::{health_check, subscriptions}, telemetry::request_span, }; -use axum::Router; -use sqlx::PgPool; +use axum::{serve::Serve, Router}; +use sqlx::{postgres::PgPoolOptions, PgPool}; +use std::net::SocketAddr; use tokio::net::TcpListener; use tower::ServiceBuilder; use tower_http::{ @@ -15,11 +17,62 @@ use tower_http::{ }; use tracing::Level; -pub async fn run( +pub struct Application { + local_addr: SocketAddr, + server: Serve, +} + +impl Application { + pub async fn build(config: Settings) -> Application { + let address = format!("{}:{}", config.application.host, config.application.port); + + let listener = TcpListener::bind(address) + .await + .expect("Failed to open listener"); + + let db_pool = get_connection_pool(&config.database); + + let sender_email = config + .email_client + .sender() + .expect("Invalid sender email address"); + let timeout = config.email_client.timeout(); + + let email_client = EmailClient::new( + config.email_client.base_url, + sender_email, + config.email_client.authorization_token, + timeout, + ); + + let local_addr = listener + .local_addr() + .expect("Failed to get local address from the listener"); + + let server = run(listener, db_pool, email_client).await; + + Self { local_addr, server } + } + + pub fn local_addr(&self) -> SocketAddr { + self.local_addr + } + + pub async fn run_until_stopped(self) -> Result<(), std::io::Error> { + tracing::info!("Listening on {}", self.local_addr); + self.server.await + } +} + +pub fn get_connection_pool(config: &DatabaseSettings) -> PgPool { + PgPoolOptions::new().connect_lazy_with(config.with_db()) +} + +async fn run( listener: TcpListener, db_pool: PgPool, email_client: EmailClient, -) -> Result<(), std::io::Error> { +) -> Serve { let app_state = AppState { db_pool, email_client, @@ -41,6 +94,5 @@ pub async fn run( .propagate_x_request_id(), ); - tracing::info!("Listening on {}", listener.local_addr()?); - axum::serve(listener, app).await + axum::serve(listener, app) } diff --git a/tests/api/helpers.rs b/tests/api/helpers.rs index e63d4fc..1b6dcb6 100644 --- a/tests/api/helpers.rs +++ b/tests/api/helpers.rs @@ -4,8 +4,7 @@ use std::net::SocketAddr; use uuid::Uuid; use zero2prod::{ configuration::{get_configuration, DatabaseSettings}, - email_client::EmailClient, - startup::run, + startup::{get_connection_pool, Application}, telemetry::{get_subscriber, init_subscriber}, }; @@ -31,34 +30,19 @@ pub async fn spawn_app() -> TestApp { let mut config = get_configuration().expect("Failed to read configuration"); config.database.database_name = Uuid::new_v4().to_string(); + config.application.port = 0; - let listener = tokio::net::TcpListener::bind("localhost:0") - .await - .expect("Failed to bind address"); + let db_pool = configure_database(&config.database).await; + let app = Application::build(config).await; - let app = TestApp { - address: listener.local_addr().expect("Failed to get local address"), - db_pool: configure_database(&config.database).await, + let test_app = TestApp { + address: app.local_addr(), + db_pool, }; - let pool = app.db_pool.clone(); - let sender_email = config.email_client.sender().expect("Invalid sender email"); - let timeout = config.email_client.timeout(); - - let email_client = EmailClient::new( - config.email_client.base_url, - sender_email, - config.email_client.authorization_token, - timeout, - ); - - tokio::spawn(async move { - run(listener, pool, email_client) - .await - .expect("Failed to run server"); - }); + tokio::spawn(async { app.run_until_stopped().await }); - app + test_app } pub fn url(addr: SocketAddr, endpoint: &str) -> String { @@ -74,9 +58,7 @@ async fn configure_database(configuration: &DatabaseSettings) -> PgPool { .await .expect("Failed to create database"); - let pool = PgPool::connect_with(configuration.with_db()) - .await - .expect("Failed to connect to Postgres"); + let pool = get_connection_pool(&configuration); sqlx::migrate!("./migrations") .run(&pool)