cloudformatious/
status.rs

1//! Types and values representing various CloudFormation statuses.
2#![allow(clippy::module_name_repetitions)]
3
4use std::convert::TryFrom;
5
6/// Common operations for statuses.
7pub trait Status: std::fmt::Debug + std::fmt::Display + private::Sealed {
8    /// Indicates whether or not a status is settled.
9    ///
10    /// A settled status is one that won't change again during the current stack operation.
11    fn is_settled(&self) -> bool;
12
13    /// Indicates the sentiment of the status.
14    ///
15    /// This is obviously a bit fuzzy, but in general:
16    ///
17    /// - Successful terminal statuses are positive.
18    /// - Failed terminal statuses and rollback statuses are negative.
19    /// - All other statuses are neutral.
20    fn sentiment(&self) -> StatusSentiment;
21}
22
23/// An indication of whether a status is positive, negative, or neutral for the affected resource.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum StatusSentiment {
26    Positive,
27    Neutral,
28    Negative,
29}
30
31impl StatusSentiment {
32    /// Is the sentiment positive?
33    #[must_use]
34    pub fn is_positive(self) -> bool {
35        self == Self::Positive
36    }
37
38    /// Is the sentiment neutral?
39    #[must_use]
40    pub fn is_neutral(self) -> bool {
41        self == Self::Neutral
42    }
43
44    /// Is the sentiment negative?
45    #[must_use]
46    pub fn is_negative(self) -> bool {
47        self == Self::Negative
48    }
49}
50
51/// Possible change set statuses.
52#[derive(Clone, Copy, Debug, Eq, PartialEq, parse_display::Display, parse_display::FromStr)]
53#[display(style = "SNAKE_CASE")]
54pub enum ChangeSetStatus {
55    CreatePending,
56    CreateInProgress,
57    CreateComplete,
58    DeletePending,
59    DeleteInProgress,
60    DeleteComplete,
61    DeleteFailed,
62    Failed,
63}
64
65impl Status for ChangeSetStatus {
66    fn is_settled(&self) -> bool {
67        match self {
68            Self::CreatePending
69            | Self::CreateInProgress
70            | Self::DeletePending
71            | Self::DeleteInProgress => false,
72            Self::CreateComplete | Self::DeleteComplete | Self::DeleteFailed | Self::Failed => true,
73        }
74    }
75
76    fn sentiment(&self) -> StatusSentiment {
77        match self {
78            Self::CreateComplete | Self::DeleteComplete => StatusSentiment::Positive,
79            Self::CreatePending
80            | Self::CreateInProgress
81            | Self::DeletePending
82            | Self::DeleteInProgress => StatusSentiment::Neutral,
83            Self::DeleteFailed | Self::Failed => StatusSentiment::Negative,
84        }
85    }
86}
87
88/// Possible stack statuses.
89#[derive(Clone, Copy, Debug, Eq, PartialEq, parse_display::Display, parse_display::FromStr)]
90#[display(style = "SNAKE_CASE")]
91pub enum StackStatus {
92    CreateInProgress,
93    CreateFailed,
94    CreateComplete,
95    RollbackInProgress,
96    RollbackFailed,
97    RollbackComplete,
98    DeleteInProgress,
99    DeleteFailed,
100    DeleteComplete,
101    UpdateInProgress,
102    UpdateCompleteCleanupInProgress,
103    UpdateComplete,
104    UpdateFailed,
105    UpdateRollbackInProgress,
106    UpdateRollbackFailed,
107    UpdateRollbackCompleteCleanupInProgress,
108    UpdateRollbackComplete,
109    ReviewInProgress,
110    ImportInProgress,
111    ImportComplete,
112    ImportRollbackInProgress,
113    ImportRollbackFailed,
114    ImportRollbackComplete,
115}
116
117impl Status for StackStatus {
118    fn is_settled(&self) -> bool {
119        match self {
120            Self::CreateInProgress
121            | Self::RollbackInProgress
122            | Self::DeleteInProgress
123            | Self::UpdateInProgress
124            | Self::UpdateCompleteCleanupInProgress
125            | Self::UpdateRollbackInProgress
126            | Self::UpdateRollbackCompleteCleanupInProgress
127            | Self::ReviewInProgress
128            | Self::ImportInProgress
129            | Self::ImportRollbackInProgress => false,
130            Self::CreateFailed
131            | Self::CreateComplete
132            | Self::RollbackFailed
133            | Self::RollbackComplete
134            | Self::DeleteFailed
135            | Self::DeleteComplete
136            | Self::UpdateComplete
137            | Self::UpdateFailed
138            | Self::UpdateRollbackFailed
139            | Self::UpdateRollbackComplete
140            | Self::ImportComplete
141            | Self::ImportRollbackFailed
142            | Self::ImportRollbackComplete => true,
143        }
144    }
145
146    fn sentiment(&self) -> StatusSentiment {
147        match self {
148            Self::CreateComplete
149            | Self::DeleteComplete
150            | Self::UpdateComplete
151            | Self::ImportComplete => StatusSentiment::Positive,
152            Self::CreateInProgress
153            | Self::DeleteInProgress
154            | Self::UpdateInProgress
155            | Self::UpdateCompleteCleanupInProgress
156            | Self::ReviewInProgress
157            | Self::ImportInProgress => StatusSentiment::Neutral,
158            Self::CreateFailed
159            | Self::RollbackInProgress
160            | Self::RollbackFailed
161            | Self::RollbackComplete
162            | Self::DeleteFailed
163            | Self::UpdateFailed
164            | Self::UpdateRollbackInProgress
165            | Self::UpdateRollbackFailed
166            | Self::UpdateRollbackCompleteCleanupInProgress
167            | Self::UpdateRollbackComplete
168            | Self::ImportRollbackInProgress
169            | Self::ImportRollbackFailed
170            | Self::ImportRollbackComplete => StatusSentiment::Negative,
171        }
172    }
173}
174
175/// Settled statuses in which a stack cannot be updated.
176///
177/// In some cases these may be recoverable, in others they may be terminal (i.e. the only option is
178/// to delete the stack).
179#[derive(Clone, Copy, Debug, Eq, PartialEq, parse_display::Display, parse_display::FromStr)]
180#[display(style = "SNAKE_CASE")]
181pub enum BlockedStackStatus {
182    CreateFailed,
183    RollbackComplete,
184    RollbackFailed,
185    DeleteFailed,
186    UpdateFailed,
187    UpdateRollbackFailed,
188}
189
190impl TryFrom<StackStatus> for BlockedStackStatus {
191    type Error = StackStatus;
192
193    fn try_from(status: StackStatus) -> Result<Self, Self::Error> {
194        match status {
195            StackStatus::CreateFailed => Ok(Self::CreateFailed),
196            StackStatus::RollbackComplete => Ok(Self::RollbackComplete),
197            StackStatus::RollbackFailed => Ok(Self::RollbackFailed),
198            StackStatus::DeleteFailed => Ok(Self::DeleteFailed),
199            StackStatus::UpdateFailed => Ok(Self::UpdateFailed),
200            StackStatus::UpdateRollbackFailed => Ok(Self::UpdateRollbackFailed),
201            StackStatus::CreateInProgress
202            | StackStatus::CreateComplete
203            | StackStatus::DeleteComplete
204            | StackStatus::DeleteInProgress
205            | StackStatus::ReviewInProgress
206            | StackStatus::RollbackInProgress
207            | StackStatus::UpdateInProgress
208            | StackStatus::UpdateCompleteCleanupInProgress
209            | StackStatus::UpdateComplete
210            | StackStatus::UpdateRollbackInProgress
211            | StackStatus::UpdateRollbackCompleteCleanupInProgress
212            | StackStatus::UpdateRollbackComplete
213            | StackStatus::ImportInProgress
214            | StackStatus::ImportComplete
215            | StackStatus::ImportRollbackInProgress
216            | StackStatus::ImportRollbackFailed
217            | StackStatus::ImportRollbackComplete => Err(status),
218        }
219    }
220}
221
222/// Possible resource statuses.
223#[derive(Clone, Copy, Debug, Eq, PartialEq, parse_display::Display, parse_display::FromStr)]
224#[display(style = "SNAKE_CASE")]
225pub enum ResourceStatus {
226    CreateInProgress,
227    CreateFailed,
228    CreateComplete,
229    DeleteInProgress,
230    DeleteFailed,
231    DeleteComplete,
232    DeleteSkipped,
233    UpdateInProgress,
234    UpdateFailed,
235    UpdateComplete,
236    ImportFailed,
237    ImportComplete,
238    ImportInProgress,
239    ImportRollbackInProgress,
240    ImportRollbackFailed,
241    ImportRollbackComplete,
242}
243
244impl Status for ResourceStatus {
245    fn is_settled(&self) -> bool {
246        match self {
247            Self::CreateInProgress
248            | Self::DeleteInProgress
249            | Self::UpdateInProgress
250            | Self::ImportInProgress
251            | Self::ImportRollbackInProgress => false,
252            Self::CreateFailed
253            | Self::CreateComplete
254            | Self::DeleteFailed
255            | Self::DeleteComplete
256            | Self::DeleteSkipped
257            | Self::UpdateFailed
258            | Self::UpdateComplete
259            | Self::ImportFailed
260            | Self::ImportComplete
261            | Self::ImportRollbackFailed
262            | Self::ImportRollbackComplete => true,
263        }
264    }
265
266    fn sentiment(&self) -> StatusSentiment {
267        match self {
268            Self::CreateComplete
269            | Self::DeleteComplete
270            | Self::UpdateComplete
271            | Self::ImportComplete => StatusSentiment::Positive,
272            Self::CreateInProgress
273            | Self::DeleteInProgress
274            | Self::DeleteSkipped
275            | Self::UpdateInProgress
276            | Self::ImportInProgress => StatusSentiment::Neutral,
277            Self::CreateFailed
278            | Self::DeleteFailed
279            | Self::UpdateFailed
280            | Self::ImportFailed
281            | Self::ImportRollbackInProgress
282            | Self::ImportRollbackFailed
283            | Self::ImportRollbackComplete => StatusSentiment::Negative,
284        }
285    }
286}
287
288mod private {
289    /// An unreachable trait used to prevent some traits from being implemented outside the crate.
290    pub trait Sealed {}
291
292    impl Sealed for super::ChangeSetStatus {}
293    impl Sealed for super::StackStatus {}
294    impl Sealed for super::ResourceStatus {}
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn stack_status() {
303        // there's no point testing every variant, but we should check one to be sure.
304        assert_eq!(
305            format!("{}", StackStatus::UpdateRollbackCompleteCleanupInProgress).as_str(),
306            "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS"
307        );
308        assert_eq!(
309            "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS".parse(),
310            Ok(StackStatus::UpdateRollbackCompleteCleanupInProgress)
311        );
312        assert!("oh no".parse::<StackStatus>().is_err());
313    }
314
315    #[test]
316    fn resource_status() {
317        // there's no point testing every variant, but we should check one to be sure.
318        assert_eq!(
319            format!("{}", ResourceStatus::ImportRollbackInProgress).as_str(),
320            "IMPORT_ROLLBACK_IN_PROGRESS"
321        );
322        assert_eq!(
323            "IMPORT_ROLLBACK_IN_PROGRESS".parse(),
324            Ok(ResourceStatus::ImportRollbackInProgress)
325        );
326        assert!("oh no".parse::<ResourceStatus>().is_err());
327    }
328}