pub trait CommandHandler: Send + Sync {
// Required method
fn execute(
&self,
context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()>;
// Provided method
fn validate(&self, _args: &HashMap<String, String>) -> Result<()> { ... }
}Expand description
Trait for command implementations
Each command in the CLI/REPL application must implement this trait. The trait is designed to be object-safe, allowing commands to be stored and invoked dynamically through trait objects.
§Object Safety
This trait is intentionally object-safe (can be used as dyn CommandHandler).
Do not add methods with generic type parameters, as this would break
object safety and prevent dynamic dispatch.
§Thread Safety
Implementations must be Send + Sync to allow:
- Sharing command handlers across threads
- Safe concurrent access to the command registry
- Future async execution support
§Execution Flow
- Parser converts user input to
HashMap<String, String> - Validator checks argument constraints
validate()is called for custom validation (optional)execute()is called with validated arguments
§Example
use std::collections::HashMap;
use dynamic_cli::error::ExecutionError;
use dynamic_cli::executor::CommandHandler;
use dynamic_cli::context::ExecutionContext;
use dynamic_cli::Result;
struct GreetCommand;
impl CommandHandler for GreetCommand {
fn execute(
&self,
_context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
let name = args.get("name")
.ok_or_else(|| {
ExecutionError::CommandFailed(
anyhow::anyhow!("Missing 'name' argument")
)
})?;
let greeting = if let Some(formal) = args.get("formal") {
if formal == "true" {
format!("Good day, {}.", name)
} else {
format!("Hi, {}!", name)
}
} else {
format!("Hello, {}!", name)
};
println!("{}", greeting);
Ok(())
}
fn validate(&self, args: &HashMap<String, String>) -> Result<()> {
// Custom validation: name must not be empty
if let Some(name) = args.get("name") {
if name.trim().is_empty() {
return Err(ExecutionError::CommandFailed(
anyhow::anyhow!("Name cannot be empty")
).into());
}
}
Ok(())
}
}Required Methods§
Sourcefn execute(
&self,
context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()>
fn execute( &self, context: &mut dyn ExecutionContext, args: &HashMap<String, String>, ) -> Result<()>
Execute the command with the given context and arguments
This is the main entry point for command execution. It receives:
- A mutable reference to the execution context (for shared state)
- A map of argument names to their string values
§Arguments
-
context- Mutable execution context for sharing state between commands. Usedowncast_refordowncast_mutfrom thecontextmodule to access your specific context type. -
args- Parsed and validated arguments as name-value pairs. All values are strings; type conversion should be done within the handler if needed.
§Returns
Ok(())if execution succeedsErr(DynamicCliError)if execution fails
§Errors
Implementations should return errors for:
- Invalid argument values (caught by validate, but can be rechecked)
- Execution failures (I/O errors, computation errors, etc.)
- Invalid context state
Use ExecutionError::CommandFailed to wrap application-specific errors:
Err(ExecutionError::CommandFailed(anyhow::anyhow!("Details")).into())§Example
struct FileCommand;
impl CommandHandler for FileCommand {
fn execute(
&self,
_context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
let path = args.get("path")
.ok_or_else(|| {
ExecutionError::CommandFailed(
anyhow::anyhow!("Missing path argument")
)
})?;
// Perform the actual work
let content = std::fs::read_to_string(path)
.map_err(|e| {
ExecutionError::CommandFailed(anyhow::anyhow!("Failed to read file: {}", e))
})?;
println!("File contains {} bytes", content.len());
Ok(())
}
}Provided Methods§
Sourcefn validate(&self, _args: &HashMap<String, String>) -> Result<()>
fn validate(&self, _args: &HashMap<String, String>) -> Result<()>
Optional custom validation for arguments
This method is called after the standard validation (type checking, required arguments, etc.) but before execution. It allows commands to implement custom validation logic.
§Default Implementation
The default implementation accepts all arguments (returns Ok(())).
Override this method only if you need custom validation.
§Arguments
args- The arguments to validate
§Returns
Ok(())if validation succeedsErr(DynamicCliError)if validation fails
§Example
struct RangeCommand;
impl CommandHandler for RangeCommand {
fn execute(
&self,
_context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
// Execution logic here
Ok(())
}
fn validate(&self, args: &HashMap<String, String>) -> Result<()> {
// Custom validation: ensure min < max
if let (Some(min), Some(max)) = (args.get("min"), args.get("max")) {
let min_val: f64 = min.parse()
.map_err(|_| {
ExecutionError::CommandFailed(anyhow::anyhow!("Invalid min value"))
})?;
let max_val: f64 = max.parse()
.map_err(|_| {ExecutionError::CommandFailed(anyhow::anyhow!("Invalid max value"))})?;
if min_val >= max_val {
return Err(ExecutionError::CommandFailed(anyhow::anyhow!("min must be less than max")).into());
}
}
Ok(())
}
}