use super::state::{SessionStatus, UnifiedSession};
use anyhow::{anyhow, Result};
use chrono::{DateTime, Duration, Utc};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] pub enum Transition {
Start,
Pause,
Resume,
Complete,
Fail,
}
pub fn validate_transition(from: &SessionStatus, to: &SessionStatus) -> Result<()> {
use SessionStatus::*;
let valid = match (from, to) {
(Initializing, Running) => true,
(Initializing, Paused) => true, (Initializing, Completed) => true, (Initializing, Failed) => true,
(Running, Paused) => true,
(Running, Completed) => true,
(Running, Failed) => true,
(Paused, Running) => true,
(Paused, Failed) => true,
(Paused, Completed) => true,
(Failed, Running) => true,
(a, b) if a == b => true,
(Completed, _) => false,
(Cancelled, _) => false,
_ => false,
};
if valid {
Ok(())
} else {
Err(anyhow!(
"Invalid status transition from {:?} to {:?}",
from,
to
))
}
}
#[allow(dead_code)] pub fn transition_status(current: &SessionStatus, transition: Transition) -> Result<SessionStatus> {
let new_status = match transition {
Transition::Start => SessionStatus::Running,
Transition::Pause => SessionStatus::Paused,
Transition::Resume => SessionStatus::Running,
Transition::Complete => SessionStatus::Completed,
Transition::Fail => SessionStatus::Failed,
};
validate_transition(current, &new_status)?;
Ok(new_status)
}
#[allow(dead_code)] pub fn calculate_duration(
started_at: DateTime<Utc>,
completed_at: Option<DateTime<Utc>>,
) -> Option<Duration> {
completed_at.map(|end| end.signed_duration_since(started_at))
}
pub fn apply_status_update(session: &mut UnifiedSession, status: SessionStatus) -> Result<()> {
validate_transition(&session.status, &status)?;
if session.status == SessionStatus::Failed && status == SessionStatus::Running {
session.completed_at = None;
}
session.status = status;
session.updated_at = Utc::now();
if matches!(
session.status,
SessionStatus::Completed | SessionStatus::Failed
) {
session.completed_at = Some(Utc::now());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use SessionStatus::*;
#[test]
fn test_validate_transition_valid() {
assert!(validate_transition(&Initializing, &Running).is_ok());
assert!(validate_transition(&Running, &Paused).is_ok());
assert!(validate_transition(&Running, &Completed).is_ok());
assert!(validate_transition(&Running, &Failed).is_ok());
assert!(validate_transition(&Paused, &Running).is_ok());
assert!(validate_transition(&Paused, &Completed).is_ok());
}
#[test]
fn test_validate_transition_invalid() {
assert!(validate_transition(&Completed, &Running).is_err());
assert!(validate_transition(&Cancelled, &Running).is_err());
assert!(validate_transition(&Completed, &Paused).is_err());
}
#[test]
fn test_validate_transition_failed_to_running() {
assert!(validate_transition(&Failed, &Running).is_ok());
assert!(validate_transition(&Failed, &Paused).is_err());
assert!(validate_transition(&Failed, &Completed).is_err());
}
#[test]
fn test_validate_transition_idempotent() {
assert!(validate_transition(&Running, &Running).is_ok());
assert!(validate_transition(&Paused, &Paused).is_ok());
assert!(validate_transition(&Initializing, &Initializing).is_ok());
}
#[test]
fn test_transition_status() {
assert_eq!(
transition_status(&Initializing, Transition::Start).unwrap(),
Running
);
assert_eq!(
transition_status(&Running, Transition::Pause).unwrap(),
Paused
);
assert_eq!(
transition_status(&Paused, Transition::Resume).unwrap(),
Running
);
assert_eq!(
transition_status(&Running, Transition::Complete).unwrap(),
Completed
);
assert_eq!(
transition_status(&Running, Transition::Fail).unwrap(),
Failed
);
}
#[test]
fn test_transition_status_invalid() {
assert!(transition_status(&Completed, Transition::Start).is_err());
assert!(transition_status(&Cancelled, Transition::Resume).is_err());
}
#[test]
fn test_transition_status_resume_from_failed() {
assert!(transition_status(&Failed, Transition::Resume).is_ok());
assert_eq!(
transition_status(&Failed, Transition::Resume).unwrap(),
Running
);
}
#[test]
fn test_calculate_duration() {
let start = Utc::now();
let end = start + Duration::hours(2);
let duration = calculate_duration(start, Some(end)).unwrap();
assert_eq!(duration.num_hours(), 2);
}
#[test]
fn test_calculate_duration_none() {
let start = Utc::now();
assert!(calculate_duration(start, None).is_none());
}
}