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 {
310 NetworkError::TimeoutError(_) => return ActrError::TimedOut,
311 NetworkError::PermissionError(msg)
312 | NetworkError::AuthenticationError(msg)
313 | NetworkError::CredentialExpired(msg) => {
314 return ActrError::PermissionDenied(msg.clone());
315 }
316 NetworkError::NoRoute(msg)
317 | NetworkError::ConnectionNotFound(msg)
318 | NetworkError::ChannelNotFound(msg)
319 | NetworkError::ServiceDiscoveryError(msg) => {
320 return ActrError::NotFound(msg.clone());
321 }
322 _ => {}
323 }
324 match err.kind() {
325 ErrorKind::Transient => ActrError::Unavailable(err.to_string()),
326 ErrorKind::Client => ActrError::NotFound(err.to_string()),
327 ErrorKind::Corrupt => ActrError::DecodeFailure(err.to_string()),
328 ErrorKind::Internal => ActrError::Internal(err.to_string()),
329 }
330 }
331}
332
333impl From<webrtc::Error> for NetworkError {
335 fn from(err: webrtc::Error) -> Self {
336 NetworkError::WebRtcError(err.to_string())
337 }
338}
339
340impl From<tokio_tungstenite::tungstenite::Error> for NetworkError {
342 fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
343 NetworkError::WebSocketError(err.to_string())
344 }
345}
346
347impl From<actr_protocol::prost::EncodeError> for NetworkError {
349 fn from(err: actr_protocol::prost::EncodeError) -> Self {
350 NetworkError::SerializationError(err.to_string())
351 }
352}
353
354impl From<actr_protocol::prost::DecodeError> for NetworkError {
356 fn from(err: actr_protocol::prost::DecodeError) -> Self {
357 NetworkError::DeserializationError(err.to_string())
358 }
359}
360
361#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
371 fn transient_network_errors() {
372 let cases = [
373 NetworkError::ConnectionError("x".into()),
374 NetworkError::ConnectionClosed("x".into()),
375 NetworkError::ChannelClosed("x".into()),
376 NetworkError::SendError("x".into()),
377 NetworkError::NetworkUnreachableError("x".into()),
378 NetworkError::ResourceExhaustedError("x".into()),
379 NetworkError::WebSocketError("x".into()),
380 NetworkError::SignalingError("x".into()),
381 NetworkError::WebRtcError("x".into()),
382 NetworkError::NatTraversalError("x".into()),
383 NetworkError::IceError("x".into()),
384 NetworkError::TimeoutError("x".into()),
385 ];
386 for e in &cases {
387 assert_eq!(e.kind(), ErrorKind::Transient, "{e} should be Transient");
388 assert!(e.is_retryable(), "{e} should be retryable");
389 }
390 }
391
392 #[test]
393 fn client_network_errors() {
394 let cases = [
395 NetworkError::ConnectionNotFound("x".into()),
396 NetworkError::ChannelNotFound("x".into()),
397 NetworkError::NoRoute("x".into()),
398 NetworkError::InvalidArgument("x".into()),
399 NetworkError::InvalidOperation("x".into()),
400 NetworkError::ConfigurationError("x".into()),
401 NetworkError::ServiceDiscoveryError("x".into()),
402 NetworkError::AuthenticationError("x".into()),
403 NetworkError::PermissionError("x".into()),
404 NetworkError::CredentialExpired("x".into()),
405 ];
406 for e in &cases {
407 assert_eq!(e.kind(), ErrorKind::Client, "{e} should be Client");
408 assert!(!e.is_retryable(), "{e} should not be retryable");
409 }
410 }
411
412 #[test]
413 fn corrupt_network_error() {
414 let e = NetworkError::DeserializationError("bad bytes".into());
415 assert_eq!(e.kind(), ErrorKind::Corrupt);
416 assert!(e.requires_dlq());
417 assert!(!e.is_retryable());
418 }
419
420 #[test]
421 fn internal_network_errors() {
422 let cases = [
423 NetworkError::ProtocolError("x".into()),
424 NetworkError::SerializationError("x".into()),
425 NetworkError::DataChannelError("x".into()),
426 NetworkError::BroadcastError("x".into()),
427 NetworkError::DtlsError("x".into()),
428 NetworkError::StunTurnError("x".into()),
429 NetworkError::NotImplemented("x".into()),
430 ];
431 for e in &cases {
432 assert_eq!(e.kind(), ErrorKind::Internal, "{e} should be Internal");
433 assert!(!e.is_retryable());
434 assert!(!e.requires_dlq());
435 }
436 }
437
438 #[test]
441 fn transient_network_error_becomes_unavailable() {
442 let e: ActrError = NetworkError::ConnectionError("lost".into()).into();
443 assert!(matches!(e, ActrError::Unavailable(_)));
444 assert!(e.is_retryable());
445 }
446
447 #[test]
448 fn client_network_error_becomes_not_found() {
449 let e: ActrError = NetworkError::NoRoute("dst".into()).into();
450 assert!(matches!(e, ActrError::NotFound(_)));
451 assert!(!e.is_retryable());
452 }
453
454 #[test]
455 fn corrupt_network_error_becomes_decode_failure() {
456 let e: ActrError = NetworkError::DeserializationError("garbled".into()).into();
457 assert!(matches!(e, ActrError::DecodeFailure(_)));
458 assert!(e.requires_dlq());
459 }
460
461 #[test]
462 fn internal_network_error_becomes_internal() {
463 let e: ActrError = NetworkError::ProtocolError("bug".into()).into();
464 assert!(matches!(e, ActrError::Internal(_)));
465 assert!(!e.is_retryable());
466 assert!(!e.requires_dlq());
467 }
468}