use pe_core::PeError;
use crate::task::{Task, TaskStatus};
pub trait TaskLifecycle: Send + Sync {
fn validate_transition(&self, task: &Task, new_status: &TaskStatus) -> Result<(), PeError>;
fn on_create(&self, _task: &Task) {}
fn on_transition(&self, _task: &Task, _old_status: &TaskStatus) {}
fn on_complete(&self, _task: &Task) {}
fn on_fail(&self, _task: &Task, _error: &str) {}
fn on_cancel(&self, _task: &Task) {}
}
#[derive(Clone, Debug, Default)]
pub struct DefaultLifecycle;
impl DefaultLifecycle {
#[must_use]
pub fn is_valid_transition(from: &TaskStatus, to: &TaskStatus) -> bool {
matches!(
(from, to),
(TaskStatus::Pending, TaskStatus::InProgress)
| (TaskStatus::Pending, TaskStatus::Blocked)
| (TaskStatus::Pending, TaskStatus::Cancelled)
| (TaskStatus::InProgress, TaskStatus::Completed)
| (TaskStatus::InProgress, TaskStatus::Failed)
| (TaskStatus::InProgress, TaskStatus::Blocked)
| (TaskStatus::InProgress, TaskStatus::Cancelled)
| (TaskStatus::Blocked, TaskStatus::Pending)
| (TaskStatus::Blocked, TaskStatus::InProgress)
| (TaskStatus::Blocked, TaskStatus::Cancelled)
| (TaskStatus::Failed, TaskStatus::Pending)
| (TaskStatus::Cancelled, TaskStatus::Pending)
)
}
}
impl TaskLifecycle for DefaultLifecycle {
fn validate_transition(&self, task: &Task, new_status: &TaskStatus) -> Result<(), PeError> {
if &task.status == new_status {
return Ok(()); }
if Self::is_valid_transition(&task.status, new_status) {
Ok(())
} else {
Err(PeError::InvalidUpdate {
details: format!(
"Invalid task transition: {:?} → {:?} for task '{}'",
task.status, new_status, task.title
),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_transitions_from_pending() {
assert!(DefaultLifecycle::is_valid_transition(
&TaskStatus::Pending,
&TaskStatus::InProgress
));
assert!(DefaultLifecycle::is_valid_transition(
&TaskStatus::Pending,
&TaskStatus::Blocked
));
assert!(DefaultLifecycle::is_valid_transition(
&TaskStatus::Pending,
&TaskStatus::Cancelled
));
assert!(!DefaultLifecycle::is_valid_transition(
&TaskStatus::Pending,
&TaskStatus::Completed
));
assert!(!DefaultLifecycle::is_valid_transition(
&TaskStatus::Pending,
&TaskStatus::Failed
));
}
#[test]
fn test_valid_transitions_from_in_progress() {
assert!(DefaultLifecycle::is_valid_transition(
&TaskStatus::InProgress,
&TaskStatus::Completed
));
assert!(DefaultLifecycle::is_valid_transition(
&TaskStatus::InProgress,
&TaskStatus::Failed
));
assert!(DefaultLifecycle::is_valid_transition(
&TaskStatus::InProgress,
&TaskStatus::Blocked
));
}
#[test]
fn test_completed_is_terminal() {
assert!(!DefaultLifecycle::is_valid_transition(
&TaskStatus::Completed,
&TaskStatus::Pending
));
assert!(!DefaultLifecycle::is_valid_transition(
&TaskStatus::Completed,
&TaskStatus::InProgress
));
}
#[test]
fn test_failed_can_retry() {
assert!(DefaultLifecycle::is_valid_transition(
&TaskStatus::Failed,
&TaskStatus::Pending
));
assert!(!DefaultLifecycle::is_valid_transition(
&TaskStatus::Failed,
&TaskStatus::InProgress
));
}
#[test]
fn test_cancelled_can_reopen() {
assert!(DefaultLifecycle::is_valid_transition(
&TaskStatus::Cancelled,
&TaskStatus::Pending
));
}
#[test]
fn test_noop_transition_always_valid() {
let lc = DefaultLifecycle;
let task = Task::new("test");
assert!(lc.validate_transition(&task, &TaskStatus::Pending).is_ok());
}
#[test]
fn test_invalid_transition_returns_error() {
let lc = DefaultLifecycle;
let task = Task::new("test");
let err = lc.validate_transition(&task, &TaskStatus::Completed);
assert!(err.is_err());
}
}