use thiserror::Error;
pub trait SupervisorResultExt<T> {
fn supervisor_error(self) -> Result<T, SupervisorError>;
fn supervisor_context<C>(self, context: C) -> Result<T, SupervisorError>
where
C: std::fmt::Display + Send + Sync + 'static;
}
impl<T, E> SupervisorResultExt<T> for Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn supervisor_error(self) -> Result<T, SupervisorError> {
self.map_err(|e| SupervisorError::RuntimeFailure {
operation: "unknown".to_string(),
source: anyhow::Error::from(e),
})
}
fn supervisor_context<C>(self, context: C) -> Result<T, SupervisorError>
where
C: std::fmt::Display + Send + Sync + 'static,
{
self.map_err(|e| SupervisorError::RuntimeFailure {
operation: context.to_string(),
source: anyhow::Error::from(e),
})
}
}
pub trait SupervisorErrorExt {
fn is_validation_error(&self, validation_type: &ValidationError) -> bool;
fn source_error(&self) -> Option<&(dyn std::error::Error + 'static)>;
}
impl SupervisorErrorExt for SupervisorError {
fn is_validation_error(&self, validation_type: &ValidationError) -> bool {
match self {
SupervisorError::DependencyValidation { reason, .. } => reason == validation_type,
_ => false,
}
}
fn source_error(&self) -> Option<&(dyn std::error::Error + 'static)> {
std::error::Error::source(self)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ErrorKind {
Configuration,
Runtime,
Execution,
System,
}
#[derive(Debug, Clone, PartialEq, Error)]
pub enum ValidationError {
#[error("unknown task ID")]
UnknownTaskId,
#[error("circular dependency")]
CircularDependency,
#[error("invalid configuration")]
InvalidConfiguration,
}
#[derive(Error, Debug)]
pub enum SupervisorError {
#[error("Task '{task_id}' has invalid dependency '{dependency_id}': {reason}")]
DependencyValidation {
task_id: String,
dependency_id: String,
reason: ValidationError,
},
#[error("Circular dependency detected: '{task_id}' -> '{dependency_id}'")]
CircularDependency {
task_id: String,
dependency_id: String,
},
#[error("Prerequisite '{name}' failed to complete")]
PrerequisiteFailed {
name: String,
#[source]
source: anyhow::Error,
},
#[error("Task '{task_name}' setup failed")]
SetupFailed {
task_id: String,
task_name: String,
#[source]
source: anyhow::Error,
},
#[error("Task '{task_name}' dependency '{dependency_id}' setup failed: {error_message}")]
DependencySetupFailed {
task_id: String,
task_name: String,
dependency_id: String,
error_message: String,
},
#[error("Task '{task_name}' failed on attempt #{attempt}")]
TaskExecutionFailed {
task_id: String,
task_name: String,
attempt: usize,
#[source]
source: anyhow::Error,
},
#[error("Task '{task_name}' panicked on attempt #{attempt}: {panic_message}")]
TaskPanicked {
task_id: String,
task_name: String,
attempt: usize,
panic_message: String,
},
#[error("Task '{task_name}' reached maximum attempts ({actual_attempts}/{max_attempts})")]
MaxAttemptsReached {
task_id: String,
task_name: String,
max_attempts: usize,
actual_attempts: usize,
},
#[error("Task '{task_name}' restart prevented on attempt #{attempt}: {reason}")]
RestartPrevented {
task_id: String,
task_name: String,
attempt: usize,
reason: String,
},
#[error("Invalid configuration: {message}")]
InvalidConfiguration {
message: String,
},
#[error("Runtime operation '{operation}' failed")]
RuntimeFailure {
operation: String,
#[source]
source: anyhow::Error,
},
#[error("Internal supervisor error: {message}")]
InternalError {
message: String,
#[source]
source: Option<anyhow::Error>,
},
}
impl SupervisorError {
pub fn kind(&self) -> ErrorKind {
match self {
SupervisorError::DependencyValidation { .. } |
SupervisorError::CircularDependency { .. } |
SupervisorError::InvalidConfiguration { .. } => ErrorKind::Configuration,
SupervisorError::PrerequisiteFailed { .. } |
SupervisorError::SetupFailed { .. } |
SupervisorError::DependencySetupFailed { .. } => ErrorKind::Runtime,
SupervisorError::TaskExecutionFailed { .. } |
SupervisorError::TaskPanicked { .. } |
SupervisorError::MaxAttemptsReached { .. } |
SupervisorError::RestartPrevented { .. } => ErrorKind::Execution,
SupervisorError::RuntimeFailure { .. } |
SupervisorError::InternalError { .. } => ErrorKind::System,
}
}
pub fn is_configuration_error(&self) -> bool {
matches!(self.kind(), ErrorKind::Configuration)
}
pub fn is_runtime_error(&self) -> bool {
matches!(self.kind(), ErrorKind::Runtime)
}
pub fn is_execution_error(&self) -> bool {
matches!(self.kind(), ErrorKind::Execution)
}
pub fn is_system_error(&self) -> bool {
matches!(self.kind(), ErrorKind::System)
}
pub fn into_anyhow(self) -> anyhow::Error {
self.into()
}
pub fn context<C>(self, context: C) -> anyhow::Error
where
C: std::fmt::Display + Send + Sync + 'static,
{
self.into_anyhow().context(context)
}
pub fn with_context<C, F>(self, f: F) -> anyhow::Error
where
C: std::fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.into_anyhow().context(f())
}
}
impl From<anyhow::Error> for SupervisorError {
fn from(error: anyhow::Error) -> Self {
SupervisorError::RuntimeFailure {
operation: "unknown".to_string(),
source: error,
}
}
}
impl SupervisorError {
pub fn dependency_validation(
task_id: impl Into<String>,
dependency_id: impl Into<String>,
reason: ValidationError,
) -> Self {
SupervisorError::DependencyValidation {
task_id: task_id.into(),
dependency_id: dependency_id.into(),
reason,
}
}
pub fn circular_dependency(
task_id: impl Into<String>,
dependency_id: impl Into<String>,
) -> Self {
SupervisorError::CircularDependency {
task_id: task_id.into(),
dependency_id: dependency_id.into(),
}
}
pub fn prerequisite_failed(
name: impl Into<String>,
source: anyhow::Error,
) -> Self {
SupervisorError::PrerequisiteFailed {
name: name.into(),
source,
}
}
pub fn setup_failed(
task_id: impl Into<String>,
task_name: impl Into<String>,
source: anyhow::Error,
) -> Self {
SupervisorError::SetupFailed {
task_id: task_id.into(),
task_name: task_name.into(),
source,
}
}
pub fn dependency_setup_failed(
task_id: impl Into<String>,
task_name: impl Into<String>,
dependency_id: impl Into<String>,
error_message: impl Into<String>,
) -> Self {
SupervisorError::DependencySetupFailed {
task_id: task_id.into(),
task_name: task_name.into(),
dependency_id: dependency_id.into(),
error_message: error_message.into(),
}
}
pub fn task_execution_failed(
task_id: impl Into<String>,
task_name: impl Into<String>,
attempt: usize,
source: anyhow::Error,
) -> Self {
SupervisorError::TaskExecutionFailed {
task_id: task_id.into(),
task_name: task_name.into(),
attempt,
source,
}
}
pub fn task_panicked(
task_id: impl Into<String>,
task_name: impl Into<String>,
attempt: usize,
panic_message: impl Into<String>,
) -> Self {
SupervisorError::TaskPanicked {
task_id: task_id.into(),
task_name: task_name.into(),
attempt,
panic_message: panic_message.into(),
}
}
pub fn max_attempts_reached(
task_id: impl Into<String>,
task_name: impl Into<String>,
max_attempts: usize,
actual_attempts: usize,
) -> Self {
SupervisorError::MaxAttemptsReached {
task_id: task_id.into(),
task_name: task_name.into(),
max_attempts,
actual_attempts,
}
}
pub fn restart_prevented(
task_id: impl Into<String>,
task_name: impl Into<String>,
attempt: usize,
reason: impl Into<String>,
) -> Self {
SupervisorError::RestartPrevented {
task_id: task_id.into(),
task_name: task_name.into(),
attempt,
reason: reason.into(),
}
}
pub fn invalid_configuration(message: impl Into<String>) -> Self {
SupervisorError::InvalidConfiguration {
message: message.into(),
}
}
pub fn runtime_failure(
operation: impl Into<String>,
source: anyhow::Error,
) -> Self {
SupervisorError::RuntimeFailure {
operation: operation.into(),
source,
}
}
pub fn internal_error(
message: impl Into<String>,
source: Option<anyhow::Error>,
) -> Self {
SupervisorError::InternalError {
message: message.into(),
source,
}
}
}