diff --git a/src/config/v1/database.rs b/src/config/v1/database.rs index ee125347..5efa824a 100644 --- a/src/config/v1/database.rs +++ b/src/config/v1/database.rs @@ -1,86 +1,20 @@ -use std::fmt; - use serde::{Deserialize, Serialize}; +use url::Url; /// Database configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Database { - /// The connection string for the database. For example: + /// The connection URL for the database. For example: /// - /// Masked: `***`. /// Sqlite: `sqlite://data.db?mode=rwc`. /// Mysql: `mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing`. - pub connect_url: ConnectOptions, + pub connect_url: Url, } impl Default for Database { fn default() -> Self { Self { - connect_url: ConnectOptions::new("sqlite://data.db?mode=rwc"), + connect_url: Url::parse("sqlite://data.db?mode=rwc").unwrap(), } } } - -/// This allows a particular case when we want to hide the connection options -/// because it contains secrets we don't want to show. -const DB_CONNECT_MASKED: &str = "***"; - -/// Prefix for connection to `SQLite` database. -const DB_CONNECT_SQLITE_PREFIX: &str = "sqlite://"; - -/// Prefix for connection to `MySQL` database. -const DB_CONNECT_MYSQL_PREFIX: &str = "mysql://"; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ConnectOptions(String); - -impl ConnectOptions { - /// # Panics - /// - /// Will panic if the connect options are empty. - #[must_use] - pub fn new(connect_options: &str) -> Self { - assert!(!connect_options.is_empty(), "database connect options cannot be empty"); - assert!( - connect_options.starts_with(DB_CONNECT_SQLITE_PREFIX) - || connect_options.starts_with(DB_CONNECT_MYSQL_PREFIX) - || connect_options.starts_with(DB_CONNECT_MASKED), - "database driver not supported" - ); - - Self(connect_options.to_owned()) - } - - #[must_use] - pub fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() - } -} - -impl fmt::Display for ConnectOptions { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[cfg(test)] -mod tests { - use super::ConnectOptions; - - #[test] - #[should_panic(expected = "database connect options cannot be empty")] - fn database_connect_options_can_not_be_empty() { - drop(ConnectOptions::new("")); - } - - #[test] - #[should_panic(expected = "database driver not supported")] - fn database_connect_options_only_supports_sqlite_and_mysql() { - drop(ConnectOptions::new("not-supported://")); - } - - #[test] - fn database_connect_options_can_be_masked() { - drop(ConnectOptions::new("***")); - } -} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index 5aa668ce..8deea44d 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use self::api::Api; use self::auth::{Auth, SecretKey}; -use self::database::{ConnectOptions, Database}; +use self::database::Database; use self::image_cache::ImageCache; use self::mail::Mail; use self::net::Network; @@ -58,7 +58,9 @@ impl Settings { pub fn remove_secrets(&mut self) { "***".clone_into(&mut self.tracker.token); - self.database.connect_url = ConnectOptions::new("***"); + if let Some(_password) = self.database.connect_url.password() { + let _ = self.database.connect_url.set_password(Some("***")); + } "***".clone_into(&mut self.mail.password); self.auth.secret_key = SecretKey::new("***"); } diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index ad0e8fd2..eccbbc4c 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -103,7 +103,7 @@ pub async fn import() { eprintln!("Tracker url: {}", tracker_url.green()); let database = Arc::new( - database::connect(&settings.database.connect_url.to_string()) + database::connect(settings.database.connect_url.as_ref()) .await .expect("unable to connect to db"), ); diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 40840953..c81bf70a 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,6 +1,7 @@ use std::env; use torrust_index::web::api::Version; +use url::Url; use super::config::{initialize_configuration, ENV_VAR_DB_CONNECT_URL, ENV_VAR_INDEX_SHARED}; use crate::common::contexts::settings::Settings; @@ -90,8 +91,14 @@ impl TestEnv { pub fn server_settings_masking_secrets(&self) -> Option { match self.starting_settings.clone() { Some(mut settings) => { + // Mask password in DB connect URL if present + let mut connect_url = Url::parse(&settings.database.connect_url).expect("valid database connect URL"); + if let Some(_password) = connect_url.password() { + let _ = connect_url.set_password(Some("***")); + settings.database.connect_url = connect_url.to_string(); + } + "***".clone_into(&mut settings.tracker.token); - "***".clone_into(&mut settings.database.connect_url); "***".clone_into(&mut settings.mail.password); "***".clone_into(&mut settings.auth.secret_key); Some(settings) diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index bad6278d..7a389930 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,8 +1,8 @@ use tempfile::TempDir; use torrust_index::config; -use torrust_index::config::v1::database::ConnectOptions; use torrust_index::config::FREE_PORT; use torrust_index::web::api::Version; +use url::Url; use super::app_starter::AppStarter; use crate::common::random; @@ -84,7 +84,7 @@ fn ephemeral(temp_dir: &TempDir) -> config::Settings { // Ephemeral SQLite database configuration.database.connect_url = - ConnectOptions::new(&format!("sqlite://{}?mode=rwc", random_database_file_path_in(temp_dir))); + Url::parse(&format!("sqlite://{}?mode=rwc", random_database_file_path_in(temp_dir))).unwrap(); configuration }