use serde::{
Deserialize,
Deserializer,
Serialize,
};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
Admitted,
Queued,
Running,
Sealing,
Done,
Failed,
Cancelled,
}
impl TaskStatus {
pub const ALL: [Self; 7] = [
Self::Admitted,
Self::Queued,
Self::Running,
Self::Sealing,
Self::Done,
Self::Failed,
Self::Cancelled,
];
#[must_use]
pub const fn is_terminal(self) -> bool {
matches!(self, Self::Done | Self::Failed | Self::Cancelled)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Admitted => "admitted",
Self::Queued => "queued",
Self::Running => "running",
Self::Sealing => "sealing",
Self::Done => "done",
Self::Failed => "failed",
Self::Cancelled => "cancelled",
}
}
pub fn parse_label(label: &str) -> Result<Self, String> {
Self::ALL
.into_iter()
.find(|status| status.as_str() == label)
.ok_or_else(|| format!("unknown task status {label:?}"))
}
pub fn validate_transition(self, next: Self) -> Result<(), TaskTransitionError> {
TaskTransition::try_new(self, next).map(|_| ())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
#[error("invalid task transition from {from} to {to}", from = .from.as_str(), to = .to.as_str())]
pub struct TaskTransitionError {
from: TaskStatus,
to: TaskStatus,
}
impl TaskTransitionError {
#[must_use]
pub const fn from(self) -> TaskStatus {
self.from
}
#[must_use]
pub const fn to(self) -> TaskStatus {
self.to
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct TaskTransition {
from: TaskStatus,
to: TaskStatus,
}
impl TaskTransition {
pub fn try_new(from: TaskStatus, to: TaskStatus) -> Result<Self, TaskTransitionError> {
let valid = match from {
TaskStatus::Admitted => matches!(
to,
TaskStatus::Queued | TaskStatus::Running | TaskStatus::Cancelled
),
TaskStatus::Queued => matches!(to, TaskStatus::Running | TaskStatus::Cancelled),
TaskStatus::Running => matches!(
to,
TaskStatus::Sealing | TaskStatus::Done | TaskStatus::Failed | TaskStatus::Cancelled
),
TaskStatus::Sealing => matches!(to, TaskStatus::Done | TaskStatus::Failed),
TaskStatus::Done | TaskStatus::Failed | TaskStatus::Cancelled => false,
};
if !valid {
return Err(TaskTransitionError { from, to });
}
Ok(Self { from, to })
}
#[must_use]
pub const fn from(self) -> TaskStatus {
self.from
}
#[must_use]
pub const fn to(self) -> TaskStatus {
self.to
}
}
impl<'de> Deserialize<'de> for TaskTransition {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct WireTransition {
from: TaskStatus,
to: TaskStatus,
}
let wire = WireTransition::deserialize(deserializer)?;
Self::try_new(wire.from, wire.to).map_err(serde::de::Error::custom)
}
}