use crate::{Child, Execution, Outcome, PhaseSpec, PlanError, Pulse, StopReason};
use alloc::collections::VecDeque;
use core::fmt;
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum RunError<E> {
Work(E),
Plan(PlanError),
}
impl<E> From<PlanError> for RunError<E> {
fn from(error: PlanError) -> Self {
Self::Plan(error)
}
}
impl<E: fmt::Display> fmt::Display for RunError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Work(error) => error.fmt(f),
Self::Plan(error) => error.fmt(f),
}
}
}
impl<E: core::error::Error + 'static> core::error::Error for RunError<E> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Work(error) => Some(error),
Self::Plan(error) => Some(error),
}
}
}
impl<E> RunError<RunError<E>> {
fn flatten(self) -> RunError<E> {
match self {
Self::Work(error) => error,
Self::Plan(error) => RunError::Plan(error),
}
}
}
#[must_use = "run and finish the stages; dropping unfinished stages abandons them"]
pub struct TryStages<'a> {
pending: VecDeque<Child<'a>>,
state: State,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum State {
Ready,
Running,
Stopped,
}
impl fmt::Debug for TryStages<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TryStages")
.field("remaining", &self.pending.len())
.field("state", &self.state)
.finish()
}
}
impl<'a> TryStages<'a> {
pub fn new(parent: &'a dyn Pulse, parts: &[PhaseSpec<'_>]) -> Result<Self, PlanError> {
Ok(Self {
pending: parent.split(Execution::Sequence, parts)?.into(),
state: State::Ready,
})
}
pub fn skip(&mut self) -> Result<(), PlanError> {
self.next_stage()?;
self.end_stage(Outcome::Skipped)
}
pub fn run<T, E>(
&mut self,
work: impl FnOnce(&dyn Pulse) -> Result<T, E>,
) -> Result<T, RunError<E>> {
let result = match self.next_stage() {
Ok(stage) => work(stage),
Err(error) => return Err(RunError::Plan(error)),
};
self.complete(result, Outcome::Failed)
}
pub fn run_stoppable<T>(
&mut self,
work: impl FnOnce(&dyn Pulse) -> Result<T, StopReason>,
) -> Result<T, RunError<StopReason>> {
let result = match self.next_stage() {
Ok(stage) => work(stage),
Err(error) => return Err(RunError::Plan(error)),
};
self.complete(result, Outcome::Cancelled)
}
pub fn run_classified<T, E>(
&mut self,
is_stop: impl FnOnce(&E) -> bool,
work: impl FnOnce(&dyn Pulse) -> Result<T, E>,
) -> Result<T, RunError<E>> {
let result = match self.next_stage() {
Ok(stage) => work(stage),
Err(error) => return Err(RunError::Plan(error)),
};
let on_error = match &result {
Err(error) if is_stop(error) => Outcome::Cancelled,
_ => Outcome::Failed,
};
self.complete(result, on_error)
}
pub fn run_nested<T, E>(
&mut self,
is_stop: impl FnOnce(&E) -> bool,
work: impl FnOnce(&dyn Pulse) -> Result<T, RunError<E>>,
) -> Result<T, RunError<E>> {
let result = match self.next_stage() {
Ok(stage) => work(stage),
Err(error) => return Err(RunError::Plan(error)),
};
let on_error = match &result {
Err(RunError::Work(error)) if is_stop(error) => Outcome::Cancelled,
_ => Outcome::Failed,
};
match self.complete(result, on_error) {
Ok(value) => Ok(value),
Err(error) => Err(error.flatten()),
}
}
#[inline(never)]
fn complete<T, E>(
&mut self,
result: Result<T, E>,
on_error: Outcome,
) -> Result<T, RunError<E>> {
match result {
Ok(value) => match self.end_stage(Outcome::Succeeded) {
Ok(()) => Ok(value),
Err(error) => Err(RunError::Plan(error)),
},
Err(error) => {
let _ = self.end_stage(on_error);
Err(RunError::Work(error))
}
}
}
fn next_stage(&mut self) -> Result<&dyn Pulse, PlanError> {
if self.state == State::Running {
let _ = self.end_stage(Outcome::Abandoned);
}
match self.pending.front() {
_ if self.state == State::Stopped => Err(PlanError::Finished),
Some(stage) => {
stage.start()?;
self.state = State::Running;
Ok(stage.pulse())
}
None => Err(PlanError::NoMoreStages),
}
}
#[inline(never)]
fn end_stage(&mut self, outcome: Outcome) -> Result<(), PlanError> {
self.state = State::Ready;
let Some(stage) = self.pending.pop_front() else {
return Err(PlanError::NoMoreStages);
};
let finished = stage.finish(outcome);
if finished.is_err() || !matches!(outcome, Outcome::Succeeded | Outcome::Skipped) {
self.state = State::Stopped;
while let Some(stage) = self.pending.pop_front() {
let _ = stage.finish(Outcome::NotRun);
}
}
finished
}
pub fn finish(self) -> Result<(), PlanError> {
if self.state == State::Stopped {
Err(PlanError::Finished)
} else if !self.pending.is_empty() {
Err(PlanError::UnfinishedChildren)
} else {
Ok(())
}
}
}