syspulse-core 0.1.2

Core library for syspulse daemon manager
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LifecycleState {
    Stopped,
    Starting,
    Running,
    Stopping,
    Failed,
    Scheduled,
}

impl LifecycleState {
    pub fn can_transition_to(&self, target: LifecycleState) -> bool {
        use LifecycleState::*;
        matches!(
            (self, target),
            (Stopped, Starting)
                | (Stopped, Scheduled)
                | (Starting, Running)
                | (Starting, Failed)
                | (Starting, Stopping)
                | (Running, Stopping)
                | (Running, Failed)
                | (Stopping, Stopped)
                | (Stopping, Failed)
                | (Failed, Starting)
                | (Failed, Stopped)
                | (Scheduled, Starting)
                | (Scheduled, Stopped)
        )
    }

    pub fn transition_to(&self, target: LifecycleState) -> crate::error::Result<LifecycleState> {
        if self.can_transition_to(target) {
            Ok(target)
        } else {
            Err(crate::error::SyspulseError::InvalidStateTransition {
                from: format!("{:?}", self),
                to: format!("{:?}", target),
            })
        }
    }

    pub fn is_active(&self) -> bool {
        matches!(self, Self::Starting | Self::Running | Self::Stopping)
    }
}

impl std::fmt::Display for LifecycleState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Stopped => write!(f, "stopped"),
            Self::Starting => write!(f, "starting"),
            Self::Running => write!(f, "running"),
            Self::Stopping => write!(f, "stopping"),
            Self::Failed => write!(f, "failed"),
            Self::Scheduled => write!(f, "scheduled"),
        }
    }
}

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

    #[test]
    fn valid_transitions() {
        let valid = vec![
            (Stopped, Starting),
            (Stopped, Scheduled),
            (Starting, Running),
            (Starting, Failed),
            (Starting, Stopping),
            (Running, Stopping),
            (Running, Failed),
            (Stopping, Stopped),
            (Stopping, Failed),
            (Failed, Starting),
            (Failed, Stopped),
            (Scheduled, Starting),
            (Scheduled, Stopped),
        ];

        for (from, to) in valid {
            assert!(
                from.can_transition_to(to),
                "{:?} -> {:?} should be valid",
                from,
                to
            );
            assert!(
                from.transition_to(to).is_ok(),
                "{:?} -> {:?} transition_to should succeed",
                from,
                to
            );
        }
    }

    #[test]
    fn invalid_transitions() {
        let invalid = vec![
            (Stopped, Running),
            (Stopped, Stopping),
            (Stopped, Failed),
            (Starting, Stopped),
            (Starting, Scheduled),
            (Running, Starting),
            (Running, Stopped),
            (Running, Scheduled),
            (Stopping, Starting),
            (Stopping, Running),
            (Stopping, Scheduled),
            (Failed, Running),
            (Failed, Stopping),
            (Failed, Scheduled),
            (Scheduled, Running),
            (Scheduled, Stopping),
            (Scheduled, Failed),
        ];

        for (from, to) in invalid {
            assert!(
                !from.can_transition_to(to),
                "{:?} -> {:?} should be invalid",
                from,
                to
            );
            assert!(
                from.transition_to(to).is_err(),
                "{:?} -> {:?} transition_to should fail",
                from,
                to
            );
        }
    }

    #[test]
    fn self_transitions_are_invalid() {
        let all_states = vec![Stopped, Starting, Running, Stopping, Failed, Scheduled];
        for state in all_states {
            assert!(
                !state.can_transition_to(state),
                "{:?} -> {:?} self-transition should be invalid",
                state,
                state
            );
        }
    }

    #[test]
    fn is_active() {
        assert!(!Stopped.is_active());
        assert!(Starting.is_active());
        assert!(Running.is_active());
        assert!(Stopping.is_active());
        assert!(!Failed.is_active());
        assert!(!Scheduled.is_active());
    }

    #[test]
    fn display() {
        assert_eq!(Stopped.to_string(), "stopped");
        assert_eq!(Starting.to_string(), "starting");
        assert_eq!(Running.to_string(), "running");
        assert_eq!(Stopping.to_string(), "stopping");
        assert_eq!(Failed.to_string(), "failed");
        assert_eq!(Scheduled.to_string(), "scheduled");
    }
}