diff --git a/Cargo.lock b/Cargo.lock index 4c7dce0..d6ad881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1353,6 +1353,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -1852,6 +1861,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207f67b28fe90fb596503a9bf0bf1ea5e831e21307658e177c5dfcdfc3ab8a0a" +dependencies = [ + "chrono", + "serde", + "serde-value", + "serde_json", +] + [[package]] name = "serde-untagged" version = "0.1.8" @@ -1863,6 +1884,16 @@ dependencies = [ "typeid", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.219" @@ -3194,6 +3225,7 @@ dependencies = [ "reqwest", "secrecy", "serde", + "serde-aux", "sqlx", "tokio", "tower-http", diff --git a/Cargo.toml b/Cargo.toml index 1bb4fb4..8e6163d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ chrono = { version = "0.4.41", default-features = false, features = ["clock"] } config = "0.15.14" secrecy = { version = "0.10.3", features = ["serde"] } serde = { version = "1.0.219", features = ["derive"] } +serde-aux = "4.7.0" sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "macros", "postgres", "uuid", "chrono", "migrate"] } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } tower-http = { version = "0.6.6", features = ["trace"] } diff --git a/configuration/local.yaml b/configuration/local.yaml index d4147f8..72cf23c 100644 --- a/configuration/local.yaml +++ b/configuration/local.yaml @@ -1,2 +1,4 @@ application: host: "127.0.0.1" +database: + require_ssl: false diff --git a/configuration/production.yaml b/configuration/production.yaml index 1ecbd34..036bab5 100644 --- a/configuration/production.yaml +++ b/configuration/production.yaml @@ -1,2 +1,4 @@ application: host: "0.0.0.0" +database: + require_ssl: true diff --git a/src/configuration.rs b/src/configuration.rs index 24b38a6..4d31d45 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,5 +1,7 @@ use secrecy::{ExposeSecret, SecretString}; use serde::Deserialize; +use serde_aux::field_attributes::deserialize_number_from_string; +use sqlx::postgres::{PgConnectOptions, PgSslMode}; pub fn get_configuration() -> Result { let base_path = std::env::current_dir().expect("Failed to determine the current directory"); @@ -13,6 +15,11 @@ pub fn get_configuration() -> Result { let settings = config::Config::builder() .add_source(config::File::from(config_dir.join("base.yaml"))) .add_source(config::File::from(config_dir.join(environment_filename))) + .add_source( + config::Environment::with_prefix("APP") + .prefix_separator("_") + .prefix("__"), + ) .build()?; settings.try_deserialize::() @@ -55,6 +62,7 @@ pub struct Settings { #[derive(Deserialize)] pub struct ApplicationSettings { + #[serde(deserialize_with = "deserialize_number_from_string")] pub port: u16, pub host: String, } @@ -63,32 +71,29 @@ pub struct ApplicationSettings { pub struct DatabaseSettings { pub username: String, pub password: SecretString, + #[serde(deserialize_with = "deserialize_number_from_string")] pub port: u16, pub host: String, pub database_name: String, + pub require_ssl: bool, } impl DatabaseSettings { - pub fn connection_string(&self) -> SecretString { - format!( - "postgres://{}:{}@{}:{}/{}", - self.username, - self.password.expose_secret(), - self.host, - self.port, - self.database_name - ) - .into() + pub fn with_db(&self) -> PgConnectOptions { + self.without_db().database(&self.database_name) } - pub fn connection_string_without_db(&self) -> SecretString { - format!( - "postgres://{}:{}@{}:{}", - self.username, - self.password.expose_secret(), - self.host, - self.port - ) - .into() + pub fn without_db(&self) -> PgConnectOptions { + let ssl_mode = if self.require_ssl { + PgSslMode::Require + } else { + PgSslMode::Prefer + }; + PgConnectOptions::new() + .host(&self.host) + .username(&self.username) + .password(self.password.expose_secret()) + .port(self.port) + .ssl_mode(ssl_mode) } } diff --git a/src/main.rs b/src/main.rs index b6ac537..e73d473 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ -use secrecy::ExposeSecret; -use sqlx::PgPool; +use sqlx::postgres::PgPoolOptions; use tokio::net::TcpListener; use zero2prod::{configuration::get_configuration, startup::run, telemetry::init_subscriber}; @@ -14,8 +13,7 @@ async fn main() { .await .unwrap(); tracing::debug!("listening on {}", listener.local_addr().unwrap()); - let connection_pool = - PgPool::connect_lazy(configuration.database.connection_string().expose_secret()).unwrap(); + let connection_pool = PgPoolOptions::new().connect_lazy_with(configuration.database.with_db()); run(listener, connection_pool).await } diff --git a/tests/health_check.rs b/tests/health_check.rs index dc7b22a..3515853 100644 --- a/tests/health_check.rs +++ b/tests/health_check.rs @@ -1,6 +1,5 @@ use once_cell::sync::Lazy; -use secrecy::ExposeSecret; -use sqlx::{Executor, PgPool}; +use sqlx::{Connection, Executor, PgConnection, PgPool}; use tokio::net::TcpListener; use uuid::Uuid; use zero2prod::{ @@ -112,7 +111,7 @@ async fn spawn_app() -> TestApp { } async fn configure_database(config: &DatabaseSettings) -> PgPool { - let connection = PgPool::connect(config.connection_string_without_db().expose_secret()) + let mut connection = PgConnection::connect_with(&config.without_db()) .await .expect("Failed to connect to Postgres"); connection @@ -120,7 +119,7 @@ async fn configure_database(config: &DatabaseSettings) -> PgPool { .await .expect("Failed to create the database"); - let connection_pool = PgPool::connect(config.connection_string().expose_secret()) + let connection_pool = PgPool::connect_with(config.with_db()) .await .expect("Failed to connect to Postgres"); sqlx::migrate!("./migrations")