1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use std::fmt;
use crate::http::HttpError;
/// REST client error.
#[derive(Debug)]
pub enum RestError {
/// I/O error.
Io(std::io::Error),
/// HTTP protocol error.
Http(HttpError),
/// Response body exceeds max size.
BodyTooLarge { size: usize, max: usize },
/// Request exceeds WriteBuf capacity.
RequestTooLarge { capacity: usize },
/// Header name/value or query parameter contains CR/LF bytes.
CrlfInjection,
/// Connection is poisoned after an I/O error mid-response.
ConnectionPoisoned,
/// Read timed out waiting for response.
ReadTimeout,
/// Connection is stale (dead socket detected after timeout).
ConnectionStale,
/// Connection closed before response complete.
ConnectionClosed(&'static str),
/// Invalid URL.
InvalidUrl(String),
/// `https://` URL used without the `tls` feature enabled.
TlsNotEnabled,
/// TLS error during connection setup (handshake, certificate
/// validation, hostname resolution).
///
/// **Steady-state TLS protocol errors** (decrypt failure, peer
/// alert, malformed record received during a request) on the
/// async `nexus-async-net` paths surface as
/// [`RestError::Io`](Self::Io) instead — the underlying
/// [`TlsError`](crate::tls::TlsError) is wrapped via
/// `io::Error::other` and reachable via `io_err.source()` or
/// `io_err.get_ref()`. This asymmetry stems from the
/// `WireStream` trait returning `io::Result` for poll
/// methods. Sync REST surfaces `Tls` directly because its
/// `TlsStream` exposes `TlsError` natively. Pattern-match on
/// both `Io` and `Tls` if you need to distinguish TLS-protocol
/// failures from generic transport failures across both
/// surfaces.
#[cfg(feature = "tls")]
Tls(crate::tls::TlsError),
}
impl fmt::Display for RestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Http(e) => write!(f, "HTTP error: {e}"),
Self::BodyTooLarge { size, max } => {
write!(f, "response body too large: {size} bytes (max: {max})")
}
Self::RequestTooLarge { capacity } => {
write!(
f,
"request exceeds write buffer capacity ({capacity} bytes)"
)
}
Self::CrlfInjection => {
write!(f, "header or query parameter contains CR/LF")
}
Self::ConnectionPoisoned => write!(f, "connection poisoned after I/O error"),
Self::ReadTimeout => write!(f, "read timed out waiting for response"),
Self::ConnectionStale => write!(f, "connection stale (dead socket)"),
Self::TlsNotEnabled => write!(f, "https:// requires the `tls` feature"),
Self::ConnectionClosed(ctx) => write!(f, "connection closed: {ctx}"),
Self::InvalidUrl(u) => write!(f, "invalid URL: {u}"),
#[cfg(feature = "tls")]
Self::Tls(e) => write!(f, "TLS error: {e}"),
}
}
}
impl std::error::Error for RestError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
Self::Http(e) => Some(e),
#[cfg(feature = "tls")]
Self::Tls(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for RestError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
impl From<HttpError> for RestError {
fn from(e: HttpError) -> Self {
Self::Http(e)
}
}
#[cfg(feature = "tls")]
impl From<crate::tls::TlsError> for RestError {
fn from(e: crate::tls::TlsError) -> Self {
match e {
crate::tls::TlsError::Io(io) => Self::Io(io),
other => Self::Tls(other),
}
}
}