1use super::ExecutionResult;
4use crate::types::{ExecutionStatus, FailureCode, RequestStatus, RunStatus, Timestamp};
5
6#[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 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#[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}