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
76impl Error {
77	pub fn connect_error(&self) -> Option<crate::ConnectError> {
78		match self {
79			Self::Connect(err) => Some(*err),
80			Self::MoqNet(moq_net::Error::Unauthorized) => Some(crate::ConnectError::Unauthorized),
81			#[cfg(feature = "quinn")]
82			Self::Quinn(err) => err.connect_error(),
83			#[cfg(feature = "noq")]
84			Self::Noq(err) => err.connect_error(),
85			#[cfg(feature = "quiche")]
86			Self::Quiche(err) => err.connect_error(),
87			#[cfg(feature = "websocket")]
88			Self::TransportRace { quic, websocket } => quic.connect_error().or_else(|| websocket.connect_error()),
89			#[cfg(feature = "websocket")]
90			Self::WebSocket(err) => err.connect_error(),
91			_ => None,
92		}
93	}
94
95	pub fn is_auth(&self) -> bool {
96		self.connect_error().is_some_and(|err| err.is_auth())
97	}
98}
99
100// The wrapped sources aren't `Clone`, so `#[from]` can't store them behind `Arc`
101// directly. These hand-written conversions keep `?` ergonomic at the call sites.
102impl From<std::io::Error> for Error {
103	fn from(err: std::io::Error) -> Self {
104		Self::Io(Arc::new(err))
105	}
106}
107
108impl From<tracing_subscriber::filter::ParseError> for Error {
109	fn from(err: tracing_subscriber::filter::ParseError) -> Self {
110		Self::Directive(Arc::new(err))
111	}
112}
113
114impl From<crate::tls::Error> for Error {
115	fn from(err: crate::tls::Error) -> Self {
116		Self::Tls(Arc::new(err))
117	}
118}
119
120#[cfg(feature = "quinn")]
121impl From<crate::quinn::Error> for Error {
122	fn from(err: crate::quinn::Error) -> Self {
123		if let Some(err) = err.connect_error() {
124			return Self::Connect(err);
125		}
126
127		Self::Quinn(Arc::new(err))
128	}
129}
130
131#[cfg(feature = "noq")]
132impl From<crate::noq::Error> for Error {
133	fn from(err: crate::noq::Error) -> Self {
134		if let Some(err) = err.connect_error() {
135			return Self::Connect(err);
136		}
137
138		Self::Noq(Arc::new(err))
139	}
140}
141
142#[cfg(feature = "quiche")]
143impl From<crate::quiche::Error> for Error {
144	fn from(err: crate::quiche::Error) -> Self {
145		if let Some(err) = err.connect_error() {
146			return Self::Connect(err);
147		}
148
149		Self::Quiche(Arc::new(err))
150	}
151}
152
153#[cfg(feature = "iroh")]
154impl From<crate::iroh::Error> for Error {
155	fn from(err: crate::iroh::Error) -> Self {
156		Self::Iroh(Arc::new(err))
157	}
158}
159
160#[cfg(feature = "websocket")]
161impl From<crate::websocket::Error> for Error {
162	fn from(err: crate::websocket::Error) -> Self {
163		if let Some(err) = err.connect_error() {
164			return Self::Connect(err);
165		}
166
167		Self::WebSocket(Arc::new(err))
168	}
169}
170
171/// Convenience alias for results produced by this crate.
172pub type Result<T> = std::result::Result<T, Error>;
173
174#[cfg(all(test, feature = "websocket"))]
175mod tests {
176	use super::*;
177
178	#[test]
179	fn transport_race_propagates_nested_connect_errors() {
180		let quic = Error::TransportRace {
181			quic: Arc::new(crate::ConnectError::Unauthorized.into()),
182			websocket: Arc::new(crate::ConnectError::Forbidden.into()),
183		};
184		assert_eq!(quic.connect_error(), Some(crate::ConnectError::Unauthorized));
185
186		let websocket = Error::TransportRace {
187			quic: Arc::new(Error::ConnectFailed),
188			websocket: Arc::new(crate::ConnectError::Forbidden.into()),
189		};
190		assert_eq!(websocket.connect_error(), Some(crate::ConnectError::Forbidden));
191	}
192}