diff --git a/src/app.rs b/src/app.rs index a360ed79..9df253e6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,6 +6,7 @@ use tokio::task::JoinHandle; use crate::bootstrap::logging; use crate::cache::image::manager::ImageCacheService; use crate::common::AppData; +use crate::config::validator::Validator; use crate::config::Configuration; use crate::databases::database; use crate::services::authentication::{DbUserAuthenticationRepository, JsonWebToken, Service}; @@ -41,8 +42,6 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running logging::setup(&log_level); - configuration.validate().await.expect("invalid configuration"); - let configuration = Arc::new(configuration); // Get configuration settings needed to build the app dependencies and @@ -50,6 +49,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let settings = configuration.settings.read().await; + settings.validate().expect("invalid settings"); + // From [database] config let database_connect_url = settings.database.connect_url.clone(); // From [importer] config diff --git a/src/config/mod.rs b/src/config/mod.rs index a2b1ec6b..24d1efc5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,6 @@ //! Configuration for the application. pub mod v1; +pub mod validator; use std::sync::Arc; use std::{env, fs}; @@ -11,7 +12,7 @@ use serde_with::{serde_as, NoneAsEmptyString}; use thiserror::Error; use tokio::sync::RwLock; use torrust_index_located_error::{Located, LocatedError}; -use url::{ParseError, Url}; +use url::Url; pub type Settings = v1::Settings; pub type Api = v1::api::Api; @@ -114,26 +115,6 @@ pub enum Error { Infallible, } -/// Errors that can occur validating the configuration. -#[derive(Error, Debug)] -pub enum ValidationError { - /// Unable to load the configuration from the configuration file. - #[error("Invalid tracker URL: {source}")] - InvalidTrackerUrl { source: LocatedError<'static, ParseError> }, - - #[error("UDP private trackers are not supported. URL schemes for private tracker URLs must be HTTP ot HTTPS")] - UdpTrackersInPrivateModeNotSupported, -} - -impl From for Error { - #[track_caller] - fn from(err: ConfigError) -> Self { - Self::ConfigError { - source: Located(err).into(), - } - } -} - /* todo: Use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. @@ -295,44 +276,17 @@ impl Configuration { settings_lock.net.base_url.clone() } +} - /// # Errors - /// - /// Will return an error if the configuration is invalid. - pub async fn validate(&self) -> Result<(), ValidationError> { - self.validate_tracker_config().await - } - - /// # Errors - /// - /// Will return an error if the `tracker` configuration section is invalid. - pub async fn validate_tracker_config(&self) -> Result<(), ValidationError> { - let settings_lock = self.settings.read().await; - - let tracker_mode = settings_lock.tracker.mode.clone(); - let tracker_url = settings_lock.tracker.url.clone(); - - let tracker_url = match parse_url(&tracker_url) { - Ok(url) => url, - Err(err) => { - return Err(ValidationError::InvalidTrackerUrl { - source: Located(err).into(), - }) - } - }; - - if tracker_mode.is_close() && (tracker_url.scheme() != "http" && tracker_url.scheme() != "https") { - return Err(ValidationError::UdpTrackersInPrivateModeNotSupported); +impl From for Error { + #[track_caller] + fn from(err: ConfigError) -> Self { + Self::ConfigError { + source: Located(err).into(), } - - Ok(()) } } -fn parse_url(url_str: &str) -> Result { - Url::parse(url_str) -} - /// The public index configuration. /// There is an endpoint to get this configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -343,6 +297,10 @@ pub struct ConfigurationPublic { email_on_signup: EmailOnSignup, } +fn parse_url(url_str: &str) -> Result { + Url::parse(url_str) +} + #[cfg(test)] mod tests { @@ -503,6 +461,7 @@ mod tests { mod syntax_checks { // todo: use rich types in configuration structs for basic syntax checks. + use crate::config::validator::Validator; use crate::config::Configuration; #[tokio::test] @@ -511,13 +470,13 @@ mod tests { let mut settings_lock = configuration.settings.write().await; settings_lock.tracker.url = "INVALID URL".to_string(); - drop(settings_lock); - assert!(configuration.validate().await.is_err()); + assert!(settings_lock.validate().is_err()); } } mod semantic_validation { + use crate::config::validator::Validator; use crate::config::{Configuration, TrackerMode}; #[tokio::test] @@ -527,9 +486,8 @@ mod tests { let mut settings_lock = configuration.settings.write().await; settings_lock.tracker.mode = TrackerMode::Private; settings_lock.tracker.url = "udp://localhost:6969".to_string(); - drop(settings_lock); - assert!(configuration.validate().await.is_err()); + assert!(settings_lock.validate().is_err()); } } } diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index c84132fe..04d8b9f9 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -19,6 +19,7 @@ use self::net::Network; use self::tracker::Tracker; use self::tracker_statistics_importer::TrackerStatisticsImporter; use self::website::Website; +use super::validator::{ValidationError, Validator}; /// The whole configuration for the index. #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] @@ -62,3 +63,9 @@ impl Settings { "***".clone_into(&mut self.auth.secret_key); } } + +impl Validator for Settings { + fn validate(&self) -> Result<(), ValidationError> { + self.tracker.validate() + } +} diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs index ff0ffe71..fd95a82e 100644 --- a/src/config/v1/tracker.rs +++ b/src/config/v1/tracker.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; +use torrust_index_located_error::Located; -use crate::config::TrackerMode; +use super::{ValidationError, Validator}; +use crate::config::{parse_url, TrackerMode}; /// Configuration for the associated tracker. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -25,6 +27,28 @@ impl Tracker { } } +impl Validator for Tracker { + fn validate(&self) -> Result<(), ValidationError> { + let tracker_mode = self.mode.clone(); + let tracker_url = self.url.clone(); + + let tracker_url = match parse_url(&tracker_url) { + Ok(url) => url, + Err(err) => { + return Err(ValidationError::InvalidTrackerUrl { + source: Located(err).into(), + }) + } + }; + + if tracker_mode.is_close() && (tracker_url.scheme() != "http" && tracker_url.scheme() != "https") { + return Err(ValidationError::UdpTrackersInPrivateModeNotSupported); + } + + Ok(()) + } +} + impl Default for Tracker { fn default() -> Self { Self { diff --git a/src/config/validator.rs b/src/config/validator.rs new file mode 100644 index 00000000..d3ef047a --- /dev/null +++ b/src/config/validator.rs @@ -0,0 +1,22 @@ +//! Trait to validate the whole settings of sections of the settings. +use thiserror::Error; +use torrust_index_located_error::LocatedError; +use url::ParseError; + +/// Errors that can occur validating the configuration. +#[derive(Error, Debug)] +pub enum ValidationError { + /// Unable to load the configuration from the configuration file. + #[error("Invalid tracker URL: {source}")] + InvalidTrackerUrl { source: LocatedError<'static, ParseError> }, + + #[error("UDP private trackers are not supported. URL schemes for private tracker URLs must be HTTP ot HTTPS")] + UdpTrackersInPrivateModeNotSupported, +} + +pub trait Validator { + /// # Errors + /// + /// Will return an error if the configuration is invalid. + fn validate(&self) -> Result<(), ValidationError>; +}