use std::io;
use crate::sdk::{ConfigError, ServiceState};
#[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),
}
#[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,
},
}
#[derive(Debug, Clone)]
pub enum BlockedReason {
WaitingOn(Vec<String>),
ConflictsWith(Vec<String>),
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 {
pub fn waiting_on(&self) -> Vec<String> {
match self {
BlockedReason::WaitingOn(v) => v.clone(),
BlockedReason::Both { waiting_on, .. } => waiting_on.clone(),
BlockedReason::ConflictsWith(_) => vec![],
}
}
pub fn conflicts_with(&self) -> Vec<String> {
match self {
BlockedReason::ConflictsWith(v) => v.clone(),
BlockedReason::Both { conflicts_with, .. } => conflicts_with.clone(),
BlockedReason::WaitingOn(_) => vec![],
}
}
}
pub type SupervisorResult<T> = Result<T, SupervisorError>;
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"]);
}
}