Skip to content

Commit 8e53982

Browse files
committed
refactor: [#599] extract types for config::v1::tracker::Tracker
1 parent b1d5267 commit 8e53982

File tree

25 files changed

+182
-123
lines changed

25 files changed

+182
-123
lines changed

share/default/config/index.development.sqlite3.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ log_level = "info"
44
name = "Torrust"
55

66
[tracker]
7-
api_url = "http://localhost:1212"
7+
api_url = "http://localhost:1212/"
88
mode = "Public"
99
token = "MyAccessToken"
1010
token_valid_seconds = 7257600

src/config/mod.rs

+15-29
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use tokio::sync::RwLock;
1515
use torrust_index_located_error::LocatedError;
1616
use url::Url;
1717

18+
use self::v1::tracker::ApiToken;
1819
use crate::web::api::server::DynError;
1920

2021
pub type Settings = v1::Settings;
@@ -55,7 +56,7 @@ pub const ENV_VAR_AUTH_SECRET_KEY: &str = "TORRUST_INDEX_AUTH_SECRET_KEY";
5556
pub struct Info {
5657
config_toml: Option<String>,
5758
config_toml_path: String,
58-
tracker_api_token: Option<String>,
59+
tracker_api_token: Option<ApiToken>,
5960
auth_secret_key: Option<String>,
6061
}
6162

@@ -88,7 +89,10 @@ impl Info {
8889
default_config_toml_path
8990
};
9091

91-
let tracker_api_token = env::var(env_var_tracker_api_admin_token).ok();
92+
let tracker_api_token = env::var(env_var_tracker_api_admin_token)
93+
.ok()
94+
.map(|token| ApiToken::new(&token));
95+
9296
let auth_secret_key = env::var(env_var_auth_secret_key).ok();
9397

9498
Ok(Self {
@@ -325,21 +329,18 @@ impl Configuration {
325329
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
326330
pub struct ConfigurationPublic {
327331
website_name: String,
328-
tracker_url: String,
332+
tracker_url: Url,
329333
tracker_mode: TrackerMode,
330334
email_on_signup: EmailOnSignup,
331335
}
332336

333-
fn parse_url(url_str: &str) -> Result<Url, url::ParseError> {
334-
Url::parse(url_str)
335-
}
336-
337337
#[cfg(test)]
338338
mod tests {
339339

340340
use url::Url;
341341

342342
use crate::config::v1::auth::SecretKey;
343+
use crate::config::v1::tracker::ApiToken;
343344
use crate::config::{Configuration, ConfigurationPublic, Info, Settings};
344345

345346
#[cfg(test)]
@@ -350,7 +351,7 @@ mod tests {
350351
[tracker]
351352
url = "udp://localhost:6969"
352353
mode = "Public"
353-
api_url = "http://localhost:1212"
354+
api_url = "http://localhost:1212/"
354355
token = "MyAccessToken"
355356
token_valid_seconds = 7257600
356357
@@ -475,15 +476,15 @@ mod tests {
475476
let info = Info {
476477
config_toml: Some(default_config_toml()),
477478
config_toml_path: String::new(),
478-
tracker_api_token: Some("OVERRIDDEN API TOKEN".to_string()),
479+
tracker_api_token: Some(ApiToken::new("OVERRIDDEN API TOKEN")),
479480
auth_secret_key: None,
480481
};
481482

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

484485
assert_eq!(
485486
configuration.get_all().await.tracker.token,
486-
"OVERRIDDEN API TOKEN".to_string()
487+
ApiToken::new("OVERRIDDEN API TOKEN")
487488
);
488489
}
489490

@@ -504,7 +505,7 @@ mod tests {
504505

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

507-
assert_eq!(settings.tracker.token, "OVERRIDDEN API TOKEN".to_string());
508+
assert_eq!(settings.tracker.token, ApiToken::new("OVERRIDDEN API TOKEN"));
508509

509510
Ok(())
510511
});
@@ -550,24 +551,9 @@ mod tests {
550551
});
551552
}
552553

553-
mod syntax_checks {
554-
// todo: use rich types in configuration structs for basic syntax checks.
555-
556-
use crate::config::validator::Validator;
557-
use crate::config::Configuration;
558-
559-
#[tokio::test]
560-
async fn tracker_url_should_be_a_valid_url() {
561-
let configuration = Configuration::default();
562-
563-
let mut settings_lock = configuration.settings.write().await;
564-
settings_lock.tracker.url = "INVALID URL".to_string();
565-
566-
assert!(settings_lock.validate().is_err());
567-
}
568-
}
569-
570554
mod semantic_validation {
555+
use url::Url;
556+
571557
use crate::config::validator::Validator;
572558
use crate::config::{Configuration, TrackerMode};
573559

@@ -577,7 +563,7 @@ mod tests {
577563

578564
let mut settings_lock = configuration.settings.write().await;
579565
settings_lock.tracker.mode = TrackerMode::Private;
580-
settings_lock.tracker.url = "udp://localhost:6969".to_string();
566+
settings_lock.tracker.url = Url::parse("udp://localhost:6969").unwrap();
581567

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

src/config/v1/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use self::database::Database;
1616
use self::image_cache::ImageCache;
1717
use self::mail::Mail;
1818
use self::net::Network;
19-
use self::tracker::Tracker;
19+
use self::tracker::{ApiToken, Tracker};
2020
use self::tracker_statistics_importer::TrackerStatisticsImporter;
2121
use self::website::Website;
2222
use super::validator::{ValidationError, Validator};
@@ -48,7 +48,7 @@ pub struct Settings {
4848
}
4949

5050
impl Settings {
51-
pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) {
51+
pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) {
5252
self.tracker.override_tracker_api_token(tracker_api_token);
5353
}
5454

@@ -57,7 +57,7 @@ impl Settings {
5757
}
5858

5959
pub fn remove_secrets(&mut self) {
60-
"***".clone_into(&mut self.tracker.token);
60+
self.tracker.token = ApiToken::new("***");
6161
if let Some(_password) = self.database.connect_url.password() {
6262
let _ = self.database.connect_url.set_password(Some("***"));
6363
}

src/config/v1/tracker.rs

+51-21
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
1+
use std::fmt;
2+
13
use serde::{Deserialize, Serialize};
2-
use torrust_index_located_error::Located;
4+
use url::Url;
35

46
use super::{ValidationError, Validator};
5-
use crate::config::{parse_url, TrackerMode};
7+
use crate::config::TrackerMode;
68

79
/// Configuration for the associated tracker.
810
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
911
pub struct Tracker {
1012
/// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`.
11-
pub url: String,
13+
pub url: Url,
1214
/// The mode of the tracker. For example: `Public`.
1315
/// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives)
1416
/// crate for more information.
1517
pub mode: TrackerMode,
16-
/// The url of the tracker API. For example: `http://localhost:1212`.
17-
pub api_url: String,
18+
/// The url of the tracker API. For example: `http://localhost:1212/`.
19+
pub api_url: Url,
1820
/// The token used to authenticate with the tracker API.
19-
pub token: String,
20-
/// The amount of seconds the token is valid.
21+
pub token: ApiToken,
22+
/// The amount of seconds the tracker API token is valid.
2123
pub token_valid_seconds: u64,
2224
}
2325

2426
impl Tracker {
25-
pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) {
26-
self.token = tracker_api_token.to_string();
27+
pub fn override_tracker_api_token(&mut self, tracker_api_token: &ApiToken) {
28+
self.token = tracker_api_token.clone();
2729
}
2830
}
2931

@@ -32,15 +34,6 @@ impl Validator for Tracker {
3234
let tracker_mode = self.mode.clone();
3335
let tracker_url = self.url.clone();
3436

35-
let tracker_url = match parse_url(&tracker_url) {
36-
Ok(url) => url,
37-
Err(err) => {
38-
return Err(ValidationError::InvalidTrackerUrl {
39-
source: Located(err).into(),
40-
})
41-
}
42-
};
43-
4437
if tracker_mode.is_close() && (tracker_url.scheme() != "http" && tracker_url.scheme() != "https") {
4538
return Err(ValidationError::UdpTrackersInPrivateModeNotSupported);
4639
}
@@ -52,11 +45,48 @@ impl Validator for Tracker {
5245
impl Default for Tracker {
5346
fn default() -> Self {
5447
Self {
55-
url: "udp://localhost:6969".to_string(),
48+
url: Url::parse("udp://localhost:6969").unwrap(),
5649
mode: TrackerMode::default(),
57-
api_url: "http://localhost:1212".to_string(),
58-
token: "MyAccessToken".to_string(),
50+
api_url: Url::parse("http://localhost:1212/").unwrap(),
51+
token: ApiToken::new("MyAccessToken"),
5952
token_valid_seconds: 7_257_600,
6053
}
6154
}
6255
}
56+
57+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
58+
pub struct ApiToken(String);
59+
60+
impl ApiToken {
61+
/// # Panics
62+
///
63+
/// Will panic if the tracker API token if empty.
64+
#[must_use]
65+
pub fn new(key: &str) -> Self {
66+
assert!(!key.is_empty(), "tracker API token cannot be empty");
67+
68+
Self(key.to_owned())
69+
}
70+
71+
#[must_use]
72+
pub fn as_bytes(&self) -> &[u8] {
73+
self.0.as_bytes()
74+
}
75+
}
76+
77+
impl fmt::Display for ApiToken {
78+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79+
write!(f, "{}", self.0)
80+
}
81+
}
82+
83+
#[cfg(test)]
84+
mod tests {
85+
use super::ApiToken;
86+
87+
#[test]
88+
#[should_panic(expected = "tracker API token cannot be empty")]
89+
fn secret_key_can_not_be_empty() {
90+
drop(ApiToken::new(""));
91+
}
92+
}

src/config/validator.rs

-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
//! Trait to validate the whole settings of sections of the settings.
22
use thiserror::Error;
3-
use torrust_index_located_error::LocatedError;
4-
use url::ParseError;
53

64
/// Errors that can occur validating the configuration.
75
#[derive(Error, Debug)]
86
pub enum ValidationError {
9-
/// Unable to load the configuration from the configuration file.
10-
#[error("Invalid tracker URL: {source}")]
11-
InvalidTrackerUrl { source: LocatedError<'static, ParseError> },
12-
137
#[error("UDP private trackers are not supported. URL schemes for private tracker URLs must be HTTP ot HTTPS")]
148
UdpTrackersInPrivateModeNotSupported,
159
}

src/console/commands/tracker_statistics_importer/app.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ pub async fn import() {
100100

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

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

105105
let database = Arc::new(
106106
database::connect(settings.database.connect_url.as_ref())

src/databases/database.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use async_trait::async_trait;
22
use chrono::{DateTime, NaiveDateTime, Utc};
33
use serde::{Deserialize, Serialize};
4+
use url::Url;
45

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

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

341342
/// Delete a torrent with `torrent_id`.
342343
async fn delete_torrent(&self, torrent_id: i64) -> Result<(), Error>;

src/databases/mysql.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use async_trait::async_trait;
55
use chrono::{DateTime, NaiveDateTime, Utc};
66
use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions};
77
use sqlx::{query, query_as, Acquire, ConnectOptions, MySqlPool};
8+
use url::Url;
89

910
use super::database::TABLES_TO_TRUNCATE;
1011
use crate::databases::database;
@@ -1072,13 +1073,13 @@ impl Database for Mysql {
10721073
async fn update_tracker_info(
10731074
&self,
10741075
torrent_id: i64,
1075-
tracker_url: &str,
1076+
tracker_url: &Url,
10761077
seeders: i64,
10771078
leechers: i64,
10781079
) -> Result<(), database::Error> {
10791080
query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers, updated_at) VALUES (?, ?, ?, ?, ?)")
10801081
.bind(torrent_id)
1081-
.bind(tracker_url)
1082+
.bind(tracker_url.to_string())
10821083
.bind(seeders)
10831084
.bind(leechers)
10841085
.bind(datetime_now())

src/databases/sqlite.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use async_trait::async_trait;
55
use chrono::{DateTime, NaiveDateTime, Utc};
66
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
77
use sqlx::{query, query_as, Acquire, ConnectOptions, SqlitePool};
8+
use url::Url;
89

910
use super::database::TABLES_TO_TRUNCATE;
1011
use crate::databases::database;
@@ -1064,13 +1065,13 @@ impl Database for Sqlite {
10641065
async fn update_tracker_info(
10651066
&self,
10661067
torrent_id: i64,
1067-
tracker_url: &str,
1068+
tracker_url: &Url,
10681069
seeders: i64,
10691070
leechers: i64,
10701071
) -> Result<(), database::Error> {
10711072
query("REPLACE INTO torrust_torrent_tracker_stats (torrent_id, tracker_url, seeders, leechers, updated_at) VALUES ($1, $2, $3, $4, $5)")
10721073
.bind(torrent_id)
1073-
.bind(tracker_url)
1074+
.bind(tracker_url.to_string())
10741075
.bind(seeders)
10751076
.bind(leechers)
10761077
.bind(datetime_now())

src/errors.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ pub enum ServiceError {
161161
#[display(fmt = "Tracker response error. The operation could not be performed.")]
162162
TrackerResponseError,
163163

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

167167
#[display(fmt = "Torrent not found in tracker.")]
@@ -253,8 +253,8 @@ impl From<TrackerAPIError> for ServiceError {
253253
fn from(e: TrackerAPIError) -> Self {
254254
eprintln!("{e}");
255255
match e {
256-
TrackerAPIError::TrackerOffline => ServiceError::TrackerOffline,
257-
TrackerAPIError::InternalServerError => ServiceError::TrackerResponseError,
256+
TrackerAPIError::TrackerOffline { error: _ } => ServiceError::TrackerOffline,
257+
TrackerAPIError::InternalServerError | TrackerAPIError::NotFound => ServiceError::TrackerResponseError,
258258
TrackerAPIError::TorrentNotFound => ServiceError::TorrentNotFoundInTracker,
259259
TrackerAPIError::UnexpectedResponseStatus
260260
| TrackerAPIError::MissingResponseBody

src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
//! [tracker]
6060
//! url = "udp://localhost:6969"
6161
//! mode = "Public"
62-
//! api_url = "http://localhost:1212"
62+
//! api_url = "http://localhost:1212/"
6363
//! token = "MyAccessToken"
6464
//! token_valid_seconds = 7257600
6565
//! ```
@@ -172,7 +172,7 @@
172172
//! [tracker]
173173
//! url = "udp://localhost:6969"
174174
//! mode = "Public"
175-
//! api_url = "http://localhost:1212"
175+
//! api_url = "http://localhost:1212/"
176176
//! token = "MyAccessToken"
177177
//! token_valid_seconds = 7257600
178178
//!

0 commit comments

Comments
 (0)