Skip to main content

cloud_sdk/action_polling/
progress.rs

1/// Provider progress supplied with one running observation.
2#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3pub enum ProgressObservation {
4    /// The provider does not expose progress.
5    Unavailable,
6    /// Ordinary provider progress in `0..=100`.
7    Percent(u8),
8    /// Provider-declared phase reset in `0..=100`.
9    Reset(u8),
10}
11
12/// Provider-specific progress transition policy.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum ProgressPolicy {
15    /// Ordinary progress must never move backwards and resets are forbidden.
16    Nondecreasing,
17    /// Explicit resets are admitted up to this hard count.
18    ExplicitResets {
19        /// Maximum accepted explicit phase resets.
20        max_resets: u16,
21    },
22    /// Percent values remain validated but ordering is provider-defined.
23    Unordered,
24}
25
26/// Validated progress transition exposed to backoff policy.
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum ProgressChange {
29    /// No progress value was supplied.
30    Unavailable,
31    /// This is the first tracked progress value.
32    Initial,
33    /// Progress did not change.
34    Unchanged,
35    /// Progress moved forward.
36    Advanced,
37    /// The provider explicitly reset progress for a new phase.
38    Reset,
39    /// Ordering is intentionally provider-defined.
40    Unordered,
41}
42
43#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub(crate) enum ProgressError {
45    Invalid,
46    Regressed,
47    ResetForbidden,
48    ResetLimit,
49}
50
51pub(crate) struct ProgressTracker {
52    policy: ProgressPolicy,
53    last: Option<u8>,
54    resets: u16,
55}
56
57impl ProgressTracker {
58    pub(crate) const fn new(policy: ProgressPolicy) -> Self {
59        Self {
60            policy,
61            last: None,
62            resets: 0,
63        }
64    }
65
66    pub(crate) fn observe(
67        &mut self,
68        observation: ProgressObservation,
69    ) -> Result<ProgressChange, ProgressError> {
70        let (value, explicit_reset) = match observation {
71            ProgressObservation::Unavailable => return Ok(ProgressChange::Unavailable),
72            ProgressObservation::Percent(value) => (value, false),
73            ProgressObservation::Reset(value) => (value, true),
74        };
75        if value > 100 {
76            return Err(ProgressError::Invalid);
77        }
78        if self.policy == ProgressPolicy::Unordered {
79            self.last = Some(value);
80            return Ok(ProgressChange::Unordered);
81        }
82        if explicit_reset {
83            let ProgressPolicy::ExplicitResets { max_resets } = self.policy else {
84                return Err(ProgressError::ResetForbidden);
85            };
86            let resets = self
87                .resets
88                .checked_add(1)
89                .ok_or(ProgressError::ResetLimit)?;
90            if resets > max_resets {
91                return Err(ProgressError::ResetLimit);
92            }
93            self.resets = resets;
94            self.last = Some(value);
95            return Ok(ProgressChange::Reset);
96        }
97        let change = match self.last {
98            None => ProgressChange::Initial,
99            Some(last) if value < last => return Err(ProgressError::Regressed),
100            Some(last) if value == last => ProgressChange::Unchanged,
101            Some(_) => ProgressChange::Advanced,
102        };
103        self.last = Some(value);
104        Ok(change)
105    }
106}