Skip to content

Commit

Permalink
refactor: [#581] move semantic validation
Browse files Browse the repository at this point in the history
to the type that has the knowledge.
  • Loading branch information
josecelano committed May 20, 2024
1 parent 790e1ec commit 8d3c8cb
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 61 deletions.
5 changes: 3 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -41,15 +42,15 @@ 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
// services: main API server and tracker torrents importer.

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
Expand Down
74 changes: 16 additions & 58 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Configuration for the application.
pub mod v1;
pub mod validator;

use std::sync::Arc;
use std::{env, fs};
Expand All @@ -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;
Expand Down Expand Up @@ -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<ConfigError> 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.
Expand Down Expand Up @@ -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<ConfigError> for Error {
#[track_caller]
fn from(err: ConfigError) -> Self {
Self::ConfigError {
source: Located(err).into(),
}

Ok(())
}
}

fn parse_url(url_str: &str) -> Result<Url, url::ParseError> {
Url::parse(url_str)
}

/// The public index configuration.
/// There is an endpoint to get this configuration.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand All @@ -343,6 +297,10 @@ pub struct ConfigurationPublic {
email_on_signup: EmailOnSignup,
}

fn parse_url(url_str: &str) -> Result<Url, url::ParseError> {
Url::parse(url_str)
}

#[cfg(test)]
mod tests {

Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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());
}
}
}
7 changes: 7 additions & 0 deletions src/config/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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()
}
}
26 changes: 25 additions & 1 deletion src/config/v1/tracker.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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 {
Expand Down
22 changes: 22 additions & 0 deletions src/config/validator.rs
Original file line number Diff line number Diff line change
@@ -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>;
}

0 comments on commit 8d3c8cb

Please sign in to comment.