Skip to main content

artifactize_core/store/
lifecycle.rs

1//! Payload-carrying lifecycle states; the legacy flat JSON is parsed at the edge.
2
3use super::ExecutionResult;
4use crate::types::{ExecutionStatus, FailureCode, RequestStatus, RunStatus, Timestamp};
5
6/// Non-active, non-verdict request states historically allow an optional completion time.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum PendingRequest {
9    Queued,
10    Blocked,
11    BudgetExhausted,
12    Stale,
13    Unreviewed,
14    WaitDependency,
15}
16
17#[derive(Debug, Clone)]
18pub enum ExecutionState {
19    Running,
20    WaitingHuman,
21    Completed {
22        result: ExecutionResult,
23        at: Timestamp,
24    },
25    Failed {
26        error: String,
27        code: Option<FailureCode>,
28        at: Timestamp,
29    },
30}
31
32#[derive(Debug, Clone)]
33pub enum RequestState {
34    Running,
35    WaitingHuman,
36    Pending {
37        kind: PendingRequest,
38        at: Option<Timestamp>,
39    },
40    Completed {
41        result: ExecutionResult,
42        at: Timestamp,
43    },
44    Failed {
45        error: String,
46        code: Option<FailureCode>,
47        at: Timestamp,
48    },
49}
50
51impl RequestState {
52    /// Construct a pending state without inventing result or error data.
53    pub fn pending(status: RequestStatus, at: Option<Timestamp>) -> Result<Self, String> {
54        Self::from_parts(status, None, None, None, at)
55    }
56
57    pub fn completed(result: ExecutionResult, at: Timestamp) -> Self {
58        Self::Completed { result, at }
59    }
60
61    pub fn failed(error: String, code: Option<FailureCode>, at: Timestamp) -> Self {
62        Self::Failed { error, code, at }
63    }
64
65    pub(super) fn from_parts(
66        status: RequestStatus,
67        result: Option<ExecutionResult>,
68        error: Option<String>,
69        code: Option<FailureCode>,
70        at: Option<Timestamp>,
71    ) -> Result<Self, String> {
72        super::validation::result(
73            status,
74            result.as_ref(),
75            error.as_deref(),
76            code.map(FailureCode::as_str),
77        )?;
78        match status {
79            RequestStatus::Green | RequestStatus::Red => Ok(Self::Completed {
80                result: result.expect("validated verdict"),
81                at: at.ok_or("A finished request needs its completion time.")?,
82            }),
83            RequestStatus::Error => Ok(Self::Failed {
84                error: error.expect("validated error"),
85                code,
86                at: at.ok_or("A finished request needs its completion time.")?,
87            }),
88            RequestStatus::Running | RequestStatus::WaitingHuman if at.is_some() => {
89                Err("An active request cannot have a completion time.".into())
90            }
91            RequestStatus::Running => Ok(Self::Running),
92            RequestStatus::WaitingHuman => Ok(Self::WaitingHuman),
93            _ => Ok(Self::Pending {
94                kind: match status {
95                    RequestStatus::Queued => PendingRequest::Queued,
96                    RequestStatus::Blocked => PendingRequest::Blocked,
97                    RequestStatus::BudgetExhausted => PendingRequest::BudgetExhausted,
98                    RequestStatus::Stale => PendingRequest::Stale,
99                    RequestStatus::Unreviewed => PendingRequest::Unreviewed,
100                    RequestStatus::WaitDependency => PendingRequest::WaitDependency,
101                    _ => unreachable!("terminal and active states handled above"),
102                },
103                at,
104            }),
105        }
106    }
107
108    pub fn status(&self) -> RequestStatus {
109        match self {
110            Self::Running => RequestStatus::Running,
111            Self::WaitingHuman => RequestStatus::WaitingHuman,
112            Self::Completed { result, .. } => match result.verdict() {
113                crate::runtime::Verdict::Green => RequestStatus::Green,
114                crate::runtime::Verdict::Red => RequestStatus::Red,
115            },
116            Self::Failed { .. } => RequestStatus::Error,
117            Self::Pending { kind, .. } => match kind {
118                PendingRequest::Queued => RequestStatus::Queued,
119                PendingRequest::Blocked => RequestStatus::Blocked,
120                PendingRequest::BudgetExhausted => RequestStatus::BudgetExhausted,
121                PendingRequest::Stale => RequestStatus::Stale,
122                PendingRequest::Unreviewed => RequestStatus::Unreviewed,
123                PendingRequest::WaitDependency => RequestStatus::WaitDependency,
124            },
125        }
126    }
127    pub fn result(&self) -> Option<&ExecutionResult> {
128        match self {
129            Self::Completed { result, .. } => Some(result),
130            _ => None,
131        }
132    }
133    pub fn error(&self) -> Option<&str> {
134        match self {
135            Self::Failed { error, .. } => Some(error),
136            _ => None,
137        }
138    }
139    pub fn error_code(&self) -> Option<FailureCode> {
140        match self {
141            Self::Failed { code, .. } => *code,
142            _ => None,
143        }
144    }
145    pub fn completed_at(&self) -> Option<Timestamp> {
146        match self {
147            Self::Completed { at, .. } | Self::Failed { at, .. } => Some(*at),
148            Self::Pending { at, .. } => *at,
149            _ => None,
150        }
151    }
152}
153
154impl ExecutionState {
155    pub(super) fn from_parts(
156        status: ExecutionStatus,
157        result: Option<ExecutionResult>,
158        error: Option<String>,
159        code: Option<FailureCode>,
160        at: Option<Timestamp>,
161    ) -> Result<Self, String> {
162        let state = RequestState::from_parts(status.into(), result, error, code, at)?;
163        Self::try_from(state)
164    }
165    pub fn status(&self) -> ExecutionStatus {
166        match self {
167            Self::Running => ExecutionStatus::Running,
168            Self::WaitingHuman => ExecutionStatus::WaitingHuman,
169            Self::Completed { result, .. } => match result.verdict() {
170                crate::runtime::Verdict::Green => ExecutionStatus::Green,
171                crate::runtime::Verdict::Red => ExecutionStatus::Red,
172            },
173            Self::Failed { .. } => ExecutionStatus::Error,
174        }
175    }
176    pub fn result(&self) -> Option<&ExecutionResult> {
177        match self {
178            Self::Completed { result, .. } => Some(result),
179            _ => None,
180        }
181    }
182    pub fn error(&self) -> Option<&str> {
183        match self {
184            Self::Failed { error, .. } => Some(error),
185            _ => None,
186        }
187    }
188    pub fn error_code(&self) -> Option<FailureCode> {
189        match self {
190            Self::Failed { code, .. } => *code,
191            _ => None,
192        }
193    }
194    pub fn completed_at(&self) -> Option<Timestamp> {
195        match self {
196            Self::Completed { at, .. } | Self::Failed { at, .. } => Some(*at),
197            _ => None,
198        }
199    }
200}
201impl TryFrom<RequestState> for ExecutionState {
202    type Error = String;
203    fn try_from(state: RequestState) -> Result<Self, String> {
204        match state {
205            RequestState::Running => Ok(Self::Running),
206            RequestState::WaitingHuman => Ok(Self::WaitingHuman),
207            RequestState::Completed { result, at } => Ok(Self::Completed { result, at }),
208            RequestState::Failed { error, code, at } => Ok(Self::Failed { error, code, at }),
209            RequestState::Pending { .. } => {
210                Err("A pending request has no execution lifecycle state.".into())
211            }
212        }
213    }
214}
215impl From<ExecutionState> for RequestState {
216    fn from(state: ExecutionState) -> Self {
217        match state {
218            ExecutionState::Running => Self::Running,
219            ExecutionState::WaitingHuman => Self::WaitingHuman,
220            ExecutionState::Completed { result, at } => Self::Completed { result, at },
221            ExecutionState::Failed { error, code, at } => Self::Failed { error, code, at },
222        }
223    }
224}
225
226/// Run completion data exists only in a finished lifecycle state.
227#[derive(Debug, Clone)]
228pub enum RunState {
229    Running,
230    Green {
231        at: Timestamp,
232    },
233    Red {
234        at: Timestamp,
235    },
236    Error {
237        at: Timestamp,
238        error: Option<String>,
239    },
240    Incomplete {
241        at: Timestamp,
242        error: Option<String>,
243    },
244}
245
246impl RunState {
247    pub(super) fn from_parts(
248        status: RunStatus,
249        at: Option<Timestamp>,
250        error: Option<String>,
251    ) -> Result<Self, String> {
252        match status {
253            RunStatus::Running if at.is_none() && error.is_none() => Ok(Self::Running),
254            RunStatus::Running => {
255                Err("A running Run cannot be completed or carry an error.".into())
256            }
257            RunStatus::Green | RunStatus::Red if error.is_some() || at.is_none() => Err(
258                "A completed verdict Run needs a completion time and cannot carry an error.".into(),
259            ),
260            RunStatus::Green => Ok(Self::Green {
261                at: at.expect("validated completion time"),
262            }),
263            RunStatus::Red => Ok(Self::Red {
264                at: at.expect("validated completion time"),
265            }),
266            RunStatus::Error => Ok(Self::Error {
267                at: at.ok_or("A finished Run needs its completion time.")?,
268                error,
269            }),
270            RunStatus::Incomplete => Ok(Self::Incomplete {
271                at: at.ok_or("A finished Run needs its completion time.")?,
272                error,
273            }),
274        }
275    }
276    pub fn status(&self) -> RunStatus {
277        match self {
278            Self::Running => RunStatus::Running,
279            Self::Green { .. } => RunStatus::Green,
280            Self::Red { .. } => RunStatus::Red,
281            Self::Error { .. } => RunStatus::Error,
282            Self::Incomplete { .. } => RunStatus::Incomplete,
283        }
284    }
285    pub fn completed_at(&self) -> Option<Timestamp> {
286        match self {
287            Self::Running => None,
288            Self::Green { at }
289            | Self::Red { at }
290            | Self::Error { at, .. }
291            | Self::Incomplete { at, .. } => Some(*at),
292        }
293    }
294    pub fn error(&self) -> Option<&str> {
295        match self {
296            Self::Error { error, .. } | Self::Incomplete { error, .. } => error.as_deref(),
297            _ => None,
298        }
299    }
300}