torrust-tracker 3.0.0

A feature rich BitTorrent tracker.
Documentation
use std::net::IpAddr;

use reqwest::{Client as ReqwestClient, Response};
use torrust_tracker::core::auth::Key;

use super::requests::announce::{self, Query};
use super::requests::scrape;

/// HTTP Tracker Client
pub struct Client {
    server_addr: std::net::SocketAddr,
    reqwest: ReqwestClient,
    key: Option<Key>,
}

/// URL components in this context:
///
/// ```text
/// http://127.0.0.1:62304/announce/YZ....rJ?info_hash=%9C8B%22%13%E3%0B%FF%21%2B0%C3%60%D2o%9A%02%13d%22
/// \_____________________/\_______________/ \__________________________________________________________/
///            |                   |                                    |
///         base url              path                                query
/// ```
impl Client {
    pub fn new(server_addr: std::net::SocketAddr) -> Self {
        Self {
            server_addr,
            reqwest: reqwest::Client::builder().build().unwrap(),
            key: None,
        }
    }

    /// Creates the new client binding it to an specific local address
    pub fn bind(server_addr: std::net::SocketAddr, local_address: IpAddr) -> Self {
        Self {
            server_addr,
            reqwest: reqwest::Client::builder().local_address(local_address).build().unwrap(),
            key: None,
        }
    }

    pub fn authenticated(server_addr: std::net::SocketAddr, key: Key) -> Self {
        Self {
            server_addr,
            reqwest: reqwest::Client::builder().build().unwrap(),
            key: Some(key),
        }
    }

    pub async fn announce(&self, query: &announce::Query) -> Response {
        self.get(&self.build_announce_path_and_query(query)).await
    }

    pub async fn scrape(&self, query: &scrape::Query) -> Response {
        self.get(&self.build_scrape_path_and_query(query)).await
    }

    pub async fn announce_with_header(&self, query: &Query, key: &str, value: &str) -> Response {
        self.get_with_header(&self.build_announce_path_and_query(query), key, value)
            .await
    }

    pub async fn health_check(&self) -> Response {
        self.get(&self.build_path("health_check")).await
    }

    pub async fn get(&self, path: &str) -> Response {
        self.reqwest.get(self.build_url(path)).send().await.unwrap()
    }

    pub async fn get_with_header(&self, path: &str, key: &str, value: &str) -> Response {
        self.reqwest
            .get(self.build_url(path))
            .header(key, value)
            .send()
            .await
            .unwrap()
    }

    fn build_announce_path_and_query(&self, query: &announce::Query) -> String {
        format!("{}?{query}", self.build_path("announce"))
    }

    fn build_scrape_path_and_query(&self, query: &scrape::Query) -> String {
        format!("{}?{query}", self.build_path("scrape"))
    }

    fn build_path(&self, path: &str) -> String {
        match &self.key {
            Some(key) => format!("{path}/{key}"),
            None => path.to_string(),
        }
    }

    fn build_url(&self, path: &str) -> String {
        let base_url = self.base_url();
        format!("{base_url}{path}")
    }

    fn base_url(&self) -> String {
        format!("http://{}/", &self.server_addr)
    }
}