use crate::orchestrator::OrchestratorError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StepFailure {
pub kind: SerializableErrorKind,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SerializableErrorKind {
StepTimeout { step_id: String, timeout_secs: u64 },
Cancelled { step_id: String },
Other,
}
impl StepFailure {
pub fn from_orchestrator_error(error: &OrchestratorError) -> Self {
let kind = match error {
OrchestratorError::StepTimeout { step_id, timeout } => {
SerializableErrorKind::StepTimeout {
step_id: step_id.clone(),
timeout_secs: timeout.as_secs(),
}
}
OrchestratorError::Cancelled { step_id } => SerializableErrorKind::Cancelled {
step_id: step_id.clone(),
},
_ => SerializableErrorKind::Other,
};
StepFailure {
kind,
message: error.to_string(),
}
}
pub fn is_timeout(&self) -> bool {
matches!(self.kind, SerializableErrorKind::StepTimeout { .. })
}
pub fn is_cancelled(&self) -> bool {
matches!(self.kind, SerializableErrorKind::Cancelled { .. })
}
}
impl PartialEq for StepFailure {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.message == other.message
}
}
impl std::fmt::Display for StepFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StepState {
Pending,
Ready,
Running,
Completed,
Failed(StepFailure),
Skipped,
PausedForApproval {
message: String,
payload: serde_json::Value,
},
}
impl PartialEq for StepState {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(StepState::Pending, StepState::Pending) => true,
(StepState::Ready, StepState::Ready) => true,
(StepState::Running, StepState::Running) => true,
(StepState::Completed, StepState::Completed) => true,
(StepState::Skipped, StepState::Skipped) => true,
(StepState::Failed(e1), StepState::Failed(e2)) => e1 == e2,
(
StepState::PausedForApproval {
message: m1,
payload: p1,
},
StepState::PausedForApproval {
message: m2,
payload: p2,
},
) => m1 == m2 && p1 == p2,
_ => false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionStateManager {
states: HashMap<String, StepState>,
}
impl ExecutionStateManager {
pub fn new() -> Self {
Self {
states: HashMap::new(),
}
}
pub fn set_state(&mut self, step_id: &str, state: StepState) {
self.states.insert(step_id.to_string(), state);
}
pub fn get_state(&self, step_id: &str) -> Option<&StepState> {
self.states.get(step_id)
}
pub fn step_count(&self) -> usize {
self.states.len()
}
pub fn get_ready_steps(&self) -> Vec<String> {
self.states
.iter()
.filter(|(_, state)| matches!(state, StepState::Ready))
.map(|(id, _)| id.clone())
.collect()
}
pub fn get_running_steps(&self) -> Vec<String> {
self.states
.iter()
.filter(|(_, state)| matches!(state, StepState::Running))
.map(|(id, _)| id.clone())
.collect()
}
pub fn get_skipped_steps(&self) -> Vec<String> {
self.states
.iter()
.filter(|(_, state)| matches!(state, StepState::Skipped))
.map(|(id, _)| id.clone())
.collect()
}
pub fn all_completed(&self) -> bool {
!self.states.is_empty()
&& self
.states
.values()
.all(|s| matches!(s, StepState::Completed))
}
pub fn all_completed_or_skipped(&self) -> bool {
!self.states.is_empty()
&& self
.states
.values()
.all(|s| matches!(s, StepState::Completed | StepState::Skipped))
}
pub fn has_failures(&self) -> bool {
self.states
.values()
.any(|s| matches!(s, StepState::Failed(_)))
}
pub fn has_ready_or_running_steps(&self) -> bool {
self.states
.values()
.any(|s| matches!(s, StepState::Ready | StepState::Running))
}
pub fn has_pending_steps(&self) -> bool {
self.states
.values()
.any(|s| matches!(s, StepState::Pending))
}
pub fn get_failed_steps(&self) -> Vec<(String, StepFailure)> {
self.states
.iter()
.filter_map(|(id, state)| {
if let StepState::Failed(err) = state {
Some((id.clone(), err.clone()))
} else {
None
}
})
.collect()
}
pub fn get_first_failure(&self) -> Option<(String, StepFailure)> {
self.states.iter().find_map(|(id, state)| {
if let StepState::Failed(err) = state {
Some((id.clone(), err.clone()))
} else {
None
}
})
}
}
impl Default for ExecutionStateManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_manager_is_empty() {
let manager = ExecutionStateManager::new();
assert_eq!(manager.step_count(), 0);
}
#[test]
fn test_set_and_get_state() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Pending);
assert_eq!(manager.get_state("step_1").unwrap(), &StepState::Pending);
}
#[test]
fn test_get_state_nonexistent() {
let manager = ExecutionStateManager::new();
assert!(manager.get_state("nonexistent").is_none());
}
#[test]
fn test_update_state() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Pending);
manager.set_state("step_1", StepState::Ready);
assert_eq!(manager.get_state("step_1").unwrap(), &StepState::Ready);
}
#[test]
fn test_get_ready_steps() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Ready);
manager.set_state("step_2", StepState::Pending);
manager.set_state("step_3", StepState::Ready);
let ready = manager.get_ready_steps();
assert_eq!(ready.len(), 2);
assert!(ready.contains(&"step_1".to_string()));
assert!(ready.contains(&"step_3".to_string()));
}
#[test]
fn test_get_ready_steps_empty() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Pending);
manager.set_state("step_2", StepState::Running);
let ready = manager.get_ready_steps();
assert_eq!(ready.len(), 0);
}
#[test]
fn test_get_running_steps() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Running);
manager.set_state("step_2", StepState::Pending);
manager.set_state("step_3", StepState::Running);
let running = manager.get_running_steps();
assert_eq!(running.len(), 2);
assert!(running.contains(&"step_1".to_string()));
assert!(running.contains(&"step_3".to_string()));
}
#[test]
fn test_all_completed() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Completed);
assert!(manager.all_completed());
}
#[test]
fn test_all_completed_false_with_pending() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Pending);
assert!(!manager.all_completed());
}
#[test]
fn test_all_completed_false_when_empty() {
let manager = ExecutionStateManager::new();
assert!(!manager.all_completed());
}
#[test]
fn test_all_completed_or_skipped() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Skipped);
assert!(manager.all_completed_or_skipped());
}
#[test]
fn test_all_completed_or_skipped_false_with_pending() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Skipped);
manager.set_state("step_3", StepState::Pending);
assert!(!manager.all_completed_or_skipped());
}
#[test]
fn test_has_failures() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state(
"step_2",
StepState::Failed(StepFailure::from_orchestrator_error(
&OrchestratorError::ExecutionFailed("test error".to_string()),
)),
);
assert!(manager.has_failures());
}
#[test]
fn test_has_failures_false() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Pending);
assert!(!manager.has_failures());
}
#[test]
fn test_has_ready_or_running_steps() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Running);
manager.set_state("step_2", StepState::Completed);
assert!(manager.has_ready_or_running_steps());
}
#[test]
fn test_has_ready_or_running_steps_with_ready() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Ready);
manager.set_state("step_2", StepState::Completed);
assert!(manager.has_ready_or_running_steps());
}
#[test]
fn test_has_ready_or_running_steps_false() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Skipped);
assert!(!manager.has_ready_or_running_steps());
}
#[test]
fn test_has_pending_steps() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Pending);
manager.set_state("step_2", StepState::Completed);
assert!(manager.has_pending_steps());
}
#[test]
fn test_has_pending_steps_false() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Ready);
assert!(!manager.has_pending_steps());
}
#[test]
fn test_get_failed_steps() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state(
"step_2",
StepState::Failed(StepFailure::from_orchestrator_error(
&OrchestratorError::ExecutionFailed("test error".to_string()),
)),
);
let failed = manager.get_failed_steps();
assert_eq!(failed.len(), 1);
assert_eq!(failed[0].0, "step_2");
assert_eq!(failed[0].1.message, "Execution failed: test error");
}
#[test]
fn test_get_failed_steps_empty() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Running);
let failed = manager.get_failed_steps();
assert_eq!(failed.len(), 0);
}
#[test]
fn test_step_state_equality() {
assert_eq!(StepState::Pending, StepState::Pending);
assert_eq!(StepState::Ready, StepState::Ready);
assert_ne!(StepState::Pending, StepState::Ready);
}
#[test]
fn test_step_state_failed_equality() {
let error1 = StepFailure::from_orchestrator_error(&OrchestratorError::ExecutionFailed(
"error1".to_string(),
));
let error2 = StepFailure::from_orchestrator_error(&OrchestratorError::ExecutionFailed(
"error2".to_string(),
));
let error1_clone = error1.clone();
assert_eq!(
StepState::Failed(error1.clone()),
StepState::Failed(error1_clone)
);
assert_ne!(StepState::Failed(error1), StepState::Failed(error2));
}
#[test]
fn test_get_first_failure() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state(
"step_2",
StepState::Failed(StepFailure::from_orchestrator_error(
&OrchestratorError::ExecutionFailed("error_2".to_string()),
)),
);
manager.set_state(
"step_3",
StepState::Failed(StepFailure::from_orchestrator_error(
&OrchestratorError::ExecutionFailed("error_3".to_string()),
)),
);
let first_failure = manager.get_first_failure();
assert!(first_failure.is_some());
let (step_id, error) = first_failure.unwrap();
assert!(step_id == "step_2" || step_id == "step_3");
assert!(error.message.contains("error_2") || error.message.contains("error_3"));
}
#[test]
fn test_get_first_failure_none() {
let mut manager = ExecutionStateManager::new();
manager.set_state("step_1", StepState::Completed);
manager.set_state("step_2", StepState::Running);
let first_failure = manager.get_first_failure();
assert!(first_failure.is_none());
}
}