Skip to content

Commit

Permalink
refactor: [#599] extract types for config::v1::tracker::Tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed May 24, 2024
1 parent b1d5267 commit 8e53982
Show file tree
Hide file tree
Showing 25 changed files with 182 additions and 123 deletions.
2 changes: 1 addition & 1 deletion share/default/config/index.development.sqlite3.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ log_level = "info"
name = "Torrust"

[tracker]
api_url = "http://localhost:1212"
api_url = "http://localhost:1212/"
mode = "Public"
token = "MyAccessToken"
token_valid_seconds = 7257600
Expand Down
44 changes: 15 additions & 29 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use tokio::sync::RwLock;
use torrust_index_located_error::LocatedError;
use url::Url;

use self::v1::tracker::ApiToken;
use crate::web::api::server::DynError;

pub type Settings = v1::Settings;
Expand Down Expand Up @@ -55,7 +56,7 @@ pub const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY";
pub struct Info {
config_toml: Option<String>,
config_toml_path: String,
tracker_api_token: Option<String>,
tracker_api_token: Option<ApiToken>,
auth_secret_key: Option<String>,
}

Expand Down Expand Up @@ -88,7 +89,10 @@ impl Info {
default_config_toml_path
};

let tracker_api_token = env::var(env_var_tracker_api_admin_token).ok();
let tracker_api_token = env::var(env_var_tracker_api_admin_token)
.ok()
.map(|token| ApiToken::new(&token));

let auth_secret_key = env::var(env_var_auth_secret_key).ok();

Ok(Self {
Expand Down Expand Up @@ -325,21 +329,18 @@ impl Configuration {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ConfigurationPublic {
website_name: String,
tracker_url: String,
tracker_url: Url,
tracker_mode: TrackerMode,
email_on_signup: EmailOnSignup,
}

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

#[cfg(test)]
mod tests {

use url::Url;

use crate::config::v1::auth::SecretKey;
use crate::config::v1::tracker::ApiToken;
use crate::config::{Configuration, ConfigurationPublic, Info, Settings};

#[cfg(test)]
Expand All @@ -350,7 +351,7 @@ mod tests {
[tracker]
url = "udp://localhost:6969"
mode = "Public"
api_url = "http://localhost:1212"
api_url = "http://localhost:1212/"
token = "MyAccessToken"
token_valid_seconds = 7257600
Expand Down Expand Up @@ -475,15 +476,15 @@ mod tests {
let info = Info {
config_toml: Some(default_config_toml()),
config_toml_path: String::new(),
tracker_api_token: Some("OVERRIDDEN API TOKEN".to_string()),
tracker_api_token: Some(ApiToken::new("OVERRIDDEN API TOKEN")),
auth_secret_key: None,
};

let configuration = Configuration::load(&info).expect("Failed to load configuration from info");

assert_eq!(
configuration.get_all().await.tracker.token,
"OVERRIDDEN API TOKEN".to_string()
ApiToken::new("OVERRIDDEN API TOKEN")
);
}

Expand All @@ -504,7 +505,7 @@ mod tests {

let settings = Configuration::load_settings(&info).expect("Could not load configuration from file");

assert_eq!(settings.tracker.token, "OVERRIDDEN API TOKEN".to_string());
assert_eq!(settings.tracker.token, ApiToken::new("OVERRIDDEN API TOKEN"));

Ok(())
});
Expand Down Expand Up @@ -550,24 +551,9 @@ 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]
async fn tracker_url_should_be_a_valid_url() {
let configuration = Configuration::default();

let mut settings_lock = configuration.settings.write().await;
settings_lock.tracker.url = "INVALID URL".to_string();

assert!(settings_lock.validate().is_err());
}
}

mod semantic_validation {
use url::Url;

use crate::config::validator::Validator;
use crate::config::{Configuration, TrackerMode};

Expand All @@ -577,7 +563,7 @@ 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();
settings_lock.tracker.url = Url::parse("udp://localhost:6969").unwrap();

assert!(settings_lock.validate().is_err());
}
Expand Down
6 changes: 3 additions & 3 deletions src/config/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use self::database::Database;
use self::image_cache::ImageCache;
use self::mail::Mail;
use self::net::Network;
use self::tracker::Tracker;
use self::tracker::{ApiToken, Tracker};
use self::tracker_statistics_importer::TrackerStatisticsImporter;
use self::website::Website;
use super::validator::{ValidationError, Validator};
Expand Down Expand Up @@ -48,7 +48,7 @@ pub struct Settings {
}

impl Settings {
pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) {
pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) {
self.tracker.override_tracker_api_token(tracker_api_token);
}

Expand All @@ -57,7 +57,7 @@ impl Settings {
}

pub fn remove_secrets(&mut self) {
"***".clone_into(&mut self.tracker.token);
self.tracker.token = ApiToken::new("***");
if let Some(_password) = self.database.connect_url.password() {
let _ = self.database.connect_url.set_password(Some("***"));
}
Expand Down
72 changes: 51 additions & 21 deletions src/config/v1/tracker.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
use std::fmt;

use serde::{Deserialize, Serialize};
use torrust_index_located_error::Located;
use url::Url;

use super::{ValidationError, Validator};
use crate::config::{parse_url, TrackerMode};
use crate::config::TrackerMode;

/// Configuration for the associated tracker.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Tracker {
/// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`.
pub url: String,
pub url: Url,
/// The mode of the tracker. For example: `Public`.
/// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives)
/// crate for more information.
pub mode: TrackerMode,
/// The url of the tracker API. For example: `http://localhost:1212`.
pub api_url: String,
/// The url of the tracker API. For example: `http://localhost:1212/`.
pub api_url: Url,
/// The token used to authenticate with the tracker API.
pub token: String,
/// The amount of seconds the token is valid.
pub token: ApiToken,
/// The amount of seconds the tracker API token is valid.
pub token_valid_seconds: u64,
}

impl Tracker {
pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) {
self.token = tracker_api_token.to_string();
pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) {
self.token = tracker_api_token.clone();
}
}

Expand All @@ -32,15 +34,6 @@ impl Validator for Tracker {
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);
}
Expand All @@ -52,11 +45,48 @@ impl Validator for Tracker {
impl Default for Tracker {
fn default() -> Self {
Self {
url: "udp://localhost:6969".to_string(),
url: Url::parse("udp://localhost:6969").unwrap(),
mode: TrackerMode::default(),
api_url: "http://localhost:1212".to_string(),
token: "MyAccessToken".to_string(),
api_url: Url::parse("http://localhost:1212/").unwrap(),
token: ApiToken::new("MyAccessToken"),
token_valid_seconds: 7_257_600,
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ApiToken(String);

impl ApiToken {
/// # Panics
///
/// Will panic if the tracker API token if empty.
#[must_use]
pub fn new(key: &str) -> Self {
assert!(!key.is_empty(), "tracker API token cannot be empty");

Self(key.to_owned())
}

#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}

impl fmt::Display for ApiToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

#[cfg(test)]
mod tests {
use super::ApiToken;

#[test]
#[should_panic(expected = "tracker API token cannot be empty")]
fn secret_key_can_not_be_empty() {
drop(ApiToken::new(""));
}
}
6 changes: 0 additions & 6 deletions src/config/validator.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
//! 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,
}
Expand Down
2 changes: 1 addition & 1 deletion src/console/commands/tracker_statistics_importer/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub async fn import() {

let tracker_url = settings.tracker.url.clone();

eprintln!("Tracker url: {}", tracker_url.green());
eprintln!("Tracker url: {}", tracker_url.to_string().green());

let database = Arc::new(
database::connect(settings.database.connect_url.as_ref())
Expand Down
3 changes: 2 additions & 1 deletion src/databases/database.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use async_trait::async_trait;
use chrono::{DateTime, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};
use url::Url;

use crate::databases::mysql::Mysql;
use crate::databases::sqlite::Sqlite;
Expand Down Expand Up @@ -336,7 +337,7 @@ pub trait Database: Sync + Send {
async fn get_tags_for_torrent_id(&self, torrent_id: i64) -> Result<Vec<TorrentTag>, Error>;

/// Update the seeders and leechers info for a torrent with `torrent_id`, `tracker_url`, `seeders` and `leechers`.
async fn update_tracker_info(&self, torrent_id: i64, tracker_url: &str, seeders: i64, leechers: i64) -> Result<(), Error>;
async fn update_tracker_info(&self, torrent_id: i64, tracker_url: &Url, seeders: i64, leechers: i64) -> Result<(), Error>;

/// Delete a torrent with `torrent_id`.
async fn delete_torrent(&self, torrent_id: i64) -> Result<(), Error>;
Expand Down
5 changes: 3 additions & 2 deletions src/databases/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use async_trait::async_trait;
use chrono::{DateTime, NaiveDateTime, Utc};
use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions};
use sqlx::{query, query_as, Acquire, ConnectOptions, MySqlPool};
use url::Url;

use super::database::TABLES_TO_TRUNCATE;
use crate::databases::database;
Expand Down Expand Up @@ -1072,13 +1073,13 @@ impl Database for Mysql {
async fn update_tracker_info(
&self,
torrent_id: i64,
tracker_url: &str,
tracker_url: &Url,
seeders: i64,
leechers: i64,
) -> Result<(), database::Error> {
query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers, updated_at) VALUES (?, ?, ?, ?, ?)")
.bind(torrent_id)
.bind(tracker_url)
.bind(tracker_url.to_string())
.bind(seeders)
.bind(leechers)
.bind(datetime_now())
Expand Down
5 changes: 3 additions & 2 deletions src/databases/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use async_trait::async_trait;
use chrono::{DateTime, NaiveDateTime, Utc};
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use sqlx::{query, query_as, Acquire, ConnectOptions, SqlitePool};
use url::Url;

use super::database::TABLES_TO_TRUNCATE;
use crate::databases::database;
Expand Down Expand Up @@ -1064,13 +1065,13 @@ impl Database for Sqlite {
async fn update_tracker_info(
&self,
torrent_id: i64,
tracker_url: &str,
tracker_url: &Url,
seeders: i64,
leechers: i64,
) -> Result<(), database::Error> {
query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers, updated_at) VALUES ($1, $2, $3, $4, $5)")
.bind(torrent_id)
.bind(tracker_url)
.bind(tracker_url.to_string())
.bind(seeders)
.bind(leechers)
.bind(datetime_now())
Expand Down
6 changes: 3 additions & 3 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ pub enum ServiceError {
#[display(fmt = "Tracker response error. The operation could not be performed.")]
TrackerResponseError,

#[display(fmt = "Tracker unknown response. Unexpected response from tracker. For example, if it can be parsed.")]
#[display(fmt = "Tracker unknown response. Unexpected response from tracker. For example, if it can't be parsed.")]
TrackerUnknownResponse,

#[display(fmt = "Torrent not found in tracker.")]
Expand Down Expand Up @@ -253,8 +253,8 @@ impl From<TrackerAPIError> for ServiceError {
fn from(e: TrackerAPIError) -> Self {
eprintln!("{e}");
match e {
TrackerAPIError::TrackerOffline => ServiceError::TrackerOffline,
TrackerAPIError::InternalServerError => ServiceError::TrackerResponseError,
TrackerAPIError::TrackerOffline { error: _ } => ServiceError::TrackerOffline,
TrackerAPIError::InternalServerError | TrackerAPIError::NotFound => ServiceError::TrackerResponseError,
TrackerAPIError::TorrentNotFound => ServiceError::TorrentNotFoundInTracker,
TrackerAPIError::UnexpectedResponseStatus
| TrackerAPIError::MissingResponseBody
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
//! [tracker]
//! url = "udp://localhost:6969"
//! mode = "Public"
//! api_url = "http://localhost:1212"
//! api_url = "http://localhost:1212/"
//! token = "MyAccessToken"
//! token_valid_seconds = 7257600
//! ```
Expand Down Expand Up @@ -172,7 +172,7 @@
//! [tracker]
//! url = "udp://localhost:6969"
//! mode = "Public"
//! api_url = "http://localhost:1212"
//! api_url = "http://localhost:1212/"
//! token = "MyAccessToken"
//! token_valid_seconds = 7257600
//!
Expand Down
Loading

0 comments on commit 8e53982

Please sign in to comment.