From 9ff4de45d19f216d29bd21ab7ec9c7cbd5307868 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 9 Feb 2024 16:12:18 +0000 Subject: [PATCH] feat: [#472] inject URL scheme in API client --- src/console/commands/seeder/app.rs | 12 ++++-- src/web/api/client/v1/client.rs | 10 ++--- src/web/api/client/v1/connection_info.rs | 54 ++++++++++++++++++++++-- src/web/api/client/v1/http.rs | 4 +- 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/console/commands/seeder/app.rs b/src/console/commands/seeder/app.rs index 8297e071..84ab55cf 100644 --- a/src/console/commands/seeder/app.rs +++ b/src/console/commands/seeder/app.rs @@ -15,7 +15,7 @@ //! //! ```text //! cargo run --bin seeder -- \ -//! --api-base-url "localhost:3001" \ +//! --api-base-url "http://localhost:3001" \ //! --number-of-torrents 1000 \ //! --user admin \ //! --password 12345678 \ @@ -127,12 +127,14 @@ //! //! As you can see the `info` dictionary is exactly the same, which produces //! the same info-hash for the torrent. +use std::str::FromStr; use std::thread::sleep; use std::time::Duration; use anyhow::Context; use clap::Parser; use log::{debug, info, LevelFilter}; +use reqwest::Url; use text_colorizer::Colorize; use uuid::Uuid; @@ -173,9 +175,11 @@ pub async fn run() -> anyhow::Result<()> { let args = Args::parse(); - let api_user = login_index_api(&args.api_base_url, &args.user, &args.password).await; + let api_url = Url::from_str(&args.api_base_url).context("failed to parse API base URL")?; - let api_client = Client::authenticated(&args.api_base_url, &api_user.token); + let api_user = login_index_api(&api_url, &args.user, &args.password).await; + + let api_client = Client::authenticated(&api_url, &api_user.token); info!(target:"seeder", "Uploading { } random torrents to the Torrust Index with a { } seconds interval...", args.number_of_torrents.to_string().yellow(), args.interval.to_string().yellow()); @@ -202,7 +206,7 @@ pub async fn run() -> anyhow::Result<()> { } /// It logs in a user in the Index API. -pub async fn login_index_api(api_url: &str, username: &str, password: &str) -> LoggedInUserData { +pub async fn login_index_api(api_url: &Url, username: &str, password: &str) -> LoggedInUserData { let unauthenticated_client = Client::unauthenticated(api_url); info!(target:"seeder", "Trying to login with username: {} ...", username.yellow()); diff --git a/src/web/api/client/v1/client.rs b/src/web/api/client/v1/client.rs index da4c4df4..72723959 100644 --- a/src/web/api/client/v1/client.rs +++ b/src/web/api/client/v1/client.rs @@ -1,4 +1,4 @@ -use reqwest::multipart; +use reqwest::{multipart, Url}; use super::connection_info::ConnectionInfo; use super::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm}; @@ -22,13 +22,13 @@ impl Client { } #[must_use] - pub fn unauthenticated(bind_address: &str) -> Self { - Self::new(ConnectionInfo::anonymous(bind_address, &Self::base_path())) + pub fn unauthenticated(base_url: &Url) -> Self { + Self::new(ConnectionInfo::anonymous(base_url, &Self::base_path())) } #[must_use] - pub fn authenticated(bind_address: &str, token: &str) -> Self { - Self::new(ConnectionInfo::new(bind_address, &Self::base_path(), token)) + pub fn authenticated(base_url: &Url, token: &str) -> Self { + Self::new(ConnectionInfo::new(base_url, &Self::base_path(), token)) } #[must_use] diff --git a/src/web/api/client/v1/connection_info.rs b/src/web/api/client/v1/connection_info.rs index 2183f4b9..4aa3e927 100644 --- a/src/web/api/client/v1/connection_info.rs +++ b/src/web/api/client/v1/connection_info.rs @@ -1,24 +1,70 @@ +use std::{fmt, str::FromStr}; + +use reqwest::Url; + +#[derive(Clone)] +pub enum Scheme { + Http, + Https, +} + +impl fmt::Display for Scheme { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Scheme::Http => write!(f, "http"), + Scheme::Https => write!(f, "https"), + } + } +} + +impl FromStr for Scheme { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "http" => Ok(Scheme::Http), + "https" => Ok(Scheme::Https), + _ => Err(()), + } + } +} + #[derive(Clone)] pub struct ConnectionInfo { + pub scheme: Scheme, pub bind_address: String, pub base_path: String, pub token: Option, } impl ConnectionInfo { + /// # Panics + /// + /// Will panic if the the base URL does not have a valid scheme: `http` or `https`. #[must_use] - pub fn new(bind_address: &str, base_path: &str, token: &str) -> Self { + pub fn new(base_url: &Url, base_path: &str, token: &str) -> Self { Self { - bind_address: bind_address.to_string(), + scheme: base_url + .scheme() + .parse() + .expect("base API URL scheme should be 'http' or 'https"), + bind_address: base_url.authority().to_string(), base_path: base_path.to_string(), token: Some(token.to_string()), } } + /// # Panics + /// + /// Will panic if the the base URL does not have a valid scheme: `http` or `https`. #[must_use] - pub fn anonymous(bind_address: &str, base_path: &str) -> Self { + pub fn anonymous(base_url: &Url, base_path: &str) -> Self { Self { - bind_address: bind_address.to_string(), + scheme: base_url + .scheme() + .parse() + .expect("base API URL scheme should be 'http' or 'https"), + bind_address: base_url.authority().to_string(), base_path: base_path.to_string(), token: None, } diff --git a/src/web/api/client/v1/http.rs b/src/web/api/client/v1/http.rs index fde56162..7ae7823e 100644 --- a/src/web/api/client/v1/http.rs +++ b/src/web/api/client/v1/http.rs @@ -267,8 +267,8 @@ impl Http { fn base_url(&self, path: &str) -> String { format!( - "http://{}{}{path}", - &self.connection_info.bind_address, &self.connection_info.base_path + "{}://{}{}{path}", + &self.connection_info.scheme, &self.connection_info.bind_address, &self.connection_info.base_path ) } }