nizaam-core 1.0.1

Domain-agnostic Rust infrastructure core for building secure, observable, reliable, and extensible Nizaam engines.
Documentation
use crate::error::InvalidTransition;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LifecycleState {
    Created,
    Starting,
    Configuring,
    Dependencies,
    Capabilities,
    Registering,
    Ready,
    Serving,
    Draining,
    Stopped,
}

impl LifecycleState {
    pub fn can_transition_to(self, next: Self) -> bool {
        matches!(
            (self, next),
            (Self::Created, Self::Starting)
                | (Self::Starting, Self::Configuring)
                | (Self::Configuring, Self::Dependencies)
                | (Self::Dependencies, Self::Capabilities)
                | (Self::Capabilities, Self::Registering)
                | (Self::Registering, Self::Ready)
                | (Self::Ready, Self::Serving)
                | (Self::Serving, Self::Draining)
                | (Self::Draining, Self::Stopped)
        )
    }
}

#[derive(Debug)]
pub struct Lifecycle {
    state: LifecycleState,
}

impl Default for Lifecycle {
    fn default() -> Self {
        Self::new()
    }
}

impl Lifecycle {
    pub const fn new() -> Self {
        Self {
            state: LifecycleState::Created,
        }
    }

    pub const fn state(&self) -> LifecycleState {
        self.state
    }

    pub fn transition(&mut self, next: LifecycleState) -> Result<(), InvalidTransition> {
        if !self.state.can_transition_to(next) {
            return Err(InvalidTransition::new(
                format!("{:?}", self.state),
                format!("{:?}", next),
            ));
        }

        self.state = next;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::{Lifecycle, LifecycleState};

    #[test]
    fn lifecycle_accepts_full_ordered_start_and_shutdown() {
        let mut lifecycle = Lifecycle::new();

        lifecycle.transition(LifecycleState::Starting).unwrap();
        lifecycle.transition(LifecycleState::Configuring).unwrap();
        lifecycle.transition(LifecycleState::Dependencies).unwrap();
        lifecycle.transition(LifecycleState::Capabilities).unwrap();
        lifecycle.transition(LifecycleState::Registering).unwrap();
        lifecycle.transition(LifecycleState::Ready).unwrap();
        lifecycle.transition(LifecycleState::Serving).unwrap();
        lifecycle.transition(LifecycleState::Draining).unwrap();
        lifecycle.transition(LifecycleState::Stopped).unwrap();

        assert_eq!(lifecycle.state(), LifecycleState::Stopped);
    }

    #[test]
    fn lifecycle_rejects_invalid_transition() {
        let mut lifecycle = Lifecycle::new();

        assert!(lifecycle.transition(LifecycleState::Ready).is_err());
        assert_eq!(lifecycle.state(), LifecycleState::Created);
    }

    #[test]
    fn lifecycle_rejects_skip_configuring() {
        let mut lifecycle = Lifecycle::new();

        lifecycle.transition(LifecycleState::Starting).unwrap();

        assert!(lifecycle.transition(LifecycleState::Dependencies).is_err());

        assert_eq!(lifecycle.state(), LifecycleState::Starting);
    }

    #[test]
    fn lifecycle_rejects_draining_to_serving() {
        let mut lifecycle = Lifecycle::new();

        lifecycle.transition(LifecycleState::Starting).unwrap();
        lifecycle.transition(LifecycleState::Configuring).unwrap();
        lifecycle.transition(LifecycleState::Dependencies).unwrap();
        lifecycle.transition(LifecycleState::Capabilities).unwrap();
        lifecycle.transition(LifecycleState::Registering).unwrap();
        lifecycle.transition(LifecycleState::Ready).unwrap();
        lifecycle.transition(LifecycleState::Serving).unwrap();
        lifecycle.transition(LifecycleState::Draining).unwrap();

        assert!(lifecycle.transition(LifecycleState::Serving).is_err());
        assert_eq!(lifecycle.state(), LifecycleState::Draining);
    }

    #[test]
    fn lifecycle_rejects_stopped_transitions() {
        let mut lifecycle = Lifecycle::new();

        lifecycle.transition(LifecycleState::Starting).unwrap();
        lifecycle.transition(LifecycleState::Configuring).unwrap();
        lifecycle.transition(LifecycleState::Dependencies).unwrap();
        lifecycle.transition(LifecycleState::Capabilities).unwrap();
        lifecycle.transition(LifecycleState::Registering).unwrap();
        lifecycle.transition(LifecycleState::Ready).unwrap();
        lifecycle.transition(LifecycleState::Serving).unwrap();
        lifecycle.transition(LifecycleState::Draining).unwrap();
        lifecycle.transition(LifecycleState::Stopped).unwrap();

        assert!(lifecycle.transition(LifecycleState::Serving).is_err());
        assert!(lifecycle.transition(LifecycleState::Draining).is_err());
        assert!(lifecycle.transition(LifecycleState::Stopped).is_err());
        assert_eq!(lifecycle.state(), LifecycleState::Stopped);
    }

    #[test]
    fn lifecycle_rejects_direct_created_to_stopped() {
        let mut lifecycle = Lifecycle::new();

        assert!(lifecycle.transition(LifecycleState::Stopped).is_err());
        assert_eq!(lifecycle.state(), LifecycleState::Created);
    }

    #[test]
    fn lifecycle_rejects_direct_starting_to_stopped() {
        let mut lifecycle = Lifecycle::new();

        lifecycle.transition(LifecycleState::Starting).unwrap();

        assert!(lifecycle.transition(LifecycleState::Stopped).is_err());
        assert_eq!(lifecycle.state(), LifecycleState::Starting);
    }

    #[test]
    fn lifecycle_rejects_direct_ready_to_stopped() {
        let mut lifecycle = Lifecycle::new();

        lifecycle.transition(LifecycleState::Starting).unwrap();
        lifecycle.transition(LifecycleState::Configuring).unwrap();
        lifecycle.transition(LifecycleState::Dependencies).unwrap();
        lifecycle.transition(LifecycleState::Capabilities).unwrap();
        lifecycle.transition(LifecycleState::Registering).unwrap();
        lifecycle.transition(LifecycleState::Ready).unwrap();

        assert!(lifecycle.transition(LifecycleState::Stopped).is_err());
        assert_eq!(lifecycle.state(), LifecycleState::Ready);
    }
}