use crate::db::models::DeploymentStatus;
use anyhow::{bail, Result};
pub fn is_terminal(status: &DeploymentStatus) -> bool {
matches!(
status,
DeploymentStatus::Cancelled
| DeploymentStatus::Stopped
| DeploymentStatus::Superseded
| DeploymentStatus::Failed
| DeploymentStatus::Expired
)
}
pub fn is_active(status: &DeploymentStatus) -> bool {
matches!(
status,
DeploymentStatus::Healthy | DeploymentStatus::Unhealthy
)
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn is_cancellable(status: &DeploymentStatus) -> bool {
matches!(
status,
DeploymentStatus::Pending
| DeploymentStatus::Building
| DeploymentStatus::Pushing
| DeploymentStatus::Pushed
| DeploymentStatus::Deploying
)
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn is_terminable(status: &DeploymentStatus) -> bool {
matches!(
status,
DeploymentStatus::Healthy | DeploymentStatus::Unhealthy
)
}
pub fn is_rollbackable(status: &DeploymentStatus) -> bool {
matches!(
status,
DeploymentStatus::Healthy | DeploymentStatus::Superseded
)
}
pub fn is_valid_transition(from: &DeploymentStatus, to: &DeploymentStatus) -> bool {
use DeploymentStatus::*;
match (from, to) {
(from, to) if from == to => true,
(from, _) if is_terminal(from) => false,
(Pending | Building | Pushing | Pushed | Deploying, Cancelling) => true,
(Cancelling, Cancelled) => true,
(Pending, Building) => true,
(Building, Pushing) => true,
(Building, Pushed) => true, (Pushing, Pushed) => true,
(Pushed, Deploying) => true,
(Deploying, Healthy) => true, (Deploying, Failed) => true,
(Healthy, Unhealthy) => true, (Unhealthy, Healthy) => true, (Unhealthy, Failed) => true,
(Healthy | Unhealthy, Terminating) => true,
(Terminating, Stopped) => true, (Terminating, Superseded) => true, (Terminating, Expired) => true,
(Pending | Building | Pushing | Pushed, Failed) => true,
_ => false,
}
}
pub fn validate_transition(from: &DeploymentStatus, to: &DeploymentStatus) -> Result<()> {
if !is_valid_transition(from, to) {
bail!(
"Invalid deployment state transition from '{}' to '{}'",
from,
to
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use DeploymentStatus::*;
#[test]
fn test_terminal_states() {
assert!(is_terminal(&Cancelled));
assert!(is_terminal(&Stopped));
assert!(is_terminal(&Superseded));
assert!(is_terminal(&Failed));
assert!(!is_terminal(&Pending));
assert!(!is_terminal(&Healthy));
assert!(!is_terminal(&Cancelling));
}
#[test]
fn test_active_states() {
assert!(is_active(&Healthy));
assert!(is_active(&Unhealthy));
assert!(!is_active(&Deploying));
assert!(!is_active(&Failed));
}
#[test]
fn test_cancellable_states() {
assert!(is_cancellable(&Pending));
assert!(is_cancellable(&Building));
assert!(is_cancellable(&Pushing));
assert!(is_cancellable(&Pushed));
assert!(is_cancellable(&Deploying));
assert!(!is_cancellable(&Healthy));
assert!(!is_cancellable(&Unhealthy));
assert!(!is_cancellable(&Cancelled));
}
#[test]
fn test_terminable_states() {
assert!(is_terminable(&Healthy));
assert!(is_terminable(&Unhealthy));
assert!(!is_terminable(&Deploying));
assert!(!is_terminable(&Stopped));
}
#[test]
fn test_valid_cancellation_path() {
assert!(is_valid_transition(&Pending, &Cancelling));
assert!(is_valid_transition(&Building, &Cancelling));
assert!(is_valid_transition(&Deploying, &Cancelling));
assert!(is_valid_transition(&Cancelling, &Cancelled));
assert!(!is_valid_transition(&Cancelling, &Failed));
}
#[test]
fn test_valid_termination_path() {
assert!(is_valid_transition(&Healthy, &Terminating));
assert!(is_valid_transition(&Unhealthy, &Terminating));
assert!(is_valid_transition(&Terminating, &Stopped));
assert!(is_valid_transition(&Terminating, &Superseded));
assert!(!is_valid_transition(&Terminating, &Failed));
}
#[test]
fn test_healthy_unhealthy_cannot_be_cancelled() {
assert!(!is_valid_transition(&Healthy, &Cancelled));
assert!(!is_valid_transition(&Unhealthy, &Cancelled));
assert!(is_valid_transition(&Healthy, &Terminating));
assert!(is_valid_transition(&Unhealthy, &Terminating));
}
#[test]
fn test_deployment_path() {
assert!(is_valid_transition(&Pending, &Building));
assert!(is_valid_transition(&Building, &Pushing));
assert!(is_valid_transition(&Pushing, &Pushed));
assert!(is_valid_transition(&Pushed, &Deploying));
assert!(is_valid_transition(&Deploying, &Healthy));
assert!(is_valid_transition(&Building, &Pushed));
assert!(is_valid_transition(&Healthy, &Unhealthy));
assert!(is_valid_transition(&Unhealthy, &Healthy));
assert!(is_valid_transition(&Unhealthy, &Failed));
}
#[test]
fn test_terminal_states_no_transitions() {
assert!(!is_valid_transition(&Cancelled, &Pending));
assert!(!is_valid_transition(&Stopped, &Healthy));
assert!(!is_valid_transition(&Superseded, &Deploying));
assert!(!is_valid_transition(&Failed, &Healthy));
}
#[test]
fn test_invalid_transitions() {
assert!(!is_valid_transition(&Pending, &Deploying));
assert!(!is_valid_transition(&Building, &Healthy));
assert!(!is_valid_transition(&Deploying, &Building));
assert!(!is_valid_transition(&Healthy, &Pending));
}
#[test]
fn test_same_status_transitions() {
assert!(is_valid_transition(&Pending, &Pending));
assert!(is_valid_transition(&Building, &Building));
assert!(is_valid_transition(&Healthy, &Healthy));
assert!(is_valid_transition(&Unhealthy, &Unhealthy));
assert!(is_valid_transition(&Failed, &Failed));
assert!(is_valid_transition(&Stopped, &Stopped));
}
}