CommandHandler

Trait CommandHandler 

Source
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

  1. Parser converts user input to HashMap<String, String>
  2. Validator checks argument constraints
  3. validate() is called for custom validation (optional)
  4. 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§

Source

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. Use downcast_ref or downcast_mut from the context module 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 succeeds
  • Err(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§

Source

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 succeeds
  • Err(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(())
    }
}

Implementors§