leyline/core/error/
classify.rs1use std::io;
2
3use super::{Error, ErrorCategory, Kind};
4use crate::core::retry::is_gateway_status;
5use crate::core::session::decompress::BodyLimit;
6use crate::h2::H2Error;
7use crate::h2::error::ErrorCode;
8use crate::tls::TlsError;
9use crate::tls::error::ProxyReply;
10
11impl Error {
12 pub fn is_timeout(&self) -> bool {
13 self.inner.kind == Kind::Timeout
14 || self
15 .io_cause()
16 .is_some_and(|e| e.kind() == io::ErrorKind::TimedOut)
17 }
18
19 pub fn is_connect(&self) -> bool {
20 match self.category() {
21 ErrorCategory::Connect | ErrorCategory::Dns => true,
22 ErrorCategory::Tls => self.tls().is_some_and(TlsError::is_connect_phase),
23 ErrorCategory::Timeout => {
24 self.inner.kind == Kind::Connect
25 || self.tls().is_some_and(TlsError::is_connect_phase)
26 }
27 _ => false,
28 }
29 }
30
31 pub(super) fn is_socket_unreachable(&self) -> bool {
32 self.io().is_some_and(|e| {
33 matches!(
34 e.kind(),
35 io::ErrorKind::ConnectionRefused
36 | io::ErrorKind::AddrNotAvailable
37 | io::ErrorKind::NotConnected
38 | io::ErrorKind::NetworkUnreachable
39 )
40 })
41 }
42
43 pub fn is_status(&self) -> bool {
44 self.inner.kind == Kind::Status
45 }
46
47 pub fn is_retryable(&self) -> bool {
48 self.is_timeout()
49 || self.is_connect()
50 || self.is_connection_closed()
51 || self.is_proxy_transient()
52 }
53
54 fn is_proxy_transient(&self) -> bool {
55 match self.tls() {
56 Some(TlsError::Proxy { status, source, .. }) => {
57 source.is_some() || status.is_some_and(is_gateway_status)
58 }
59 Some(TlsError::ProxyTargetUnreachable {
60 reply: ProxyReply::HttpStatus(code),
61 ..
62 }) => is_gateway_status(*code),
63 _ => false,
64 }
65 }
66
67 pub(crate) fn is_connection_closed(&self) -> bool {
68 if self.io().is_some_and(|e| {
69 matches!(
70 e.kind(),
71 io::ErrorKind::UnexpectedEof
72 | io::ErrorKind::ConnectionReset
73 | io::ErrorKind::ConnectionAborted
74 | io::ErrorKind::BrokenPipe
75 )
76 }) {
77 return true;
78 }
79 if matches!(
80 self.h2(),
81 Some(
82 H2Error::Io(_)
83 | H2Error::Connection {
84 code: ErrorCode::NoError,
85 ..
86 }
87 )
88 ) || self.is_refused_stream()
89 {
90 return true;
91 }
92 self.tls().is_some_and(TlsError::is_handshake)
93 }
94
95 pub(crate) fn is_refused_stream(&self) -> bool {
96 matches!(
97 self.h2(),
98 Some(H2Error::Stream {
99 code: ErrorCode::RefusedStream,
100 ..
101 })
102 ) || self.not_processed()
103 }
104
105 pub fn is_body_limit(&self) -> bool {
106 self.body_limit().is_some()
107 }
108
109 pub(crate) fn body_limit(&self) -> Option<BodyLimit> {
110 self.source_as::<BodyLimit>()
111 .copied()
112 .or_else(|| self.io().and_then(BodyLimit::of_io))
113 }
114
115 pub fn is_proxy(&self) -> bool {
116 matches!(self.tls(), Some(TlsError::Proxy { .. }))
117 }
118
119 fn io_cause(&self) -> Option<&io::Error> {
120 self.io()
121 .or_else(|| self.tls().and_then(TlsError::io_source))
122 }
123}