use super::context::ExecutionContext;
use crate::decider::{Action, ActionKind, ActionResult};
use std::fmt;
#[derive(Debug)]
pub enum ExecutorError {
UnsupportedAction(ActionKind),
CommandFailed(String),
FileNotFound(String),
Timeout,
Other(String),
}
impl fmt::Display for ExecutorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedAction(kind) => write!(f, "Unsupported action: {:?}", kind),
Self::CommandFailed(msg) => write!(f, "Command failed: {}", msg),
Self::FileNotFound(path) => write!(f, "File not found: {}", path),
Self::Timeout => write!(f, "Execution timeout"),
Self::Other(msg) => write!(f, "{}", msg),
}
}
}
impl std::error::Error for ExecutorError {}
pub trait Executor: Send + Sync {
fn execute(
&self,
action: &Action,
ctx: &ExecutionContext,
) -> Result<ActionResult, ExecutorError>;
fn supported_kinds(&self) -> &[ActionKind];
fn can_execute(&self, action: &Action) -> bool {
self.supported_kinds().contains(&action.kind)
}
fn name(&self) -> &'static str {
"Executor"
}
}
pub struct CompositeExecutor {
executors: Vec<Box<dyn Executor>>,
}
impl CompositeExecutor {
pub fn new() -> Self {
Self {
executors: Vec::new(),
}
}
pub fn with<E: Executor + 'static>(mut self, executor: E) -> Self {
self.executors.push(Box::new(executor));
self
}
}
impl Default for CompositeExecutor {
fn default() -> Self {
Self::new()
}
}
impl Executor for CompositeExecutor {
fn execute(
&self,
action: &Action,
ctx: &ExecutionContext,
) -> Result<ActionResult, ExecutorError> {
for executor in &self.executors {
if executor.can_execute(action) {
return executor.execute(action, ctx);
}
}
Err(ExecutorError::UnsupportedAction(action.kind))
}
fn supported_kinds(&self) -> &[ActionKind] {
&[]
}
fn can_execute(&self, action: &Action) -> bool {
self.executors.iter().any(|e| e.can_execute(action))
}
fn name(&self) -> &'static str {
"CompositeExecutor"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_executor_error_display() {
let err = ExecutorError::UnsupportedAction(ActionKind::Read);
assert!(err.to_string().contains("Unsupported"));
let err = ExecutorError::FileNotFound("test.rs".to_string());
assert!(err.to_string().contains("test.rs"));
}
}