1use serde::{
4 Deserialize,
5 Deserializer,
6 Serialize,
7};
8use thiserror::Error;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum TaskStatus {
14 Admitted,
16 Queued,
18 Running,
20 Sealing,
22 Done,
24 Failed,
26 Cancelled,
28}
29
30impl TaskStatus {
31 pub const ALL: [Self; 7] = [
33 Self::Admitted,
34 Self::Queued,
35 Self::Running,
36 Self::Sealing,
37 Self::Done,
38 Self::Failed,
39 Self::Cancelled,
40 ];
41
42 #[must_use]
44 pub const fn is_terminal(self) -> bool {
45 matches!(self, Self::Done | Self::Failed | Self::Cancelled)
46 }
47
48 #[must_use]
50 pub const fn as_str(self) -> &'static str {
51 match self {
52 Self::Admitted => "admitted",
53 Self::Queued => "queued",
54 Self::Running => "running",
55 Self::Sealing => "sealing",
56 Self::Done => "done",
57 Self::Failed => "failed",
58 Self::Cancelled => "cancelled",
59 }
60 }
61
62 pub fn parse_label(label: &str) -> Result<Self, String> {
68 Self::ALL
69 .into_iter()
70 .find(|status| status.as_str() == label)
71 .ok_or_else(|| format!("unknown task status {label:?}"))
72 }
73
74 pub fn validate_transition(self, next: Self) -> Result<(), TaskTransitionError> {
80 TaskTransition::try_new(self, next).map(|_| ())
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
86#[error("invalid task transition from {from} to {to}", from = .from.as_str(), to = .to.as_str())]
87pub struct TaskTransitionError {
88 from: TaskStatus,
89 to: TaskStatus,
90}
91
92impl TaskTransitionError {
93 #[must_use]
95 pub const fn from(self) -> TaskStatus {
96 self.from
97 }
98
99 #[must_use]
101 pub const fn to(self) -> TaskStatus {
102 self.to
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
108pub struct TaskTransition {
109 from: TaskStatus,
110 to: TaskStatus,
111}
112
113impl TaskTransition {
114 pub fn try_new(from: TaskStatus, to: TaskStatus) -> Result<Self, TaskTransitionError> {
121 let valid = match from {
122 TaskStatus::Admitted => matches!(
123 to,
124 TaskStatus::Queued | TaskStatus::Running | TaskStatus::Cancelled
125 ),
126 TaskStatus::Queued => matches!(to, TaskStatus::Running | TaskStatus::Cancelled),
127 TaskStatus::Running => matches!(
128 to,
129 TaskStatus::Sealing | TaskStatus::Done | TaskStatus::Failed | TaskStatus::Cancelled
130 ),
131 TaskStatus::Sealing => matches!(to, TaskStatus::Done | TaskStatus::Failed),
132 TaskStatus::Done | TaskStatus::Failed | TaskStatus::Cancelled => false,
133 };
134 if !valid {
135 return Err(TaskTransitionError { from, to });
136 }
137 Ok(Self { from, to })
138 }
139
140 #[must_use]
142 pub const fn from(self) -> TaskStatus {
143 self.from
144 }
145
146 #[must_use]
148 pub const fn to(self) -> TaskStatus {
149 self.to
150 }
151}
152
153impl<'de> Deserialize<'de> for TaskTransition {
154 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155 where
156 D: Deserializer<'de>,
157 {
158 #[derive(Deserialize)]
159 struct WireTransition {
160 from: TaskStatus,
161 to: TaskStatus,
162 }
163
164 let wire = WireTransition::deserialize(deserializer)?;
165 Self::try_new(wire.from, wire.to).map_err(serde::de::Error::custom)
166 }
167}