pub mod requests;
pub mod responses;
use std::net::IpAddr;
use std::sync::Arc;
use std::time::Duration;
use hyper::StatusCode;
use requests::{announce, scrape};
use reqwest::{Response, Url};
use thiserror::Error;
use crate::core::auth::Key;
#[derive(Debug, Clone, Error)]
pub enum Error {
#[error("Failed to Build a Http Client: {err:?}")]
ClientBuildingError { err: Arc<reqwest::Error> },
#[error("Failed to get a response: {err:?}")]
ResponseError { err: Arc<reqwest::Error> },
#[error("Returned a non-success code: \"{code}\" with the response: \"{response:?}\"")]
UnsuccessfulResponse { code: StatusCode, response: Arc<Response> },
}
pub struct Client {
client: reqwest::Client,
base_url: Url,
key: Option<Key>,
}
impl Client {
pub fn new(base_url: Url, timeout: Duration) -> Result<Self, Error> {
let client = reqwest::Client::builder()
.timeout(timeout)
.build()
.map_err(|e| Error::ClientBuildingError { err: e.into() })?;
Ok(Self {
base_url,
client,
key: None,
})
}
pub fn bind(base_url: Url, timeout: Duration, local_address: IpAddr) -> Result<Self, Error> {
let client = reqwest::Client::builder()
.timeout(timeout)
.local_address(local_address)
.build()
.map_err(|e| Error::ClientBuildingError { err: e.into() })?;
Ok(Self {
base_url,
client,
key: None,
})
}
pub fn authenticated(base_url: Url, timeout: Duration, key: Key) -> Result<Self, Error> {
let client = reqwest::Client::builder()
.timeout(timeout)
.build()
.map_err(|e| Error::ClientBuildingError { err: e.into() })?;
Ok(Self {
base_url,
client,
key: Some(key),
})
}
pub async fn announce(&self, query: &announce::Query) -> Result<Response, Error> {
let response = self.get(&self.build_announce_path_and_query(query)).await?;
if response.status().is_success() {
Ok(response)
} else {
Err(Error::UnsuccessfulResponse {
code: response.status(),
response: response.into(),
})
}
}
pub async fn scrape(&self, query: &scrape::Query) -> Result<Response, Error> {
let response = self.get(&self.build_scrape_path_and_query(query)).await?;
if response.status().is_success() {
Ok(response)
} else {
Err(Error::UnsuccessfulResponse {
code: response.status(),
response: response.into(),
})
}
}
pub async fn announce_with_header(&self, query: &announce::Query, key: &str, value: &str) -> Result<Response, Error> {
let response = self
.get_with_header(&self.build_announce_path_and_query(query), key, value)
.await?;
if response.status().is_success() {
Ok(response)
} else {
Err(Error::UnsuccessfulResponse {
code: response.status(),
response: response.into(),
})
}
}
pub async fn health_check(&self) -> Result<Response, Error> {
let response = self.get(&self.build_path("health_check")).await?;
if response.status().is_success() {
Ok(response)
} else {
Err(Error::UnsuccessfulResponse {
code: response.status(),
response: response.into(),
})
}
}
pub async fn get(&self, path: &str) -> Result<Response, Error> {
self.client
.get(self.build_url(path))
.send()
.await
.map_err(|e| Error::ResponseError { err: e.into() })
}
pub async fn get_with_header(&self, path: &str, key: &str, value: &str) -> Result<Response, Error> {
self.client
.get(self.build_url(path))
.header(key, value)
.send()
.await
.map_err(|e| Error::ResponseError { err: e.into() })
}
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 {
self.base_url.to_string()
}
}