use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TerminationVerdict {
Success { reason: SuccessReason },
Failure { reason: FailureReason },
Timeout { partial_success: bool },
ExternalStop { reason: String },
}
impl TerminationVerdict {
pub fn is_success(&self) -> bool {
matches!(
self,
TerminationVerdict::Success { .. }
| TerminationVerdict::Timeout {
partial_success: true
}
)
}
pub fn is_failure(&self) -> bool {
!self.is_success()
}
pub fn description(&self) -> String {
match self {
TerminationVerdict::Success { reason } => format!("Success: {}", reason.description()),
TerminationVerdict::Failure { reason } => format!("Failure: {}", reason.description()),
TerminationVerdict::Timeout { partial_success } => {
if *partial_success {
"Timeout (partial success)".to_string()
} else {
"Timeout (failure)".to_string()
}
}
TerminationVerdict::ExternalStop { reason } => format!("External stop: {}", reason),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SuccessReason {
WorkerDone {
worker_id: usize,
message: Option<String>,
},
ConditionsMet,
ExplorationComplete,
GoalActionCompleted { action: String },
}
impl SuccessReason {
pub fn description(&self) -> String {
match self {
SuccessReason::WorkerDone { worker_id, message } => {
let msg = message.as_deref().unwrap_or("(no message)");
format!("Worker {} completed: {}", worker_id, msg)
}
SuccessReason::ConditionsMet => "All success conditions met".to_string(),
SuccessReason::ExplorationComplete => "Exploration completed".to_string(),
SuccessReason::GoalActionCompleted { action } => {
format!("Goal action '{}' completed", action)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FailureReason {
WorkerFailed {
worker_id: usize,
message: Option<String>,
},
ConditionFailed { condition_name: String },
MaxErrorsExceeded { count: u64, limit: u64 },
ExplorationExhausted,
NoValidActions,
InternalError { message: String },
}
impl FailureReason {
pub fn description(&self) -> String {
match self {
FailureReason::WorkerFailed { worker_id, message } => {
let msg = message.as_deref().unwrap_or("(no message)");
format!("Worker {} failed: {}", worker_id, msg)
}
FailureReason::ConditionFailed { condition_name } => {
format!("Failure condition '{}' triggered", condition_name)
}
FailureReason::MaxErrorsExceeded { count, limit } => {
format!("Max errors exceeded ({}/{})", count, limit)
}
FailureReason::ExplorationExhausted => {
"Exploration exhausted without reaching goal".to_string()
}
FailureReason::NoValidActions => "No valid actions available".to_string(),
FailureReason::InternalError { message } => format!("Internal error: {}", message),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verdict_is_success() {
assert!(TerminationVerdict::Success {
reason: SuccessReason::ConditionsMet
}
.is_success());
assert!(TerminationVerdict::Timeout {
partial_success: true
}
.is_success());
assert!(!TerminationVerdict::Timeout {
partial_success: false
}
.is_success());
assert!(!TerminationVerdict::Failure {
reason: FailureReason::ExplorationExhausted
}
.is_success());
}
}