From a7c9ee7dc4bd5778792988fd4cec3ed696e654d4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 16 Feb 2026 13:35:48 +0000 Subject: [PATCH 1/3] refactor: extract MySQL fields into MysqlTemplateConfig struct - Create new MysqlTemplateConfig struct with all MySQL connection parameters - Replace 5 optional fields in TrackerContext with single Option - Use #[serde(flatten)] to maintain template compatibility - Improve type safety: MySQL fields are all present or all absent - Update tests to work with new structure - Fix clippy doc_markdown warnings by adding backticks around MySQL/SQLite --- .../wrapper/tracker_config/context.rs | 107 +++++++++--------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs b/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs index dbdcaeaf1..ae1935a09 100644 --- a/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs +++ b/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs @@ -71,25 +71,13 @@ pub struct TrackerContext { /// Database file name (e.g., "tracker.db", "sqlite3.db") - used for `SQLite` pub tracker_database_name: String, - /// `MySQL` host (e.g., "mysql", "localhost") - only used when driver is "mysql" - #[serde(skip_serializing_if = "Option::is_none")] - pub mysql_host: Option, - - /// `MySQL` port (typically 3306) - only used when driver is "mysql" - #[serde(skip_serializing_if = "Option::is_none")] - pub mysql_port: Option, - - /// `MySQL` database name - only used when driver is "mysql" - #[serde(skip_serializing_if = "Option::is_none")] - pub mysql_database: Option, - - /// `MySQL` username - only used when driver is "mysql" - #[serde(skip_serializing_if = "Option::is_none")] - pub mysql_user: Option, - - /// `MySQL` password - only used when driver is "mysql" + /// MySQL-specific configuration + /// + /// When `Some`, flattens `MySQL` fields at top level for template compatibility. + /// When `None`, tracker uses `SQLite` database. + #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] - pub mysql_password: Option, + pub mysql: Option, /// Whether tracker is in private mode pub tracker_core_private: bool, @@ -117,6 +105,28 @@ pub struct TrackerContext { pub health_check_api_bind_address: String, } +/// MySQL-specific configuration for template rendering +/// +/// Contains all `MySQL` connection parameters. When present in `TrackerContext`, +/// these fields are flattened into the parent struct for template compatibility. +#[derive(Debug, Clone, Serialize)] +pub struct MysqlTemplateConfig { + /// `MySQL` host (e.g., "mysql", "localhost") + pub mysql_host: String, + + /// `MySQL` port (typically 3306) + pub mysql_port: u16, + + /// `MySQL` database name + pub mysql_database: String, + + /// `MySQL` username + pub mysql_user: String, + + /// `MySQL` password + pub mysql_password: String, +} + /// UDP tracker entry for template rendering #[derive(Debug, Clone, Serialize)] pub struct UdpTrackerEntry { @@ -140,27 +150,22 @@ impl TrackerContext { pub fn from_config(metadata: TemplateMetadata, config: &TrackerConfig) -> Self { use crate::domain::tracker::DatabaseConfig; - let (mysql_host, mysql_port, mysql_database, mysql_user, mysql_password) = - match config.core().database() { - DatabaseConfig::Mysql(mysql_config) => ( - Some(mysql_config.host().to_string()), - Some(mysql_config.port()), - Some(mysql_config.database_name().to_string()), - Some(mysql_config.username().to_string()), - Some(mysql_config.password().expose_secret().to_string()), - ), - DatabaseConfig::Sqlite(..) => (None, None, None, None, None), - }; + let mysql = match config.core().database() { + DatabaseConfig::Mysql(mysql_config) => Some(MysqlTemplateConfig { + mysql_host: mysql_config.host().to_string(), + mysql_port: mysql_config.port(), + mysql_database: mysql_config.database_name().to_string(), + mysql_user: mysql_config.username().to_string(), + mysql_password: mysql_config.password().expose_secret().to_string(), + }), + DatabaseConfig::Sqlite(..) => None, + }; Self { metadata, database_driver: config.core().database().driver_name().to_string(), tracker_database_name: config.core().database().database_name().to_string(), - mysql_host, - mysql_port, - mysql_database, - mysql_user, - mysql_password, + mysql, tracker_core_private: config.core().private(), on_reverse_proxy: config.any_http_tracker_uses_tls_proxy(), udp_trackers: config @@ -200,11 +205,7 @@ impl TrackerContext { metadata, database_driver: "sqlite3".to_string(), tracker_database_name: "sqlite3.db".to_string(), - mysql_host: None, - mysql_port: None, - mysql_database: None, - mysql_user: None, - mysql_password: None, + mysql: None, tracker_core_private: false, on_reverse_proxy: false, // Default: no HTTP trackers use TLS proxy udp_trackers: vec![ @@ -275,11 +276,7 @@ mod tests { assert_eq!(context.database_driver, "sqlite3"); assert_eq!(context.tracker_database_name, "test_tracker.db"); - assert!(context.mysql_host.is_none()); - assert!(context.mysql_port.is_none()); - assert!(context.mysql_database.is_none()); - assert!(context.mysql_user.is_none()); - assert!(context.mysql_password.is_none()); + assert!(context.mysql.is_none()); assert!(context.tracker_core_private); assert_eq!(context.udp_trackers.len(), 2); assert_eq!(context.udp_trackers[0].bind_address, "0.0.0.0:6868"); @@ -328,11 +325,17 @@ mod tests { assert_eq!(context.database_driver, "mysql"); assert_eq!(context.tracker_database_name, "tracker_db"); - assert_eq!(context.mysql_host, Some("mysql".to_string())); - assert_eq!(context.mysql_port, Some(3306)); - assert_eq!(context.mysql_database, Some("tracker_db".to_string())); - assert_eq!(context.mysql_user, Some("tracker_user".to_string())); - assert_eq!(context.mysql_password, Some("secure_pass".to_string())); + + let mysql = context + .mysql + .as_ref() + .expect("MySQL config should be present"); + assert_eq!(mysql.mysql_host, "mysql"); + assert_eq!(mysql.mysql_port, 3306); + assert_eq!(mysql.mysql_database, "tracker_db"); + assert_eq!(mysql.mysql_user, "tracker_user"); + assert_eq!(mysql.mysql_password, "secure_pass"); + assert!(!context.tracker_core_private); } @@ -343,11 +346,7 @@ mod tests { assert_eq!(context.database_driver, "sqlite3"); assert_eq!(context.tracker_database_name, "sqlite3.db"); - assert!(context.mysql_host.is_none()); - assert!(context.mysql_port.is_none()); - assert!(context.mysql_database.is_none()); - assert!(context.mysql_user.is_none()); - assert!(context.mysql_password.is_none()); + assert!(context.mysql.is_none()); assert!(!context.tracker_core_private); assert_eq!(context.udp_trackers.len(), 2); assert_eq!(context.http_trackers.len(), 1); From 145e61cd7fdda4fd1e3ec2ada36ef559cf7e7e7e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 16 Feb 2026 15:31:43 +0000 Subject: [PATCH 2/3] refactor: extract SQLite fields into SqliteTemplateConfig struct - Create new SqliteTemplateConfig struct with tracker_database_name field - Make database configuration symmetric: both SQLite and MySQL use Option structs - Update TrackerContext to have optional sqlite and mysql fields - Use #[serde(flatten)] to maintain template compatibility for both database types - Update all tests to work with new structure - Improve clarity: now explicit that tracker_database_name is SQLite-specific --- .../wrapper/tracker_config/context.rs | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs b/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs index ae1935a09..0ebf6273a 100644 --- a/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs +++ b/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs @@ -68,8 +68,13 @@ pub struct TrackerContext { /// Database driver: "sqlite3" or "mysql" pub database_driver: String, - /// Database file name (e.g., "tracker.db", "sqlite3.db") - used for `SQLite` - pub tracker_database_name: String, + /// SQLite-specific configuration + /// + /// When `Some`, flattens `SQLite` fields at top level for template compatibility. + /// When `None`, tracker uses `MySQL` database. + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub sqlite: Option, /// MySQL-specific configuration /// @@ -105,6 +110,16 @@ pub struct TrackerContext { pub health_check_api_bind_address: String, } +/// SQLite-specific configuration for template rendering +/// +/// Contains `SQLite` database parameters. When present in `TrackerContext`, +/// these fields are flattened into the parent struct for template compatibility. +#[derive(Debug, Clone, Serialize)] +pub struct SqliteTemplateConfig { + /// Database file name (e.g., "tracker.db", "sqlite3.db") + pub tracker_database_name: String, +} + /// MySQL-specific configuration for template rendering /// /// Contains all `MySQL` connection parameters. When present in `TrackerContext`, @@ -150,21 +165,29 @@ impl TrackerContext { pub fn from_config(metadata: TemplateMetadata, config: &TrackerConfig) -> Self { use crate::domain::tracker::DatabaseConfig; - let mysql = match config.core().database() { - DatabaseConfig::Mysql(mysql_config) => Some(MysqlTemplateConfig { - mysql_host: mysql_config.host().to_string(), - mysql_port: mysql_config.port(), - mysql_database: mysql_config.database_name().to_string(), - mysql_user: mysql_config.username().to_string(), - mysql_password: mysql_config.password().expose_secret().to_string(), - }), - DatabaseConfig::Sqlite(..) => None, + let (sqlite, mysql) = match config.core().database() { + DatabaseConfig::Mysql(mysql_config) => ( + None, + Some(MysqlTemplateConfig { + mysql_host: mysql_config.host().to_string(), + mysql_port: mysql_config.port(), + mysql_database: mysql_config.database_name().to_string(), + mysql_user: mysql_config.username().to_string(), + mysql_password: mysql_config.password().expose_secret().to_string(), + }), + ), + DatabaseConfig::Sqlite(sqlite_config) => ( + Some(SqliteTemplateConfig { + tracker_database_name: sqlite_config.database_name().to_string(), + }), + None, + ), }; Self { metadata, database_driver: config.core().database().driver_name().to_string(), - tracker_database_name: config.core().database().database_name().to_string(), + sqlite, mysql, tracker_core_private: config.core().private(), on_reverse_proxy: config.any_http_tracker_uses_tls_proxy(), @@ -204,7 +227,9 @@ impl TrackerContext { Self { metadata, database_driver: "sqlite3".to_string(), - tracker_database_name: "sqlite3.db".to_string(), + sqlite: Some(SqliteTemplateConfig { + tracker_database_name: "sqlite3.db".to_string(), + }), mysql: None, tracker_core_private: false, on_reverse_proxy: false, // Default: no HTTP trackers use TLS proxy @@ -275,7 +300,13 @@ mod tests { let context = TrackerContext::from_config(metadata, &config); assert_eq!(context.database_driver, "sqlite3"); - assert_eq!(context.tracker_database_name, "test_tracker.db"); + + let sqlite = context + .sqlite + .as_ref() + .expect("SQLite config should be present"); + assert_eq!(sqlite.tracker_database_name, "test_tracker.db"); + assert!(context.mysql.is_none()); assert!(context.tracker_core_private); assert_eq!(context.udp_trackers.len(), 2); @@ -324,7 +355,8 @@ mod tests { let context = TrackerContext::from_config(metadata, &config); assert_eq!(context.database_driver, "mysql"); - assert_eq!(context.tracker_database_name, "tracker_db"); + + assert!(context.sqlite.is_none()); let mysql = context .mysql @@ -345,7 +377,13 @@ mod tests { let context = TrackerContext::default_config(metadata); assert_eq!(context.database_driver, "sqlite3"); - assert_eq!(context.tracker_database_name, "sqlite3.db"); + + let sqlite = context + .sqlite + .as_ref() + .expect("SQLite config should be present"); + assert_eq!(sqlite.tracker_database_name, "sqlite3.db"); + assert!(context.mysql.is_none()); assert!(!context.tracker_core_private); assert_eq!(context.udp_trackers.len(), 2); @@ -369,7 +407,7 @@ mod tests { let context = TrackerContext::from_config(metadata, &config); let cloned = context.clone(); - assert_eq!(context.tracker_database_name, cloned.tracker_database_name); + assert_eq!(context.database_driver, cloned.database_driver); assert_eq!(context.tracker_core_private, cloned.tracker_core_private); assert_eq!(context.udp_trackers.len(), cloned.udp_trackers.len()); assert_eq!(context.http_trackers.len(), cloned.http_trackers.len()); @@ -382,6 +420,6 @@ mod tests { let debug_output = format!("{context:?}"); assert!(debug_output.contains("TrackerContext")); - assert!(debug_output.contains("tracker_database_name")); + assert!(debug_output.contains("database_driver")); } } From 26b78307e081ccee69dce4710dc0d74fc7dd9466 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 16 Feb 2026 15:34:33 +0000 Subject: [PATCH 3/3] refactor: replace database_driver String with DatabaseDriver enum - Create DatabaseDriver enum with Sqlite3 and Mysql variants - Use #[serde(rename_all = "lowercase")] for template compatibility - Improve type safety: enum prevents invalid database driver values - Update from_config to pattern match on DatabaseConfig enum - Update default_config to use DatabaseDriver::Sqlite3 - Update all tests to use enum variants instead of string comparisons - Add backticks to enum variant documentation for clippy compliance --- .../wrapper/tracker_config/context.rs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs b/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs index 0ebf6273a..9166d2b70 100644 --- a/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs +++ b/src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs @@ -65,8 +65,8 @@ pub struct TrackerContext { #[serde(flatten)] pub metadata: TemplateMetadata, - /// Database driver: "sqlite3" or "mysql" - pub database_driver: String, + /// Database driver type + pub database_driver: DatabaseDriver, /// SQLite-specific configuration /// @@ -110,6 +110,19 @@ pub struct TrackerContext { pub health_check_api_bind_address: String, } +/// Database driver type for tracker configuration +/// +/// Represents the database backend used by the tracker. +/// Serializes to lowercase string values for template compatibility. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum DatabaseDriver { + /// `SQLite3` database driver + Sqlite3, + /// `MySQL` database driver + Mysql, +} + /// SQLite-specific configuration for template rendering /// /// Contains `SQLite` database parameters. When present in `TrackerContext`, @@ -184,9 +197,14 @@ impl TrackerContext { ), }; + let database_driver = match config.core().database() { + DatabaseConfig::Mysql(..) => DatabaseDriver::Mysql, + DatabaseConfig::Sqlite(..) => DatabaseDriver::Sqlite3, + }; + Self { metadata, - database_driver: config.core().database().driver_name().to_string(), + database_driver, sqlite, mysql, tracker_core_private: config.core().private(), @@ -226,7 +244,7 @@ impl TrackerContext { pub fn default_config(metadata: TemplateMetadata) -> Self { Self { metadata, - database_driver: "sqlite3".to_string(), + database_driver: DatabaseDriver::Sqlite3, sqlite: Some(SqliteTemplateConfig { tracker_database_name: "sqlite3.db".to_string(), }), @@ -299,7 +317,7 @@ mod tests { let metadata = create_test_metadata(); let context = TrackerContext::from_config(metadata, &config); - assert_eq!(context.database_driver, "sqlite3"); + assert_eq!(context.database_driver, DatabaseDriver::Sqlite3); let sqlite = context .sqlite @@ -354,7 +372,7 @@ mod tests { let metadata = create_test_metadata(); let context = TrackerContext::from_config(metadata, &config); - assert_eq!(context.database_driver, "mysql"); + assert_eq!(context.database_driver, DatabaseDriver::Mysql); assert!(context.sqlite.is_none()); @@ -376,7 +394,7 @@ mod tests { let metadata = create_test_metadata(); let context = TrackerContext::default_config(metadata); - assert_eq!(context.database_driver, "sqlite3"); + assert_eq!(context.database_driver, DatabaseDriver::Sqlite3); let sqlite = context .sqlite