pub mod claude;
pub mod claude_error;
pub mod environment;
pub mod execution_env;
pub mod executor;
pub mod handler;
pub mod progress;
pub mod retry_helpers;
pub mod shell;
pub mod step_error;
pub use claude::{execute_claude_command_effect, execute_claude_command_with_retry};
pub use claude_error::ClaudeError;
pub use environment::{WorkflowEnv, WorkflowEnvBuilder};
pub use execution_env::{ExecutionEnv, ExecutionEnvBuilder};
pub use executor::{execute_claude_step_with_retry, execute_step, execute_workflow};
pub use handler::execute_handler_effect;
pub use progress::{StepResult, WorkflowProgress, WorkflowResult};
pub use retry_helpers::{default_claude_retry_policy, parse_retry_policy, shell_retry_policy};
pub use shell::execute_shell_command_effect;
pub use step_error::{StepError, WorkflowError};
#[derive(Debug, Clone)]
pub struct CommandOutput {
pub stdout: String,
pub stderr: String,
pub exit_code: Option<i32>,
pub success: bool,
pub variables: std::collections::HashMap<String, String>,
pub json_log_location: Option<String>,
}
impl CommandOutput {
pub fn success(stdout: String) -> Self {
Self {
stdout,
stderr: String::new(),
exit_code: Some(0),
success: true,
variables: std::collections::HashMap::new(),
json_log_location: None,
}
}
pub fn failure(stderr: String, exit_code: Option<i32>) -> Self {
Self {
stdout: String::new(),
stderr,
exit_code,
success: false,
variables: std::collections::HashMap::new(),
json_log_location: None,
}
}
pub fn with_variables(mut self, variables: std::collections::HashMap<String, String>) -> Self {
self.variables = variables;
self
}
pub fn with_json_log_location(mut self, location: String) -> Self {
self.json_log_location = Some(location);
self
}
}
#[derive(Debug, Clone)]
pub enum CommandError {
ExecutionFailed {
message: String,
exit_code: Option<i32>,
},
Timeout { seconds: u64 },
HandlerNotFound { name: String },
InvalidConfiguration { message: String },
IoError { message: String },
}
impl std::fmt::Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CommandError::ExecutionFailed { message, exit_code } => {
write!(
f,
"Command execution failed: {} (exit code: {:?})",
message, exit_code
)
}
CommandError::Timeout { seconds } => {
write!(f, "Command timed out after {} seconds", seconds)
}
CommandError::HandlerNotFound { name } => {
write!(f, "Handler not found: {}", name)
}
CommandError::InvalidConfiguration { message } => {
write!(f, "Invalid command configuration: {}", message)
}
CommandError::IoError { message } => {
write!(f, "I/O error: {}", message)
}
}
}
}
impl std::error::Error for CommandError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_output_success() {
let output = CommandOutput::success("hello world".to_string());
assert!(output.success);
assert_eq!(output.exit_code, Some(0));
assert_eq!(output.stdout, "hello world");
assert!(output.stderr.is_empty());
}
#[test]
fn test_command_output_failure() {
let output = CommandOutput::failure("error occurred".to_string(), Some(1));
assert!(!output.success);
assert_eq!(output.exit_code, Some(1));
assert_eq!(output.stderr, "error occurred");
assert!(output.stdout.is_empty());
}
#[test]
fn test_command_output_with_variables() {
let mut vars = std::collections::HashMap::new();
vars.insert("key".to_string(), "value".to_string());
let output = CommandOutput::success("output".to_string()).with_variables(vars);
assert_eq!(output.variables.get("key"), Some(&"value".to_string()));
}
#[test]
fn test_command_output_with_json_log() {
let output = CommandOutput::success("output".to_string())
.with_json_log_location("/tmp/log.json".to_string());
assert_eq!(output.json_log_location, Some("/tmp/log.json".to_string()));
}
#[test]
fn test_command_error_display() {
let err = CommandError::ExecutionFailed {
message: "test failure".to_string(),
exit_code: Some(1),
};
assert!(err.to_string().contains("test failure"));
let err = CommandError::Timeout { seconds: 30 };
assert!(err.to_string().contains("30 seconds"));
let err = CommandError::HandlerNotFound {
name: "missing".to_string(),
};
assert!(err.to_string().contains("missing"));
}
}