torrust-tracker 3.0.0

A feature rich BitTorrent tracker.
Documentation
use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use hyper::StatusCode;
use reqwest::{Client as HttpClient, Response};
use serde::Serialize;
use thiserror::Error;
use url::Url;

#[derive(Debug, Clone, Error, Serialize)]
#[serde(into = "String")]
pub enum Error {
    #[error("Failed to Build a Http Client: {err:?}")]
    ClientBuildingError { err: Arc<reqwest::Error> },
    #[error("Heath check failed to get a response: {err:?}")]
    ResponseError { err: Arc<reqwest::Error> },
    #[error("Http check returned a non-success code: \"{code}\" with the response: \"{response:?}\"")]
    UnsuccessfulResponse { code: StatusCode, response: Arc<Response> },
}

impl From<Error> for String {
    fn from(value: Error) -> Self {
        value.to_string()
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct Checks {
    url: Url,
    result: Result<String, Error>,
}

pub async fn run(health_checks: Vec<Url>, timeout: Duration) -> Vec<Result<Checks, Checks>> {
    let mut results = Vec::default();

    tracing::debug!("Health checks ...");

    for url in health_checks {
        let result = match run_health_check(url.clone(), timeout).await {
            Ok(response) => Ok(response.status().to_string()),
            Err(err) => Err(err),
        };

        let check = Checks { url, result };

        if check.result.is_err() {
            results.push(Err(check));
        } else {
            results.push(Ok(check));
        }
    }

    results
}

async fn run_health_check(url: Url, timeout: Duration) -> Result<Response, Error> {
    let client = HttpClient::builder()
        .timeout(timeout)
        .build()
        .map_err(|e| Error::ClientBuildingError { err: e.into() })?;

    let response = client
        .get(url.clone())
        .send()
        .await
        .map_err(|e| Error::ResponseError { err: e.into() })?;

    if response.status().is_success() {
        Ok(response)
    } else {
        Err(Error::UnsuccessfulResponse {
            code: response.status(),
            response: response.into(),
        })
    }
}