leyline-http 0.1.1

HTTP client that mimics browsers on the wire
Documentation
use std::io;

use super::{Error, ErrorCategory, Kind};
use crate::core::retry::is_gateway_status;
use crate::core::session::decompress::BodyLimit;
use crate::h2::H2Error;
use crate::h2::error::ErrorCode;
use crate::tls::TlsError;
use crate::tls::error::ProxyReply;

impl Error {
    pub fn is_timeout(&self) -> bool {
        self.inner.kind == Kind::Timeout
            || self
                .io_cause()
                .is_some_and(|e| e.kind() == io::ErrorKind::TimedOut)
    }

    pub fn is_connect(&self) -> bool {
        match self.category() {
            ErrorCategory::Connect | ErrorCategory::Dns => true,
            ErrorCategory::Tls => self.tls().is_some_and(TlsError::is_connect_phase),
            ErrorCategory::Timeout => {
                self.inner.kind == Kind::Connect
                    || self.tls().is_some_and(TlsError::is_connect_phase)
            }
            _ => false,
        }
    }

    pub(super) fn is_socket_unreachable(&self) -> bool {
        self.io().is_some_and(|e| {
            matches!(
                e.kind(),
                io::ErrorKind::ConnectionRefused
                    | io::ErrorKind::AddrNotAvailable
                    | io::ErrorKind::NotConnected
                    | io::ErrorKind::NetworkUnreachable
            )
        })
    }

    pub fn is_status(&self) -> bool {
        self.inner.kind == Kind::Status
    }

    pub fn is_retryable(&self) -> bool {
        self.is_timeout()
            || self.is_connect()
            || self.is_connection_closed()
            || self.is_proxy_transient()
    }

    fn is_proxy_transient(&self) -> bool {
        match self.tls() {
            Some(TlsError::Proxy { status, source, .. }) => {
                source.is_some() || status.is_some_and(is_gateway_status)
            }
            Some(TlsError::ProxyTargetUnreachable {
                reply: ProxyReply::HttpStatus(code),
                ..
            }) => is_gateway_status(*code),
            _ => false,
        }
    }

    pub(crate) fn is_connection_closed(&self) -> bool {
        if self.io().is_some_and(|e| {
            matches!(
                e.kind(),
                io::ErrorKind::UnexpectedEof
                    | io::ErrorKind::ConnectionReset
                    | io::ErrorKind::ConnectionAborted
                    | io::ErrorKind::BrokenPipe
            )
        }) {
            return true;
        }
        if matches!(
            self.h2(),
            Some(
                H2Error::Io(_)
                    | H2Error::Connection {
                        code: ErrorCode::NoError,
                        ..
                    }
            )
        ) || self.is_refused_stream()
        {
            return true;
        }
        self.tls().is_some_and(TlsError::is_handshake)
    }

    pub(crate) fn is_refused_stream(&self) -> bool {
        matches!(
            self.h2(),
            Some(H2Error::Stream {
                code: ErrorCode::RefusedStream,
                ..
            })
        ) || self.not_processed()
    }

    pub fn is_body_limit(&self) -> bool {
        self.body_limit().is_some()
    }

    pub(crate) fn body_limit(&self) -> Option<BodyLimit> {
        self.source_as::<BodyLimit>()
            .copied()
            .or_else(|| self.io().and_then(BodyLimit::of_io))
    }

    pub fn is_proxy(&self) -> bool {
        matches!(self.tls(), Some(TlsError::Proxy { .. }))
    }

    fn io_cause(&self) -> Option<&io::Error> {
        self.io()
            .or_else(|| self.tls().and_then(TlsError::io_source))
    }
}