1use actr_protocol::{ActrError, Classify, ErrorKind};
4use thiserror::Error;
5
6#[derive(Error, Debug)]
8pub enum NetworkError {
9 #[error("Connection error: {0}")]
11 ConnectionError(String),
12
13 #[error("Signaling error: {0}")]
15 SignalingError(String),
16
17 #[error("WebRTC error: {0}")]
19 WebRtcError(String),
20
21 #[error("Protocol error: {0}")]
23 ProtocolError(String),
24
25 #[error("Serialization error: {0}")]
27 SerializationError(String),
28
29 #[error("Deserialization error: {0}")]
31 DeserializationError(String),
32
33 #[error("Timeout error: {0}")]
35 TimeoutError(String),
36
37 #[error("Authentication error: {0}")]
39 AuthenticationError(String),
40
41 #[error("Credential expired: {0}")]
43 CredentialExpired(String),
44
45 #[error("Permission error: {0}")]
47 PermissionError(String),
48
49 #[error("Configuration error: {0}")]
51 ConfigurationError(String),
52
53 #[error("Resource exhausted: {0}")]
55 ResourceExhaustedError(String),
56
57 #[error("Network unreachable: {0}")]
59 NetworkUnreachableError(String),
60
61 #[error("Service discovery error: {0}")]
63 ServiceDiscoveryError(String),
64
65 #[error("NAT traversal error: {0}")]
67 NatTraversalError(String),
68
69 #[error("Data channel error: {0}")]
71 DataChannelError(String),
72
73 #[error("Broadcast error: {0}")]
75 BroadcastError(String),
76
77 #[error("ICE error: {0}")]
79 IceError(String),
80
81 #[error("DTLS error: {0}")]
83 DtlsError(String),
84
85 #[error("STUN/TURN error: {0}")]
87 StunTurnError(String),
88
89 #[error("WebSocket error: {0}")]
91 WebSocketError(String),
92
93 #[error("Connection not found: {0}")]
95 ConnectionNotFound(String),
96
97 #[error("Connection closed: {0}")]
99 ConnectionClosed(String),
100
101 #[error("Not implemented: {0}")]
103 NotImplemented(String),
104
105 #[error("Channel closed: {0}")]
107 ChannelClosed(String),
108
109 #[error("Send error: {0}")]
111 SendError(String),
112
113 #[error("No route: {0}")]
115 NoRoute(String),
116
117 #[error("Invalid operation: {0}")]
119 InvalidOperation(String),
120
121 #[error("Invalid argument: {0}")]
123 InvalidArgument(String),
124
125 #[error("Channel not found: {0}")]
127 ChannelNotFound(String),
128
129 #[error("IO error: {0}")]
131 IoError(#[from] std::io::Error),
132
133 #[error("URL parse error: {0}")]
135 UrlParseError(#[from] url::ParseError),
136
137 #[error("JSON error: {0}")]
139 JsonError(#[from] serde_json::Error),
140
141 #[error("Other error: {0}")]
143 Other(#[from] anyhow::Error),
144}
145
146impl Classify for NetworkError {
147 fn kind(&self) -> ErrorKind {
148 match self {
149 NetworkError::ConnectionError(_)
151 | NetworkError::ConnectionClosed(_)
152 | NetworkError::ChannelClosed(_)
153 | NetworkError::SendError(_)
154 | NetworkError::NetworkUnreachableError(_)
155 | NetworkError::ResourceExhaustedError(_)
156 | NetworkError::WebSocketError(_)
157 | NetworkError::SignalingError(_)
158 | NetworkError::WebRtcError(_)
159 | NetworkError::NatTraversalError(_)
160 | NetworkError::IceError(_) => ErrorKind::Transient,
161
162 NetworkError::TimeoutError(_) => ErrorKind::Transient,
164
165 NetworkError::ConnectionNotFound(_)
167 | NetworkError::ChannelNotFound(_)
168 | NetworkError::NoRoute(_)
169 | NetworkError::InvalidArgument(_)
170 | NetworkError::InvalidOperation(_)
171 | NetworkError::ConfigurationError(_)
172 | NetworkError::ServiceDiscoveryError(_) => ErrorKind::Client,
173
174 NetworkError::AuthenticationError(_)
176 | NetworkError::PermissionError(_)
177 | NetworkError::CredentialExpired(_) => ErrorKind::Client,
178
179 NetworkError::DeserializationError(_) => ErrorKind::Corrupt,
181
182 NetworkError::ProtocolError(_)
184 | NetworkError::SerializationError(_)
185 | NetworkError::DataChannelError(_)
186 | NetworkError::BroadcastError(_)
187 | NetworkError::DtlsError(_)
188 | NetworkError::StunTurnError(_)
189 | NetworkError::NotImplemented(_)
190 | NetworkError::IoError(_)
191 | NetworkError::UrlParseError(_)
192 | NetworkError::JsonError(_)
193 | NetworkError::Other(_) => ErrorKind::Internal,
194 }
195 }
196}
197
198impl NetworkError {
199 pub fn category(&self) -> &'static str {
201 match self {
202 NetworkError::ConnectionError(_) => "connection",
203 NetworkError::SignalingError(_) => "signaling",
204 NetworkError::WebRtcError(_) => "webrtc",
205 NetworkError::ProtocolError(_) => "protocol",
206 NetworkError::SerializationError(_) | NetworkError::DeserializationError(_) => {
207 "serialization"
208 }
209 NetworkError::TimeoutError(_) => "timeout",
210 NetworkError::AuthenticationError(_) => "authentication",
211 NetworkError::PermissionError(_) => "permission",
212 NetworkError::ConfigurationError(_) => "configuration",
213 NetworkError::ResourceExhaustedError(_) => "resource_exhausted",
214 NetworkError::NetworkUnreachableError(_) => "network_unreachable",
215 NetworkError::ServiceDiscoveryError(_) => "service_discovery",
216 NetworkError::NatTraversalError(_) => "nat_traversal",
217 NetworkError::DataChannelError(_) => "data_channel",
218 NetworkError::IceError(_) => "ice",
219 NetworkError::DtlsError(_) => "dtls",
220 NetworkError::StunTurnError(_) => "stun_turn",
221 NetworkError::WebSocketError(_) => "websocket",
222 NetworkError::ConnectionNotFound(_) => "connection_not_found",
223 NetworkError::ConnectionClosed(_) => "connection_closed",
224 NetworkError::NotImplemented(_) => "not_implemented",
225 NetworkError::ChannelClosed(_) => "channel_closed",
226 NetworkError::SendError(_) => "send_error",
227 NetworkError::NoRoute(_) => "no_route",
228 NetworkError::InvalidOperation(_) => "invalid_operation",
229 NetworkError::InvalidArgument(_) => "invalid_argument",
230 NetworkError::ChannelNotFound(_) => "channel_not_found",
231 NetworkError::IoError(_) => "io",
232 NetworkError::UrlParseError(_) => "url_parse",
233 NetworkError::JsonError(_) => "json",
234 NetworkError::BroadcastError(_) => "broadcast",
235 NetworkError::CredentialExpired(_) => "credential_expired",
236 NetworkError::Other(_) => "other",
237 }
238 }
239
240 pub fn severity(&self) -> u8 {
242 match self {
243 NetworkError::ConfigurationError(_)
244 | NetworkError::AuthenticationError(_)
245 | NetworkError::PermissionError(_)
246 | NetworkError::CredentialExpired(_) => 10,
247
248 NetworkError::WebRtcError(_)
249 | NetworkError::SignalingError(_)
250 | NetworkError::ProtocolError(_) => 8,
251
252 NetworkError::ConnectionError(_) | NetworkError::NetworkUnreachableError(_) => 7,
253
254 NetworkError::NatTraversalError(_)
255 | NetworkError::IceError(_)
256 | NetworkError::DtlsError(_) => 6,
257
258 NetworkError::TimeoutError(_) | NetworkError::ResourceExhaustedError(_) => 5,
259
260 NetworkError::ServiceDiscoveryError(_)
261 | NetworkError::DataChannelError(_)
262 | NetworkError::BroadcastError(_) => 4,
263
264 NetworkError::SerializationError(_) | NetworkError::DeserializationError(_) => 3,
265
266 NetworkError::WebSocketError(_) | NetworkError::StunTurnError(_) => 3,
267
268 NetworkError::ConnectionNotFound(_)
269 | NetworkError::ConnectionClosed(_)
270 | NetworkError::ChannelClosed(_)
271 | NetworkError::SendError(_)
272 | NetworkError::NoRoute(_)
273 | NetworkError::ChannelNotFound(_) => 4,
274
275 NetworkError::InvalidOperation(_) | NetworkError::InvalidArgument(_) => 6,
276
277 NetworkError::NotImplemented(_) => 8,
278
279 NetworkError::IoError(_)
280 | NetworkError::UrlParseError(_)
281 | NetworkError::JsonError(_) => 2,
282
283 NetworkError::Other(_) => 1,
284 }
285 }
286}
287
288pub type NetworkResult<T> = Result<T, NetworkError>;
293
294impl From<actr_protocol::ActrIdError> for NetworkError {
296 fn from(err: actr_protocol::ActrIdError) -> Self {
297 NetworkError::InvalidArgument(err.to_string())
298 }
299}
300
301impl From<NetworkError> for ActrError {
305 fn from(err: NetworkError) -> Self {
306 match err.kind() {
307 ErrorKind::Transient => ActrError::Unavailable(err.to_string()),
308 ErrorKind::Client => ActrError::NotFound(err.to_string()),
309 ErrorKind::Corrupt => ActrError::DecodeFailure(err.to_string()),
310 ErrorKind::Internal => ActrError::Internal(err.to_string()),
311 }
312 }
313}
314
315impl From<webrtc::Error> for NetworkError {
317 fn from(err: webrtc::Error) -> Self {
318 NetworkError::WebRtcError(err.to_string())
319 }
320}
321
322impl From<tokio_tungstenite::tungstenite::Error> for NetworkError {
324 fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
325 NetworkError::WebSocketError(err.to_string())
326 }
327}
328
329impl From<actr_protocol::prost::EncodeError> for NetworkError {
331 fn from(err: actr_protocol::prost::EncodeError) -> Self {
332 NetworkError::SerializationError(err.to_string())
333 }
334}
335
336impl From<actr_protocol::prost::DecodeError> for NetworkError {
338 fn from(err: actr_protocol::prost::DecodeError) -> Self {
339 NetworkError::DeserializationError(err.to_string())
340 }
341}
342
343#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
353 fn transient_network_errors() {
354 let cases = [
355 NetworkError::ConnectionError("x".into()),
356 NetworkError::ConnectionClosed("x".into()),
357 NetworkError::ChannelClosed("x".into()),
358 NetworkError::SendError("x".into()),
359 NetworkError::NetworkUnreachableError("x".into()),
360 NetworkError::ResourceExhaustedError("x".into()),
361 NetworkError::WebSocketError("x".into()),
362 NetworkError::SignalingError("x".into()),
363 NetworkError::WebRtcError("x".into()),
364 NetworkError::NatTraversalError("x".into()),
365 NetworkError::IceError("x".into()),
366 NetworkError::TimeoutError("x".into()),
367 ];
368 for e in &cases {
369 assert_eq!(e.kind(), ErrorKind::Transient, "{e} should be Transient");
370 assert!(e.is_retryable(), "{e} should be retryable");
371 }
372 }
373
374 #[test]
375 fn client_network_errors() {
376 let cases = [
377 NetworkError::ConnectionNotFound("x".into()),
378 NetworkError::ChannelNotFound("x".into()),
379 NetworkError::NoRoute("x".into()),
380 NetworkError::InvalidArgument("x".into()),
381 NetworkError::InvalidOperation("x".into()),
382 NetworkError::ConfigurationError("x".into()),
383 NetworkError::ServiceDiscoveryError("x".into()),
384 NetworkError::AuthenticationError("x".into()),
385 NetworkError::PermissionError("x".into()),
386 NetworkError::CredentialExpired("x".into()),
387 ];
388 for e in &cases {
389 assert_eq!(e.kind(), ErrorKind::Client, "{e} should be Client");
390 assert!(!e.is_retryable(), "{e} should not be retryable");
391 }
392 }
393
394 #[test]
395 fn corrupt_network_error() {
396 let e = NetworkError::DeserializationError("bad bytes".into());
397 assert_eq!(e.kind(), ErrorKind::Corrupt);
398 assert!(e.requires_dlq());
399 assert!(!e.is_retryable());
400 }
401
402 #[test]
403 fn internal_network_errors() {
404 let cases = [
405 NetworkError::ProtocolError("x".into()),
406 NetworkError::SerializationError("x".into()),
407 NetworkError::DataChannelError("x".into()),
408 NetworkError::BroadcastError("x".into()),
409 NetworkError::DtlsError("x".into()),
410 NetworkError::StunTurnError("x".into()),
411 NetworkError::NotImplemented("x".into()),
412 ];
413 for e in &cases {
414 assert_eq!(e.kind(), ErrorKind::Internal, "{e} should be Internal");
415 assert!(!e.is_retryable());
416 assert!(!e.requires_dlq());
417 }
418 }
419
420 #[test]
423 fn transient_network_error_becomes_unavailable() {
424 let e: ActrError = NetworkError::ConnectionError("lost".into()).into();
425 assert!(matches!(e, ActrError::Unavailable(_)));
426 assert!(e.is_retryable());
427 }
428
429 #[test]
430 fn client_network_error_becomes_not_found() {
431 let e: ActrError = NetworkError::NoRoute("dst".into()).into();
432 assert!(matches!(e, ActrError::NotFound(_)));
433 assert!(!e.is_retryable());
434 }
435
436 #[test]
437 fn corrupt_network_error_becomes_decode_failure() {
438 let e: ActrError = NetworkError::DeserializationError("garbled".into()).into();
439 assert!(matches!(e, ActrError::DecodeFailure(_)));
440 assert!(e.requires_dlq());
441 }
442
443 #[test]
444 fn internal_network_error_becomes_internal() {
445 let e: ActrError = NetworkError::ProtocolError("bug".into()).into();
446 assert!(matches!(e, ActrError::Internal(_)));
447 assert!(!e.is_retryable());
448 assert!(!e.requires_dlq());
449 }
450}