Skip to main content

motorcortex_rust/core/
state.rs

1//! Connection state published by the driver to consumers via
2//! `tokio::sync::watch`. See ARCHITECTURE.md §"ConnectionState".
3
4/// Observable connection state of a `Request` or `Subscribe` handle.
5///
6/// Transitions are session-layer only — the NNG dialer's own redial
7/// loop runs underneath and doesn't generate a separate observable
8/// state. The caller sees `Connected → ConnectionLost → Connected`
9/// (or `→ SessionExpired`) and nothing in between.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum ConnectionState {
12    /// Initial state + after a successful `disconnect()`.
13    Disconnected,
14    /// Socket is up, session is restored (if one was ever established).
15    Connected,
16    /// NNG pipe event `REM_POST` fired. The dialer is redialling the
17    /// transport underneath; we'll transition back to `Connected` (or
18    /// to `SessionExpired`) once the pipe comes back.
19    ConnectionLost,
20    /// The socket came back, but `RestoreSession(token)` was rejected.
21    /// Caller action required — re-login with fresh credentials, or
22    /// give up. The library will not silently re-login with cached
23    /// credentials.
24    SessionExpired,
25}
26
27impl ConnectionState {
28    /// Convenience: is the socket currently usable for RPCs?
29    pub fn is_connected(self) -> bool {
30        matches!(self, Self::Connected)
31    }
32
33    /// Convenience: is this a terminal state that needs caller action?
34    pub fn is_terminal(self) -> bool {
35        matches!(self, Self::Disconnected | Self::SessionExpired)
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn is_connected_only_true_for_connected() {
45        assert!(ConnectionState::Connected.is_connected());
46        assert!(!ConnectionState::Disconnected.is_connected());
47        assert!(!ConnectionState::ConnectionLost.is_connected());
48        assert!(!ConnectionState::SessionExpired.is_connected());
49    }
50
51    #[test]
52    fn is_terminal_covers_disconnected_and_expired() {
53        assert!(ConnectionState::Disconnected.is_terminal());
54        assert!(ConnectionState::SessionExpired.is_terminal());
55        assert!(!ConnectionState::Connected.is_terminal());
56        assert!(!ConnectionState::ConnectionLost.is_terminal());
57    }
58
59    #[test]
60    fn derives_equality_and_copy() {
61        let a = ConnectionState::Connected;
62        let b = a; // Copy
63        assert_eq!(a, b);
64        assert_ne!(a, ConnectionState::Disconnected);
65    }
66
67    #[test]
68    fn debug_is_non_empty_for_every_variant() {
69        for v in [
70            ConnectionState::Disconnected,
71            ConnectionState::Connected,
72            ConnectionState::ConnectionLost,
73            ConnectionState::SessionExpired,
74        ] {
75            assert!(!format!("{v:?}").is_empty());
76        }
77    }
78}