Skip to main content

leyline/core/error/
category.rs

1use 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}