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("failed to connect to server")]
50 ConnectFailed,
51
52 #[error("connect timed out after {0:?}")]
60 ConnectTimeout(std::time::Duration),
61
62 #[error(transparent)]
64 Connect(#[from] crate::ConnectError),
65
66 #[cfg(feature = "websocket")]
68 #[error("failed to connect to server: QUIC failed: {quic}; WebSocket failed: {websocket}")]
69 TransportRace {
70 quic: Arc<Error>,
72 websocket: Arc<Error>,
74 },
75
76 #[cfg(feature = "iroh")]
78 #[error("Iroh support is not enabled")]
79 IrohDisabled,
80
81 #[error("tls.root (mTLS) is not supported by the selected QUIC backend")]
83 MtlsUnsupported,
84
85 #[error("invalid status code")]
87 InvalidStatusCode,
88
89 #[error("{0}")]
91 Reconnect(String),
92
93 #[error(transparent)]
95 Tls(Arc<crate::tls::Error>),
96
97 #[cfg(feature = "quinn")]
99 #[error(transparent)]
100 Quinn(Arc<crate::quinn::Error>),
101
102 #[cfg(feature = "noq")]
104 #[error(transparent)]
105 Noq(Arc<crate::noq::Error>),
106
107 #[cfg(feature = "quiche")]
109 #[error(transparent)]
110 Quiche(Arc<crate::quiche::Error>),
111
112 #[cfg(feature = "iroh")]
114 #[error(transparent)]
115 Iroh(Arc<crate::iroh::Error>),
116
117 #[cfg(feature = "websocket")]
119 #[error(transparent)]
120 WebSocket(Arc<crate::websocket::Error>),
121
122 #[cfg(feature = "tcp")]
124 #[error(transparent)]
125 Tcp(Arc<crate::tcp::Error>),
126
127 #[cfg(all(feature = "uds", unix))]
129 #[error(transparent)]
130 Unix(Arc<crate::unix::Error>),
131}
132
133impl Error {
134 pub fn connect_error(&self) -> Option<crate::ConnectError> {
136 match self {
137 Self::Connect(err) => Some(*err),
138 Self::MoqNet(moq_net::Error::Unauthorized) => Some(crate::ConnectError::Unauthorized),
139 #[cfg(feature = "quinn")]
140 Self::Quinn(err) => err.connect_error(),
141 #[cfg(feature = "noq")]
142 Self::Noq(err) => err.connect_error(),
143 #[cfg(feature = "quiche")]
144 Self::Quiche(err) => err.connect_error(),
145 #[cfg(feature = "websocket")]
146 Self::TransportRace { quic, websocket } => quic.connect_error().or_else(|| websocket.connect_error()),
147 #[cfg(feature = "websocket")]
148 Self::WebSocket(err) => err.connect_error(),
149 _ => None,
150 }
151 }
152
153 pub fn is_auth(&self) -> bool {
155 self.connect_error().is_some_and(|err| err.is_auth())
156 }
157
158 pub fn status(&self) -> Option<u16> {
167 match self {
168 #[cfg(feature = "websocket")]
172 Self::TransportRace { quic, websocket } => match (quic.status(), websocket.status()) {
173 (Some(quic), Some(websocket)) if !status_retryable(quic) && !status_retryable(websocket) => Some(quic),
174 _ => None,
175 },
176
177 #[cfg(feature = "quinn")]
178 Self::Quinn(err) => err.status(),
179 #[cfg(feature = "noq")]
180 Self::Noq(err) => err.status(),
181 #[cfg(feature = "quiche")]
182 Self::Quiche(err) => err.status(),
183 #[cfg(feature = "websocket")]
184 Self::WebSocket(err) => err.status(),
185 _ => None,
186 }
187 }
188}
189
190impl From<std::io::Error> for Error {
193 fn from(err: std::io::Error) -> Self {
194 Self::Io(Arc::new(err))
195 }
196}
197
198impl From<tracing_subscriber::filter::ParseError> for Error {
199 fn from(err: tracing_subscriber::filter::ParseError) -> Self {
200 Self::Directive(Arc::new(err))
201 }
202}
203
204impl From<crate::tls::Error> for Error {
205 fn from(err: crate::tls::Error) -> Self {
206 Self::Tls(Arc::new(err))
207 }
208}
209
210#[cfg(feature = "quinn")]
211impl From<crate::quinn::Error> for Error {
212 fn from(err: crate::quinn::Error) -> Self {
213 if let Some(err) = err.connect_error() {
214 return Self::Connect(err);
215 }
216
217 Self::Quinn(Arc::new(err))
218 }
219}
220
221#[cfg(feature = "noq")]
222impl From<crate::noq::Error> for Error {
223 fn from(err: crate::noq::Error) -> Self {
224 if let Some(err) = err.connect_error() {
225 return Self::Connect(err);
226 }
227
228 Self::Noq(Arc::new(err))
229 }
230}
231
232#[cfg(feature = "quiche")]
233impl From<crate::quiche::Error> for Error {
234 fn from(err: crate::quiche::Error) -> Self {
235 if let Some(err) = err.connect_error() {
236 return Self::Connect(err);
237 }
238
239 Self::Quiche(Arc::new(err))
240 }
241}
242
243#[cfg(feature = "iroh")]
244impl From<crate::iroh::Error> for Error {
245 fn from(err: crate::iroh::Error) -> Self {
246 Self::Iroh(Arc::new(err))
247 }
248}
249
250#[cfg(feature = "websocket")]
251impl From<crate::websocket::Error> for Error {
252 fn from(err: crate::websocket::Error) -> Self {
253 if let Some(err) = err.connect_error() {
254 return Self::Connect(err);
255 }
256
257 Self::WebSocket(Arc::new(err))
258 }
259}
260
261#[cfg(feature = "tcp")]
262impl From<crate::tcp::Error> for Error {
263 fn from(err: crate::tcp::Error) -> Self {
264 Self::Tcp(Arc::new(err))
265 }
266}
267
268#[cfg(all(feature = "uds", unix))]
269impl From<crate::unix::Error> for Error {
270 fn from(err: crate::unix::Error) -> Self {
271 Self::Unix(Arc::new(err))
272 }
273}
274
275pub type Result<T> = std::result::Result<T, Error>;
277
278#[cfg(all(test, feature = "websocket"))]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn transport_race_propagates_nested_connect_errors() {
284 let quic = Error::TransportRace {
285 quic: Arc::new(crate::ConnectError::Unauthorized.into()),
286 websocket: Arc::new(crate::ConnectError::Forbidden.into()),
287 };
288 assert_eq!(quic.connect_error(), Some(crate::ConnectError::Unauthorized));
289
290 let websocket = Error::TransportRace {
291 quic: Arc::new(Error::ConnectFailed),
292 websocket: Arc::new(crate::ConnectError::Forbidden.into()),
293 };
294 assert_eq!(websocket.connect_error(), Some(crate::ConnectError::Forbidden));
295 }
296}