use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "kebab-case")] pub enum RecoverableTaskState { Pending, Running, WaitingForConfirmation, WaitingForUserInput, Other, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RecoveryStep { ResumeRunning { index: usize }, WaitForExternalInput, StartPending { index: usize }, Idle, } pub fn next_recovery_step(tasks: &[RecoverableTaskState]) -> RecoveryStep { if let Some(index) = tasks .iter() .position(|state| *state == RecoverableTaskState::Running) { return RecoveryStep::ResumeRunning { index }; } if tasks.iter().any(|state| { matches!( state, RecoverableTaskState::WaitingForConfirmation | RecoverableTaskState::WaitingForUserInput ) }) { return RecoveryStep::WaitForExternalInput; } tasks .iter() .position(|state| *state == RecoverableTaskState::Pending) .map_or(RecoveryStep::Idle, |index| RecoveryStep::StartPending { index, }) } #[cfg(test)] mod tests { use super::*; #[test] fn recovery_prefers_first_running_task() { assert_eq!( next_recovery_step(&[ RecoverableTaskState::Pending, RecoverableTaskState::Running, RecoverableTaskState::Running, RecoverableTaskState::WaitingForConfirmation, ]), RecoveryStep::ResumeRunning { index: 1 } ); } #[test] fn external_input_blocks_pending_recovery() { assert_eq!( next_recovery_step(&[ RecoverableTaskState::Pending, RecoverableTaskState::WaitingForUserInput, ]), RecoveryStep::WaitForExternalInput ); } #[test] fn recovery_starts_first_pending_or_remains_idle() { assert_eq!( next_recovery_step(&[ RecoverableTaskState::Other, RecoverableTaskState::Pending, RecoverableTaskState::Pending, ]), RecoveryStep::StartPending { index: 1 } ); assert_eq!( next_recovery_step(&[RecoverableTaskState::Other]), RecoveryStep::Idle ); } }