leyline-http 0.1.5

HTTP client that impersonates Chrome, Firefox, and Safari on the wire: TLS (JA3/JA4), HTTP/2, and HTTP/3 fingerprints
Documentation
use std::future::Future;
use std::time::Duration;

use tokio::time::Instant;

use crate::core::config::TimeoutConfig;
use crate::core::error::{Error, Kind, Result};

#[derive(Debug, Clone, Copy)]
pub(crate) struct Deadline {
    at: Option<Instant>,
    response_header: Option<Duration>,
    read: Option<Duration>,
    body: Option<Duration>,
    error_body: Option<Duration>,
}

#[derive(Debug)]
pub(crate) struct Elapsed;

impl From<tokio::time::error::Elapsed> for Elapsed {
    fn from(_: tokio::time::error::Elapsed) -> Self {
        Self
    }
}

impl Deadline {
    pub(crate) fn new(session: &TimeoutConfig, request: Option<&TimeoutConfig>) -> Self {
        let merged = request.map_or(*session, |request| request.over(session));
        Self {
            at: merged
                .total_limit()
                .and_then(|total| Instant::now().checked_add(total)),
            response_header: merged.response_header_limit(),
            read: merged.read_limit(),
            body: merged.body_limit(),
            error_body: merged.error_body_limit(),
        }
    }

    pub(crate) fn remaining(&self) -> Duration {
        self.at.map_or(Duration::MAX, |at| {
            at.saturating_duration_since(Instant::now())
        })
    }

    pub(crate) fn check(&self) -> Result<()> {
        if self.remaining().is_zero() {
            return Err(Error::new(Kind::Timeout));
        }
        Ok(())
    }

    pub(crate) fn read(&self) -> Option<Duration> {
        self.read
    }

    pub(crate) fn body(&self) -> Option<Duration> {
        self.body
    }

    pub(crate) fn error_body(&self) -> Option<Duration> {
        self.error_body
    }

    pub(crate) fn error_body_wait(&self) -> Duration {
        let remaining = self.remaining();
        self.error_body()
            .map_or(remaining, |cap| cap.min(remaining))
    }

    #[cfg(feature = "html")]
    pub(crate) fn alarm(at: Instant) -> std::pin::Pin<Box<tokio::time::Sleep>> {
        Box::pin(tokio::time::sleep_until(at))
    }

    pub(crate) async fn sleep(&self, wait: Duration) {
        tokio::time::sleep(wait.min(self.remaining())).await;
    }

    pub(crate) async fn total<T>(&self, fut: impl Future<Output = Result<T>>) -> Result<T> {
        match self.at {
            Some(at) => tokio::time::timeout_at(at, fut)
                .await
                .map_err(|e| Error::new(Kind::Timeout).with_source(e))?,
            None => fut.await,
        }
    }

    pub(crate) async fn response_header<T>(
        &self,
        fut: impl Future<Output = Result<T>>,
    ) -> Result<T> {
        within(self.response_header, fut)
            .await
            .map_err(|Elapsed| Error::new(Kind::Timeout))?
    }
}

pub(crate) async fn within<F: Future>(
    limit: Option<Duration>,
    fut: F,
) -> std::result::Result<F::Output, Elapsed> {
    match limit {
        Some(limit) => tokio::time::timeout(limit, fut)
            .await
            .map_err(Elapsed::from),
        None => Ok(fut.await),
    }
}