use std::fmt;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use ferrin_spec::BoxFuture;
use ferrin_spec::BoxStream;
use ferrin_spec::Headers;
use http::Method;
use http::StatusCode;
use tokio_util::sync::CancellationToken;
use url::Url;
use super::request_body::RequestBody;
pub type BodyStream = BoxStream<'static, Result<Bytes, TransportError>>;
pub type SharedTransport = Arc<dyn HttpTransport>;
pub trait HttpTransport: Send + Sync + 'static {
fn execute(&self, request: HttpRequest) -> BoxFuture<'_, Result<HttpResponse, TransportError>>;
}
impl<T: HttpTransport + ?Sized> HttpTransport for Arc<T> {
fn execute(&self, request: HttpRequest) -> BoxFuture<'_, Result<HttpResponse, TransportError>> {
(**self).execute(request)
}
}
#[derive(Debug)]
pub struct HttpRequest {
pub method: Method,
pub url: Url,
pub headers: Headers,
pub body: RequestBody,
pub cancellation: CancellationToken,
pub timeout: Option<Duration>,
pub pinned_addresses: Vec<SocketAddr>,
}
impl HttpRequest {
#[must_use]
pub fn new(method: Method, url: Url) -> Self {
Self {
method,
url,
headers: Headers::new(),
body: RequestBody::Empty,
cancellation: CancellationToken::new(),
timeout: None,
pinned_addresses: Vec::new(),
}
}
#[must_use]
pub fn get(url: Url) -> Self {
Self::new(Method::GET, url)
}
#[must_use]
pub fn post(url: Url) -> Self {
Self::new(Method::POST, url)
}
#[must_use]
pub fn with_headers(mut self, headers: Headers) -> Self {
self.headers = headers;
self
}
#[must_use]
pub fn with_body(mut self, body: RequestBody) -> Self {
self.body = body;
self
}
#[must_use]
pub fn with_cancellation(mut self, cancellation: CancellationToken) -> Self {
self.cancellation = cancellation;
self
}
#[must_use]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
#[must_use]
pub fn with_pinned_addresses(mut self, addresses: Vec<SocketAddr>) -> Self {
self.pinned_addresses = addresses;
self
}
}
#[derive(Debug, Clone)]
pub struct ResponseHead {
pub status: StatusCode,
pub headers: Headers,
}
pub struct HttpResponse {
pub status: StatusCode,
pub headers: Headers,
pub body: BodyStream,
}
impl fmt::Debug for HttpResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HttpResponse")
.field("status", &self.status)
.field("headers", &self.headers)
.finish_non_exhaustive()
}
}
impl HttpResponse {
#[must_use]
pub fn from_bytes(status: StatusCode, headers: Headers, body: Bytes) -> Self {
Self {
status,
headers,
body: Box::pin(futures_util::stream::once(std::future::ready(Ok(body)))),
}
}
#[must_use]
pub fn from_stream(status: StatusCode, headers: Headers, body: BodyStream) -> Self {
Self {
status,
headers,
body,
}
}
#[must_use]
pub fn head(&self) -> ResponseHead {
ResponseHead {
status: self.status,
headers: self.headers.clone(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TransportErrorKind {
Connect,
Timeout,
Reset,
Io,
Tls,
InvalidUrl,
InvalidRequest,
Body,
BodyTooLarge,
Cancelled,
Other,
}
#[derive(Debug, thiserror::Error)]
#[error("{kind:?}: {message}")]
pub struct TransportError {
pub kind: TransportErrorKind,
pub message: String,
#[source]
pub cause: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl TransportError {
#[must_use]
pub fn new(kind: TransportErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
cause: None,
}
}
#[must_use]
pub fn with_cause(mut self, cause: impl std::error::Error + Send + Sync + 'static) -> Self {
self.cause = Some(Box::new(cause));
self
}
#[must_use]
pub fn cancelled() -> Self {
Self::new(TransportErrorKind::Cancelled, "request cancelled")
}
#[must_use]
pub fn is_retryable(&self) -> bool {
matches!(
self.kind,
TransportErrorKind::Connect
| TransportErrorKind::Timeout
| TransportErrorKind::Reset
| TransportErrorKind::Io
)
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.kind == TransportErrorKind::Cancelled
}
}