Share startup logic.

This commit is contained in:
LukeMathWalker 2021-02-14 16:27:04 +00:00
parent beeeacdafb
commit 55da2e2e2e
4 changed files with 59 additions and 58 deletions

View file

@ -3,21 +3,21 @@ use serde_aux::field_attributes::deserialize_number_from_string;
use sqlx::postgres::{PgConnectOptions, PgSslMode}; use sqlx::postgres::{PgConnectOptions, PgSslMode};
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Clone)]
pub struct Settings { pub struct Settings {
pub database: DatabaseSettings, pub database: DatabaseSettings,
pub application: ApplicationSettings, pub application: ApplicationSettings,
pub email_client: EmailClientSettings, pub email_client: EmailClientSettings,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Clone)]
pub struct ApplicationSettings { pub struct ApplicationSettings {
#[serde(deserialize_with = "deserialize_number_from_string")] #[serde(deserialize_with = "deserialize_number_from_string")]
pub port: u16, pub port: u16,
pub host: String, pub host: String,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Clone)]
pub struct DatabaseSettings { pub struct DatabaseSettings {
pub username: String, pub username: String,
pub password: String, pub password: String,
@ -48,7 +48,7 @@ impl DatabaseSettings {
} }
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Clone)]
pub struct EmailClientSettings { pub struct EmailClientSettings {
pub base_url: String, pub base_url: String,
pub sender_email: String, pub sender_email: String,

View file

@ -1,8 +1,5 @@
use sqlx::postgres::PgPoolOptions;
use std::net::TcpListener;
use zero2prod::configuration::get_configuration; use zero2prod::configuration::get_configuration;
use zero2prod::email_client::EmailClient; use zero2prod::startup::build;
use zero2prod::startup::run;
use zero2prod::telemetry::{get_subscriber, init_subscriber}; use zero2prod::telemetry::{get_subscriber, init_subscriber};
#[actix_web::main] #[actix_web::main]
@ -11,27 +8,7 @@ async fn main() -> std::io::Result<()> {
init_subscriber(subscriber); init_subscriber(subscriber);
let configuration = get_configuration().expect("Failed to read configuration."); let configuration = get_configuration().expect("Failed to read configuration.");
let connection_pool = PgPoolOptions::new() let server = build(configuration).await?;
.connect_timeout(std::time::Duration::from_secs(2)) server.await?;
.connect_with(configuration.database.with_db())
.await
.expect("Failed to connect to Postgres.");
let sender_email = configuration
.email_client
.sender()
.expect("Invalid sender email address.");
let email_client = EmailClient::new(
configuration.email_client.base_url,
sender_email,
configuration.email_client.authorization_token,
);
let address = format!(
"{}:{}",
configuration.application.host, configuration.application.port
);
let listener = TcpListener::bind(address)?;
run(listener, connection_pool, email_client)?.await?;
Ok(()) Ok(())
} }

View file

@ -1,12 +1,44 @@
use crate::configuration::{DatabaseSettings, Settings};
use crate::email_client::EmailClient; use crate::email_client::EmailClient;
use crate::routes::{health_check, subscribe}; use crate::routes::{health_check, subscribe};
use actix_web::dev::Server; use actix_web::dev::Server;
use actix_web::web::Data; use actix_web::web::Data;
use actix_web::{web, App, HttpServer}; use actix_web::{web, App, HttpServer};
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool; use sqlx::PgPool;
use std::net::TcpListener; use std::net::TcpListener;
use tracing_actix_web::TracingLogger; use tracing_actix_web::TracingLogger;
pub async fn build(configuration: Settings) -> Result<Server, std::io::Error> {
let connection_pool = get_connection_pool(&configuration.database)
.await
.expect("Failed to connect to Postgres.");
let sender_email = configuration
.email_client
.sender()
.expect("Invalid sender email address.");
let email_client = EmailClient::new(
configuration.email_client.base_url,
sender_email,
configuration.email_client.authorization_token,
);
let address = format!(
"{}:{}",
configuration.application.host, configuration.application.port
);
let listener = TcpListener::bind(address)?;
run(listener, connection_pool, email_client)
}
pub async fn get_connection_pool(configuration: &DatabaseSettings) -> Result<PgPool, sqlx::Error> {
PgPoolOptions::new()
.connect_timeout(std::time::Duration::from_secs(2))
.connect_with(configuration.with_db())
.await
}
pub fn run( pub fn run(
listener: TcpListener, listener: TcpListener,
db_pool: PgPool, db_pool: PgPool,

View file

@ -1,9 +1,7 @@
use sqlx::{Connection, Executor, PgConnection, PgPool}; use sqlx::{Connection, Executor, PgConnection, PgPool};
use std::net::TcpListener;
use uuid::Uuid; use uuid::Uuid;
use zero2prod::configuration::{get_configuration, DatabaseSettings}; use zero2prod::configuration::{get_configuration, DatabaseSettings};
use zero2prod::email_client::EmailClient; use zero2prod::startup::{build, get_connection_pool};
use zero2prod::startup::run;
use zero2prod::telemetry::{get_subscriber, init_subscriber}; use zero2prod::telemetry::{get_subscriber, init_subscriber};
// Ensure that the `tracing` stack is only initialised once using `lazy_static` // Ensure that the `tracing` stack is only initialised once using `lazy_static`
@ -21,35 +19,29 @@ pub struct TestApp {
} }
pub async fn spawn_app() -> TestApp { pub async fn spawn_app() -> TestApp {
// The first time `initialize` is invoked the code in `TRACING` is executed. // Randomise configuration to ensure test isolation
// All other invocations will instead skip execution. let configuration = {
lazy_static::initialize(&TRACING); let mut c = get_configuration().expect("Failed to read configuration.");
// Use a different database for each test case
c.database.database_name = Uuid::new_v4().to_string();
// Use a random OS port
c.application.port = 0;
c
};
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port"); // Create and migrate the database
// We retrieve the port assigned to us by the OS configure_database(&configuration.database).await;
let port = listener.local_addr().unwrap().port();
let address = format!("http://127.0.0.1:{}", port);
let mut configuration = get_configuration().expect("Failed to read configuration."); // Launch the application as a background task
configuration.database.database_name = Uuid::new_v4().to_string(); let server = build(configuration.clone())
let connection_pool = configure_database(&configuration.database).await; .await
.expect("Failed to build application.");
let sender_email = configuration
.email_client
.sender()
.expect("Invalid sender email address.");
let email_client = EmailClient::new(
configuration.email_client.base_url,
sender_email,
configuration.email_client.authorization_token,
);
let server =
run(listener, connection_pool.clone(), email_client).expect("Failed to bind address");
let _ = tokio::spawn(server); let _ = tokio::spawn(server);
TestApp { TestApp {
address, address: todo!(),
db_pool: connection_pool, db_pool: get_connection_pool(&configuration.database)
.await
.expect("Failed to connect to the database"),
} }
} }