Skip to main content

how_far/
phases.rs

1//! Independently selected phases for branches, attempts, and recoverable failures.
2use crate::{AsStopReason, Child, Complete, Execution, Outcome, PhaseSpec, PlanError, Pulse};
3use alloc::vec::Vec;
4
5/// A fixed set of owned phases, selected by index rather than automatic sequence.
6/// A failed attempt leaves other phases available. The library decides whether
7/// to recover, retry, or propagate its error. Hand the final result to `complete`
8/// to resolve untouched phases; Drop without that handoff records abandonment.
9#[must_use = "run phases and hand over the final result; Drop records abandonment"]
10pub struct Phases<'a> {
11    parent: &'a dyn Pulse,
12    children: Vec<Option<Child<'a>>>,
13}
14impl<'a> Phases<'a> {
15    /// Declare a fixed plan. Rejected plans retain cancellation via untracked children.
16    #[track_caller]
17    pub fn new(parent: &'a dyn Pulse, execution: Execution, parts: &[PhaseSpec<'_>]) -> Self {
18        Self {
19            parent,
20            children: parent
21                .plan(execution, parts)
22                .into_iter()
23                .map(Some)
24                .collect(),
25        }
26    }
27    /// Run one phase at most once. A missing/repeated index is diagnosed and
28    /// still runs the work through a cancellation-only view.
29    #[track_caller]
30    pub fn run<T, E>(
31        &mut self,
32        index: usize,
33        work: impl FnOnce(&dyn Pulse) -> Result<T, E>,
34    ) -> Result<T, E>
35    where
36        E: AsStopReason,
37    {
38        let child = self.begin(index);
39        let result = work(&child);
40        child.complete(result)
41    }
42    /// Run with an explicit classifier when the error type cannot implement the
43    /// borrowed conversion. The original result is returned unchanged.
44    #[track_caller]
45    pub fn run_classified<T, E>(
46        &mut self,
47        index: usize,
48        is_stop: impl FnOnce(&E) -> bool,
49        work: impl FnOnce(&dyn Pulse) -> Result<T, E>,
50    ) -> Result<T, E> {
51        let child = self.begin(index);
52        let result = work(&child);
53        child.complete_classified(result, is_stop)
54    }
55    /// Explicitly discharge a phase which is unnecessary.
56    #[track_caller]
57    pub fn skip(&mut self, index: usize) {
58        self.take(index).complete_as(Outcome::Skipped);
59    }
60
61    #[track_caller]
62    fn begin(&mut self, index: usize) -> Child<'a> {
63        let child = self.take(index);
64        if let Err(error) = child.start() {
65            self.parent.record_issue(error);
66        }
67        child
68    }
69
70    #[track_caller]
71    fn take(&mut self, index: usize) -> Child<'a> {
72        if let Some(Some(child)) = self.children.get_mut(index).map(Option::take) {
73            return child;
74        }
75        self.parent.record_issue(PlanError::NoMoreStages);
76        Child::untracked(self.parent)
77    }
78}
79impl Complete for Phases<'_> {
80    fn complete_as(self, outcome: Outcome) {
81        let untouched = match outcome {
82            Outcome::Succeeded | Outcome::Skipped => Outcome::Skipped,
83            Outcome::Abandoned => Outcome::Abandoned,
84            _ => Outcome::NotRun,
85        };
86        for child in self.children.into_iter().flatten() {
87            child.complete_inferred(untouched);
88        }
89    }
90}