spry 0.0.4

Resilient, self-healing async process hierarchies in the style of Erlang/OTP
Documentation
//! Common error types

use std::any::Any;

use crate::core::ChildName;
use crate::internal::ProcessOutcome;

/// Reasons a system may have failed and be escalating.
#[derive(Debug)]
pub enum Escalation {
  /// The system failed on its initial startup attempt.
  Startup(StartupFailure),
  /// The system has attempted to restart too many times and exceeded its [crate::breaker::Breaker].
  TooManyRestarts(RestartReason),
  /// A failure occurred for unknown reasons. This should never happen.
  Unknown(Box<dyn Any + Send>),
}

/// Reasons a startup operation, either initial or during a restart, has failed.
#[derive(Debug)]
pub enum StartupFailure {
  /// A key was used twice during a startup operation.
  ReusedKey(ChildName),
  /// The named child failed to start.
  ChildFailure(ChildName, Box<dyn Any + Send>),
  /// The startup was interrupted by a settlement request.
  Interrupted,
  /// A failure occurred outside a child's startup function.
  NonChildFailure(Box<dyn Any + Send>),
}

impl StartupFailure {
  pub fn message(&self) -> String {
    match self {
      StartupFailure::ReusedKey(name) => format!("Key was reused for child {}", name.to_string()),
      StartupFailure::ChildFailure(name, fail) => {
        let reason = if let Some(m) = fail.downcast_ref::<&str>() {
          m.to_string()
        } else if let Some(m) = fail.downcast_ref::<String>() {
          m.clone()
        } else if let Some(m) = fail.downcast_ref::<Escalation>() {
          format!("{:?}", m)
        } else {
          String::from("unknown")
        };
        format!("Child {} failed to start: {}", name.to_string(), reason)
      }
      StartupFailure::Interrupted => String::from("Settlement interrupted"),
      StartupFailure::NonChildFailure(x) => {
        if let Some(m) = x.downcast_ref::<&str>() {
          m.to_string()
        } else if let Some(m) = x.downcast_ref::<String>() {
          m.clone()
        } else if let Some(m) = x.downcast_ref::<Escalation>() {
          format!("{:?}", m)
        } else {
          String::from("unknown")
        }
      }
    }
  }
}

/// Reasons for a system to want to be restarted. Despite these reasons, a restart is only attempted
/// if the system's [crate::breaker::Breaker] allows for it.
#[derive(Debug)]
pub enum RestartReason {
  /// A permanent child has terminated, normally or otherwise, for the given reason.
  PermanentChildTerminated(ChildName, TerminationReason),
  /// A transient child has failed with the given panic.
  TransientChildFailed(ChildName, Box<dyn Any + Send>),
  /// A failure occurred during a restart attempt.
  RestartFailure(StartupFailure),
}

/// Reasons for a child to have terminated.
#[derive(Debug)]
pub enum TerminationReason {
  Normal,
  Aborted,
  Panicked(Box<dyn Any + Send>),
}

impl TerminationReason {
  pub(crate) fn as_outcome(&self) -> ProcessOutcome {
    match self {
      TerminationReason::Normal => ProcessOutcome::Normal,
      TerminationReason::Aborted => ProcessOutcome::Aborted,
      TerminationReason::Panicked(_) => ProcessOutcome::Panicked,
    }
  }
}