Skip to main content

moq_native/
error.rs

1use std::sync::Arc;
2
3/// Errors produced while configuring or establishing native MoQ connections.
4///
5/// Backend-specific failures live in per-backend error types ([`crate::tls::Error`],
6/// [`crate::quinn::Error`], etc.). They're wrapped in `Arc` here so the aggregate
7/// stays `Clone` even though the underlying transport/IO errors are not.
8#[derive(Debug, Clone, thiserror::Error)]
9#[non_exhaustive]
10pub enum Error {
11	#[error(transparent)]
12	Io(Arc<std::io::Error>),
13
14	#[error(transparent)]
15	MoqNet(#[from] moq_net::Error),
16
17	#[error("invalid log directive")]
18	Directive(#[source] Arc<tracing_subscriber::filter::ParseError>),
19
20	#[error("failed to set global tracing subscriber")]
21	SetSubscriber(#[source] Arc<tracing_subscriber::util::TryInitError>),
22
23	#[error("failed to initialize Android logcat layer")]
24	Logcat(#[source] Arc<std::io::Error>),
25
26	#[error("{0}")]
27	NoBackend(&'static str),
28
29	#[error("failed to connect to server")]
30	ConnectFailed,
31
32	#[error(transparent)]
33	Connect(#[from] crate::ConnectError),
34
35	#[cfg(feature = "websocket")]
36	#[error("failed to connect to server: QUIC failed: {quic}; WebSocket failed: {websocket}")]
37	TransportRace { quic: Arc<Error>, websocket: Arc<Error> },
38
39	#[cfg(feature = "iroh")]
40	#[error("Iroh support is not enabled")]
41	IrohDisabled,
42
43	#[error("tls.root (mTLS) is not supported by the selected QUIC backend")]
44	MtlsUnsupported,
45
46	#[error("invalid status code")]
47	InvalidStatusCode,
48
49	#[error("{0}")]
50	Reconnect(String),
51
52	#[error(transparent)]
53	Tls(Arc<crate::tls::Error>),
54
55	#[cfg(feature = "quinn")]
56	#[error(transparent)]
57	Quinn(Arc<crate::quinn::Error>),
58
59	#[cfg(feature = "noq")]
60	#[error(transparent)]
61	Noq(Arc<crate::noq::Error>),
62
63	#[cfg(feature = "quiche")]
64	#[error(transparent)]
65	Quiche(Arc<crate::quiche::Error>),
66
67	#[cfg(feature = "iroh")]
68	#[error(transparent)]
69	Iroh(Arc<crate::iroh::Error>),
70
71	#[cfg(feature = "websocket")]
72	#[error(transparent)]
73	WebSocket(Arc<crate::websocket::Error>),
74
75	#[cfg(feature = "tcp")]
76	#[error(transparent)]
77	Tcp(Arc<crate::tcp::Error>),
78
79	#[cfg(all(feature = "uds", unix))]
80	#[error(transparent)]
81	Unix(Arc<crate::unix::Error>),
82}
83
84impl Error {
85	pub fn connect_error(&self) -> Option<crate::ConnectError> {
86		match self {
87			Self::Connect(err) => Some(*err),
88			Self::MoqNet(moq_net::Error::Unauthorized) => Some(crate::ConnectError::Unauthorized),
89			#[cfg(feature = "quinn")]
90			Self::Quinn(err) => err.connect_error(),
91			#[cfg(feature = "noq")]
92			Self::Noq(err) => err.connect_error(),
93			#[cfg(feature = "quiche")]
94			Self::Quiche(err) => err.connect_error(),
95			#[cfg(feature = "websocket")]
96			Self::TransportRace { quic, websocket } => quic.connect_error().or_else(|| websocket.connect_error()),
97			#[cfg(feature = "websocket")]
98			Self::WebSocket(err) => err.connect_error(),
99			_ => None,
100		}
101	}
102
103	pub fn is_auth(&self) -> bool {
104		self.connect_error().is_some_and(|err| err.is_auth())
105	}
106}
107
108// The wrapped sources aren't `Clone`, so `#[from]` can't store them behind `Arc`
109// directly. These hand-written conversions keep `?` ergonomic at the call sites.
110impl From<std::io::Error> for Error {
111	fn from(err: std::io::Error) -> Self {
112		Self::Io(Arc::new(err))
113	}
114}
115
116impl From<tracing_subscriber::filter::ParseError> for Error {
117	fn from(err: tracing_subscriber::filter::ParseError) -> Self {
118		Self::Directive(Arc::new(err))
119	}
120}
121
122impl From<crate::tls::Error> for Error {
123	fn from(err: crate::tls::Error) -> Self {
124		Self::Tls(Arc::new(err))
125	}
126}
127
128#[cfg(feature = "quinn")]
129impl From<crate::quinn::Error> for Error {
130	fn from(err: crate::quinn::Error) -> Self {
131		if let Some(err) = err.connect_error() {
132			return Self::Connect(err);
133		}
134
135		Self::Quinn(Arc::new(err))
136	}
137}
138
139#[cfg(feature = "noq")]
140impl From<crate::noq::Error> for Error {
141	fn from(err: crate::noq::Error) -> Self {
142		if let Some(err) = err.connect_error() {
143			return Self::Connect(err);
144		}
145
146		Self::Noq(Arc::new(err))
147	}
148}
149
150#[cfg(feature = "quiche")]
151impl From<crate::quiche::Error> for Error {
152	fn from(err: crate::quiche::Error) -> Self {
153		if let Some(err) = err.connect_error() {
154			return Self::Connect(err);
155		}
156
157		Self::Quiche(Arc::new(err))
158	}
159}
160
161#[cfg(feature = "iroh")]
162impl From<crate::iroh::Error> for Error {
163	fn from(err: crate::iroh::Error) -> Self {
164		Self::Iroh(Arc::new(err))
165	}
166}
167
168#[cfg(feature = "websocket")]
169impl From<crate::websocket::Error> for Error {
170	fn from(err: crate::websocket::Error) -> Self {
171		if let Some(err) = err.connect_error() {
172			return Self::Connect(err);
173		}
174
175		Self::WebSocket(Arc::new(err))
176	}
177}
178
179#[cfg(feature = "tcp")]
180impl From<crate::tcp::Error> for Error {
181	fn from(err: crate::tcp::Error) -> Self {
182		Self::Tcp(Arc::new(err))
183	}
184}
185
186#[cfg(all(feature = "uds", unix))]
187impl From<crate::unix::Error> for Error {
188	fn from(err: crate::unix::Error) -> Self {
189		Self::Unix(Arc::new(err))
190	}
191}
192
193/// Convenience alias for results produced by this crate.
194pub type Result<T> = std::result::Result<T, Error>;
195
196#[cfg(all(test, feature = "websocket"))]
197mod tests {
198	use super::*;
199
200	#[test]
201	fn transport_race_propagates_nested_connect_errors() {
202		let quic = Error::TransportRace {
203			quic: Arc::new(crate::ConnectError::Unauthorized.into()),
204			websocket: Arc::new(crate::ConnectError::Forbidden.into()),
205		};
206		assert_eq!(quic.connect_error(), Some(crate::ConnectError::Unauthorized));
207
208		let websocket = Error::TransportRace {
209			quic: Arc::new(Error::ConnectFailed),
210			websocket: Arc::new(crate::ConnectError::Forbidden.into()),
211		};
212		assert_eq!(websocket.connect_error(), Some(crate::ConnectError::Forbidden));
213	}
214}