leyline/core/error/
category.rs1use http::StatusCode;
2
3use super::{Error, Kind};
4use crate::tls::TlsError;
5
6const LABELS: [&str; 16] = [
7 "timeout",
8 "dns",
9 "connect",
10 "tls",
11 "proxy",
12 "proxy_target",
13 "status",
14 "body_limit",
15 "body",
16 "decode",
17 "protocol",
18 "redirect",
19 "url",
20 "config",
21 "request",
22 "other",
23];
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[non_exhaustive]
27pub enum ErrorCategory {
28 Timeout,
29 Dns,
30 Connect,
31 Tls,
32 Proxy,
33 ProxyTarget,
34 Status,
35 BodyLimit,
36 Body,
37 Decode,
38 Protocol,
39 Redirect,
40 Url,
41 Config,
42 Request,
43 Other,
44}
45
46const _: () = assert!(LABELS.len() == ErrorCategory::Other as usize + 1);
47
48impl ErrorCategory {
49 pub(crate) const ALL: [ErrorCategory; 16] = [
50 Self::Timeout,
51 Self::Dns,
52 Self::Connect,
53 Self::Tls,
54 Self::Proxy,
55 Self::ProxyTarget,
56 Self::Status,
57 Self::BodyLimit,
58 Self::Body,
59 Self::Decode,
60 Self::Protocol,
61 Self::Redirect,
62 Self::Url,
63 Self::Config,
64 Self::Request,
65 Self::Other,
66 ];
67
68 pub fn as_str(self) -> &'static str {
69 LABELS[self as usize]
70 }
71
72 pub fn gateway_status(self) -> Option<StatusCode> {
73 match self {
74 Self::Status => None,
75 Self::Timeout => Some(StatusCode::GATEWAY_TIMEOUT),
76 Self::Url | Self::Config | Self::Request => Some(StatusCode::INTERNAL_SERVER_ERROR),
77 _ => Some(StatusCode::BAD_GATEWAY),
78 }
79 }
80}
81
82impl std::fmt::Display for ErrorCategory {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.write_str(self.as_str())
85 }
86}
87
88impl Error {
89 pub fn category(&self) -> ErrorCategory {
90 if self.is_timeout() {
91 return ErrorCategory::Timeout;
92 }
93 if self.is_status() {
94 return ErrorCategory::Status;
95 }
96 if self.is_body_limit() {
97 return ErrorCategory::BodyLimit;
98 }
99 match self.tls() {
100 Some(tls) => tls_category(tls),
101 None => self.kind_category(),
102 }
103 }
104
105 pub fn is_dns(&self) -> bool {
106 matches!(self.tls(), Some(TlsError::Dns(_)))
107 }
108
109 fn kind_category(&self) -> ErrorCategory {
110 match self.inner.kind {
111 Kind::Proxy => ErrorCategory::Proxy,
112 Kind::Tls => ErrorCategory::Tls,
113 Kind::Connect => ErrorCategory::Connect,
114 _ if self.is_socket_unreachable() => ErrorCategory::Connect,
115 Kind::Status => ErrorCategory::Status,
116 Kind::Body => ErrorCategory::Body,
117 Kind::Decode | Kind::Json => ErrorCategory::Decode,
118 Kind::Http2 | Kind::Http3 => ErrorCategory::Protocol,
119 Kind::Redirect => ErrorCategory::Redirect,
120 Kind::Url => ErrorCategory::Url,
121 Kind::Config => ErrorCategory::Config,
122 Kind::Request => ErrorCategory::Request,
123 _ => ErrorCategory::Other,
124 }
125 }
126}
127
128fn tls_category(err: &TlsError) -> ErrorCategory {
129 match err {
130 TlsError::Dns(_) => ErrorCategory::Dns,
131 TlsError::TcpConnect(_) => ErrorCategory::Connect,
132 TlsError::ProxyTargetUnreachable { .. } => ErrorCategory::ProxyTarget,
133 TlsError::Proxy { .. } => ErrorCategory::Proxy,
134 TlsError::SslConfig(_) | TlsError::Profile(_) | TlsError::TrustStore(_) => {
135 ErrorCategory::Config
136 }
137 TlsError::Handshake(_)
138 | TlsError::Rejected(_)
139 | TlsError::HandshakeIo(_)
140 | TlsError::Certificate { .. }
141 | TlsError::Hostname(_)
142 | TlsError::Pinning(_) => ErrorCategory::Tls,
143 }
144}