1use std::sync::Arc;
2
3pub(crate) fn status_retryable(status: u16) -> bool {
9 matches!(status, 408 | 429 | 502 | 503 | 504)
10}
11
12#[derive(Debug, Clone, thiserror::Error)]
18#[non_exhaustive]
19pub enum Error {
20 #[error(transparent)]
22 Io(Arc<std::io::Error>),
23
24 #[error(transparent)]
26 MoqNet(#[from] moq_net::Error),
27
28 #[error("invalid log directive")]
30 Directive(#[source] Arc<tracing_subscriber::filter::ParseError>),
31
32 #[error("failed to set global tracing subscriber")]
34 SetSubscriber(#[source] Arc<tracing_subscriber::util::TryInitError>),
35
36 #[error("failed to initialize Android logcat layer")]
38 Logcat(#[source] Arc<std::io::Error>),
39
40 #[error("{0}")]
42 NoBackend(&'static str),
43
44 #[error("qlog capture requires the 'qlog' feature")]
46 QlogUnsupported,
47
48 #[error("idle timeout must be under 2^62 milliseconds")]
50 IdleTimeoutRange,
51
52 #[error("backoff initial, multiplier, and max must all be non-zero, or reconnecting spins")]
54 BackoffUnpaced,
55
56 #[error("failed to connect to server")]
58 ConnectFailed,
59
60 #[error("connect timed out after {0:?}")]
68 ConnectTimeout(std::time::Duration),
69
70 #[error(transparent)]
72 Connect(#[from] crate::ConnectError),
73
74 #[cfg(feature = "websocket")]
76 #[error("failed to connect to server: QUIC failed: {quic}; WebSocket failed: {websocket}")]
77 TransportRace {
78 quic: Arc<Error>,
80 websocket: Arc<Error>,
82 },
83
84 #[cfg(feature = "iroh")]
86 #[error("Iroh support is not enabled")]
87 IrohDisabled,
88
89 #[error("tls.root (mTLS) is not supported by the selected QUIC backend")]
91 MtlsUnsupported,
92
93 #[error("invalid status code")]
95 InvalidStatusCode,
96
97 #[error("{0}")]
99 Reconnect(String),
100
101 #[error(transparent)]
103 Tls(Arc<crate::tls::Error>),
104
105 #[cfg(feature = "quinn")]
107 #[error(transparent)]
108 Quinn(Arc<crate::quinn::Error>),
109
110 #[cfg(feature = "noq")]
112 #[error(transparent)]
113 Noq(Arc<crate::noq::Error>),
114
115 #[cfg(feature = "quiche")]
117 #[error(transparent)]
118 Quiche(Arc<crate::quiche::Error>),
119
120 #[cfg(feature = "iroh")]
122 #[error(transparent)]
123 Iroh(Arc<crate::iroh::Error>),
124
125 #[cfg(feature = "websocket")]
127 #[error(transparent)]
128 WebSocket(Arc<crate::websocket::Error>),
129
130 #[cfg(feature = "tcp")]
132 #[error(transparent)]
133 Tcp(Arc<crate::tcp::Error>),
134
135 #[cfg(all(feature = "uds", unix))]
137 #[error(transparent)]
138 Unix(Arc<crate::unix::Error>),
139}
140
141impl Error {
142 pub fn connect_error(&self) -> Option<crate::ConnectError> {
144 match self {
145 Self::Connect(err) => Some(*err),
146 Self::MoqNet(moq_net::Error::Unauthorized) => Some(crate::ConnectError::Unauthorized),
147 #[cfg(feature = "quinn")]
148 Self::Quinn(err) => err.connect_error(),
149 #[cfg(feature = "noq")]
150 Self::Noq(err) => err.connect_error(),
151 #[cfg(feature = "quiche")]
152 Self::Quiche(err) => err.connect_error(),
153 #[cfg(feature = "websocket")]
154 Self::TransportRace { quic, websocket } => quic.connect_error().or_else(|| websocket.connect_error()),
155 #[cfg(feature = "websocket")]
156 Self::WebSocket(err) => err.connect_error(),
157 _ => None,
158 }
159 }
160
161 pub fn is_auth(&self) -> bool {
163 self.connect_error().is_some_and(|err| err.is_auth())
164 }
165
166 pub fn status(&self) -> Option<u16> {
175 match self {
176 #[cfg(feature = "websocket")]
180 Self::TransportRace { quic, websocket } => match (quic.status(), websocket.status()) {
181 (Some(quic), Some(websocket)) if !status_retryable(quic) && !status_retryable(websocket) => Some(quic),
182 _ => None,
183 },
184
185 #[cfg(feature = "quinn")]
186 Self::Quinn(err) => err.status(),
187 #[cfg(feature = "noq")]
188 Self::Noq(err) => err.status(),
189 #[cfg(feature = "quiche")]
190 Self::Quiche(err) => err.status(),
191 #[cfg(feature = "websocket")]
192 Self::WebSocket(err) => err.status(),
193 _ => None,
194 }
195 }
196}
197
198impl From<std::io::Error> for Error {
201 fn from(err: std::io::Error) -> Self {
202 Self::Io(Arc::new(err))
203 }
204}
205
206impl From<tracing_subscriber::filter::ParseError> for Error {
207 fn from(err: tracing_subscriber::filter::ParseError) -> Self {
208 Self::Directive(Arc::new(err))
209 }
210}
211
212impl From<crate::tls::Error> for Error {
213 fn from(err: crate::tls::Error) -> Self {
214 Self::Tls(Arc::new(err))
215 }
216}
217
218#[cfg(feature = "quinn")]
219impl From<crate::quinn::Error> for Error {
220 fn from(err: crate::quinn::Error) -> Self {
221 if let Some(err) = err.connect_error() {
222 return Self::Connect(err);
223 }
224
225 Self::Quinn(Arc::new(err))
226 }
227}
228
229#[cfg(feature = "noq")]
230impl From<crate::noq::Error> for Error {
231 fn from(err: crate::noq::Error) -> Self {
232 if let Some(err) = err.connect_error() {
233 return Self::Connect(err);
234 }
235
236 Self::Noq(Arc::new(err))
237 }
238}
239
240#[cfg(feature = "quiche")]
241impl From<crate::quiche::Error> for Error {
242 fn from(err: crate::quiche::Error) -> Self {
243 if let Some(err) = err.connect_error() {
244 return Self::Connect(err);
245 }
246
247 Self::Quiche(Arc::new(err))
248 }
249}
250
251#[cfg(feature = "iroh")]
252impl From<crate::iroh::Error> for Error {
253 fn from(err: crate::iroh::Error) -> Self {
254 Self::Iroh(Arc::new(err))
255 }
256}
257
258#[cfg(feature = "websocket")]
259impl From<crate::websocket::Error> for Error {
260 fn from(err: crate::websocket::Error) -> Self {
261 if let Some(err) = err.connect_error() {
262 return Self::Connect(err);
263 }
264
265 Self::WebSocket(Arc::new(err))
266 }
267}
268
269#[cfg(feature = "tcp")]
270impl From<crate::tcp::Error> for Error {
271 fn from(err: crate::tcp::Error) -> Self {
272 Self::Tcp(Arc::new(err))
273 }
274}
275
276#[cfg(all(feature = "uds", unix))]
277impl From<crate::unix::Error> for Error {
278 fn from(err: crate::unix::Error) -> Self {
279 Self::Unix(Arc::new(err))
280 }
281}
282
283pub type Result<T> = std::result::Result<T, Error>;
285
286#[cfg(all(test, feature = "websocket"))]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn transport_race_propagates_nested_connect_errors() {
292 let quic = Error::TransportRace {
293 quic: Arc::new(crate::ConnectError::Unauthorized.into()),
294 websocket: Arc::new(crate::ConnectError::Forbidden.into()),
295 };
296 assert_eq!(quic.connect_error(), Some(crate::ConnectError::Unauthorized));
297
298 let websocket = Error::TransportRace {
299 quic: Arc::new(Error::ConnectFailed),
300 websocket: Arc::new(crate::ConnectError::Forbidden.into()),
301 };
302 assert_eq!(websocket.connect_error(), Some(crate::ConnectError::Forbidden));
303 }
304}