1use pe_core::PeError;
19
20use crate::task::{Task, TaskStatus};
21
22pub trait TaskLifecycle: Send + Sync {
39 fn validate_transition(&self, task: &Task, new_status: &TaskStatus) -> Result<(), PeError>;
41
42 fn on_create(&self, _task: &Task) {}
44
45 fn on_transition(&self, _task: &Task, _old_status: &TaskStatus) {}
47
48 fn on_complete(&self, _task: &Task) {}
50
51 fn on_fail(&self, _task: &Task, _error: &str) {}
53
54 fn on_cancel(&self, _task: &Task) {}
56}
57
58#[derive(Clone, Debug, Default)]
62pub struct DefaultLifecycle;
63
64impl DefaultLifecycle {
65 #[must_use]
67 pub fn is_valid_transition(from: &TaskStatus, to: &TaskStatus) -> bool {
68 matches!(
69 (from, to),
70 (TaskStatus::Pending, TaskStatus::InProgress)
72 | (TaskStatus::Pending, TaskStatus::Blocked)
73 | (TaskStatus::Pending, TaskStatus::Cancelled)
74 | (TaskStatus::InProgress, TaskStatus::Completed)
76 | (TaskStatus::InProgress, TaskStatus::Failed)
77 | (TaskStatus::InProgress, TaskStatus::Blocked)
78 | (TaskStatus::InProgress, TaskStatus::Cancelled)
79 | (TaskStatus::Blocked, TaskStatus::Pending)
81 | (TaskStatus::Blocked, TaskStatus::InProgress)
82 | (TaskStatus::Blocked, TaskStatus::Cancelled)
83 | (TaskStatus::Failed, TaskStatus::Pending)
85 | (TaskStatus::Cancelled, TaskStatus::Pending)
87 )
88 }
89}
90
91impl TaskLifecycle for DefaultLifecycle {
92 fn validate_transition(&self, task: &Task, new_status: &TaskStatus) -> Result<(), PeError> {
93 if &task.status == new_status {
94 return Ok(()); }
96 if Self::is_valid_transition(&task.status, new_status) {
97 Ok(())
98 } else {
99 Err(PeError::InvalidUpdate {
100 details: format!(
101 "Invalid task transition: {:?} → {:?} for task '{}'",
102 task.status, new_status, task.title
103 ),
104 })
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_valid_transitions_from_pending() {
115 assert!(DefaultLifecycle::is_valid_transition(
116 &TaskStatus::Pending,
117 &TaskStatus::InProgress
118 ));
119 assert!(DefaultLifecycle::is_valid_transition(
120 &TaskStatus::Pending,
121 &TaskStatus::Blocked
122 ));
123 assert!(DefaultLifecycle::is_valid_transition(
124 &TaskStatus::Pending,
125 &TaskStatus::Cancelled
126 ));
127 assert!(!DefaultLifecycle::is_valid_transition(
129 &TaskStatus::Pending,
130 &TaskStatus::Completed
131 ));
132 assert!(!DefaultLifecycle::is_valid_transition(
133 &TaskStatus::Pending,
134 &TaskStatus::Failed
135 ));
136 }
137
138 #[test]
139 fn test_valid_transitions_from_in_progress() {
140 assert!(DefaultLifecycle::is_valid_transition(
141 &TaskStatus::InProgress,
142 &TaskStatus::Completed
143 ));
144 assert!(DefaultLifecycle::is_valid_transition(
145 &TaskStatus::InProgress,
146 &TaskStatus::Failed
147 ));
148 assert!(DefaultLifecycle::is_valid_transition(
149 &TaskStatus::InProgress,
150 &TaskStatus::Blocked
151 ));
152 }
153
154 #[test]
155 fn test_completed_is_terminal() {
156 assert!(!DefaultLifecycle::is_valid_transition(
157 &TaskStatus::Completed,
158 &TaskStatus::Pending
159 ));
160 assert!(!DefaultLifecycle::is_valid_transition(
161 &TaskStatus::Completed,
162 &TaskStatus::InProgress
163 ));
164 }
165
166 #[test]
167 fn test_failed_can_retry() {
168 assert!(DefaultLifecycle::is_valid_transition(
169 &TaskStatus::Failed,
170 &TaskStatus::Pending
171 ));
172 assert!(!DefaultLifecycle::is_valid_transition(
173 &TaskStatus::Failed,
174 &TaskStatus::InProgress
175 ));
176 }
177
178 #[test]
179 fn test_cancelled_can_reopen() {
180 assert!(DefaultLifecycle::is_valid_transition(
181 &TaskStatus::Cancelled,
182 &TaskStatus::Pending
183 ));
184 }
185
186 #[test]
187 fn test_noop_transition_always_valid() {
188 let lc = DefaultLifecycle;
189 let task = Task::new("test");
190 assert!(lc.validate_transition(&task, &TaskStatus::Pending).is_ok());
191 }
192
193 #[test]
194 fn test_invalid_transition_returns_error() {
195 let lc = DefaultLifecycle;
196 let task = Task::new("test");
197 let err = lc.validate_transition(&task, &TaskStatus::Completed);
198 assert!(err.is_err());
199 }
200}