motorcortex-rust 0.5.0

Motorcortex Rust: a Rust client for the Motorcortex Core real-time control system (async + blocking).
Documentation
//! Connection state published by the driver to consumers via
//! `tokio::sync::watch`. See ARCHITECTURE.md §"ConnectionState".

/// Observable connection state of a `Request` or `Subscribe` handle.
///
/// Transitions are session-layer only — the NNG dialer's own redial
/// loop runs underneath and doesn't generate a separate observable
/// state. The caller sees `Connected → ConnectionLost → Connected`
/// (or `→ SessionExpired`) and nothing in between.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConnectionState {
    /// Initial state + after a successful `disconnect()`.
    Disconnected,
    /// Socket is up, session is restored (if one was ever established).
    Connected,
    /// NNG pipe event `REM_POST` fired. The dialer is redialling the
    /// transport underneath; we'll transition back to `Connected` (or
    /// to `SessionExpired`) once the pipe comes back.
    ConnectionLost,
    /// The socket came back, but `RestoreSession(token)` was rejected.
    /// Caller action required — re-login with fresh credentials, or
    /// give up. The library will not silently re-login with cached
    /// credentials.
    SessionExpired,
}

impl ConnectionState {
    /// Convenience: is the socket currently usable for RPCs?
    pub fn is_connected(self) -> bool {
        matches!(self, Self::Connected)
    }

    /// Convenience: is this a terminal state that needs caller action?
    pub fn is_terminal(self) -> bool {
        matches!(self, Self::Disconnected | Self::SessionExpired)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn is_connected_only_true_for_connected() {
        assert!(ConnectionState::Connected.is_connected());
        assert!(!ConnectionState::Disconnected.is_connected());
        assert!(!ConnectionState::ConnectionLost.is_connected());
        assert!(!ConnectionState::SessionExpired.is_connected());
    }

    #[test]
    fn is_terminal_covers_disconnected_and_expired() {
        assert!(ConnectionState::Disconnected.is_terminal());
        assert!(ConnectionState::SessionExpired.is_terminal());
        assert!(!ConnectionState::Connected.is_terminal());
        assert!(!ConnectionState::ConnectionLost.is_terminal());
    }

    #[test]
    fn derives_equality_and_copy() {
        let a = ConnectionState::Connected;
        let b = a; // Copy
        assert_eq!(a, b);
        assert_ne!(a, ConnectionState::Disconnected);
    }

    #[test]
    fn debug_is_non_empty_for_every_variant() {
        for v in [
            ConnectionState::Disconnected,
            ConnectionState::Connected,
            ConnectionState::ConnectionLost,
            ConnectionState::SessionExpired,
        ] {
            assert!(!format!("{v:?}").is_empty());
        }
    }
}