chore: initial sanitized public snapshot
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
use super::{Client, TransportError, TransportErrorKind};
|
||||
use crate::tracker::{
|
||||
TrackerRequestModel, TrackerResponseModel, TrackerScrapeModel, TrackerTransport,
|
||||
};
|
||||
|
||||
/// `reqwest`-backed HTTP tracker transport.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ReqwestTrackerTransport {
|
||||
/// Shared blocking HTTP client used for announce and scrape requests.
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl ReqwestTrackerTransport {
|
||||
/// Creates a tracker transport backed by a default blocking `reqwest` client.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error when the HTTP client cannot be constructed.
|
||||
pub fn new() -> Result<Self, TransportError> {
|
||||
let client = Client::builder()
|
||||
.build()
|
||||
.map_err(|error| tracker_transport_error(TransportErrorKind::Io, error.to_string()))?;
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
/// Fetches the raw response bytes for one tracker announce or scrape URL.
|
||||
fn fetch_bytes(&self, url: &str) -> Result<Vec<u8>, TransportError> {
|
||||
let response = self
|
||||
.client
|
||||
.get(url)
|
||||
.send()
|
||||
.map_err(map_reqwest_tracker_error)?;
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(tracker_transport_error(
|
||||
TransportErrorKind::ProtocolViolation,
|
||||
format!(
|
||||
"tracker request failed with http status {}",
|
||||
status.as_u16()
|
||||
),
|
||||
));
|
||||
}
|
||||
response
|
||||
.bytes()
|
||||
.map(Vec::from)
|
||||
.map_err(map_reqwest_tracker_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ReqwestTrackerTransport {
|
||||
fn default() -> Self {
|
||||
Self::new().expect("reqwest tracker transport should build")
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackerTransport for ReqwestTrackerTransport {
|
||||
fn announce(
|
||||
&self,
|
||||
request: &TrackerRequestModel,
|
||||
) -> Result<TrackerResponseModel, TransportError> {
|
||||
let url = request.announce_url().map_err(|error| {
|
||||
tracker_transport_error(TransportErrorKind::ProtocolViolation, error.to_string())
|
||||
})?;
|
||||
let bytes = self.fetch_bytes(&url)?;
|
||||
TrackerResponseModel::from_announce_bytes(&bytes).map_err(|error| {
|
||||
tracker_transport_error(
|
||||
TransportErrorKind::ProtocolViolation,
|
||||
format!("invalid tracker announce payload: {error}"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn scrape(&self, announce_url: &str) -> Result<TrackerScrapeModel, TransportError> {
|
||||
let url = super::parsing::tracker_scrape_url(announce_url);
|
||||
let bytes = self.fetch_bytes(&url)?;
|
||||
TrackerResponseModel::from_scrape_bytes(&bytes).map_err(|error| {
|
||||
tracker_transport_error(
|
||||
TransportErrorKind::ProtocolViolation,
|
||||
format!("invalid tracker scrape payload: {error}"),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a transport error with tracker-specific context already normalized.
|
||||
fn tracker_transport_error(kind: TransportErrorKind, message: impl Into<String>) -> TransportError {
|
||||
TransportError {
|
||||
kind,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
context: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a reqwest tracker fetch failure into the transport error model.
|
||||
fn map_reqwest_tracker_error(error: reqwest::Error) -> TransportError {
|
||||
let kind = if error.is_timeout() {
|
||||
TransportErrorKind::Timeout
|
||||
} else if error.is_connect() {
|
||||
TransportErrorKind::NotConnected
|
||||
} else if error.is_decode() {
|
||||
TransportErrorKind::ProtocolViolation
|
||||
} else {
|
||||
TransportErrorKind::Io
|
||||
};
|
||||
tracker_transport_error(kind, error.to_string())
|
||||
}
|
||||
Reference in New Issue
Block a user