Skip to main content

actr_hyper/transport/
error.rs

1//! Network layer error definitions
2
3use actr_protocol::{ActrError, Classify, ErrorKind};
4use thiserror::Error;
5
6/// Network layer error types
7#[derive(Error, Debug)]
8pub enum NetworkError {
9    /// Connection error
10    #[error("Connection error: {0}")]
11    ConnectionError(String),
12
13    /// Signaling error
14    #[error("Signaling error: {0}")]
15    SignalingError(String),
16
17    /// WebRTC error
18    #[error("WebRTC error: {0}")]
19    WebRtcError(String),
20
21    /// Protocol error
22    #[error("Protocol error: {0}")]
23    ProtocolError(String),
24
25    /// Serialization error
26    #[error("Serialization error: {0}")]
27    SerializationError(String),
28
29    /// Deserialization error
30    #[error("Deserialization error: {0}")]
31    DeserializationError(String),
32
33    /// Timeout error
34    #[error("Timeout error: {0}")]
35    TimeoutError(String),
36
37    /// Authentication error
38    #[error("Authentication error: {0}")]
39    AuthenticationError(String),
40
41    /// Credential expired error (requires re-registration)
42    #[error("Credential expired: {0}")]
43    CredentialExpired(String),
44
45    /// Permission error
46    #[error("Permission error: {0}")]
47    PermissionError(String),
48
49    /// Configuration error
50    #[error("Configuration error: {0}")]
51    ConfigurationError(String),
52
53    /// Resource exhausted error
54    #[error("Resource exhausted: {0}")]
55    ResourceExhaustedError(String),
56
57    /// Network unreachable error
58    #[error("Network unreachable: {0}")]
59    NetworkUnreachableError(String),
60
61    /// Service discovery error
62    #[error("Service discovery error: {0}")]
63    ServiceDiscoveryError(String),
64
65    /// NAT traversal error
66    #[error("NAT traversal error: {0}")]
67    NatTraversalError(String),
68
69    /// Data channel error
70    #[error("Data channel error: {0}")]
71    DataChannelError(String),
72
73    /// Broadcast error
74    #[error("Broadcast error: {0}")]
75    BroadcastError(String),
76
77    /// ICE error
78    #[error("ICE error: {0}")]
79    IceError(String),
80
81    /// DTLS error
82    #[error("DTLS error: {0}")]
83    DtlsError(String),
84
85    /// STUN/TURN error
86    #[error("STUN/TURN error: {0}")]
87    StunTurnError(String),
88
89    /// WebSocket error
90    #[error("WebSocket error: {0}")]
91    WebSocketError(String),
92
93    /// Connection not found error
94    #[error("Connection not found: {0}")]
95    ConnectionNotFound(String),
96
97    /// Connection closed error (e.g., cancelled during creation)
98    #[error("Connection closed: {0}")]
99    ConnectionClosed(String),
100
101    /// Feature not implemented error
102    #[error("Not implemented: {0}")]
103    NotImplemented(String),
104
105    /// Channel closed error
106    #[error("Channel closed: {0}")]
107    ChannelClosed(String),
108
109    /// Send error
110    #[error("Send error: {0}")]
111    SendError(String),
112
113    /// No route error
114    #[error("No route: {0}")]
115    NoRoute(String),
116
117    /// Invalid operation error
118    #[error("Invalid operation: {0}")]
119    InvalidOperation(String),
120
121    /// Invalid argument error
122    #[error("Invalid argument: {0}")]
123    InvalidArgument(String),
124
125    /// Channel not found error
126    #[error("Channel not found: {0}")]
127    ChannelNotFound(String),
128
129    /// IO error
130    #[error("IO error: {0}")]
131    IoError(#[from] std::io::Error),
132
133    /// URL parse error
134    #[error("URL parse error: {0}")]
135    UrlParseError(#[from] url::ParseError),
136
137    /// JSON error
138    #[error("JSON error: {0}")]
139    JsonError(#[from] serde_json::Error),
140
141    /// Other error
142    #[error("Other error: {0}")]
143    Other(#[from] anyhow::Error),
144}
145
146impl Classify for NetworkError {
147    fn kind(&self) -> ErrorKind {
148        match self {
149            // Transient: connection-level failures that may resolve on retry
150            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            // Transient: timeout (framework-internal; caller-set deadlines should be Client)
163            NetworkError::TimeoutError(_) => ErrorKind::Transient,
164
165            // Client: caller or config errors that won't fix themselves
166            NetworkError::ConnectionNotFound(_)
167            | NetworkError::ChannelNotFound(_)
168            | NetworkError::NoRoute(_)
169            | NetworkError::InvalidArgument(_)
170            | NetworkError::InvalidOperation(_)
171            | NetworkError::ConfigurationError(_)
172            | NetworkError::ServiceDiscoveryError(_) => ErrorKind::Client,
173
174            // Client: auth/permission
175            NetworkError::AuthenticationError(_)
176            | NetworkError::PermissionError(_)
177            | NetworkError::CredentialExpired(_) => ErrorKind::Client,
178
179            // Corrupt: data cannot be decoded
180            NetworkError::DeserializationError(_) => ErrorKind::Corrupt,
181
182            // Internal: framework-level issues
183            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    /// Get error category
200    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    /// Get error severity (1-10, 10 is most severe)
241    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
288// TODO: Implement UnifiedError trait (when actr-protocol provides error_unified module)
289// impl UnifiedError for NetworkError { ... }
290
291/// Network layer result type
292pub type NetworkResult<T> = Result<T, NetworkError>;
293
294/// Convert from `ActrIdError` (identity parsing) to `NetworkError`
295impl From<actr_protocol::ActrIdError> for NetworkError {
296    fn from(err: actr_protocol::ActrIdError) -> Self {
297        NetworkError::InvalidArgument(err.to_string())
298    }
299}
300
301/// Convert `NetworkError` to the public top-level `ActrError`.
302///
303/// This is the single boundary where transport failures become user-visible errors.
304impl 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
315/// Convert from WebRTC error
316impl From<webrtc::Error> for NetworkError {
317    fn from(err: webrtc::Error) -> Self {
318        NetworkError::WebRtcError(err.to_string())
319    }
320}
321
322/// Convert from WebSocket error
323impl 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
329/// Convert from protobuf encode error
330impl 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
336/// Convert from protobuf decode error
337impl 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// TODO: In future, if error statistics needed, can add ErrorStats struct
344// Recommend using arrays instead of HashMap (error categories and severities are fixed)
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    // ── NetworkError::kind() classification ──────────────────────────────────
351
352    #[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    // ── From<NetworkError> for ActrError (single boundary conversion) ─────────
421
422    #[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}