pub mod traits;
pub use traits::CommandHandler;
#[cfg(test)]
mod tests {
use super::*;
use crate::context::ExecutionContext;
use crate::error::ExecutionError;
use std::any::Any;
use std::collections::HashMap;
#[derive(Default)]
struct IntegrationContext {
log: Vec<String>,
state: HashMap<String, String>,
}
impl ExecutionContext for IntegrationContext {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
struct LogCommand;
impl CommandHandler for LogCommand {
fn execute(
&self,
context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> crate::error::Result<()> {
let ctx =
crate::context::downcast_mut::<IntegrationContext>(context).ok_or_else(|| {
ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type"))
})?;
let message = args
.get("message")
.map(|s| s.as_str())
.unwrap_or("default message");
ctx.log.push(message.to_string());
Ok(())
}
}
struct SetCommand;
impl CommandHandler for SetCommand {
fn execute(
&self,
context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> crate::error::Result<()> {
let ctx =
crate::context::downcast_mut::<IntegrationContext>(context).ok_or_else(|| {
ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type"))
})?;
if let (Some(key), Some(value)) = (args.get("key"), args.get("value")) {
ctx.state.insert(key.clone(), value.clone());
}
Ok(())
}
}
struct GetCommand;
impl CommandHandler for GetCommand {
fn execute(
&self,
context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> crate::error::Result<()> {
let ctx =
crate::context::downcast_mut::<IntegrationContext>(context).ok_or_else(|| {
ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type"))
})?;
if let Some(key) = args.get("key") {
if let Some(value) = ctx.state.get(key) {
ctx.log.push(format!("{} = {}", key, value));
} else {
ctx.log.push(format!("{} not found", key));
}
}
Ok(())
}
}
#[test]
fn test_module_reexports() {
fn _accepts_handler(_: &dyn CommandHandler) {}
let handler = LogCommand;
_accepts_handler(&handler);
}
#[test]
fn test_command_sequence() {
let mut context = IntegrationContext::default();
let log_cmd = LogCommand;
let mut args1 = HashMap::new();
args1.insert("message".to_string(), "First".to_string());
log_cmd.execute(&mut context, &args1).unwrap();
let mut args2 = HashMap::new();
args2.insert("message".to_string(), "Second".to_string());
log_cmd.execute(&mut context, &args2).unwrap();
assert_eq!(context.log.len(), 2);
assert_eq!(context.log[0], "First");
assert_eq!(context.log[1], "Second");
}
#[test]
fn test_stateful_workflow() {
let mut context = IntegrationContext::default();
let set_cmd = SetCommand;
let mut args1 = HashMap::new();
args1.insert("key".to_string(), "name".to_string());
args1.insert("value".to_string(), "Alice".to_string());
set_cmd.execute(&mut context, &args1).unwrap();
let mut args2 = HashMap::new();
args2.insert("key".to_string(), "age".to_string());
args2.insert("value".to_string(), "30".to_string());
set_cmd.execute(&mut context, &args2).unwrap();
let get_cmd = GetCommand;
let mut args3 = HashMap::new();
args3.insert("key".to_string(), "name".to_string());
get_cmd.execute(&mut context, &args3).unwrap();
let mut args4 = HashMap::new();
args4.insert("key".to_string(), "age".to_string());
get_cmd.execute(&mut context, &args4).unwrap();
assert_eq!(context.state.len(), 2);
assert_eq!(context.state.get("name"), Some(&"Alice".to_string()));
assert_eq!(context.state.get("age"), Some(&"30".to_string()));
assert_eq!(context.log.len(), 2);
assert_eq!(context.log[0], "name = Alice");
assert_eq!(context.log[1], "age = 30");
}
#[test]
fn test_heterogeneous_handler_collection() {
let handlers: Vec<Box<dyn CommandHandler>> = vec![
Box::new(LogCommand),
Box::new(SetCommand),
Box::new(GetCommand),
];
assert_eq!(handlers.len(), 3);
let mut context = IntegrationContext::default();
let mut args1 = HashMap::new();
args1.insert("message".to_string(), "test".to_string());
handlers[0].execute(&mut context, &args1).unwrap();
let mut args2 = HashMap::new();
args2.insert("key".to_string(), "k".to_string());
args2.insert("value".to_string(), "v".to_string());
handlers[1].execute(&mut context, &args2).unwrap();
let mut args3 = HashMap::new();
args3.insert("key".to_string(), "k".to_string());
handlers[2].execute(&mut context, &args3).unwrap();
assert_eq!(context.log.len(), 2);
assert_eq!(context.state.len(), 1);
}
#[test]
fn test_context_isolation_between_commands() {
let mut context = IntegrationContext::default();
let set_cmd = SetCommand;
let mut args = HashMap::new();
args.insert("key".to_string(), "shared".to_string());
args.insert("value".to_string(), "value".to_string());
set_cmd.execute(&mut context, &args).unwrap();
let get_cmd = GetCommand;
let mut args2 = HashMap::new();
args2.insert("key".to_string(), "shared".to_string());
get_cmd.execute(&mut context, &args2).unwrap();
assert!(context.log[0].contains("shared = value"));
}
#[test]
fn test_error_propagation() {
struct FailingCommand;
impl CommandHandler for FailingCommand {
fn execute(
&self,
_context: &mut dyn ExecutionContext,
_args: &HashMap<String, String>,
) -> crate::error::Result<()> {
Err(ExecutionError::CommandFailed(anyhow::anyhow!("Intentional failure")).into())
}
}
let handler = FailingCommand;
let mut context = IntegrationContext::default();
let args = HashMap::new();
let result = handler.execute(&mut context, &args);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("Intentional failure"));
}
#[test]
fn test_validation_before_execution() {
struct ValidatingCommand;
impl CommandHandler for ValidatingCommand {
fn execute(
&self,
context: &mut dyn ExecutionContext,
_args: &HashMap<String, String>,
) -> crate::error::Result<()> {
let ctx = crate::context::downcast_mut::<IntegrationContext>(context).ok_or_else(
|| ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type")),
)?;
ctx.log.push("executed".to_string());
Ok(())
}
fn validate(&self, args: &HashMap<String, String>) -> crate::error::Result<()> {
if !args.contains_key("required") {
return Err(ExecutionError::CommandFailed(anyhow::anyhow!(
"Missing required argument"
))
.into());
}
Ok(())
}
}
let handler = ValidatingCommand;
let mut context = IntegrationContext::default();
let args_invalid = HashMap::new();
assert!(handler.validate(&args_invalid).is_err());
assert!(handler.execute(&mut context, &args_invalid).is_ok());
let mut args_valid = HashMap::new();
args_valid.insert("required".to_string(), "value".to_string());
assert!(handler.validate(&args_valid).is_ok());
assert!(handler.execute(&mut context, &args_valid).is_ok());
}
#[test]
fn test_command_handler_documentation_example() {
#[derive(Default)]
struct AppContext {
counter: i32,
}
impl ExecutionContext for AppContext {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
struct IncrementCommand;
impl CommandHandler for IncrementCommand {
fn execute(
&self,
context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> crate::error::Result<()> {
let ctx = crate::context::downcast_mut::<AppContext>(context).ok_or_else(|| {
ExecutionError::CommandFailed(anyhow::anyhow!("Wrong context type"))
})?;
let amount: i32 = args.get("amount").and_then(|s| s.parse().ok()).unwrap_or(1);
ctx.counter += amount;
Ok(())
}
}
let handler = IncrementCommand;
let mut context = AppContext::default();
let mut args = HashMap::new();
args.insert("amount".to_string(), "5".to_string());
handler.execute(&mut context, &args).unwrap();
assert_eq!(context.counter, 5);
}
#[test]
fn test_complex_multi_step_workflow() {
let mut context = IntegrationContext::default();
let set_cmd = SetCommand;
let mut args1 = HashMap::new();
args1.insert("key".to_string(), "initialized".to_string());
args1.insert("value".to_string(), "true".to_string());
set_cmd.execute(&mut context, &args1).unwrap();
let log_cmd = LogCommand;
let mut args2 = HashMap::new();
args2.insert("message".to_string(), "System initialized".to_string());
log_cmd.execute(&mut context, &args2).unwrap();
let mut args3 = HashMap::new();
args3.insert("key".to_string(), "user".to_string());
args3.insert("value".to_string(), "admin".to_string());
set_cmd.execute(&mut context, &args3).unwrap();
let get_cmd = GetCommand;
let mut args4 = HashMap::new();
args4.insert("key".to_string(), "user".to_string());
get_cmd.execute(&mut context, &args4).unwrap();
assert_eq!(context.state.len(), 2);
assert_eq!(context.log.len(), 2);
assert_eq!(context.log[0], "System initialized");
assert_eq!(context.log[1], "user = admin");
}
}