zinit 0.3.6

Process supervisor with dependency management
Documentation
//! Error types for the zinit supervisor.

use std::io;

use crate::sdk::{ConfigError, ServiceState};

/// Errors that can occur in the supervisor.
#[derive(Debug, thiserror::Error)]
pub enum SupervisorError {
    #[error("service not found: {0}")]
    ServiceNotFound(String),

    #[error("service already running: {0}")]
    ServiceAlreadyRunning(String),

    #[error("service not running: {0}")]
    ServiceNotRunning(String),

    #[error("invalid state for service '{service}': expected {expected}, got {actual}")]
    InvalidState {
        service: String,
        expected: String,
        actual: ServiceState,
    },

    #[error("cannot start service '{service}': {reason}")]
    CannotStart { service: String, reason: String },

    #[error("cyclic dependency detected: {}", .0.join(" -> "))]
    CyclicDependency(Vec<String>),

    #[error("config error: {0}")]
    Config(#[from] ConfigError),

    #[error("spawn error for service '{service}': {message}")]
    SpawnError { service: String, message: String },

    #[error("signal error for service '{service}' (signal {signal}): {message}")]
    SignalError {
        service: String,
        signal: String,
        message: String,
    },

    #[error("validation error: {0}")]
    Validation(String),

    #[error("graph error: {0}")]
    Graph(#[from] GraphError),

    #[error("IO error: {0}")]
    Io(#[from] io::Error),
}

/// Errors specific to graph operations.
#[derive(Debug, thiserror::Error)]
pub enum GraphError {
    #[error("service not found: {0}")]
    ServiceNotFound(String),

    #[error("service already exists: {0}")]
    ServiceAlreadyExists(String),

    #[error("dependency not found: {0}")]
    DependencyNotFound(String),

    #[error("cyclic dependency detected: {}", .0.join(" -> "))]
    CyclicDependency(Vec<String>),

    #[error("cannot remove service '{service}': still has dependents: {}", dependents.join(", "))]
    HasDependents {
        service: String,
        dependents: Vec<String>,
    },

    #[error("conflict: service '{service}' conflicts with running service '{conflicts_with}'")]
    Conflict {
        service: String,
        conflicts_with: String,
    },
}

/// Reason why a service cannot start.
#[derive(Debug, Clone)]
pub enum BlockedReason {
    /// Waiting on these services to be running.
    WaitingOn(Vec<String>),
    /// Conflicts with these running services.
    ConflictsWith(Vec<String>),
    /// Both waiting and conflicting.
    Both {
        waiting_on: Vec<String>,
        conflicts_with: Vec<String>,
    },
}

impl std::fmt::Display for BlockedReason {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BlockedReason::WaitingOn(services) => {
                write!(f, "waiting on: {}", services.join(", "))
            }
            BlockedReason::ConflictsWith(services) => {
                write!(f, "conflicts with: {}", services.join(", "))
            }
            BlockedReason::Both {
                waiting_on,
                conflicts_with,
            } => {
                write!(
                    f,
                    "waiting on: {}; conflicts with: {}",
                    waiting_on.join(", "),
                    conflicts_with.join(", ")
                )
            }
        }
    }
}

impl BlockedReason {
    /// Get the list of services this is waiting on.
    pub fn waiting_on(&self) -> Vec<String> {
        match self {
            BlockedReason::WaitingOn(v) => v.clone(),
            BlockedReason::Both { waiting_on, .. } => waiting_on.clone(),
            BlockedReason::ConflictsWith(_) => vec![],
        }
    }

    /// Get the list of services this conflicts with.
    pub fn conflicts_with(&self) -> Vec<String> {
        match self {
            BlockedReason::ConflictsWith(v) => v.clone(),
            BlockedReason::Both { conflicts_with, .. } => conflicts_with.clone(),
            BlockedReason::WaitingOn(_) => vec![],
        }
    }
}

/// Result type for supervisor operations.
pub type SupervisorResult<T> = Result<T, SupervisorError>;

/// Result type for graph operations.
pub type GraphResult<T> = Result<T, GraphError>;

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

    #[test]
    fn test_supervisor_error_display() {
        let err = SupervisorError::ServiceNotFound("test".to_string());
        assert_eq!(err.to_string(), "service not found: test");

        let err = SupervisorError::CyclicDependency(vec!["a".to_string(), "b".to_string()]);
        assert_eq!(err.to_string(), "cyclic dependency detected: a -> b");
    }

    #[test]
    fn test_blocked_reason_display() {
        let reason = BlockedReason::WaitingOn(vec!["a".to_string(), "b".to_string()]);
        assert_eq!(reason.to_string(), "waiting on: a, b");

        let reason = BlockedReason::ConflictsWith(vec!["c".to_string()]);
        assert_eq!(reason.to_string(), "conflicts with: c");
    }

    #[test]
    fn test_blocked_reason_accessors() {
        let reason = BlockedReason::Both {
            waiting_on: vec!["a".to_string()],
            conflicts_with: vec!["b".to_string()],
        };
        assert_eq!(reason.waiting_on(), vec!["a"]);
        assert_eq!(reason.conflicts_with(), vec!["b"]);
    }
}