use crate::error::TransportError;
#[derive(Debug, Clone)]
pub struct HttpRequest {
pub method: &'static str,
pub url: String,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
impl HttpRequest {
pub fn header(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value.as_str())
}
}
#[derive(Debug, Clone)]
pub struct HttpResponse {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
impl HttpResponse {
pub fn header(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value.as_str())
}
}
pub trait Transport: Send + Sync {
fn execute(&self, request: HttpRequest) -> Result<HttpResponse, TransportError>;
}
pub trait Sleeper: Send + Sync {
fn sleep(&self, seconds: f64);
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ThreadSleeper;
impl Sleeper for ThreadSleeper {
fn sleep(&self, seconds: f64) {
if seconds > 0.0 {
std::thread::sleep(std::time::Duration::from_secs_f64(seconds));
}
}
}
#[cfg(feature = "ureq-transport")]
mod ureq_transport {
use super::{HttpRequest, HttpResponse, Transport};
use crate::error::TransportError;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct UreqTransport {
timeout: Option<Duration>,
}
impl UreqTransport {
pub fn new() -> Self {
Self { timeout: None }
}
pub fn with_timeout(timeout: Duration) -> Self {
Self {
timeout: Some(timeout),
}
}
}
impl Default for UreqTransport {
fn default() -> Self {
Self::new()
}
}
impl Transport for UreqTransport {
fn execute(&self, request: HttpRequest) -> Result<HttpResponse, TransportError> {
let mut config = ureq::Agent::config_builder().http_status_as_error(false);
if let Some(timeout) = self.timeout {
config = config.timeout_global(Some(timeout));
}
let agent: ureq::Agent = config.build().into();
let mut outgoing = agent.post(&request.url);
for (name, value) in &request.headers {
outgoing = outgoing.header(name.as_str(), value.as_str());
}
let response = outgoing.send(&request.body[..]).map_err(|error| {
TransportError::with_source(
format!("request to {} failed: {error}", request.url),
error,
)
})?;
let status = response.status().as_u16();
let headers = response
.headers()
.iter()
.map(|(name, value)| {
(
name.as_str().to_owned(),
value.to_str().unwrap_or_default().to_owned(),
)
})
.collect();
let body = response.into_body().read_to_vec().map_err(|error| {
TransportError::new(format!("could not read the response: {error}"))
})?;
Ok(HttpResponse {
status,
headers,
body,
})
}
}
}
#[cfg(feature = "ureq-transport")]
pub use ureq_transport::UreqTransport;